diff --git a/benchmark_subchunk_agg_check_maxazi_rev11_chunk_sweep.m b/benchmark_subchunk_agg_check_maxazi_rev11_chunk_sweep.m new file mode 100644 index 0000000..d85103a --- /dev/null +++ b/benchmark_subchunk_agg_check_maxazi_rev11_chunk_sweep.m @@ -0,0 +1,82 @@ +function results=benchmark_subchunk_agg_check_maxazi_rev11_chunk_sweep(app,cell_aas_dist_data,array_bs_azi_data,radar_beamwidth,min_azimuth,max_azimuth,base_protection_pts,point_idx,on_list_bs,cell_sim_chunk_idx,rand_seed1,agg_check_reliability,on_full_Pr_dBm,clutter_loss,custom_antenna_pattern,sub_point_idx,varargin) +%BENCHMARK_SUBCHUNK_AGG_CHECK_MAXAZI_REV11_CHUNK_SWEEP +% Runtime sweep for rev11 AZI_CHUNK tuning. +% +% Optional name/value: +% 'ChunkValues' (default [64 128 256 512]) +% 'NumTrials' (default 3) + +opts=parse_inputs(varargin{:}); + +if exist('subchunk_agg_check_maxazi_rev11','file')~=2 + error('benchmark_subchunk_agg_check_maxazi_rev11_chunk_sweep:MissingRev11', ... + 'subchunk_agg_check_maxazi_rev11.m was not found on MATLAB path.'); +end + +chunk_values=opts.ChunkValues(:).'; +num_chunks=numel(chunk_values); +runtime_trials=NaN(num_chunks,opts.NumTrials); + +fprintf('\n=== rev11 AZI_CHUNK sweep benchmark ===\n'); +fprintf('Chunk values: %s\n',mat2str(chunk_values)); +fprintf('Trials per chunk: %d\n',opts.NumTrials); + +for c=1:1:num_chunks + azi_chunk=chunk_values(c); + for t=1:1:opts.NumTrials + run_tic=tic; + subchunk_agg_check_maxazi_rev11(app,cell_aas_dist_data,array_bs_azi_data, ... + radar_beamwidth,min_azimuth,max_azimuth,base_protection_pts,point_idx,on_list_bs, ... + cell_sim_chunk_idx,rand_seed1,agg_check_reliability,on_full_Pr_dBm,clutter_loss, ... + custom_antenna_pattern,sub_point_idx,azi_chunk); + runtime_trials(c,t)=toc(run_tic); + end +end + +runtime_median=median(runtime_trials,2,'omitnan'); +runtime_mean=mean(runtime_trials,2,'omitnan'); +runtime_min=min(runtime_trials,[],2,'omitnan'); + +[best_runtime,best_idx]=min(runtime_median); +best_chunk=chunk_values(best_idx); + +fprintf('\nSummary table (seconds):\n'); +fprintf(' AZI_CHUNK | median | mean | min\n'); +for c=1:1:num_chunks + fprintf(' %9d | %8.4f | %8.4f | %8.4f\n',chunk_values(c),runtime_median(c),runtime_mean(c),runtime_min(c)); +end +fprintf('\nRecommended AZI_CHUNK: %d (median runtime %.4f s)\n',best_chunk,best_runtime); + +results=struct(); +results.chunk_values=chunk_values; +results.runtime_trials_s=runtime_trials; +results.runtime_median_s=runtime_median; +results.runtime_mean_s=runtime_mean; +results.runtime_min_s=runtime_min; +results.best_chunk=best_chunk; +results.best_runtime_median_s=best_runtime; + +end + +function opts=parse_inputs(varargin) +opts=struct(); +opts.ChunkValues=[64 128 256 512]; +opts.NumTrials=3; + +if mod(numel(varargin),2)~=0 + error('Optional arguments must be name/value pairs.'); +end + +for i=1:2:numel(varargin) + name=varargin{i}; + value=varargin{i+1}; + switch lower(string(name)) + case "chunkvalues" + opts.ChunkValues=unique(max(1,round(value(:).')),'stable'); + case "numtrials" + opts.NumTrials=max(1,round(value)); + otherwise + error('Unknown option: %s',name); + end +end +end diff --git a/subchunk_agg_check_maxazi_rev11.m b/subchunk_agg_check_maxazi_rev11.m new file mode 100644 index 0000000..050d213 --- /dev/null +++ b/subchunk_agg_check_maxazi_rev11.m @@ -0,0 +1,121 @@ +function [sub_array_agg_check_mc_dBm]=subchunk_agg_check_maxazi_rev11(app,cell_aas_dist_data,array_bs_azi_data,radar_beamwidth,min_azimuth,max_azimuth,base_protection_pts,point_idx,on_list_bs,cell_sim_chunk_idx,rand_seed1,agg_check_reliability,on_full_Pr_dBm,clutter_loss,custom_antenna_pattern,sub_point_idx,varargin) +%SUBCHUNK_AGG_CHECK_MAXAZI_REV11 Monte Carlo aggregate check with tunable azimuth chunking. +% rev11 goals: +% 1) remove per-iteration RNG reseeding overhead; +% 2) keep azimuth chunking as a memory/performance tuning knob; +% 3) preserve rev9/rev10 output contract (max aggregate dBm over sim azimuth). + +% Tuning knob: larger chunks can improve compute throughput but may increase peak memory. +AZI_CHUNK_DEFAULT=128; +DEBUG_CHECKS=false; +azi_chunk=AZI_CHUNK_DEFAULT; +if ~isempty(varargin) + azi_chunk=varargin{1}; +end +azi_chunk=max(1,round(azi_chunk)); + +array_aas_dist_data=cell_aas_dist_data{2}; +aas_dist_azimuth=cell_aas_dist_data{1}; +mod_azi_diff_bs=array_bs_azi_data(:,4); + +% Off-axis EIRP lookup at BS-relative azimuth. +nn_azi_idx=nearestpoint_app(app,mod_azi_diff_bs,aas_dist_azimuth); +super_array_bs_eirp_dist=array_aas_dist_data(nn_azi_idx,:); + +% Simulation azimuth grid. +[array_sim_azimuth,num_sim_azi]=calc_sim_azimuths_rev3_360_azimuths_app(app,radar_beamwidth,min_azimuth,max_azimuth); + +% BS->point azimuths. +sim_pt=base_protection_pts(point_idx,:); +bs_azimuth=azimuth(sim_pt(1),sim_pt(2),on_list_bs(:,1),on_list_bs(:,2)); + +% MC iteration indices for this sub-point. +sub_mc_idx=cell_sim_chunk_idx{sub_point_idx}; %#ok +num_mc_idx=length(sub_mc_idx); +num_bs=length(bs_azimuth); +sub_array_agg_check_mc_dBm=NaN(num_mc_idx,1); + +% ------------------------------------------------------------------------- +% STEP 1: MC random pre-generation using a single RNG seeding call. +% Draw in [rel_min, rel_max] for PR, EIRP, clutter random reliabilities. +% ------------------------------------------------------------------------- +rel_min=min(agg_check_reliability); +rel_max=max(agg_check_reliability); + +if rel_min==rel_max + rand_pr_all=repmat(rel_min,num_bs,num_mc_idx); + rand_eirp_all=rand_pr_all; + rand_clutter_all=rand_pr_all; +else + rng(rand_seed1); + rel_span=(rel_max-rel_min); + rand_pr_all=rel_min+rel_span.*rand(num_bs,num_mc_idx); + rand_eirp_all=rel_min+rel_span.*rand(num_bs,num_mc_idx); + rand_clutter_all=rel_min+rel_span.*rand(num_bs,num_mc_idx); +end + +% ------------------------------------------------------------------------- +% STEP 2: Precompute off-axis gain matrix once for all (bs,sim_azimuth). +% Keep nearestpoint semantics stable. +% ------------------------------------------------------------------------- +pat_az=mod(custom_antenna_pattern(:,1),360); +pat_gain=custom_antenna_pattern(:,2); +[pat_az_unique,ia_unique]=unique(pat_az,'stable'); +pat_gain_unique=pat_gain(ia_unique); + +off_axis_gain_matrix=NaN(num_bs,num_sim_azi); +for azimuth_idx=1:1:num_sim_azi + sim_azimuth=array_sim_azimuth(azimuth_idx); + rel_az=mod(bs_azimuth-sim_azimuth,360); + ant_deg_idx=nearestpoint_app(app,rel_az,pat_az_unique); + off_axis_gain_matrix(:,azimuth_idx)=pat_gain_unique(ant_deg_idx); +end + +% ------------------------------------------------------------------------- +% STEP 3: RNG-free MC pathloss terms for each MC realization. +% ------------------------------------------------------------------------- +sort_monte_carlo_pr_dBm_all=NaN(num_bs,num_mc_idx); +for loop_idx=1:1:num_mc_idx + pre_sort_monte_carlo_pr_dBm=monte_carlo_Pr_dBm_rev2_app(app,agg_check_reliability,on_full_Pr_dBm,rand_pr_all(:,loop_idx)); + rand_norm_eirp=monte_carlo_super_bs_eirp_dist_rev5(app,super_array_bs_eirp_dist,agg_check_reliability,rand_eirp_all(:,loop_idx)); + monte_carlo_clutter_loss=monte_carlo_clutter_rev3_app(app,agg_check_reliability,clutter_loss,rand_clutter_all(:,loop_idx)); + + sort_monte_carlo_pr_dBm_all(:,loop_idx)=pre_sort_monte_carlo_pr_dBm+rand_norm_eirp-monte_carlo_clutter_loss; +end + +% ------------------------------------------------------------------------- +% STEP 4: Aggregate over BS in watts, convert back to dBm, then max over az. +% ------------------------------------------------------------------------- +for loop_idx=1:1:num_mc_idx + base_mc=sort_monte_carlo_pr_dBm_all(:,loop_idx); + max_azi_agg=-Inf; + + for azi_start=1:azi_chunk:num_sim_azi + azi_end=min(azi_start+azi_chunk-1,num_sim_azi); + chunk_gain=off_axis_gain_matrix(:,azi_start:azi_end); + sort_temp_mc_dBm=base_mc+chunk_gain; + + if DEBUG_CHECKS + if any(isnan(sort_temp_mc_dBm),'all') + error('subchunk_agg_check_maxazi_rev11:NaNTempDbm','NaN detected in sort_temp_mc_dBm'); + end + end + + binary_sort_mc_watts=db2pow(sort_temp_mc_dBm)/1000; + if DEBUG_CHECKS + if any(isnan(binary_sort_mc_watts),'all') + error('subchunk_agg_check_maxazi_rev11:NaNWatt','NaN detected in binary_sort_mc_watts'); + end + end + + azimuth_agg_dBm_chunk=pow2db(sum(binary_sort_mc_watts,1,'omitnan')*1000); + chunk_max=max(azimuth_agg_dBm_chunk,[],'omitnan'); + if chunk_max>max_azi_agg + max_azi_agg=chunk_max; + end + end + + sub_array_agg_check_mc_dBm(loop_idx,1)=max_azi_agg; +end + +end diff --git a/validate_subchunk_agg_check_maxazi_rev10_rev11_statistical.m b/validate_subchunk_agg_check_maxazi_rev10_rev11_statistical.m new file mode 100644 index 0000000..6f13c6f --- /dev/null +++ b/validate_subchunk_agg_check_maxazi_rev10_rev11_statistical.m @@ -0,0 +1,195 @@ +function results=validate_subchunk_agg_check_maxazi_rev10_rev11_statistical(app,cell_aas_dist_data,array_bs_azi_data,radar_beamwidth,min_azimuth,max_azimuth,base_protection_pts,point_idx,on_list_bs,cell_sim_chunk_idx,rand_seed1,agg_check_reliability,on_full_Pr_dBm,clutter_loss,custom_antenna_pattern,sub_point_idx,varargin) +%VALIDATE_SUBCHUNK_AGG_CHECK_MAXAZI_REV10_REV11_STATISTICAL +% Compare rev10 vs rev11 on identical real inputs. +% Returns a results struct and prints PASS/FAIL summary. +% +% Optional name/value: +% 'AziChunk' (default 128) +% 'EnableP999' (default true) +% 'AbsDiffThreshold_dB' (default 0.50) +% 'RelDiffThreshold' (default 0.05) + +opts=parse_inputs(varargin{:}); + +if exist('subchunk_agg_check_maxazi_rev10','file')~=2 + error('validate_subchunk_agg_check_maxazi_rev10_rev11_statistical:MissingRev10', ... + 'subchunk_agg_check_maxazi_rev10.m was not found on MATLAB path.'); +end +if exist('subchunk_agg_check_maxazi_rev11','file')~=2 + error('validate_subchunk_agg_check_maxazi_rev10_rev11_statistical:MissingRev11', ... + 'subchunk_agg_check_maxazi_rev11.m was not found on MATLAB path.'); +end + +fprintf('\n=== rev10 vs rev11 statistical validation ===\n'); +fprintf('AZI_CHUNK (rev11): %d\n',opts.AziChunk); + +% Run rev10 +rev10_tic=tic; +out_rev10=subchunk_agg_check_maxazi_rev10(app,cell_aas_dist_data,array_bs_azi_data, ... + radar_beamwidth,min_azimuth,max_azimuth,base_protection_pts,point_idx,on_list_bs, ... + cell_sim_chunk_idx,rand_seed1,agg_check_reliability,on_full_Pr_dBm,clutter_loss, ... + custom_antenna_pattern,sub_point_idx); +runtime_rev10=toc(rev10_tic); + +% Run rev11 +rev11_tic=tic; +out_rev11=subchunk_agg_check_maxazi_rev11(app,cell_aas_dist_data,array_bs_azi_data, ... + radar_beamwidth,min_azimuth,max_azimuth,base_protection_pts,point_idx,on_list_bs, ... + cell_sim_chunk_idx,rand_seed1,agg_check_reliability,on_full_Pr_dBm,clutter_loss, ... + custom_antenna_pattern,sub_point_idx,opts.AziChunk); +runtime_rev11=toc(rev11_tic); + +speedup=runtime_rev10./runtime_rev11; + +% Flatten and sanitize for summary stats +x10=out_rev10(:); +x11=out_rev11(:); +finite_mask=isfinite(x10) & isfinite(x11); +x10=x10(finite_mask); +x11=x11(finite_mask); + +if isempty(x10) + error('validate_subchunk_agg_check_maxazi_rev10_rev11_statistical:NoFiniteSamples', ... + 'No finite paired samples available for statistical comparison.'); +end + +metrics={'mean','std','min','max','median','p90','p95','p99'}; +q=[0.90 0.95 0.99]; + +s10.mean=mean(x10,'omitnan'); +s10.std=std(x10,0,'omitnan'); +s10.min=min(x10,[],'omitnan'); +s10.max=max(x10,[],'omitnan'); +s10.median=median(x10,'omitnan'); +q10=quantile(x10,q); +s10.p90=q10(1); s10.p95=q10(2); s10.p99=q10(3); + +s11.mean=mean(x11,'omitnan'); +s11.std=std(x11,0,'omitnan'); +s11.min=min(x11,[],'omitnan'); +s11.max=max(x11,[],'omitnan'); +s11.median=median(x11,'omitnan'); +q11=quantile(x11,q); +s11.p90=q11(1); s11.p95=q11(2); s11.p99=q11(3); + +if opts.EnableP999 && numel(x10)>=1000 + metrics=[metrics {'p99_9'}]; %#ok + s10.p99_9=quantile(x10,0.999); + s11.p99_9=quantile(x11,0.999); +end + +% Drift checks (documented): pass if abs diff <= max(abs threshold, rel threshold*|baseline|) +diff_table=struct(); +pass_flags=true(1,numel(metrics)); +for k=1:1:numel(metrics) + m=metrics{k}; + v10=s10.(m); + v11=s11.(m); + abs_diff=abs(v11-v10); + rel_diff=abs_diff/max(abs(v10),eps); + allowed=max(opts.AbsDiffThreshold_dB,opts.RelDiffThreshold*max(abs(v10),1)); + + diff_table.(m).rev10=v10; + diff_table.(m).rev11=v11; + diff_table.(m).abs_diff=abs_diff; + diff_table.(m).rel_diff=rel_diff; + diff_table.(m).allowed_abs=allowed; + diff_table.(m).pass=abs_diff<=allowed; + + pass_flags(k)=diff_table.(m).pass; +end + +% Upper-tail focused checks +tail_check=struct(); +tail_fields=intersect({'p95','p99','p99_9'},metrics,'stable'); +tail_pass=true; +for k=1:1:numel(tail_fields) + tf=tail_fields{k}; + v10=diff_table.(tf).rev10; + v11=diff_table.(tf).rev11; + abs_diff=abs(v11-v10); + allowed=max(opts.AbsDiffThreshold_dB,opts.RelDiffThreshold*max(abs(v10),1)); + tail_check.(tf).abs_diff=abs_diff; + tail_check.(tf).allowed_abs=allowed; + tail_check.(tf).pass=abs_diff<=allowed; + tail_pass=tail_pass && tail_check.(tf).pass; +end + +overall_pass=all(pass_flags) && tail_pass; + +fprintf('Runtime rev10: %.6f s\n',runtime_rev10); +fprintf('Runtime rev11: %.6f s\n',runtime_rev11); +fprintf('Speedup rev10/rev11: %.3fx\n',speedup); + +fprintf('\nMetric comparison (rev11 - rev10):\n'); +for k=1:1:numel(metrics) + m=metrics{k}; + fprintf(' %-7s | rev10=%10.4f | rev11=%10.4f | abs=%.4f | allow=%.4f | %s\n', ... + m,diff_table.(m).rev10,diff_table.(m).rev11,diff_table.(m).abs_diff, ... + diff_table.(m).allowed_abs,passfail(diff_table.(m).pass)); +end + +fprintf('\nUpper-tail checks:\n'); +for k=1:1:numel(tail_fields) + tf=tail_fields{k}; + fprintf(' %-7s | abs=%.4f | allow=%.4f | %s\n',tf,tail_check.(tf).abs_diff, ... + tail_check.(tf).allowed_abs,passfail(tail_check.(tf).pass)); +end + +if overall_pass + fprintf('\nPASS: rev11 is statistically equivalent to rev10 under configured thresholds.\n'); +else + fprintf('\nFAIL: rev11 drift exceeded configured thresholds.\n'); +end + +results=struct(); +results.runtime_rev10_s=runtime_rev10; +results.runtime_rev11_s=runtime_rev11; +results.speedup_rev10_over_rev11=speedup; +results.n_samples=numel(x10); +results.metrics=metrics; +results.summary_rev10=s10; +results.summary_rev11=s11; +results.diffs=diff_table; +results.upper_tail=tail_check; +results.thresholds=opts; +results.pass=overall_pass; + +end + +function opts=parse_inputs(varargin) +opts=struct(); +opts.AziChunk=128; +opts.EnableP999=true; +opts.AbsDiffThreshold_dB=0.50; +opts.RelDiffThreshold=0.05; + +if mod(numel(varargin),2)~=0 + error('Optional arguments must be name/value pairs.'); +end + +for i=1:2:numel(varargin) + name=varargin{i}; + value=varargin{i+1}; + switch lower(string(name)) + case "azichunk" + opts.AziChunk=max(1,round(value)); + case "enablep999" + opts.EnableP999=logical(value); + case "absdiffthreshold_db" + opts.AbsDiffThreshold_dB=double(value); + case "reldiffthreshold" + opts.RelDiffThreshold=double(value); + otherwise + error('Unknown option: %s',name); + end +end +end + +function txt=passfail(tf) +if tf + txt='PASS'; +else + txt='FAIL'; +end +end