function setup = prepare_osi_gain_runtime(projectFolder, dt, hs) %PREPARE_OSI_GAIN_RUNTIME Prepare workspace variables for Simulink OSI gain. % Keeps M9.1 and gain-law defaults out of main_ColumnSetup.m while leaving % the main test knobs in the main hs struct. defaults = struct( ... 'target_outer_ncf', 0.50, ... 'm91_window_s', 5, ... 'm91_update_interval_s', 0.05, ... 'osi_gain_max', 1.0, ... 'osi_gain_down_deadband', 0.005, ... 'osi_gain_down_rate', 0.15, ... 'osi_gain_down_immediate_margin_factor', 0.25, ... 'osi_gain_down_confirm_n', 2, ... 'osi_gain_up_deadband', 0.035, ... 'osi_gain_power', 1.25, ... 'osi_gain_smooth_down', 0.85, ... 'osi_gain_up_rate', 0.0015, ... 'osi_gain_up_hold_s', 1.0, ... 'osi_gain_margin_guard_up_rate', 0.025, ... 'osi_gain_margin_guard_down_rate', 0.025, ... 'osi_gain_margin_filter_alpha', 0.08, ... 'm91_use_residual_branch', true, ... 'm91_branch_split_hz', NaN); target_outer_ncf = defaults.target_outer_ncf; if isfield(hs, 'target_outer_ncf') && ~isempty(hs.target_outer_ncf) target_outer_ncf = hs.target_outer_ncf; end down_immediate_margin = ... defaults.osi_gain_down_immediate_margin_factor*target_outer_ncf; use_residual_branch = defaults.m91_use_residual_branch; if isfield(hs, 'osi_use_residual_branch') ... && ~isempty(hs.osi_use_residual_branch) use_residual_branch = logical(hs.osi_use_residual_branch); end branch_split_hz = defaults.m91_branch_split_hz; if isfield(hs, 'osi_branch_split_hz') ... && ~isempty(hs.osi_branch_split_hz) branch_split_hz = hs.osi_branch_split_hz; end st_branch_hz = [NaN NaN]; residual_branch_hz = [NaN NaN]; if isfinite(branch_split_hz) st_branch_hz = [NaN branch_split_hz]; residual_branch_hz = [branch_split_hz NaN]; end if isfield(hs, 'osi_st_branch_hz') && ~isempty(hs.osi_st_branch_hz) st_branch_hz = hs.osi_st_branch_hz; end if isfield(hs, 'osi_residual_branch_hz') ... && ~isempty(hs.osi_residual_branch_hz) residual_branch_hz = hs.osi_residual_branch_hz; end [exp_tf_data, exp_tf_file, exp_tf_name] = ... load_saved_result_file(projectFolder, 'exp_tf', false, hs.exp_tf_file); assert(isfield(exp_tf_data, 'H_dc_dm'), ... ['The selected exp_tf file must contain H_dc_dm. Run ' ... 'control_and_HS\b_system_id\sys_id.m first.']) H_dc_dm = exp_tf_data.H_dc_dm; if get(H_dc_dm, 'Ts') == 0 H_dc_dm = c2d(H_dc_dm, dt, 'zoh'); elseif abs(get(H_dc_dm, 'Ts') - dt) > 10*eps(dt) H_dc_dm = d2d(H_dc_dm, dt, 'zoh'); end if isfield(exp_tf_data, 'pid_values') osi_pid_values = exp_tf_data.pid_values; else osi_pid_values = struct('P', 0.15, 'I', 20, 'D', 0.0015, ... 'derivative_filter_N', 512); end if ~isfield(osi_pid_values, 'derivative_filter_N') osi_pid_values.derivative_filter_N = 512; end osi_pid_controller = pid(osi_pid_values.P, osi_pid_values.I, ... osi_pid_values.D, 1/osi_pid_values.derivative_filter_N, dt, ... 'IFormula', 'ForwardEuler', 'DFormula', 'ForwardEuler'); m91_override = struct('threshold', target_outer_ncf, ... 'window_s', defaults.m91_window_s, ... 'update_interval_s', defaults.m91_update_interval_s, ... 'use_residual_branch', use_residual_branch, ... 'st_branch_hz', st_branch_hz, ... 'residual_branch_hz', residual_branch_hz); m91_cfg = m91_adaptive_margin('config', dt, osi_pid_controller*H_dc_dm, ... m91_override); m91_state_initial = m91_adaptive_margin('state', m91_cfg); osi_gain_cfg = struct('initial', hs.osi_gain_initial, ... 'target_outer_ncf', target_outer_ncf, ... 'min', hs.osi_gain_min, ... 'max', defaults.osi_gain_max, ... 'down_deadband', defaults.osi_gain_down_deadband, ... 'down_rate', defaults.osi_gain_down_rate, ... 'down_immediate_margin', down_immediate_margin, ... 'down_confirm_n', defaults.osi_gain_down_confirm_n, ... 'up_deadband', defaults.osi_gain_up_deadband, ... 'power', defaults.osi_gain_power, ... 'smooth_down', defaults.osi_gain_smooth_down, ... 'up_rate', defaults.osi_gain_up_rate, ... 'up_hold_s', defaults.osi_gain_up_hold_s, ... 'margin_guard_up_rate', defaults.osi_gain_margin_guard_up_rate, ... 'margin_guard_down_rate', defaults.osi_gain_margin_guard_down_rate, ... 'margin_filter_alpha', defaults.osi_gain_margin_filter_alpha); m91s = [m91_cfg.dt; m91_cfg.fs; m91_cfg.t_start; ... m91_cfg.band_hz(:); m91_cfg.df_hz; m91_cfg.window_s; ... m91_cfg.update_interval_s; m91_cfg.segment_s; ... m91_cfg.segment_hop_s; m91_cfg.bootstrap_strength; ... m91_cfg.input_power_range_db; m91_cfg.gamma_min; ... m91_cfg.min_bins; m91_cfg.warm_windows; ... m91_cfg.margin_filter_n; m91_cfg.detect_consecutive; ... m91_cfg.calibration_s; m91_cfg.cap; m91_cfg.threshold; ... m91_cfg.residual_floor; m91_cfg.residual_prctile; ... double(m91_cfg.residual_early_all_bins); ... m91_cfg.residual_early_match_hz; ... m91_cfg.residual_early_st_floor_frac; ... m91_cfg.residual_early_st_gate_frac; ... m91_cfg.st_prctile; m91_cfg.st_power; ... m91_cfg.freq_medfilt_n; m91_cfg.line_reject_hz(:); ... m91_cfg.window_n; m91_cfg.segment_n; m91_cfg.segment_hop_n; ... m91_cfg.update_interval_n; m91_cfg.update_interval_actual_s; ... m91_cfg.calibration_windows; m91_cfg.nfft; ... m91_cfg.n_segment; m91_cfg.nominal; ... m91_cfg.nominal_index; m91_cfg.nominal_hz; ... double(m91_cfg.use_residual_branch); ... m91_cfg.st_branch_hz(:); m91_cfg.residual_branch_hz(:)]; m91f = m91_cfg.freq_hz; m91b = m91_cfg.bin; m91seg = m91_cfg.segment_start; m91w = m91_cfg.window; % Nonzero entries are both the keep mask and the residual branch's local % nominal NCF anchor for each frequency. m91keep = double(m91_cfg.freq_keep).*m91_cfg.nominal_curve; m91buf0 = zeros(m91_cfg.window_n, 1); m91res0 = m91_state_initial.residual_cal; m91base0 = m91_state_initial.residual_baseline; m91hist0 = m91_state_initial.margin_history; m91below0 = double(m91_state_initial.below_history); osigs = [osi_gain_cfg.initial; ... osi_gain_cfg.target_outer_ncf; osi_gain_cfg.min; ... osi_gain_cfg.max; osi_gain_cfg.down_deadband; ... osi_gain_cfg.down_rate; osi_gain_cfg.down_immediate_margin; ... osi_gain_cfg.down_confirm_n; osi_gain_cfg.up_deadband; ... osi_gain_cfg.power; osi_gain_cfg.smooth_down; ... osi_gain_cfg.up_rate; osi_gain_cfg.up_hold_s; ... osi_gain_cfg.margin_guard_up_rate; ... osi_gain_cfg.margin_guard_down_rate; ... osi_gain_cfg.margin_filter_alpha]; assignin('caller', 'm91s', m91s); assignin('caller', 'm91f', m91f); assignin('caller', 'm91b', m91b); assignin('caller', 'm91seg', m91seg); assignin('caller', 'm91w', m91w); assignin('caller', 'm91keep', m91keep); assignin('caller', 'm91buf0', m91buf0); assignin('caller', 'm91res0', m91res0); assignin('caller', 'm91base0', m91base0); assignin('caller', 'm91hist0', m91hist0); assignin('caller', 'm91below0', m91below0); assignin('caller', 'osigs', osigs); setup = struct('exp_tf_file', exp_tf_file, ... 'exp_tf_name', exp_tf_name, ... 'm91_cfg', m91_cfg, ... 'gain_initial', hs.osi_gain_initial, ... 'gain_min', hs.osi_gain_min, ... 'target_outer_ncf', target_outer_ncf); end