From eec404382a42e0d27e265eed3f172e35a342853a Mon Sep 17 00:00:00 2001 From: Nick LaSorte <37697540+nicklasorte@users.noreply.github.com> Date: Fri, 27 Mar 2026 09:45:17 -0400 Subject: [PATCH] Add rev11 real chunk sweep benchmark and scoped rev12 + validation --- ..._agg_check_maxazi_rev11_chunk_sweep_real.m | 97 ++++++++++ subchunk_agg_check_maxazi_rev12.m | 116 ++++++++++++ ...agg_check_maxazi_rev11_rev12_statistical.m | 179 ++++++++++++++++++ 3 files changed, 392 insertions(+) create mode 100644 benchmark_subchunk_agg_check_maxazi_rev11_chunk_sweep_real.m create mode 100644 subchunk_agg_check_maxazi_rev12.m create mode 100644 validate_subchunk_agg_check_maxazi_rev11_rev12_statistical.m diff --git a/benchmark_subchunk_agg_check_maxazi_rev11_chunk_sweep_real.m b/benchmark_subchunk_agg_check_maxazi_rev11_chunk_sweep_real.m new file mode 100644 index 0000000..cafa876 --- /dev/null +++ b/benchmark_subchunk_agg_check_maxazi_rev11_chunk_sweep_real.m @@ -0,0 +1,97 @@ +function results = benchmark_subchunk_agg_check_maxazi_rev11_chunk_sweep_real( ... + 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) +%BENCHMARK_SUBCHUNK_AGG_CHECK_MAXAZI_REV11_CHUNK_SWEEP_REAL +% Benchmark rev11 with real inputs over a sweep of AZI_CHUNK values. + +if exist('subchunk_agg_check_maxazi_rev11','file')~=2 + error('benchmark_subchunk_agg_check_maxazi_rev11_chunk_sweep_real:MissingRev11', ... + 'subchunk_agg_check_maxazi_rev11.m was not found on MATLAB path.'); +end + +% Required set plus optional boundary values when practical. +chunk_sizes = [32 64 128 256 512 1024]; +runtimes = NaN(size(chunk_sizes)); + +fprintf('\n=== REV11 CHUNK SWEEP (REAL INPUTS) ===\n'); + +for idx = 1:numel(chunk_sizes) + azi_chunk = chunk_sizes(idx); + + f = @() 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); + + try + if exist('timeit','file')==2 + runtimes(idx) = timeit(f); + else + t0 = tic; + f(); + runtimes(idx) = toc(t0); + end + catch chunkErr + runtimes(idx) = NaN; + warning('benchmark_subchunk_agg_check_maxazi_rev11_chunk_sweep_real:ChunkFailed', ... + 'Chunk size %d failed (%s). Continuing with remaining chunk sizes.', ... + azi_chunk, chunkErr.message); + end +end + +valid_mask = isfinite(runtimes); +if ~any(valid_mask) + error('benchmark_subchunk_agg_check_maxazi_rev11_chunk_sweep_real:NoValidRuns', ... + 'No successful runtime measurements were produced.'); +end + +valid_chunks = chunk_sizes(valid_mask); +valid_runtimes = runtimes(valid_mask); +[best_runtime,best_idx] = min(valid_runtimes); +best_chunk = valid_chunks(best_idx); + +fprintf('Chunk Runtime (s) Relative to Best\n'); +for idx = 1:numel(chunk_sizes) + if isfinite(runtimes(idx)) + rel = runtimes(idx) ./ best_runtime; + fprintf('%5d %10.6f %8.3fx\n',chunk_sizes(idx),runtimes(idx),rel); + else + fprintf('%5d %10s %8s\n',chunk_sizes(idx),'FAILED','-'); + end +end + +fprintf('Best chunk: %d\n',best_chunk); +fprintf('Best runtime: %.6f s\n',best_runtime); + +results = struct(); +results.chunk_sizes = chunk_sizes; +results.runtimes = runtimes; +results.best_chunk = best_chunk; +results.best_runtime = best_runtime; + +idx_128 = find(chunk_sizes==128,1,'first'); +if ~isempty(idx_128) && isfinite(runtimes(idx_128)) + results.speedup_vs_128 = runtimes(idx_128) ./ best_runtime; + fprintf('Speedup vs chunk 128: %.3fx\n',results.speedup_vs_128); +else + results.speedup_vs_128 = NaN; + fprintf('Speedup vs chunk 128: N/A\n'); +end + +fprintf('Recommended chunk size for rev12 default: %d\n',best_chunk); + +end diff --git a/subchunk_agg_check_maxazi_rev12.m b/subchunk_agg_check_maxazi_rev12.m new file mode 100644 index 0000000..86c9d96 --- /dev/null +++ b/subchunk_agg_check_maxazi_rev12.m @@ -0,0 +1,116 @@ +function [sub_array_agg_check_mc_dBm]=subchunk_agg_check_maxazi_rev12(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_REV12 +% Carefully scoped rev12 optimization over rev11: +% 1) preserve one-time RNG seeding and pre-generated MC random matrices; +% 2) preserve AZI chunking and allow optional AZI_CHUNK override via varargin{1}; +% 3) reduce off-axis matrix build overhead with one vectorized nearest-neighbor pass; +% 4) reduce aggregation temporary allocation pressure via direct mW accumulation. +% +% Output contract is unchanged: max aggregate dBm over simulation azimuth for each MC. + +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); + +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,:); + +[array_sim_azimuth,num_sim_azi]=calc_sim_azimuths_rev3_360_azimuths_app(app,radar_beamwidth,min_azimuth,max_azimuth); + +sim_pt=base_protection_pts(point_idx,:); +bs_azimuth=azimuth(sim_pt(1),sim_pt(2),on_list_bs(:,1),on_list_bs(:,2)); + +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: one-time RNG and pre-generated random matrices. +% ------------------------------------------------------------------------- +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: off-axis gain matrix construction (vectorized nearest-neighbor). +% Semantics are preserved via nearestpoint_app lookup against unique pattern azimuths. +% ------------------------------------------------------------------------- +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); + +rel_az_matrix=mod(bs_azimuth-array_sim_azimuth,360); +ant_deg_idx_flat=nearestpoint_app(app,rel_az_matrix(:),pat_az_unique); +off_axis_gain_matrix=reshape(pat_gain_unique(ant_deg_idx_flat),num_bs,num_sim_azi); + +if DEBUG_CHECKS + if any(~isfinite(off_axis_gain_matrix),'all') + error('subchunk_agg_check_maxazi_rev12:OffAxisNotFinite', ... + 'off_axis_gain_matrix contains non-finite values.'); + end +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: chunked azimuth aggregation in linear mW domain. +% max-over-azimuth is tracked in linear domain and converted once per MC. +% ------------------------------------------------------------------------- +for loop_idx=1:1:num_mc_idx + base_mc=sort_monte_carlo_pr_dBm_all(:,loop_idx); + max_azi_agg_mw=0; + + 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); + + if DEBUG_CHECKS + if any(isnan(chunk_gain),'all') || any(isnan(base_mc),'all') + error('subchunk_agg_check_maxazi_rev12:NaNInputs','NaN detected before aggregation.'); + end + end + + chunk_agg_mw=sum(10.^((base_mc+chunk_gain)./10),1,'omitnan'); + chunk_max_mw=max(chunk_agg_mw,[],'omitnan'); + + if chunk_max_mw>max_azi_agg_mw + max_azi_agg_mw=chunk_max_mw; + end + end + + sub_array_agg_check_mc_dBm(loop_idx,1)=10.*log10(max(max_azi_agg_mw,realmin('double'))); +end + +end diff --git a/validate_subchunk_agg_check_maxazi_rev11_rev12_statistical.m b/validate_subchunk_agg_check_maxazi_rev11_rev12_statistical.m new file mode 100644 index 0000000..ef97b2d --- /dev/null +++ b/validate_subchunk_agg_check_maxazi_rev11_rev12_statistical.m @@ -0,0 +1,179 @@ +function results = validate_subchunk_agg_check_maxazi_rev11_rev12_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) +%VALIDATE_SUBCHUNK_AGG_CHECK_MAXAZI_REV11_REV12_STATISTICAL +% Statistical and runtime comparison for rev11 vs rev12 using identical real inputs. + +if exist('subchunk_agg_check_maxazi_rev11','file')~=2 + error('validate_subchunk_agg_check_maxazi_rev11_rev12_statistical:MissingRev11', ... + 'subchunk_agg_check_maxazi_rev11.m was not found on MATLAB path.'); +end +if exist('subchunk_agg_check_maxazi_rev12','file')~=2 + error('validate_subchunk_agg_check_maxazi_rev11_rev12_statistical:MissingRev12', ... + 'subchunk_agg_check_maxazi_rev12.m was not found on MATLAB path.'); +end + +% Thresholds aligned in style with prior rev10-vs-rev11 validation. +opts = struct(); +opts.AziChunkRev11 = 128; +opts.AziChunkRev12 = 128; +opts.AbsDiffThreshold_dB = 0.50; +opts.RelDiffThreshold = 0.05; +opts.EnableP999 = true; + +fprintf('\n=== REV11 vs REV12 STATISTICAL VALIDATION ===\n'); +fprintf('AZI_CHUNK rev11: %d\n',opts.AziChunkRev11); +fprintf('AZI_CHUNK rev12: %d\n',opts.AziChunkRev12); + +% Runtime + output capture for 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.AziChunkRev11); +runtime_rev11=toc(rev11_tic); + +% Runtime + output capture for rev12. +rev12_tic=tic; +out_rev12=subchunk_agg_check_maxazi_rev12(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.AziChunkRev12); +runtime_rev12=toc(rev12_tic); + +speedup = runtime_rev11 ./ runtime_rev12; + +x11=out_rev11(:); +x12=out_rev12(:); +finite_mask=isfinite(x11) & isfinite(x12); +x11=x11(finite_mask); +x12=x12(finite_mask); + +if isempty(x11) + error('validate_subchunk_agg_check_maxazi_rev11_rev12_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]; + +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); + +s12.mean=mean(x12,'omitnan'); +s12.std=std(x12,0,'omitnan'); +s12.min=min(x12,[],'omitnan'); +s12.max=max(x12,[],'omitnan'); +s12.median=median(x12,'omitnan'); +q12=quantile(x12,q); +s12.p90=q12(1); s12.p95=q12(2); s12.p99=q12(3); + +if opts.EnableP999 && numel(x11)>=1000 + metrics=[metrics {'p99_9'}]; %#ok + s11.p99_9=quantile(x11,0.999); + s12.p99_9=quantile(x12,0.999); +end + +diff_table=struct(); +pass_flags=true(1,numel(metrics)); +for k=1:1:numel(metrics) + m=metrics{k}; + v11=s11.(m); + v12=s12.(m); + abs_diff=abs(v12-v11); + rel_diff=abs_diff/max(abs(v11),eps); + allowed=max(opts.AbsDiffThreshold_dB,opts.RelDiffThreshold*max(abs(v11),1)); + + diff_table.(m).rev11=v11; + diff_table.(m).rev12=v12; + 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 + +tail_fields=intersect({'p95','p99','p99_9'},metrics,'stable'); +tail_check=struct(); +tail_pass=true; +for k=1:1:numel(tail_fields) + tf=tail_fields{k}; + abs_diff=diff_table.(tf).abs_diff; + allowed=diff_table.(tf).allowed_abs; + 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 rev11: %.6f s\n',runtime_rev11); +fprintf('Runtime rev12: %.6f s\n',runtime_rev12); +fprintf('Speedup rev11/rev12: %.3fx\n',speedup); + +fprintf('\nMetric comparison (rev12 - rev11):\n'); +for k=1:1:numel(metrics) + m=metrics{k}; + fprintf(' %-7s | rev11=%10.4f | rev12=%10.4f | abs=%.4f | allow=%.4f | %s\n', ... + m,diff_table.(m).rev11,diff_table.(m).rev12,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: rev12 is statistically equivalent to rev11 under configured thresholds.\n'); +else + fprintf('\nFAIL: rev12 drift exceeded configured thresholds.\n'); + error('validate_subchunk_agg_check_maxazi_rev11_rev12_statistical:DriftExceeded', ... + 'Fail-closed: statistical drift exceeded configured thresholds.'); +end + +results=struct(); +results.runtime_rev11_s=runtime_rev11; +results.runtime_rev12_s=runtime_rev12; +results.speedup_rev11_over_rev12=speedup; +results.n_samples=numel(x11); +results.metrics=metrics; +results.summary_rev11=s11; +results.summary_rev12=s12; +results.diffs=diff_table; +results.upper_tail=tail_check; +results.thresholds=opts; +results.pass=overall_pass; + +end + +function txt=passfail(tf) +if tf + txt='PASS'; +else + txt='FAIL'; +end +end