diff --git a/GRF_Representativeness/.gitignore b/GRF_Representativeness/.gitignore new file mode 100644 index 0000000..65ba5a6 --- /dev/null +++ b/GRF_Representativeness/.gitignore @@ -0,0 +1,9 @@ +# Local data configuration +MyWRFdata.mat + +# MATLAB artifacts +*.asv +*.autosave + +# macOS / editor +.DS_Store diff --git a/GRF_Representativeness/README.md b/GRF_Representativeness/README.md new file mode 100644 index 0000000..4430e08 --- /dev/null +++ b/GRF_Representativeness/README.md @@ -0,0 +1,27 @@ +# GRF_Representativeness + +MATLAB project scaffold for evaluating representativeness of forecast days using KS-distance based selection, empirical alternatives, and UTCI impact analysis. + +## Quick start +1. Open MATLAB in this folder. +2. Copy your local data configuration into `MyWRFdata.mat` (kept out of git by default). +3. Run: + ```matlab + main + ``` + +## Repository layout +- `config/`: path, parameter, and plotting configuration. +- `data_io/`: loading/saving interfaces for WRF, station, and intermediate data. +- `core/`: reusable methods (KS distance, UTCI, CV, perturbation). +- `stream1_forecast_based/`: forecast-driven representativeness workflow. +- `stream2_empirical_methods/`: empirical heuristics and comparison workflow. +- `stream3_utci_impact/`: UTCI-focused impact workflow. +- `figures/`: figure scripts and common save helper. +- `results/`: output folders by stream and final tables. +- `intermediate/`: cached intermediates. +- `tests/`: basic unit/smoke test scripts. + +## Notes +- This scaffold provides function signatures and minimal defaults to accelerate implementation. +- Replace placeholder logic with your project-specific algorithms and data handling. diff --git a/GRF_Representativeness/config/config_parameters.m b/GRF_Representativeness/config/config_parameters.m new file mode 100644 index 0000000..c9b5dd1 --- /dev/null +++ b/GRF_Representativeness/config/config_parameters.m @@ -0,0 +1,11 @@ +function params = config_parameters() +%CONFIG_PARAMETERS Set default analysis parameters. + +params.random_seed = 42; +params.ks_alpha = 0.05; +params.cv_folds = 5; +params.representative_day_count = 10; +params.ensemble_perturbation_std = 0.1; + +rng(params.random_seed); +end diff --git a/GRF_Representativeness/config/config_paths.m b/GRF_Representativeness/config/config_paths.m new file mode 100644 index 0000000..d2dd5a0 --- /dev/null +++ b/GRF_Representativeness/config/config_paths.m @@ -0,0 +1,15 @@ +function paths = config_paths() +%CONFIG_PATHS Define project directory paths. + +repoRoot = fileparts(fileparts(mfilename('fullpath'))); +paths.repo_root = repoRoot; +paths.config_root = fullfile(repoRoot, 'config'); +paths.data_io_root = fullfile(repoRoot, 'data_io'); +paths.core_root = fullfile(repoRoot, 'core'); +paths.results_root = fullfile(repoRoot, 'results'); +paths.intermediate_root = fullfile(repoRoot, 'intermediate'); +paths.figures_root = fullfile(repoRoot, 'figures'); + +% Optional local file with user-specific paths. +paths.local_data_file = fullfile(repoRoot, 'MyWRFdata.mat'); +end diff --git a/GRF_Representativeness/config/config_plot.m b/GRF_Representativeness/config/config_plot.m new file mode 100644 index 0000000..0d79f1b --- /dev/null +++ b/GRF_Representativeness/config/config_plot.m @@ -0,0 +1,8 @@ +function plotCfg = config_plot() +%CONFIG_PLOT Plotting defaults. + +plotCfg.font_size = 11; +plotCfg.line_width = 1.5; +plotCfg.colormap = parula(64); +plotCfg.save_format = 'png'; +end diff --git a/GRF_Representativeness/core/compute_distribution.m b/GRF_Representativeness/core/compute_distribution.m new file mode 100644 index 0000000..a7e1871 --- /dev/null +++ b/GRF_Representativeness/core/compute_distribution.m @@ -0,0 +1,13 @@ +function dist = compute_distribution(x, nBins) +%COMPUTE_DISTRIBUTION Compute histogram-based distribution summary. + +arguments + x (:,1) double + nBins (1,1) double {mustBePositive,mustBeInteger} = 20 +end + +[counts, edges] = histcounts(x, nBins, 'Normalization', 'probability'); +dist.counts = counts; +dist.edges = edges; +dist.bin_centers = edges(1:end-1) + diff(edges)/2; +end diff --git a/GRF_Representativeness/core/compute_ks_distance.m b/GRF_Representativeness/core/compute_ks_distance.m new file mode 100644 index 0000000..ba1a879 --- /dev/null +++ b/GRF_Representativeness/core/compute_ks_distance.m @@ -0,0 +1,20 @@ +function ks = compute_ks_distance(x, y) +%COMPUTE_KS_DISTANCE Compute two-sample KS distance. + +arguments + x (:,1) double + y (:,1) double +end + +x = sort(x(~isnan(x))); +y = sort(y(~isnan(y))); + +if isempty(x) || isempty(y) + error('Inputs x and y must both contain at least one non-NaN value.'); +end + +vals = unique([x; y]); +Fx = arrayfun(@(v) mean(x <= v), vals); +Fy = arrayfun(@(v) mean(y <= v), vals); +ks = max(abs(Fx - Fy)); +end diff --git a/GRF_Representativeness/core/compute_utci.m b/GRF_Representativeness/core/compute_utci.m new file mode 100644 index 0000000..6746f8c --- /dev/null +++ b/GRF_Representativeness/core/compute_utci.m @@ -0,0 +1,13 @@ +function utci = compute_utci(tAir, rh, wind, mrt) +%COMPUTE_UTCI Simplified UTCI proxy calculation. +% NOTE: Replace with the official UTCI polynomial for production use. + +arguments + tAir (:,:) double + rh (:,:) double + wind (:,:) double + mrt (:,:) double +end + +utci = tAir + 0.2*(mrt - tAir) - 0.02*(100-rh) - 0.7*wind; +end diff --git a/GRF_Representativeness/core/cross_validation_split.m b/GRF_Representativeness/core/cross_validation_split.m new file mode 100644 index 0000000..bfdd259 --- /dev/null +++ b/GRF_Representativeness/core/cross_validation_split.m @@ -0,0 +1,20 @@ +function folds = cross_validation_split(nSamples, k) +%CROSS_VALIDATION_SPLIT Create k-fold index sets. + +arguments + nSamples (1,1) double {mustBePositive,mustBeInteger} + k (1,1) double {mustBePositive,mustBeInteger} +end + +indices = randperm(nSamples); +foldSize = ceil(nSamples/k); +folds = cell(k,1); + +for i = 1:k + lo = (i-1)*foldSize + 1; + hi = min(i*foldSize, nSamples); + testIdx = indices(lo:hi); + trainIdx = setdiff(indices, testIdx); + folds{i} = struct('train', trainIdx, 'test', testIdx); +end +end diff --git a/GRF_Representativeness/core/generate_ensemble_perturbation.m b/GRF_Representativeness/core/generate_ensemble_perturbation.m new file mode 100644 index 0000000..6a8248b --- /dev/null +++ b/GRF_Representativeness/core/generate_ensemble_perturbation.m @@ -0,0 +1,10 @@ +function perturbed = generate_ensemble_perturbation(baseField, sigma) +%GENERATE_ENSEMBLE_PERTURBATION Add Gaussian perturbation to base field. + +arguments + baseField (:,:) double + sigma (1,1) double {mustBeNonnegative} = 0.1 +end + +perturbed = baseField + sigma .* randn(size(baseField)); +end diff --git a/GRF_Representativeness/core/select_representative_days.m b/GRF_Representativeness/core/select_representative_days.m new file mode 100644 index 0000000..090bb2b --- /dev/null +++ b/GRF_Representativeness/core/select_representative_days.m @@ -0,0 +1,11 @@ +function idx = select_representative_days(scores, k) +%SELECT_REPRESENTATIVE_DAYS Select indices of best representative days. + +arguments + scores (:,1) double + k (1,1) double {mustBePositive,mustBeInteger} +end + +[~, order] = sort(scores, 'ascend'); +idx = order(1:min(k, numel(order))); +end diff --git a/GRF_Representativeness/data_io/load_preprocessed_mat.m b/GRF_Representativeness/data_io/load_preprocessed_mat.m new file mode 100644 index 0000000..8e6e227 --- /dev/null +++ b/GRF_Representativeness/data_io/load_preprocessed_mat.m @@ -0,0 +1,9 @@ +function S = load_preprocessed_mat(filePath) +%LOAD_PREPROCESSED_MAT Load intermediate MAT-file data. + +arguments + filePath (1,:) char +end + +S = load(filePath); +end diff --git a/GRF_Representativeness/data_io/load_station_data.m b/GRF_Representativeness/data_io/load_station_data.m new file mode 100644 index 0000000..fef6fc8 --- /dev/null +++ b/GRF_Representativeness/data_io/load_station_data.m @@ -0,0 +1,9 @@ +function stationData = load_station_data(filePath) +%LOAD_STATION_DATA Load station observations. + +arguments + filePath (1,:) char +end + +stationData = readtable(filePath); +end diff --git a/GRF_Representativeness/data_io/load_wrf_field.m b/GRF_Representativeness/data_io/load_wrf_field.m new file mode 100644 index 0000000..b13a101 --- /dev/null +++ b/GRF_Representativeness/data_io/load_wrf_field.m @@ -0,0 +1,12 @@ +function field = load_wrf_field(filePath, variableName) +%LOAD_WRF_FIELD Load a variable from a WRF data file. + +arguments + filePath (1,:) char + variableName (1,:) char +end + +% Placeholder implementation. Replace with ncread/readmatrix logic as needed. +S = load(filePath, variableName); +field = S.(variableName); +end diff --git a/GRF_Representativeness/data_io/save_intermediate.m b/GRF_Representativeness/data_io/save_intermediate.m new file mode 100644 index 0000000..ed13047 --- /dev/null +++ b/GRF_Representativeness/data_io/save_intermediate.m @@ -0,0 +1,10 @@ +function save_intermediate(filePath, dataStruct) +%SAVE_INTERMEDIATE Save struct data to a MAT file. + +arguments + filePath (1,:) char + dataStruct (1,1) struct +end + +save(filePath, '-struct', 'dataStruct'); +end diff --git a/GRF_Representativeness/figures/fig01_framework.m b/GRF_Representativeness/figures/fig01_framework.m new file mode 100644 index 0000000..a108207 --- /dev/null +++ b/GRF_Representativeness/figures/fig01_framework.m @@ -0,0 +1,7 @@ +function fig01_framework() +%FIG01_FRAMEWORK Placeholder framework diagram figure. + +figure; +text(0.1, 0.5, 'GRF Representativeness Framework', 'FontSize', 14); +axis off; +end diff --git a/GRF_Representativeness/figures/fig02_spatial_map.m b/GRF_Representativeness/figures/fig02_spatial_map.m new file mode 100644 index 0000000..48be90c --- /dev/null +++ b/GRF_Representativeness/figures/fig02_spatial_map.m @@ -0,0 +1,9 @@ +function fig02_spatial_map(field) +%FIG02_SPATIAL_MAP Plot a spatial field. + +figure; +imagesc(field); +colorbar; +title('Spatial Map'); +axis image; +end diff --git a/GRF_Representativeness/figures/fig03_ks_comparison.m b/GRF_Representativeness/figures/fig03_ks_comparison.m new file mode 100644 index 0000000..6166ac4 --- /dev/null +++ b/GRF_Representativeness/figures/fig03_ks_comparison.m @@ -0,0 +1,9 @@ +function fig03_ks_comparison(scoresA, scoresB) +%FIG03_KS_COMPARISON Compare two KS score sets. + +figure; +plot(sort(scoresA), 'LineWidth', 1.5); hold on; +plot(sort(scoresB), 'LineWidth', 1.5); +legend('Method A', 'Method B'); +title('KS Comparison'); +end diff --git a/GRF_Representativeness/figures/fig04_utci_impact.m b/GRF_Representativeness/figures/fig04_utci_impact.m new file mode 100644 index 0000000..8223a0f --- /dev/null +++ b/GRF_Representativeness/figures/fig04_utci_impact.m @@ -0,0 +1,8 @@ +function fig04_utci_impact(utciValues) +%FIG04_UTCI_IMPACT Plot UTCI distribution. + +figure; +histogram(utciValues(:), 20); +title('UTCI Impact Distribution'); +xlabel('UTCI'); ylabel('Frequency'); +end diff --git a/GRF_Representativeness/figures/save_figure.m b/GRF_Representativeness/figures/save_figure.m new file mode 100644 index 0000000..699fec5 --- /dev/null +++ b/GRF_Representativeness/figures/save_figure.m @@ -0,0 +1,14 @@ +function save_figure(figHandle, outPath) +%SAVE_FIGURE Save figure to disk, creating folder when needed. + +arguments + figHandle (1,1) matlab.ui.Figure + outPath (1,:) char +end + +outDir = fileparts(outPath); +if ~isempty(outDir) && ~exist(outDir, 'dir') + mkdir(outDir); +end +saveas(figHandle, outPath); +end diff --git a/GRF_Representativeness/main.m b/GRF_Representativeness/main.m new file mode 100644 index 0000000..005aedb --- /dev/null +++ b/GRF_Representativeness/main.m @@ -0,0 +1,21 @@ +%% main.m +% Entry point for the GRF_Representativeness workflow. + +clear; clc; + +addpath(genpath(fileparts(mfilename('fullpath')))); + +paths = config_paths(); +params = config_parameters(); +plotCfg = config_plot(); %#ok + +fprintf('Running GRF_Representativeness pipeline...\n'); + +stream1Results = run_stream1(paths, params); +stream2Results = run_stream2(paths, params, stream1Results); +stream3Results = run_stream3(paths, params, stream1Results, stream2Results); + +save(fullfile(paths.results_root, 'final_tables', 'pipeline_summary.mat'), ... + 'stream1Results', 'stream2Results', 'stream3Results'); + +fprintf('Pipeline complete. Results saved in %s\n', paths.results_root); diff --git a/GRF_Representativeness/stream1_forecast_based/build_daily_summary.m b/GRF_Representativeness/stream1_forecast_based/build_daily_summary.m new file mode 100644 index 0000000..781521d --- /dev/null +++ b/GRF_Representativeness/stream1_forecast_based/build_daily_summary.m @@ -0,0 +1,11 @@ +function daily = build_daily_summary(paths, params) +%BUILD_DAILY_SUMMARY Build daily summaries from forecast data. + +daily = struct(); +daily.info = 'Placeholder daily summary'; +daily.params = params; +daily.generated_at = datetime('now'); + +outFile = fullfile(paths.intermediate_root, 'daily_summaries', 'stream1_daily_summary.mat'); +save(outFile, 'daily'); +end diff --git a/GRF_Representativeness/stream1_forecast_based/evaluate_forecast_representativeness.m b/GRF_Representativeness/stream1_forecast_based/evaluate_forecast_representativeness.m new file mode 100644 index 0000000..e36dc7e --- /dev/null +++ b/GRF_Representativeness/stream1_forecast_based/evaluate_forecast_representativeness.m @@ -0,0 +1,9 @@ +function results = evaluate_forecast_representativeness(daily, params) +%EVALUATE_FORECAST_REPRESENTATIVENESS Evaluate representativeness scores. + +n = params.representative_day_count * 3; +scores = rand(n,1); +selected = select_representative_days(scores, params.representative_day_count); + +results = struct('daily', daily, 'scores', scores, 'selected_days', selected); +end diff --git a/GRF_Representativeness/stream1_forecast_based/run_stream1.m b/GRF_Representativeness/stream1_forecast_based/run_stream1.m new file mode 100644 index 0000000..5fa577a --- /dev/null +++ b/GRF_Representativeness/stream1_forecast_based/run_stream1.m @@ -0,0 +1,7 @@ +function results = run_stream1(paths, params) +%RUN_STREAM1 Execute stream 1 (forecast-based workflow). + +daily = build_daily_summary(paths, params); +results = evaluate_forecast_representativeness(daily, params); +results.summary = summarize_stream1_results(results); +end diff --git a/GRF_Representativeness/stream1_forecast_based/summarize_stream1_results.m b/GRF_Representativeness/stream1_forecast_based/summarize_stream1_results.m new file mode 100644 index 0000000..014e6a6 --- /dev/null +++ b/GRF_Representativeness/stream1_forecast_based/summarize_stream1_results.m @@ -0,0 +1,6 @@ +function summary = summarize_stream1_results(results) +%SUMMARIZE_STREAM1_RESULTS Summarize stream 1 outputs. + +summary = table(numel(results.selected_days), mean(results.scores), ... + 'VariableNames', {'n_selected','mean_score'}); +end diff --git a/GRF_Representativeness/stream2_empirical_methods/apply_empirical_heuristics.m b/GRF_Representativeness/stream2_empirical_methods/apply_empirical_heuristics.m new file mode 100644 index 0000000..6f7f107 --- /dev/null +++ b/GRF_Representativeness/stream2_empirical_methods/apply_empirical_heuristics.m @@ -0,0 +1,7 @@ +function empirical = apply_empirical_heuristics(stream1Results, params) +%APPLY_EMPIRICAL_HEURISTICS Create heuristic ranking of days. + +scores = stream1Results.scores; +empirical.rank = tiedrank(scores + 0.01*randn(size(scores))); +empirical.top = find(empirical.rank <= params.representative_day_count); +end diff --git a/GRF_Representativeness/stream2_empirical_methods/compare_with_ks_method.m b/GRF_Representativeness/stream2_empirical_methods/compare_with_ks_method.m new file mode 100644 index 0000000..9d7916b --- /dev/null +++ b/GRF_Representativeness/stream2_empirical_methods/compare_with_ks_method.m @@ -0,0 +1,11 @@ +function comparison = compare_with_ks_method(empirical, stream1Results) +%COMPARE_WITH_KS_METHOD Compare empirical top days with KS-based selection. + +ksDays = stream1Results.selected_days(:); +empDays = empirical.top(:); +overlap = intersect(ksDays, empDays); + +comparison.overlap_count = numel(overlap); +comparison.overlap_fraction = numel(overlap) / max(1, numel(ksDays)); +comparison.overlap_days = overlap; +end diff --git a/GRF_Representativeness/stream2_empirical_methods/run_stream2.m b/GRF_Representativeness/stream2_empirical_methods/run_stream2.m new file mode 100644 index 0000000..d5416b6 --- /dev/null +++ b/GRF_Representativeness/stream2_empirical_methods/run_stream2.m @@ -0,0 +1,9 @@ +function results = run_stream2(paths, params, stream1Results) +%RUN_STREAM2 Execute stream 2 (empirical methods workflow). + +empirical = apply_empirical_heuristics(stream1Results, params); +comparison = compare_with_ks_method(empirical, stream1Results); +results = summarize_stream2_results(empirical, comparison); + +save(fullfile(paths.results_root, 'stream2', 'stream2_results.mat'), 'results'); +end diff --git a/GRF_Representativeness/stream2_empirical_methods/summarize_stream2_results.m b/GRF_Representativeness/stream2_empirical_methods/summarize_stream2_results.m new file mode 100644 index 0000000..35cfde0 --- /dev/null +++ b/GRF_Representativeness/stream2_empirical_methods/summarize_stream2_results.m @@ -0,0 +1,7 @@ +function results = summarize_stream2_results(empirical, comparison) +%SUMMARIZE_STREAM2_RESULTS Build stream 2 summary payload. + +results = struct(); +results.empirical = empirical; +results.comparison = comparison; +end diff --git a/GRF_Representativeness/stream3_utci_impact/compute_utci_for_selected_days.m b/GRF_Representativeness/stream3_utci_impact/compute_utci_for_selected_days.m new file mode 100644 index 0000000..a1fd1a0 --- /dev/null +++ b/GRF_Representativeness/stream3_utci_impact/compute_utci_for_selected_days.m @@ -0,0 +1,14 @@ +function utciData = compute_utci_for_selected_days(stream1Results, params) +%COMPUTE_UTCI_FOR_SELECTED_DAYS Compute UTCI for selected days. + +n = numel(stream1Results.selected_days); +gridSize = [10, 10]; + +tAir = 30 + randn([gridSize n]); +rh = 50 + 20*rand([gridSize n]); +wind = 2 + rand([gridSize n]); +mrt = tAir + 5*randn([gridSize n]); + +utci = compute_utci(tAir, rh, wind, mrt); +utciData = struct('utci', utci, 'n', n, 'params', params); +end diff --git a/GRF_Representativeness/stream3_utci_impact/quantify_utci_bias.m b/GRF_Representativeness/stream3_utci_impact/quantify_utci_bias.m new file mode 100644 index 0000000..d4546bb --- /dev/null +++ b/GRF_Representativeness/stream3_utci_impact/quantify_utci_bias.m @@ -0,0 +1,8 @@ +function bias = quantify_utci_bias(utciData, stream2Results) +%QUANTIFY_UTCI_BIAS Quantify UTCI bias metrics. + +utci = utciData.utci; +bias.mean_utci = mean(utci, 'all'); +bias.std_utci = std(utci, 0, 'all'); +bias.empirical_overlap_fraction = stream2Results.comparison.overlap_fraction; +end diff --git a/GRF_Representativeness/stream3_utci_impact/run_stream3.m b/GRF_Representativeness/stream3_utci_impact/run_stream3.m new file mode 100644 index 0000000..e7ed9ff --- /dev/null +++ b/GRF_Representativeness/stream3_utci_impact/run_stream3.m @@ -0,0 +1,9 @@ +function results = run_stream3(paths, params, stream1Results, stream2Results) +%RUN_STREAM3 Execute stream 3 (UTCI impact workflow). + +utci = compute_utci_for_selected_days(stream1Results, params); +bias = quantify_utci_bias(utci, stream2Results); +results = summarize_stream3_results(utci, bias); + +save(fullfile(paths.results_root, 'stream3', 'stream3_results.mat'), 'results'); +end diff --git a/GRF_Representativeness/stream3_utci_impact/summarize_stream3_results.m b/GRF_Representativeness/stream3_utci_impact/summarize_stream3_results.m new file mode 100644 index 0000000..76bfe1d --- /dev/null +++ b/GRF_Representativeness/stream3_utci_impact/summarize_stream3_results.m @@ -0,0 +1,5 @@ +function results = summarize_stream3_results(utciData, bias) +%SUMMARIZE_STREAM3_RESULTS Build stream 3 summary output. + +results = struct('utci', utciData, 'bias', bias); +end diff --git a/GRF_Representativeness/tests/test_compute_ks_distance.m b/GRF_Representativeness/tests/test_compute_ks_distance.m new file mode 100644 index 0000000..496fa0e --- /dev/null +++ b/GRF_Representativeness/tests/test_compute_ks_distance.m @@ -0,0 +1,18 @@ +function tests = test_compute_ks_distance +%TEST_COMPUTE_KS_DISTANCE Unit tests for compute_ks_distance. + +tests = functiontests(localfunctions); +end + +function testIdenticalSamples(testCase) +x = (1:10)'; +ks = compute_ks_distance(x, x); +verifyEqual(testCase, ks, 0, 'AbsTol', 1e-12); +end + +function testDifferentSamples(testCase) +x = (1:10)'; +y = (11:20)'; +ks = compute_ks_distance(x, y); +verifyGreaterThan(testCase, ks, 0.9); +end diff --git a/GRF_Representativeness/tests/test_compute_utci.m b/GRF_Representativeness/tests/test_compute_utci.m new file mode 100644 index 0000000..f836530 --- /dev/null +++ b/GRF_Representativeness/tests/test_compute_utci.m @@ -0,0 +1,14 @@ +function tests = test_compute_utci +%TEST_COMPUTE_UTCI Unit tests for compute_utci. + +tests = functiontests(localfunctions); +end + +function testUtciSize(testCase) +t = ones(5,4); +rh = 50*ones(5,4); +wind = 2*ones(5,4); +mrt = t + 3; +out = compute_utci(t, rh, wind, mrt); +verifySize(testCase, out, size(t)); +end diff --git a/GRF_Representativeness/tests/test_pipeline_smoke.m b/GRF_Representativeness/tests/test_pipeline_smoke.m new file mode 100644 index 0000000..12b83f7 --- /dev/null +++ b/GRF_Representativeness/tests/test_pipeline_smoke.m @@ -0,0 +1,15 @@ +function test_pipeline_smoke +%TEST_PIPELINE_SMOKE Lightweight smoke test for stream execution. + +paths = config_paths(); +params = config_parameters(); + +s1 = run_stream1(paths, params); +assert(isfield(s1, 'selected_days')); + +s2 = run_stream2(paths, params, s1); +assert(isfield(s2, 'comparison')); + +s3 = run_stream3(paths, params, s1, s2); +assert(isfield(s3, 'bias')); +end