From 240ac2e9b2d979f53dc94c9fc26002cca312b1f4 Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Sat, 29 Feb 2020 12:03:18 -0800 Subject: [PATCH 01/56] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dd22f70..3111634 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![DOI](https://zenodo.org/badge/190667091.svg)](https://zenodo.org/badge/latestdoi/190667091) [![MIT license](https://img.shields.io/badge/License-MIT-blue.svg)](https://lbesson.mit-license.org/) -[![Version](https://img.shields.io/badge/Version-1.1+-blue.svg)]() +[![Version](https://img.shields.io/badge/Version-2.1+-blue.svg)]() This program, originally released with [Sipkens et al. (2020a)][1_JAS1], is designed to invert tandem measurements of aerosol size distributions. @@ -434,7 +434,7 @@ evaluation of the Bayes factor for a range of methods, namely the in the `invert` package, this means that data uncertainties can be included in the procedure by giving `Lb*A` as an input to the program in the place of `A`. The methods general take `lambda` as a separate parameter, to promote the -stability of the algorithm. +stability of the algorithm. ### 4.5 +tools From 9bffd0193f2190019d85c0bf277416b6ab9f033e Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Sun, 1 Mar 2020 15:49:49 -0800 Subject: [PATCH 02/56] Update to overlay* functions This includes moving overlay_line from the Grid class to the tools package, to be consistent with other overlay functions. + Bug fixes in some of the overlay functions. --- +tools/overlay_isomass.m | 2 +- +tools/overlay_isorho.m | 2 +- +tools/overlay_line.m | 43 +++++++++++++++++++++++++++++++++++++++ +tools/overlay_phantom.m | 8 +++++++- +tools/plot2d_scatter.m | 4 ++-- @Grid/Grid.m | 44 ---------------------------------------- @Phantom/Phantom.m | 1 + main_bayes.m | 20 ++++++++++-------- 8 files changed, 67 insertions(+), 57 deletions(-) create mode 100644 +tools/overlay_line.m diff --git a/+tools/overlay_isomass.m b/+tools/overlay_isomass.m index a88a8a0..0d8d362 100644 --- a/+tools/overlay_isomass.m +++ b/+tools/overlay_isomass.m @@ -17,7 +17,7 @@ isom_vec = 10.^(-4:1:3); for ii=1:length(isom_vec) - hl = grid.overlay_line(... + hl = tools.overlay_line(grid,... [1,log10(6*isom_vec(ii)/(pi*10^3).*1e9)],-3); % line for rho_eff = 0.1 hl.Color = color; end diff --git a/+tools/overlay_isorho.m b/+tools/overlay_isorho.m index a4814b2..8767fb9 100644 --- a/+tools/overlay_isorho.m +++ b/+tools/overlay_isorho.m @@ -16,7 +16,7 @@ isorho_vec = 10.^(-1:1:7); for ii=1:length(isorho_vec) - hl = grid.overlay_line(... + hl = tools.overlay_line(grid,... [1,log10(isorho_vec(ii)*10^3./1e9)],3); % line for rho_eff = 0.1 hl.Color = color; end diff --git a/+tools/overlay_line.m b/+tools/overlay_line.m new file mode 100644 index 0000000..2c09335 --- /dev/null +++ b/+tools/overlay_line.m @@ -0,0 +1,43 @@ + +% OVERLAY_LINE Plots a line on top of the current grid +% Author: Timothy Sipkens, 2019-07-15 +% +% Inputs: +% logr0 A single point on the line +% slope Slope of the line +% c_spec Color specification string, e.g. 'k' for a black line +% Outputs: +% h Line object +%=========================================================================% + +function h = overlay_line(grid,logr0,slope,cspec) + +if ~exist('cspec','var'); cspec = 'w'; end + +if strcmp(get(gca,'XScale'),'log') % for log-scale plots + rmin = log10(min([grid.edges{:}])); + rmax = log10(max([grid.edges{:}])); + + hold on; + h = loglog(10.^[rmin,rmax],... + 10.^[logr0(2)+slope*(rmin-logr0(1)),... + logr0(2)+slope*(rmax-logr0(1))],cspec); + hold off; + +else % for linear scale plots + rmin = min([grid.edges{:}]); + rmax = max([grid.edges{:}]); + + hold on; + h = plot([rmin,rmax],... + [logr0(2)+slope*(rmin-logr0(1)),... + logr0(2)+slope*(rmax-logr0(1))],cspec); + hold off; +end + +if nargout==0; clear h; end + +end + + + diff --git a/+tools/overlay_phantom.m b/+tools/overlay_phantom.m index b7d0907..94802fe 100644 --- a/+tools/overlay_phantom.m +++ b/+tools/overlay_phantom.m @@ -5,6 +5,9 @@ function [pha,N] = overlay_phantom(x_pha,grid,color) +ylim_st = ylim; +xlim_st = xlim; + if isa(x_pha,'Phantom') pha = x_pha; grid = pha.grid; @@ -24,10 +27,13 @@ h1 = tools.overlay_ellipse(pha.mu,pha.Sigma,3); h1.Color = color; -h1 = grid.overlay_line(fliplr(pha.mu),... +h1 = tools.overlay_line(grid,fliplr(pha.mu),... pha.p.Dm); h1.Color = color; +ylim(ylim_st); % restore original plot bounds +xlim(xlim_st); + if nargout==0; clear pha N; end % prevent unnecessary output end diff --git a/+tools/plot2d_scatter.m b/+tools/plot2d_scatter.m index c9b378e..b0ad845 100644 --- a/+tools/plot2d_scatter.m +++ b/+tools/plot2d_scatter.m @@ -27,9 +27,9 @@ end clf; -hold on; for ii=1:length(m) - plot(log10(d(ii)),log10(m(ii)),'.',... + if ii==2; hold on; end + loglog(d(ii),m(ii),'.',... 'Color',color(ii,:),... 'MarkerSize',marker_size(ii)); % marker_size is also logscale diff --git a/@Grid/Grid.m b/@Grid/Grid.m index 8b13bb7..9c7d3eb 100644 --- a/@Grid/Grid.m +++ b/@Grid/Grid.m @@ -883,50 +883,6 @@ - %== OVERLAY_LINE =================================================% - % Plots a line on top of the current grid - % Author: Timothy Sipkens, 2019-07-15 - %-----------------------------------------------------------------% - % Inputs: - % logr0 A single point on the line - % slope Slope of the line - % c_spec Color specification string, e.g. 'k' for a black line - % Outputs: - % h Line object - %-----------------------------------------------------------------% - function h = overlay_line(obj,logr0,slope,cspec) - - if ~exist('cspec','var'); cspec = 'w'; end - - if strcmp(get(gca,'XScale'),'log') % for log-scale plots - rmin = log10(min([obj.edges{:}])); - rmax = log10(max([obj.edges{:}])); - - hold on; - h = loglog(10.^[rmin,rmax],... - 10.^[logr0(2)+slope*(rmin-logr0(1)),... - logr0(2)+slope*(rmax-logr0(1))],cspec); - hold off; - - else % for linear scale plots - rmin = min([obj.edges{:}]); - rmax = max([obj.edges{:}]); - - hold on; - h = plot([rmin,rmax],... - [logr0(2)+slope*(rmin-logr0(1)),... - logr0(2)+slope*(rmax-logr0(1))],cspec); - hold off; - end - - - - if nargout==0; clear h; end - - end - %=================================================================% - - %=====================================================================% %-- SUPPORT FOR PARTIAL GRIDS ----------------------------------------% diff --git a/@Phantom/Phantom.m b/@Phantom/Phantom.m index 9b11d35..d5fb973 100644 --- a/@Phantom/Phantom.m +++ b/@Phantom/Phantom.m @@ -439,6 +439,7 @@ N = y1(1); % scaling parameter denoting total number of particles disp('Complete.'); + disp(' '); end %=================================================================% end diff --git a/main_bayes.m b/main_bayes.m index 222928a..2187671 100644 --- a/main_bayes.m +++ b/main_bayes.m @@ -113,13 +113,15 @@ %% %== STEP 4: Visualize the results ========================================% -% ind = out_tk1.ind_min; -% x_plot = out_tk1(ind).x; -% out = out_tk1; +ind = out_tk1.ind_min; +x_plot = out_tk1(ind).x; +out = out_tk1; +lambda = out_tk1(ind).lambda; -[~,ind] = max([out_ed_lam.B]); -x_plot = out_ed_lam(ind).x; -out = out_ed_lam; +% [~,ind] = max([out_ed_lam.B]); +% x_plot = out_ed_lam(ind).x; +% out = out_ed_lam; +% lambda = lambda_ed_lam; @@ -142,6 +144,8 @@ % grid_x.plot2d(spo); % colorbar; + + %-- Exponential distance --% if iscell(phantom.Sigma) Gd = phantom.Sigma{1}; @@ -151,7 +155,7 @@ Lpr = invert.exp_dist_lpr(Gd,grid_x.elements(:,2),... grid_x.elements(:,1)); [~,spo] = tools.get_posterior(... - A,Lb,lambda_ed_lam.*Lpr); + A,Lb,lambda.*Lpr); figure(12); colormap(gcf,cm_alt); grid_x.plot2d(spo); @@ -161,7 +165,7 @@ %-{ %-- Plot regularization parameter selection schemes ----------------------% -figure(12); +figure(13); loglog([out.lambda],[out.chi]); % plot absolute Euclidean error hold on; loglog([out.lambda],-([out.B])); % plot Bayes factor From 5cc0b24ca976f8e4bf3467d8b4761e65f3a90cbc Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Tue, 3 Mar 2020 13:02:11 -0800 Subject: [PATCH 03/56] Updated Phantom.fit This function can now take the vec1 and vec2 vectors (lists of elements) directly. This allows compatibility with ungridded experimental data. --- +io/import_c.m | 24 ++++++++++++++++-------- @Grid/Grid.m | 2 +- @Phantom/Phantom.m | 16 ++++++++++++++-- README.md | 8 ++++---- 4 files changed, 35 insertions(+), 15 deletions(-) diff --git a/+io/import_c.m b/+io/import_c.m index 23af1e5..fd5fe9a 100644 --- a/+io/import_c.m +++ b/+io/import_c.m @@ -41,11 +41,12 @@ t = readtable(fn, 'HeaderLines',ii-1); %-- PMA setpoints -----------------------------------------------% - m_star = table2array(t(:,12)); - V = table2array(t(:,13)); - omega = table2array(t(:,14)); % centerline radial speed - p = table2array(t(:,15)); % PMA pressure - T = table2array(t(:,16)); % PMA tempreature + m_star = table2array(t(:,3)); + V = table2array(t(:,4)); + omega = table2array(t(:,5)); % centerline radial speed + Rm = table2array(t(:,6)); % CPMA implied resolution + p = table2array(t(:,7)); % PMA pressure + T = table2array(t(:,8)); % PMA tempreature prop_pma = kernel.prop_pma(' CPMA'); prop_pma.mass_mob_pref = 0.0612; % assume CPMA uses soot properties @@ -53,23 +54,30 @@ % prop.mass_mob_pref = 524; % prop.mass_mob_exp = 3; + prop_pma.Q = mean(table2array(t(:,9)))/1000/60; % current averages over all setpoint + % and does not support changing Q during measurements + clear sp; % reset PMA setpoints for ii=1:length(V) prop_pma.T = T(ii)+273.15; % pressure converted to Kelvin prop_pma.p = p(ii)/101325; % pressure converted to atm sp(ii,1) = tfer_pma.get_setpoint(prop_pma,... - 'omega',omega(ii),'V',V(ii)); + 'm_star',m_star(ii).*1e-18,'Rm',Rm(ii)); end %----------------------------------------------------------------% %-- DMA setpoints -----------------------------------------------% - d_star = table2array(t(:,4)); % DMA setpoints + d_star = table2array(t(:,12)); % DMA setpoints prop_dma = kernel.prop_dma(' Electrostatic Classifier Model 3080'); + prop_dma.Q_a = mean(table2array(t(:,9)))/1000/60; % aerosol flow (same as PMA) + prop_dma.Q_s = prop_dma.Q_a; + prop_dma.Q_c = mean(table2array(t(:,14)))/1000/60; % shield flow + prop_dma.Q_m = prop_dma.Q_c; %----------------------------------------------------------------% - data = table2array(t(:,19)); % data as a vector + data = table2array(t(:,20)); % data as a vector %-- Append to arrays --------------------------------------------% diff --git a/@Grid/Grid.m b/@Grid/Grid.m index 9c7d3eb..3814857 100644 --- a/@Grid/Grid.m +++ b/@Grid/Grid.m @@ -528,7 +528,7 @@ %-----------------------------------------------------------------% function [x,t1,t2] = vectorize(obj,x) if exist('x','var'); x = x(:); else; x = []; end - + if nargout>1; t1 = obj.elements(:,1); end if nargout>2; t2 = obj.elements(:,2); end end diff --git a/@Phantom/Phantom.m b/@Phantom/Phantom.m index d5fb973..4042e9b 100644 --- a/@Phantom/Phantom.m +++ b/@Phantom/Phantom.m @@ -415,10 +415,22 @@ % x - input data (2D distribution data) % grid - 'Grid' object on which input data is evaluated %-----------------------------------------------------------------% - function [phantom,N] = fit(x,grid) + function [phantom,N] = fit(x,vec1_grid,vec2) disp('Fitting phantom object...'); - [~,vec1,vec2] = grid.vectorize(); + + %-- Parse inputs ---------------------------------------------% + if isa(vec1_grid,'Grid') + grid = vec1_grid; + [~,vec1,vec2] = grid.vectorize(); + else + vec1 = vec1_grid(:); + vec2 = vec2(:); + grid = [min(vec1),max(vec1);min(vec2),max(vec2)]; + % specify a span for a grid + end + %-------------------------------------------------------------% + corr2cov = @(sigma,R) diag(sigma)*R*diag(sigma); diff --git a/README.md b/README.md index 3111634..c93e860 100644 --- a/README.md +++ b/README.md @@ -113,20 +113,20 @@ one must have knowledge of the setpoint before computing `A`. Accordingly, the data may first be imported prior to Step 2A. Also in this step, one should include some definition of the expected uncertainties in each point in `b`, encoded in the matrix -`Lb`. For those cases involving counting noise, this can be +`Lb`. For those cases involving simple counting noise, this can be approximated as ```Matlab Lb = theta*diag(sqrt(b)); ``` - where `theta` is related to the total number of particle counts as described in -[Sipkens et al. (2020a)][1_JAS1]. The function `add_noise` is + where `theta` is related to the total number of particle counts as described in +[Sipkens et al. (2020a)][1_JAS1]. The function `get_noise` is included in the `+tools` package to help with noise creation, and more information on the noise model is provided in [Sipkens et al. (2017)][6_AO17]. - **STEP 3**: With this information, one can proceed to implement various inversion -approaches, such as those available in the `+invert` package described below. +approaches, such as those available in the `invert` package described below. Preset groupings on inversion approaches are available in the `run_inversions*` scripts, also described below. From 26596c1599c3c57559b3eb120f8aa293a743df00 Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Tue, 3 Mar 2020 17:28:17 -0800 Subject: [PATCH 04/56] Updated n_B in get_setpoint + Other updateds to get_setpoint. + Added contourf functionality to plot2d functions. + Update to mass-mobility relation parameters in io.import_c. + Added fblue colormap. + Minor README updates. --- +io/import_c.m | 12 ++++---- +tfer_pma/get_setpoint.m | 58 +++++++++++++++++++++++++++++++++------ .gitignore | 1 + @Grid/Grid.m | 30 ++++++++++++++------ README.md | 16 ++++------- cmap/fblue.mat | Bin 0 -> 305 bytes 6 files changed, 84 insertions(+), 33 deletions(-) create mode 100644 cmap/fblue.mat diff --git a/+io/import_c.m b/+io/import_c.m index fd5fe9a..7345a93 100644 --- a/+io/import_c.m +++ b/+io/import_c.m @@ -49,10 +49,10 @@ T = table2array(t(:,8)); % PMA tempreature prop_pma = kernel.prop_pma(' CPMA'); - prop_pma.mass_mob_pref = 0.0612; % assume CPMA uses soot properties - prop_pma.mass_mob_exp = 2.48; - % prop.mass_mob_pref = 524; - % prop.mass_mob_exp = 3; + % prop_pma.mass_mob_pref = 0.0612; % assume CPMA uses soot properties + % prop_pma.mass_mob_exp = 2.48; + prop.mass_mob_pref = 524; + prop.mass_mob_exp = 3; prop_pma.Q = mean(table2array(t(:,9)))/1000/60; % current averages over all setpoint % and does not support changing Q during measurements @@ -61,8 +61,10 @@ for ii=1:length(V) prop_pma.T = T(ii)+273.15; % pressure converted to Kelvin prop_pma.p = p(ii)/101325; % pressure converted to atm + % sp(ii,1) = tfer_pma.get_setpoint(prop_pma,... + % 'm_star',m_star(ii).*1e-18,'Rm',Rm(ii)); sp(ii,1) = tfer_pma.get_setpoint(prop_pma,... - 'm_star',m_star(ii).*1e-18,'Rm',Rm(ii)); + 'V',V(ii),'omega',omega(ii)); end %----------------------------------------------------------------% diff --git a/+tfer_pma/get_setpoint.m b/+tfer_pma/get_setpoint.m index 66e2565..297edac 100644 --- a/+tfer_pma/get_setpoint.m +++ b/+tfer_pma/get_setpoint.m @@ -149,7 +149,7 @@ %-- Use definition of Rm to derive angular speed at centerline -------% %-- See Reavell et al. (2011) for resolution definition --% - n_B = -0.6436; + n_B = get_nb(sp.m_star,prop); B_star = tfer_pma.mp2zp(sp.m_star,1,prop.T,prop.p,prop); % involves invoking mass-mobility relation % z = 1 for the setpoint @@ -177,17 +177,57 @@ %-- Calculate resolution -------------------------------------------------% if isempty(sp.Rm) % if resolution is not specified - n_B = -0.6436; - B_star = tfer_pma.mp2zp(sp.m_star,1,prop.T,prop.p,prop); - t0 = prop.Q/(sp.m_star*B_star*2*pi*prop.L*... - sp.omega^2*prop.rc^2); - m_rat = @(Rm) 1/Rm+1; - fun = @(Rm) (m_rat(Rm))^(n_B+1)-(m_rat(Rm))^n_B; - sp.Rm = fminsearch(@(Rm) (t0-fun(Rm))^2,5); - sp.m_max = sp.m_star*(1/sp.Rm+1); + [sp.Rm,sp.m_max] = get_resolution(sp.m_star,sp.omega,prop); + % evaluate resolution in corresponding subfunction + % involves a minimization routine end m_star = sp.m_star; % output m_star independently end + + + + +%== GET_RESOLUTION =======================================================% +% Solver to evaluate the resolution from m_star and prop. +function [Rm,m_max] = get_resolution(m_star,omega,prop) + +n_B = get_nb(m_star,prop); + +B_star = tfer_pma.mp2zp(m_star,1,... + prop.T,prop.p,prop); % mechanical mobility for z = 1 + +t0 = prop.Q/(m_star*B_star*2*pi*prop.L*... + omega^2*prop.rc^2); % RHS of Eq. (10) in Reveall et al. + +m_rat = @(Rm) 1/Rm+1; % function for mass ratio +fun = @(Rm) (m_rat(Rm))^(n_B+1)-(m_rat(Rm))^n_B; % LHS of Eq. (10) in Reveall et al. + +Rm = fminsearch(@(Rm) (t0-fun(Rm))^2,5); % minimization ot find Rm +m_max = m_star*(1/Rm+1); % approx. upper end of non-diffusing tfer. function + +end + + + +%== GET_NB ===============================================================% +% Function to evaluate n_B constant. Taken from Olfert laboratory. +% Note: Previous versions of this program would output a constant +% value of n_B = -0.6436. This will cause some rather minor +% compatiblity issues. +function n_B = get_nb(m_star,prop) + +m_high = m_star*1.001; % perturb m_star up +m_low = m_star*.999; % perturb m_star down + +B_high = tfer_pma.mp2zp(m_high,1,prop.T,prop.p,prop); +B_low = tfer_pma.mp2zp(m_low,1,prop.T,prop.p,prop); + +n_B = log10(B_high/B_low)/log10(m_high/m_low); % constant from Reveall et al. + +% n_B = -0.6436; % deprecated value + +end + diff --git a/.gitignore b/.gitignore index c9b725c..da72210 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ main_tfer.m CPMASP2KernelV0.m ImportSP2.m +kernel/gen_c2.m +cmap/correct.m test_import.m test_cred.m diff --git a/@Grid/Grid.m b/@Grid/Grid.m index 3814857..1fce8f5 100644 --- a/@Grid/Grid.m +++ b/@Grid/Grid.m @@ -652,7 +652,10 @@ %== PLOT2D =======================================================% % Plots x as a 2D function on the grid. % Author: Timothy Sipkens, 2018-11-21 - function [h,x] = plot2d(obj,x) + function [h,x] = plot2d(obj,x,f_contf) + + if ~exist('f_contf','var'); f_contf = []; end % set empty contourf flag + if isempty(f_contf); f_contf = 0; end % set contourf flag to false %-- Issue warning if grid edges are not be uniform -----------% % The imagesc function used here does not conserve proportions. @@ -664,8 +667,13 @@ x = obj.reshape(x); - imagesc(obj.edges{2},obj.edges{1},x); - set(gca,'YDir','normal'); + %-- Plot -------------------------------% + if f_contf==0 % plot as image + imagesc(obj.edges{2},obj.edges{1},x); + set(gca,'YDir','normal'); + else % plot as contourf + contourf(obj.edges{2},obj.edges{1},x,15,'EdgeColor','none'); + end %-- Adjust tick marks for log scale ----% if strcmp('logarithmic',obj.discrete) @@ -686,13 +694,17 @@ %== PLOT2D_MARG ==================================================% % Plots x as a 2D function on the grid, with marginalized distributions. % Author: Timothy Sipkens, 2018-11-21 - function [h,x_m] = plot2d_marg(obj,x,obj_t,x_t) + function [h,x_m] = plot2d_marg(obj,x,obj_t,x_t,f_contf) + + if ~exist('f_contf','var'); f_contf = []; end % set empty contourf flag + if ~exist('x_t','var'); x_t = []; end subplot(4,4,[5,15]); - obj.plot2d(x); + obj.plot2d(x,f_contf); x_m = obj.marginalize(x); + %-- Plot marginal distribution (dim 2) -----------------------% subplot(4,4,[1,3]); marg_dim = 2; @@ -701,7 +713,8 @@ xlim([min(obj.edges{marg_dim}),max(obj.edges{marg_dim})]); set(gca,'XScale','log'); - if nargin>2 % also plot marginal of the true distribution + %-- Also plot marginal of the true distribution --------------% + if ~isempty(x_t) x_m_t = obj_t.marginalize(x_t); hold on; @@ -710,6 +723,7 @@ hold off; end + %-- Plot marginal distribution (dim 1) -----------------------% subplot(4,4,[8,16]); marg_dim = 1; @@ -718,8 +732,8 @@ ylim([min(obj.edges{marg_dim}),max(obj.edges{marg_dim})]); set(gca,'YScale','log'); - if nargin>2 % also plot marginal of the true distribution - hold on; + %-- Also plot marginal of the true distribution --------------% + if ~isempty(x_t) plot([0;x_m_t{marg_dim}],... obj_t.nodes{marg_dim},'color',[0.6,0.6,0.6]); hold off; diff --git a/README.md b/README.md index c93e860..6b9f3ad 100644 --- a/README.md +++ b/README.md @@ -341,13 +341,7 @@ that work. The package uses a `sp` structure to define the PMA setpoints. #### 4.1.1 sp The `sp` or setpoint structure is a structured array containing the information -necessary to define the device setpoints. For a DMA, the setpoint mobility diameter, -`d_star`, is sufficient to accomplish this task. For a PMA, a pair of parameters -is required to establish the setpoint. Pairings can be converted into a `sp` structured -array using the `get_setpoint` function included with the `tfer_pma` package described -below. Generally, this function can be placed inside a loop that generates an entry -in `sp` for each available setpoint. The output structure will contain all of the -relevant parameters that could be used to specify that setpoint, including +necessary to define the PMA setpoints. Defining the quantity requires a pair of parameters and a property structure defining the physical dimensions of the PMA. Pairings can be converted into a `sp` structured array using the `get_setpoint` function included with the `tfer_pma` package described below. Generally, this function can be placed inside a loop that generates an entry in `sp` for each available setpoint. The output structure will contain all of the relevant parameters that could be used to specify that setpoint, including mass setpoint (assuming a singly charged particle), `m_star`; the resolution, `Rm`; the voltage, `V`; and the electrode speeds, `omega*`. A sample `sp` is shown below. @@ -360,12 +354,12 @@ the voltage, `V`; and the electrode speeds, `omega*`. A sample `sp` is shown bel | ... | As an example, the array can be generated from a vector of mass setpoints assuming -a resolution of *R*m = 10 and PMA properties specified -in `prop_pma` using: +a resolution of *R*m = 10 and PMA properties specified in `prop_pma` using: ```Matlab -sp(ii) = tfer_pma.get_setpoint(prop_pma,... - 'm_star',m_star,'Rm',10); % generate iith setpoint +m_star = 1e-18.*logspace(log10(0.1),log10(100),25); % mass setpoints +sp = tfer_pma.get_setpoint(prop_pma,... + 'm_star',m_star,'Rm',10); % get PMA setpoints ``` #### 4.1.2 grid_b diff --git a/cmap/fblue.mat b/cmap/fblue.mat new file mode 100644 index 0000000000000000000000000000000000000000..f2d5c1402e2c6dc3921deafe7d03826647dc0d93 GIT binary patch literal 305 zcmeZu4DoSvQZUssQ1EpO(M`+DN!3vZ$Vn_o%P-2cQV4Jk_w+L}(NSD#Ffvde5-`93qo*%Fki8O!E9TgqKF#HrD8l-n zmxKAKkmn=cYtA!Ho{6Yr)PML%DKAxFqLIrw&X*;VCK(mY{Q14;N7mMFQg0@7q{J%O zU9S7N?UR*t#v{YR&gd@Hi48m7s~KiJzFzoB$ Date: Tue, 3 Mar 2020 18:47:13 -0800 Subject: [PATCH 05/56] Minor update to io.import_c.m --- +io/import_c.m | 2 +- +tfer_pma/mp2zp.m | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/+io/import_c.m b/+io/import_c.m index 7345a93..473dc37 100644 --- a/+io/import_c.m +++ b/+io/import_c.m @@ -51,7 +51,7 @@ prop_pma = kernel.prop_pma(' CPMA'); % prop_pma.mass_mob_pref = 0.0612; % assume CPMA uses soot properties % prop_pma.mass_mob_exp = 2.48; - prop.mass_mob_pref = 524; + prop.mass_mob_pref = pi*1000/6; prop.mass_mob_exp = 3; prop_pma.Q = mean(table2array(t(:,9)))/1000/60; % current averages over all setpoint diff --git a/+tfer_pma/mp2zp.m b/+tfer_pma/mp2zp.m index 9b8964e..a959139 100644 --- a/+tfer_pma/mp2zp.m +++ b/+tfer_pma/mp2zp.m @@ -32,8 +32,8 @@ if or(isempty(prop),... ~and(isfield(prop,'mass_mob_pref'),... isfield(prop,'mass_mob_exp'))) % get parameters for the mass-mobility relation - mass_mob_pref = 0.0612; - mass_mob_exp = 2.48; + mass_mob_pref = 0.0612; % 524; + mass_mob_exp = 2.48; % 3; else mass_mob_pref = prop.mass_mob_pref; mass_mob_exp = prop.mass_mob_exp; From 920b7db5624bde36794aa0ab50ac316145a68c5e Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Tue, 3 Mar 2020 22:39:56 -0800 Subject: [PATCH 06/56] Update import_c.m --- +io/import_c.m | 1 + 1 file changed, 1 insertion(+) diff --git a/+io/import_c.m b/+io/import_c.m index 473dc37..09ea873 100644 --- a/+io/import_c.m +++ b/+io/import_c.m @@ -56,6 +56,7 @@ prop_pma.Q = mean(table2array(t(:,9)))/1000/60; % current averages over all setpoint % and does not support changing Q during measurements + prop_pma.v_bar = prop_pma.Q/prop_pma.A; % average flow velocity clear sp; % reset PMA setpoints for ii=1:length(V) From 8436d016d21bf0cc0bc654db555afe0ab55ff8f3 Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Wed, 4 Mar 2020 00:16:55 -0800 Subject: [PATCH 07/56] Bug fixes to Grid + Bug fix to mass2rho + Added forange colormap --- +tools/mass2rho.m | 2 +- @Grid/Grid.m | 21 ++++++++++++--------- cmap/forange.mat | Bin 0 -> 434 bytes main_bayes.m | 3 ++- 4 files changed, 15 insertions(+), 11 deletions(-) create mode 100644 cmap/forange.mat diff --git a/+tools/mass2rho.m b/+tools/mass2rho.m index c2e265b..0504a7a 100644 --- a/+tools/mass2rho.m +++ b/+tools/mass2rho.m @@ -21,7 +21,7 @@ [n_rho,length(grid_x.edges{2})],'logarithmic'); % generate grid -x_rs = reshape(x,grid_x.ne); +x_rs = grid_x.reshape(x); n_d = grid_x.ne(2); y = zeros(grid_x.ne(2),length(rho_n)); diff --git a/@Grid/Grid.m b/@Grid/Grid.m index 1fce8f5..265c822 100644 --- a/@Grid/Grid.m +++ b/@Grid/Grid.m @@ -491,8 +491,9 @@ x = obj.reshape(x); [dr,dr1,dr2] = obj.dr; % generate differential area of elements + dr = obj.reshape(dr); % fills out dr for partial grids - tot = sum(x(:).*dr); % integrated total + tot = sum(x(:).*dr(:)); % integrated total marg{1} = sum(dr2.*x,2); % integrate over diameter marg{2} = sum(dr1.*x,1); % integrate over mass @@ -659,8 +660,9 @@ %-- Issue warning if grid edges are not be uniform -----------% % The imagesc function used here does not conserve proportions. - dr = obj.dr; - if ~all(abs(dr(2:end)-dr(1))<1e-10) + [~,dr1,dr2] = obj.dr; % used to give warning below + dr0 = dr1(:).*dr2(:); + if ~all(abs(dr0(2:end)-dr0(1))<1e-10) warning(['The plot2d method does not display ',... 'correct proportions for non-uniform grids.']); end @@ -734,6 +736,7 @@ %-- Also plot marginal of the true distribution --------------% if ~isempty(x_t) + hold on; plot([0;x_m_t{marg_dim}],... obj_t.nodes{marg_dim},'color',[0.6,0.6,0.6]); hold off; @@ -905,14 +908,14 @@ %== PARTIAL ======================================================% % Convert to a partial grid. Currently takes a y-intercept, r0, % and slope as arguements and cuts upper triangle. - function obj = partial(obj,r0,slope) + function obj = partial(obj,r0,slope0) - if ~exist('slope','var'); slope = []; end - if isempty(slope); slope = 1; end + if ~exist('slope','var'); slope0 = []; end + if isempty(slope0); slope0 = 1; end if ~exist('r0','var'); r0 = []; end if length(r0)==1; b = r0; end % if scalar, use as y-intercept - if length(r0)==2; b = r0(1)-slope*r0(2); end + if length(r0)==2; b = r0(1)-slope0*r0(2); end % if coordinates, find y-intercept if isempty(r0); b = 0; end % if not specified, use b = 0 @@ -922,13 +925,13 @@ t0 = obj.elements; end - f_above = t0(:,1)>(t0(:,2).*slope+b); + f_above = t0(:,1)>(t0(:,2).*slope0+b); t1 = 1:length(f_above); %-- Update grid properties -----------------% obj.ispartial = 1; obj.missing = t1(f_above); - obj.cut = [b,slope]; + obj.cut = [b,slope0]; obj.elements = obj.elements(~f_above,:); obj.nelements = obj.nelements(~f_above,:); diff --git a/cmap/forange.mat b/cmap/forange.mat new file mode 100644 index 0000000000000000000000000000000000000000..757bf787416662d4481b2e881630fd15c2251a5a GIT binary patch literal 434 zcmeZu4DoSvQZUssQ1EpO(M`+DN!3vZ$Vn_o%P-2cQV4Jk_w+L}(NSI+KJWee#&xE& znDpMQ*I#r0*`V@$ZEZl5x5w|^?ce5CK3+C2usXf|)BDzP?&|wDuh+lS;1sLL|9^Vz z(+e`4-@mPm5}qDk^>3ut@al8k zsm!%sY^d@4^P9`xjJM?N2JOBKP|Z}9<%+q&VI4iR^wsW)m%e_N|N9$t>7_rvuc=SS7Yse8|LNsITZXg#_3r>hNxzQ( literal 0 HcmV?d00001 diff --git a/main_bayes.m b/main_bayes.m index 2187671..6d7d134 100644 --- a/main_bayes.m +++ b/main_bayes.m @@ -147,6 +147,7 @@ %-- Exponential distance --% +%{ if iscell(phantom.Sigma) Gd = phantom.Sigma{1}; else @@ -163,8 +164,8 @@ -%-{ %-- Plot regularization parameter selection schemes ----------------------% +%-{ figure(13); loglog([out.lambda],[out.chi]); % plot absolute Euclidean error hold on; From d0f478f79b1d219de3ef2f3ba9418fd509f7a06b Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Wed, 4 Mar 2020 01:49:40 -0800 Subject: [PATCH 08/56] Added lower cut for partial grids --- @Grid/Grid.m | 64 ++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/@Grid/Grid.m b/@Grid/Grid.m index 265c822..49eb416 100644 --- a/@Grid/Grid.m +++ b/@Grid/Grid.m @@ -476,6 +476,20 @@ % upper, left triangle dr = t0+t1+t2; + if length(obj.cut)==4 % Note: Ignores if both rays pass through element + [~,r_min,r_max] = obj.ray_sum([0,obj.cut(3)],obj.cut(4),0); + t0 = (log10(obj.nelements(:,2))-r_max(:,1)).*... + (log10(obj.nelements(:,2))-log10(obj.nelements(:,1))); + % upper rectangle + t1 = (r_min(:,2)-log10(obj.nelements(:,3))).*... + (r_max(:,1)-log10(obj.nelements(:,1))); + % left rectangle + t2 = 1/2.*(r_max(:,1)-r_min(:,1)).*... + (r_max(:,2)-r_min(:,2)); + % lower, right triangle + dr = t0+t1+t2; + end + end %-------------------------------------------------------------% end @@ -599,7 +613,7 @@ %-- Ray-sum matrix ---------% [~,jj,a] = find(chord'); if ~isempty(a) - C(ii,:) = sparse(1,jj,a,1,obj.Ne,ceil(0.1*obj.Ne)); + C(ii,:) = sparse(1,jj,a,1,obj.Ne,ceil(0.5*obj.Ne)); end if f_bar, tools.textbar(ii/m); end @@ -907,17 +921,29 @@ %== PARTIAL ======================================================% % Convert to a partial grid. Currently takes a y-intercept, r0, - % and slope as arguements and cuts upper triangle. - function obj = partial(obj,r0,slope0) + % and slope0 as arguements and cuts upper triangle. + % Added r1 and slope1 arguments will also cut a lower triangle. + function obj = partial(obj,r0,slope0,r1,slope1) - if ~exist('slope','var'); slope0 = []; end + %-- Parse inputs ----------------------------% + if ~exist('slope0','var'); slope0 = []; end if isempty(slope0); slope0 = 1; end if ~exist('r0','var'); r0 = []; end - if length(r0)==1; b = r0; end % if scalar, use as y-intercept - if length(r0)==2; b = r0(1)-slope0*r0(2); end - % if coordinates, find y-intercept - if isempty(r0); b = 0; end % if not specified, use b = 0 + if length(r0)==1; b0 = r0; end % if scalar, use as y-intercept + if length(r0)==2; b0 = r0(1)-slope0*r0(2); end % if coordinates, find y-intercept + if isempty(r0); b0 = 0; end % if not specified, use b = 0 + + %-- For bottom triangle --% + if ~exist('slope1','var'); slope1 = []; end + if isempty(slope1); slope1 = 0; end + + if ~exist('r1','var'); r1 = -inf; end + if length(r1)==1; b1 = r1; end % if scalar, use as y-intercept + if length(r1)==2; b1 = r1(1)-slope1*r1(2); end % if coordinates, find y-intercept + if isempty(r1); b1 = 0; end % if not specified, use b = 0 + %--------------------------------------------% + if strcmp(obj.discrete,'logarithmic') t0 = log10(obj.elements); @@ -925,16 +951,26 @@ t0 = obj.elements; end - f_above = t0(:,1)>(t0(:,2).*slope0+b); - t1 = 1:length(f_above); + %-- Cut upper triangle ---------------------% + f_missing = t0(:,1)>(t0(:,2).*slope0+b0); + t1 = 1:prod(obj.ne); + obj.cut = [b0,slope0]; + + + %-- Consider cutting lower triangle --------% + if ~isinf(slope1) + f_missing1 = t0(:,1)<(t0(:,2).*slope1+b1); + f_missing = or(f_missing,f_missing1); + obj.cut = [obj.cut,b1,slope1]; + end + %-- Update grid properties -----------------% obj.ispartial = 1; - obj.missing = t1(f_above); - obj.cut = [b,slope0]; + obj.missing = t1(f_missing); - obj.elements = obj.elements(~f_above,:); - obj.nelements = obj.nelements(~f_above,:); + obj.elements = obj.elements(~f_missing,:); + obj.nelements = obj.nelements(~f_missing,:); obj.Ne = size(obj.elements,1); obj = obj.padjacency; From f53a659f914ea5420af55c9b9703195bfd8fe7ea Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Wed, 4 Mar 2020 13:44:38 -0800 Subject: [PATCH 09/56] Rename prop_pma.Dm and prop_pma.rho0 This is associated with syncing the program with updates to the tfer_pma package. Default mass-mobiltiy relations used in defining the PMA setpoint are now specifies in the prop_pma functions instead of in the mp2zp function. + Minor updates to import_c. + Minor bug fix in Grid class. --- +io/import_c.m | 25 ++++++++++++++----------- +kernel/prop_pma.m | 33 ++++++++++++++++++++++----------- +tfer_pma/mp2zp.m | 14 +++++--------- +tfer_pma/prop_pma.m | 35 +++++++++++++++++------------------ @Grid/Grid.m | 4 ++-- 5 files changed, 60 insertions(+), 51 deletions(-) diff --git a/+io/import_c.m b/+io/import_c.m index 09ea873..93184e8 100644 --- a/+io/import_c.m +++ b/+io/import_c.m @@ -1,16 +1,20 @@ % IMPORT_C Read tandem CPMA-SMPS data from a list-format (newer format) CSV file. % Author: Timothy Sipkens, 2019-12-17 +% +% Note: +% The prop_pma input woudl override deriving some of the properties from +% the imported data file. %=========================================================================% function [data0,d_star0,sp0,prop_dma,prop_pma,out] = ... - import_c(fns) + import_c(fnames,prop_pma) -if ~isstruct(fns) - t0 = fns; - fns = struct(); - [fns.folder,fns.name,ext] = fileparts(t0); - fns.name = [fns.name,ext]; +if ~isstruct(fnames) + t0 = fnames; + fnames = struct(); + [fnames.folder,fnames.name,ext] = fileparts(t0); + fnames.name = [fnames.name,ext]; end data0 = []; @@ -18,10 +22,10 @@ disp('Reading files...'); -N = length(fns); % number of files to read +N = length(fnames); % number of files to read if N>1; tools.textbar(0); end for ff=1:N - fn = [fns(ff).folder,'\',fns(ff).name]; + fn = [fnames(ff).folder,'\',fnames(ff).name]; %== Open file and read data =========% @@ -48,9 +52,8 @@ p = table2array(t(:,7)); % PMA pressure T = table2array(t(:,8)); % PMA tempreature - prop_pma = kernel.prop_pma(' CPMA'); - % prop_pma.mass_mob_pref = 0.0612; % assume CPMA uses soot properties - % prop_pma.mass_mob_exp = 2.48; + if ~exist('prop_pma','var'); prop_pma = []; end + if isempty(prop_pma); prop_pma = kernel.prop_pma(' CPMA'); end prop.mass_mob_pref = pi*1000/6; prop.mass_mob_exp = 3; diff --git a/+kernel/prop_pma.m b/+kernel/prop_pma.m index 04c1cab..31770db 100644 --- a/+kernel/prop_pma.m +++ b/+kernel/prop_pma.m @@ -13,11 +13,11 @@ function [prop] = prop_pma(opts) if ~exist('opts','var'); opts = []; end -if isempty(opts); opts = 'Olfert'; end +if isempty(opts); opts = 'olfert'; end -prop.mass_mob_pref = 524; -prop.mass_mob_exp = 3; +prop.rho0 = pi*1000/6; % ~524; +prop.Dm = 3; %-- For soot --% % prop.mass_mob_pref = 0.0612; @@ -26,7 +26,7 @@ switch opts %-- CPMA parameters from Olfert lab ----------------------------------% - case {'Olfert','cpma',' CPMA'} + case {'olfert','cpma',' CPMA'} prop.r1 = 0.06; % inner electrode radius [m] prop.r2 = 0.061; % outer electrode radius [m] prop.L = 0.2; % length of chamber [m] @@ -36,7 +36,7 @@ prop.omega_hat = 32/33; % ratio of angular speeds %-- CPMA/APM parameters from Buckley et al. --------------------------% - case 'Buckley' + case 'buckley' prop.r2 = 0.025; % outer electrode radius [m] prop.r1 = 0.024; % inner electrode radius [m] prop.L = 0.1; % length of APM [m] @@ -48,7 +48,7 @@ prop.p = 1; % system pressure [atm] %-- CPMA parameters from Olfert lab ----------------------------------% - case {'FlareNet18','fn18'} + case {'flarenet18','fn18'} prop.r1 = 0.06; % inner electrode radius [m] prop.r2 = 0.061; % outer electrode radius [m] prop.L = 0.2; % length of chamber [m] @@ -56,11 +56,11 @@ prop.T = 293; % system temperature [K] prop.Q = 0.3/1000/60; % volume flow rate (m^3/s) (prev: ~1 lpm) prop.omega_hat = 32/33; % ratio of angular speed - prop.mass_mob_pref = 524; - prop.mass_mob_exp = 3; + prop.rho0 = pi*1000/6; % ~524; + prop.Dm = 3; %-- APM parameters from Ehara et al. -------------% - case 'Ehara' + case 'ehara' prop.r2 = 0.103; % outer electrode radius [m] prop.r1 = 0.1; % inner electrode radius [m] prop.L = 0.2; % length of APM [m] @@ -71,7 +71,7 @@ %-- Parameters from Olfert and Collings -------------% % Nearly identical to the Ehara et al. case - case 'Olfert-Collings' + case 'olfert-collings' prop.r2 = 0.103; % outer electrode radius [m] prop.r1 = 0.1; % inner electrode radius [m] prop.L = 0.2; @@ -81,7 +81,7 @@ prop.p = 1; % system pressure [atm] %-- Parameters from Kuwata --------------------------% - case 'Kuwata' + case 'kuwata' prop.r2 = 0.052; % outer electrode radius [m] prop.r1 = 0.05; % inner electrode radius [m] prop.L = 0.25; @@ -89,6 +89,17 @@ prop.Q = 1.67e-5; % aerosol flowrate [m^3/s] prop.T = 295; % system temperature [K] prop.p = 1; % system pressure [atm] + + case 'santavac' + prop.r1 = 0.06; % inner electrode radius [m] + prop.r2 = 0.061; % outer electrode radius [m] + prop.L = 0.2; % length of chamber [m] + prop.p = 1; % pressure [atm] + prop.T = 293; % system temperature [K] + prop.Q = 0.3/1000/60; % volume flow rate (m^3/s) (prev: ~1 lpm) + prop.omega_hat = 32/33; % ratio of angular speed + prop.rho0 = pi*1000/6; % ~524; + prop.Dm = 3; end diff --git a/+tfer_pma/mp2zp.m b/+tfer_pma/mp2zp.m index a959139..fe91909 100644 --- a/+tfer_pma/mp2zp.m +++ b/+tfer_pma/mp2zp.m @@ -4,7 +4,6 @@ %=========================================================================% function [Zp,B,d] = mp2zp(m,z,T,P,prop) - %-------------------------------------------------------------------------% % Inputs: % m Particle mass @@ -30,18 +29,15 @@ if ~exist('prop','var'); prop = []; end if or(isempty(prop),... - ~and(isfield(prop,'mass_mob_pref'),... - isfield(prop,'mass_mob_exp'))) % get parameters for the mass-mobility relation - mass_mob_pref = 0.0612; % 524; - mass_mob_exp = 2.48; % 3; -else - mass_mob_pref = prop.mass_mob_pref; - mass_mob_exp = prop.mass_mob_exp; + ~and(isfield(prop,'rho0'),... + isfield(prop,'Dm'))) % get parameters for the mass-mobility relation + error(['Please specify the mass-mobility relation parameters ',... + 'in the prop structure.']); end %-------------------------------------------------------------------------% -d = (m./mass_mob_pref).^(1/mass_mob_exp); +d = (m./prop.rho0).^(1/prop.Dm); % use mass-mobility relationship to get mobility diameter diff --git a/+tfer_pma/prop_pma.m b/+tfer_pma/prop_pma.m index e4ea2dd..e9baa4a 100644 --- a/+tfer_pma/prop_pma.m +++ b/+tfer_pma/prop_pma.m @@ -14,16 +14,21 @@ %-------------------------------------------------------------------------% -if ~exist('opts','var') % if properties set is not specified - opts = 'Olfert'; +if ~exist('opts','var') % if type of property set is not specified + opts = 'olfert'; elseif isempty(opts) - opts = 'Olfert'; + opts = 'olfert'; end +%-- Default mass-mobility information -------------% +prop.Dm = 524; % mass-mobility exponent +prop.rho0 = 3; % mass-mobility relation density +% Common alternate: Dm = 2.48; rho0 = 0.0612; + switch opts %-- CPMA parameters from Olfert lab ----------------------------------% - case 'Olfert' + case 'olfert' prop.r1 = 0.06; % inner electrode radius [m] prop.r2 = 0.061; % outer electrode radius [m] prop.L = 0.2; % length of chamber [m] @@ -33,7 +38,7 @@ prop.omega_hat = 32/33; % ratio of angular speeds %-- CPMA/APM parameters from Buckley et al. --------------------------% - case 'Buckley' + case 'buckley' prop.r2 = 0.025; % outer electrode radius [m] prop.r1 = 0.024; % inner electrode radius [m] prop.L = 0.1; % length of APM [m] @@ -44,18 +49,8 @@ prop.T = 298; % system temperature [K] prop.p = 1; % system pressure [atm] - %-- CPMA parameters from Olfert lab ----------------------------------% - case 'FlareNet18' - prop.r1 = 0.06; % inner electrode radius [m] - prop.r2 = 0.061; % outer electrode radius [m] - prop.L = 0.2; % length of chamber [m] - prop.p = 1; % pressure [atm] - prop.T = 293; % system temperature [K] - prop.Q = 0.3/1000/60;%0.3/1000/60;%1.5/1000/60; % volume flow rate (m^3/s) (prev: ~1 lpm) - prop.omega_hat = 32/33; % ratio of angular speeds - %-- APM parameters from Ehara et al. -------------% - case 'Ehara' + case 'ehara' prop.r2 = 0.103; % outer electrode radius [m] prop.r1 = 0.1; % inner electrode radius [m] prop.L = 0.2; % length of APM [m] @@ -66,7 +61,7 @@ %-- Parameters from Olfert and Collings -------------% % Nearly identical to the Ehara et al. case - case 'Olfert-Collings' + case 'olfert-collings' prop.r2 = 0.103; % outer electrode radius [m] prop.r1 = 0.1; % inner electrode radius [m] prop.L = 0.2; @@ -76,7 +71,7 @@ prop.p = 1; % system pressure [atm] %-- Parameters from Kuwata --------------------------% - case 'Kuwata' + case 'kuwata' prop.r2 = 0.052; % outer electrode radius [m] prop.r1 = 0.05; % inner electrode radius [m] prop.L = 0.25; @@ -84,6 +79,10 @@ prop.Q = 1.67e-5; % aerosol flowrate [m^3/s] prop.T = 295; % system temperature [K] prop.p = 1; % system pressure [atm] + + otherwise + error(['PMA properties not available for provided case. ',... + 'Try a different string in the `prop_pma(str)` call.']); end diff --git a/@Grid/Grid.m b/@Grid/Grid.m index 49eb416..c1758d7 100644 --- a/@Grid/Grid.m +++ b/@Grid/Grid.m @@ -613,7 +613,7 @@ %-- Ray-sum matrix ---------% [~,jj,a] = find(chord'); if ~isempty(a) - C(ii,:) = sparse(1,jj,a,1,obj.Ne,ceil(0.5*obj.Ne)); + C(ii,:) = sparse(1,jj,a,1,obj.Ne,ceil(0.6*obj.Ne)); end if f_bar, tools.textbar(ii/m); end @@ -958,7 +958,7 @@ %-- Consider cutting lower triangle --------% - if ~isinf(slope1) + if ~isinf(r1) f_missing1 = t0(:,1)<(t0(:,2).*slope1+b1); f_missing = or(f_missing,f_missing1); obj.cut = [obj.cut,b1,slope1]; From 1f68c512bbcfc72d4975921f6dd22408c14c2e58 Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Wed, 4 Mar 2020 14:11:35 -0800 Subject: [PATCH 10/56] Removed Dm and rho0 defn. in import + Update to .gitignore --- +io/import_c.m | 2 -- .gitignore | 20 +++++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/+io/import_c.m b/+io/import_c.m index 93184e8..7455f56 100644 --- a/+io/import_c.m +++ b/+io/import_c.m @@ -54,8 +54,6 @@ if ~exist('prop_pma','var'); prop_pma = []; end if isempty(prop_pma); prop_pma = kernel.prop_pma(' CPMA'); end - prop.mass_mob_pref = pi*1000/6; - prop.mass_mob_exp = 3; prop_pma.Q = mean(table2array(t(:,9)))/1000/60; % current averages over all setpoint % and does not support changing Q during measurements diff --git a/.gitignore b/.gitignore index da72210..cf17e17 100644 --- a/.gitignore +++ b/.gitignore @@ -7,18 +7,20 @@ results/ +uncertainty *.asv -main_exper_fn18_invert.m -main_exper_fn18_post.m -main_exper_ua19_salt.m -main_exper_gra_test.m -main_exper_gra.m -main_exper_ua19_nov_a.m -main_exper_ua19_nov_b.m -main_exper_ua19_santavac.m +main_x_fn18_invert.m +main_x_fn18_post.m +main_x_ua19_salt.m +main_x_gra_test.m +main_x_gra.m +main_x_ua19_nov_a.m +main_x_ua19_nov_b.m +main_x_ua19_santavac.m +main_x_ua19_santavac_import.m +main_x_sp2.m + main_jas19_noise.m main_bayes_sp2.m main_kernel_sp2.m -main_exper_sp2.m main_tfer.m CPMASP2KernelV0.m From 68a1b35c5a689f71a79b0df0d369a9cf9ad107bb Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Thu, 5 Mar 2020 11:10:30 -0800 Subject: [PATCH 11/56] Update gen_grid_c2.m with prop_pma.Dm name change --- +kernel/gen_grid_c2.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/+kernel/gen_grid_c2.m b/+kernel/gen_grid_c2.m index 294f2d9..dbdb52c 100644 --- a/+kernel/gen_grid_c2.m +++ b/+kernel/gen_grid_c2.m @@ -47,8 +47,8 @@ r = grid_i.elements; m = r(:,2); mrbc = r(:,1); -d = (m.*1e-18./prop_pma.mass_mob_pref).^... - (1/prop_pma.mass_mob_exp).*1e9; +d = (m.*1e-18./prop_pma.rho0).^... + (1/prop_pma.Dm).*1e9; % invoke mass-mobility relation From 35bae90df013f82233f74f12bbc9cd9b71910e93 Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Thu, 5 Mar 2020 19:03:16 -0800 Subject: [PATCH 12/56] Intro. varargin input to overlay*.m These functions now take a varaible number of input which is eventually passed to the plot function to allow for line specifications. + Added custom legend to plot2d_scatter.m. + Commenting updates to gen*.m functions. --- +kernel/gen.m | 6 ++++-- +kernel/gen_grid.m | 5 ++++- +kernel/gen_grid_c2.m | 4 +++- +kernel/prop_pma.m | 4 ++-- +tools/overlay_ellipse.m | 10 +++++----- +tools/overlay_isorho.m | 16 +++------------- +tools/overlay_line.m | 9 +++++---- +tools/overlay_phantom.m | 22 +++++++++------------- +tools/plot2d_scatter.m | 30 +++++++++++++++++++++++------- 9 files changed, 58 insertions(+), 48 deletions(-) diff --git a/+kernel/gen.m b/+kernel/gen.m index 22e5fa6..131a031 100644 --- a/+kernel/gen.m +++ b/+kernel/gen.m @@ -1,12 +1,14 @@ -% GEN Generate A matrix describing kernel/transfer functions for DMA-PMA +% GEN Evaluate kernel/transfer functions for DMA-PMA to find A. % Author: Timothy Sipkens, 2020-02-04 % % Notes: -% Cell arrays are used for Omega_mat and Lambda_mat in order to +% 1. Cell arrays are used for Omega_mat and Lambda_mat in order to % allow for the use of sparse matrices, which is necessary to % store information on higher resolutions grids % (such as those used for phantoms). +% 2. By default, this function uses the analytical PMA transfer function +% corresponding to Case 1C from Sipkens et al. (Aerosol Sci. Technol. 2020b). % % Inputs: % sp PMA setpoint structure diff --git a/+kernel/gen_grid.m b/+kernel/gen_grid.m index 4fde631..69eeee0 100644 --- a/+kernel/gen_grid.m +++ b/+kernel/gen_grid.m @@ -1,5 +1,5 @@ -% GENL_GRID Generate A matrix describing kernel/transfer functions for DMA-PMA. +% GEN_GRID Evaluate kernel/transfer functions for DMA-PMA to find A, exploiting grid structure. % Author: Timothy Sipkens, 2018-11-27 % % Notes: @@ -9,6 +9,9 @@ % allow for the use of sparse matrices, which is necessary to % store information on higher resolutions grids % (such as those used for phantoms). +% 3. By default, this function uses the analytical PMA transfer function +% corresponding to Case 1C from Sipkens et al. (Aerosol Sci. Technol. 2020b). +% % %-------------------------------------------------------------------------% % Inputs: diff --git a/+kernel/gen_grid_c2.m b/+kernel/gen_grid_c2.m index dbdb52c..44656f0 100644 --- a/+kernel/gen_grid_c2.m +++ b/+kernel/gen_grid_c2.m @@ -1,5 +1,5 @@ -% GEN_GRID_C2 Generate A matrix describing kernel/transfer functions for PMA-SP2. +% GEN_GRID_C2 Evaluate kernel/transfer functions for PMA-SP2 to find A, exploiting grid structure. % Author: Timothy Sipkens, 2018-11-27 % % Notes: @@ -9,6 +9,8 @@ % allow for the use of sparse matrices, which is necessary to % store information on higher resolutions grids % (such as those used for phantoms). +% 3. By default, this function uses the analytical PMA transfer function +% corresponding to Case 1C from Sipkens et al. (Aerosol Sci. Technol. 2020b). % % Inputs: % grid_b Grid on which the data exists diff --git a/+kernel/prop_pma.m b/+kernel/prop_pma.m index 31770db..dfa3173 100644 --- a/+kernel/prop_pma.m +++ b/+kernel/prop_pma.m @@ -1,5 +1,5 @@ -% PROP_PMA Generates the prop struct used to summarize CPMA parameters. +% PROP_PMA Generates the prop struct used to summarize PMA parameters. % Author: Timothy Sipkens, 2019-06-26 %-------------------------------------------------------------------------% % Input: @@ -26,7 +26,7 @@ switch opts %-- CPMA parameters from Olfert lab ----------------------------------% - case {'olfert','cpma',' CPMA'} + case {'olfert','cpma',' CPMA'} % final entry associated with data import prop.r1 = 0.06; % inner electrode radius [m] prop.r2 = 0.061; % outer electrode radius [m] prop.L = 0.2; % length of chamber [m] diff --git a/+tools/overlay_ellipse.m b/+tools/overlay_ellipse.m index c31dc6a..edb2788 100644 --- a/+tools/overlay_ellipse.m +++ b/+tools/overlay_ellipse.m @@ -5,10 +5,10 @@ % is the minor axis %=========================================================================% -function h = overlay_ellipse(mu,Sigma,s,cspec) +function h = overlay_ellipse(mu,Sigma,s,varargin) -if ~exist('cspec','var'); cspec = []; end -if isempty(cspec); cspec = 'w'; end +if isempty(varargin); varargin = {'w'}; end + % specify line properties if ~exist('s','var'); s = []; end if isempty(s); s = 1; end @@ -27,9 +27,9 @@ hold on; if strcmp(get(gca,'XScale'),'log') - h = loglog(10.^(a(1, :)+mu(1)), 10.^(a(2, :)+mu(2)), cspec); + h = loglog(10.^(a(1, :)+mu(1)), 10.^(a(2, :)+mu(2)), varargin{:}); else - h = plot(a(1, :)+mu(1), a(2, :)+mu(2), cspec); + h = plot(a(1, :)+mu(1), a(2, :)+mu(2), varargin{:}); end hold off; diff --git a/+tools/overlay_isorho.m b/+tools/overlay_isorho.m index 8767fb9..a37a523 100644 --- a/+tools/overlay_isorho.m +++ b/+tools/overlay_isorho.m @@ -3,22 +3,12 @@ % Author: Timothy Sipkens, 2019-11-01 %=========================================================================% -function [] = overlay_isorho(grid,color) - -%-- Parse inputs ---------------------------------------------------------% -if ~exist('color','var'); color = []; end -if isempty(color) - alpha = 1; - color = [1,1,1,alpha]; -end -%-------------------------------------------------------------------------% - +function [] = overlay_isorho(grid,varargin) isorho_vec = 10.^(-1:1:7); for ii=1:length(isorho_vec) - hl = tools.overlay_line(grid,... - [1,log10(isorho_vec(ii)*10^3./1e9)],3); % line for rho_eff = 0.1 - hl.Color = color; + tools.overlay_line(grid,... + [1,log10(isorho_vec(ii)*10^3./1e9)],3,varargin{:}); end end \ No newline at end of file diff --git a/+tools/overlay_line.m b/+tools/overlay_line.m index 2c09335..1ed2cef 100644 --- a/+tools/overlay_line.m +++ b/+tools/overlay_line.m @@ -10,9 +10,10 @@ % h Line object %=========================================================================% -function h = overlay_line(grid,logr0,slope,cspec) +function h = overlay_line(grid,logr0,slope,varargin) -if ~exist('cspec','var'); cspec = 'w'; end +if isempty(varargin); varargin = {'w'}; end + % specify line properties (default, white, solid line) if strcmp(get(gca,'XScale'),'log') % for log-scale plots rmin = log10(min([grid.edges{:}])); @@ -21,7 +22,7 @@ hold on; h = loglog(10.^[rmin,rmax],... 10.^[logr0(2)+slope*(rmin-logr0(1)),... - logr0(2)+slope*(rmax-logr0(1))],cspec); + logr0(2)+slope*(rmax-logr0(1))],varargin{:}); hold off; else % for linear scale plots @@ -31,7 +32,7 @@ hold on; h = plot([rmin,rmax],... [logr0(2)+slope*(rmin-logr0(1)),... - logr0(2)+slope*(rmax-logr0(1))],cspec); + logr0(2)+slope*(rmax-logr0(1))],varargin{:}); hold off; end diff --git a/+tools/overlay_phantom.m b/+tools/overlay_phantom.m index 94802fe..67fadc7 100644 --- a/+tools/overlay_phantom.m +++ b/+tools/overlay_phantom.m @@ -3,7 +3,7 @@ % Author: Timothy Sipkens, 2019-10-31 %=========================================================================% -function [pha,N] = overlay_phantom(x_pha,grid,color) +function [pha,N] = overlay_phantom(x_pha,grid,varargin) ylim_st = ylim; xlim_st = xlim; @@ -16,20 +16,16 @@ [pha,N] = Phantom.fit(x_pha,grid); end -if ~exist('color','var'); color = []; end -if isempty(color); alpha = 1; color = [1,1,0,alpha]; end +if isempty(varargin); varargin = {'Color',[1,1,0,1]}; end + % specify line properties (default: yellow, no transparency) +%-- Proceed with plotting ----------------% +tools.overlay_ellipse(pha.mu,pha.Sigma,1,varargin{:}); +tools.overlay_ellipse(pha.mu,pha.Sigma,2,varargin{:}); +tools.overlay_ellipse(pha.mu,pha.Sigma,3,varargin{:}); -h1 = tools.overlay_ellipse(pha.mu,pha.Sigma,1); -h1.Color = color; -h1 = tools.overlay_ellipse(pha.mu,pha.Sigma,2); -h1.Color = color; -h1 = tools.overlay_ellipse(pha.mu,pha.Sigma,3); -h1.Color = color; - -h1 = tools.overlay_line(grid,fliplr(pha.mu),... - pha.p.Dm); -h1.Color = color; +tools.overlay_line(grid,fliplr(pha.mu),... + pha.p.Dm,varargin{:}); ylim(ylim_st); % restore original plot bounds xlim(xlim_st); diff --git a/+tools/plot2d_scatter.m b/+tools/plot2d_scatter.m index b0ad845..0c6b435 100644 --- a/+tools/plot2d_scatter.m +++ b/+tools/plot2d_scatter.m @@ -18,13 +18,11 @@ corder = 1-bscl; % color is logscale if ~exist('cmap','var'); cmap = []; end -if isempty(cmap) - color = corder*ones(1,3); -else - N = size(cmap,1); - color = cmap(round(corder.*(N-1)+1),:); - color(corder==1,:) = 1; -end +if isempty(cmap); cmap = colormap('gray'); end + +N = size(cmap,1); +color = cmap(round(corder.*(N-1)+1),:); +color(corder==1,:) = 1; clf; for ii=1:length(m) @@ -36,4 +34,22 @@ end hold off; +%-- Add custom legend ----------------------% +hold on; +for ii=5:-1:0 + t0 = (bmax+ii-5-bmin)/(bmax-bmin); + if t0>=0 + h(6-ii) = plot(NaN,NaN,'.',... + 'Color',cmap(round((1-t0)*(N-1)+1),:),... + 'MarkerSize',12*t0+0.1); + text{6-ii} = num2str(10^(ii-5)); + end +end +text{end} = ['<',text{end}]; +hold off; + +lgd = legend(h,text,'Location','northwest'); +title(lgd,'{\times} b_{max}'); +%-------------------------------------------% + end \ No newline at end of file From 83fd4bc2a0f4774eac30c30b168621f33a47a542 Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Thu, 5 Mar 2020 19:09:40 -0800 Subject: [PATCH 13/56] Bug fix in overlay_isorho.m The function was missing a pi/6 factor such that lines were plotted incorrectly. --- +tools/overlay_isorho.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/+tools/overlay_isorho.m b/+tools/overlay_isorho.m index a37a523..c8b56d5 100644 --- a/+tools/overlay_isorho.m +++ b/+tools/overlay_isorho.m @@ -5,10 +5,10 @@ function [] = overlay_isorho(grid,varargin) -isorho_vec = 10.^(-1:1:7); +isorho_vec = 10.^(-1:1:9); for ii=1:length(isorho_vec) tools.overlay_line(grid,... - [1,log10(isorho_vec(ii)*10^3./1e9)],3,varargin{:}); + [1,log10(pi/6*isorho_vec(ii)*10^3./1e9)],3,varargin{:}); end end \ No newline at end of file From d9e5e71e9c9f8549ebc7471df38fea54ffa4c56e Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Sat, 7 Mar 2020 15:32:46 -0800 Subject: [PATCH 14/56] Updates to f* colormaps + Updated .gitignore --- .gitignore | 3 +++ cmap/README.md | 10 ++++++++-- cmap/docs/fblue.jpg | Bin 0 -> 2124 bytes cmap/docs/fred.jpg | Bin 0 -> 1980 bytes cmap/docs/phase.jpg | Bin 2307 -> 0 bytes cmap/fblue.mat | Bin 305 -> 583 bytes cmap/forange.mat | Bin 434 -> 0 bytes cmap/fred.mat | Bin 0 -> 590 bytes cmap/generate_swages.m | 2 +- 9 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 cmap/docs/fblue.jpg create mode 100644 cmap/docs/fred.jpg delete mode 100644 cmap/docs/phase.jpg delete mode 100644 cmap/forange.mat create mode 100644 cmap/fred.mat diff --git a/.gitignore b/.gitignore index cf17e17..fcd2790 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ results/ main_x_fn18_invert.m main_x_fn18_post.m main_x_ua19_salt.m +main_x_ua19_salt_post.m main_x_gra_test.m main_x_gra.m main_x_ua19_nov_a.m @@ -27,6 +28,8 @@ CPMASP2KernelV0.m ImportSP2.m +kernel/gen_c2.m cmap/correct.m +cmap/rgb2hsl.m +cmap/hsl2rgb.m test_import.m test_cred.m diff --git a/cmap/README.md b/cmap/README.md index b33b712..1a9be72 100644 --- a/cmap/README.md +++ b/cmap/README.md @@ -14,7 +14,7 @@ http://dx.doi.org/10.5670/oceanog.2016.66 (More information is available at https://matplotlib.org/cmocean/). 3. *colorbrewer2* - Colormaps by Cynthia Brewer and Mark Harrower. (More information - available at http://colorbrewer2.org/). + available at http://colorbrewer2.org/). 4. *turbo* - A. Mikhailov. Turbo, An Improved Rainbow Colormap for Visualization. (More information is available at https://ai.googleblog.com/2019/08/turbo-improved-rainbow-colormap-for.html). @@ -29,7 +29,7 @@ It is also noted that the *deep*, *dense*, *matter*, and *tempo* colormaps are reversed from their original order, such that the darker color is always first. -The colormaps, and swages indicating their color progression, are included below. +The colormaps, and swages indicating their color progression, are included below. Custom colormaps are specific to this package. ## Sequantial colormaps @@ -69,6 +69,12 @@ The colormaps, and swages indicating their color progression, are included below ![RdPu](docs/RdPu.jpg) *RdPu* +#### Custom: + +![fblue](docs/fblue.jpg) *fblue* + +![fred](docs/fred.jpg) *fred* + ## Divergent colormaps #### From cmocean: diff --git a/cmap/docs/fblue.jpg b/cmap/docs/fblue.jpg new file mode 100644 index 0000000000000000000000000000000000000000..206180d54ba0b25a8344daa95071f49a1daab4ec GIT binary patch literal 2124 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 literal 0 HcmV?d00001 diff --git a/cmap/docs/phase.jpg b/cmap/docs/phase.jpg deleted file mode 100644 index b235ce225d830b79796a98aa38de96add353144e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2307 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 R-Y<1Qims^84IkAsX_mzXUaI+KJWee#&xE& znDpMQ*I#r0*`V@$ZEZl5x5w|^?ce5CK3+C2usXf|)BDzP?&|wDuh+lS;1sLL|9^Vz z(+e`4-@mPm5}qDk^>3ut@al8k zsm!%sY^d@4^P9`xjJM?N2JOBKP|Z}9<%+q&VI4iR^wsW)m%e_N|N9$t>7_rvuc=SS7Yse8|LNsITZXg#_3r>hNxzQ( diff --git a/cmap/fred.mat b/cmap/fred.mat new file mode 100644 index 0000000000000000000000000000000000000000..5874da1822db7eacd6836da39cd6383419091900 GIT binary patch literal 590 zcmeZu4DoSvQZUssQ1EpO(M`+DN!3vZ$Vn_o%P-2cQV4Jk_w+L}(NS1h23^2gx>B|pfA7f--sF?FOIU%9qz=;C~ z*p^sID41|;aQf-waE9kd504Me$(=kra~!_0E%@~&{<^ICUh9nd36`G@KR%rPR(JOI ziG9#Roma(zPC#K`k5X1@4rlq_uTYiV(rZ(TdPh-&b!4fTXRBd$ETft z59zNJw)t>vuhbF8?Y-rCsg--Sr_U?7p|$w)-oGy`tM1NCzkj=W?{16FN$-=Yoz2qy z1b?c23&=N6*(%m)%I(wgnX|DYX5-Fsx%_R{CtFOcDt~NP{WV$JC$&lcR(9;3bfvpx zclW(+oFu>cKx}p7!3WZO|K)G||EGU&=kcc-YlEg09qHP${9E_8pR2xKvDw~#-A1>d z<~nP&qW!xa`irK&St_A=UFX97l>LjhI=oJ`p3ql0qtf~9q`&L`TxZRiHT}(X8QnU? zJ4Gk|-P3W*zsEcM&GhxxR!!yGn^K?EdPmJ|-Rf`V)%|B@RljNbyHb9exVq$>jo0lb z-CeWu-@e+MqRB^EQ~BR6mhFlVnJjnPd;j$Nk$V5G{M%OEe`a#&`8V=Lrhe}}R44K? J1l#U!1pv#t88!d_ literal 0 HcmV?d00001 diff --git a/cmap/generate_swages.m b/cmap/generate_swages.m index c8a7f98..01b54e0 100644 --- a/cmap/generate_swages.m +++ b/cmap/generate_swages.m @@ -16,7 +16,7 @@ colormap(cm); F = getframe(gca); - imwrite(F.cdata,['imgs/',cmap_name,'.jpg']); + imwrite(F.cdata,['docs/',cmap_name,'.jpg']); end end From e5891fa12ec721423b2e8153d210b7320ca1fe54 Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Mon, 9 Mar 2020 12:42:56 -0700 Subject: [PATCH 15/56] Update plot2d_marg_b.m Based on sugggestions by @ArashNaseri. --- +tools/plot2d_marg_b.m | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/+tools/plot2d_marg_b.m b/+tools/plot2d_marg_b.m index 914a8fe..2a4fb14 100644 --- a/+tools/plot2d_marg_b.m +++ b/+tools/plot2d_marg_b.m @@ -1,9 +1,9 @@ % PLOT2D_MARG_B Plots x as a 2D function on the grid, with marginalized distributions. -% Author: Timothy Sipkens, 2020-02-03 +% Author: Timothy Sipkens, Arash Naseri 2020-02-03 %==================================================================% -function [h,x_m] = plot2d_marg_b(grid,x,obj_t,x_t) +function [h,x_m] = plot2d_marg_b(grid,x,d,obj_t,x_t) subplot(4,6,[8,23]); grid.plot2d(x); @@ -15,11 +15,11 @@ marg_dim = 2; stairs(grid.nodes{marg_dim},... [x_m{marg_dim},0],'k'); + xlim([min(grid.edges{marg_dim}),max(grid.edges{marg_dim})]); set(gca,'XScale','log'); - ylabel({'d{\itN}/dlog {\itm}_{p}'},'Rotation',90) -if nargin>2 % also plot marginal of the true distribution +if nargin>3 % also plot marginal of the true distribution x_m_t = obj_t.marginalize(x_t); hold on; @@ -36,8 +36,18 @@ ylim([min(grid.edges{marg_dim}),max(grid.edges{marg_dim})]); set(gca,'YScale','log'); + +%-- Plot marginal SP2 data ----------------------------% +if and(nargin>3, exist('d','var')) + hold on; + stairs(d,grid.edges{1, 1},'b'); + hold off; +end + + +%-- Plot true distribution ----------------------------% xlabel({'d{\itN}/dlog {\itm}_{rBC}'},'Rotation',0); -if nargin>2 % also plot marginal of the true distribution +if nargin>3 % also plot marginal of the true distribution hold on; plot([0;x_m_t{marg_dim}],... obj_t.nodes{marg_dim},'color',[0.6,0.6,0.6]); From 60048ce3f0840a3fe1a60ebe126dd5be97ba12b5 Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Mon, 9 Mar 2020 13:48:30 -0700 Subject: [PATCH 16/56] Updated marginalize functions Added marginalize_op function to generate marginalization operators. + Updated both marginalize functions to include partial elements (where only part of the element lies below the cut line). --- @Grid/Grid.m | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/@Grid/Grid.m b/@Grid/Grid.m index c1758d7..4c3f937 100644 --- a/@Grid/Grid.m +++ b/@Grid/Grid.m @@ -509,6 +509,10 @@ tot = sum(x(:).*dr(:)); % integrated total + t0 = dr2; % added processing for partial elements + dr2 = dr./dr1; + dr1 = dr./t0; + marg{1} = sum(dr2.*x,2); % integrate over diameter marg{2} = sum(dr1.*x,1); % integrate over mass @@ -517,6 +521,40 @@ + %== MARGINALIZE_OP ===============================================% + % A marginalizing operator, C1, to act on 2D distributions. + % Author: Timothy Sipkens, Arash Naseri, 2020-03-09 + function C1 = marginalize_op(obj,dim) + + if ~exist('dim','var'); dim = []; end + if isempty(dim); dim = 1; end + + switch dim % determine which dimension to sum over + case 2 % integrate in column direction + C1 = kron(speye(obj.ne(2)),ones(1,obj.ne(1))); + + case 1 % integrate in row direction + C1 = repmat(speye(obj.ne(1),obj.ne(1)),[1,obj.ne(2)]); + end + + if obj.ispartial==1 + C1(:,obj.missing) = []; % remove missing elements + + %-- Extra processing to account for partial elements -----% + [dr,dr1,dr2] = obj.dr; + switch dim % determine which dimension to sum over + case 2 % integrate in column direction + dr1 = obj.full2partial(dr1(:)); + C1 = bsxfun(@times,C1,(dr./dr1)'); + case 1 % integrate in row direction + dr2 = obj.full2partial(dr2(:)); + C1 = bsxfun(@times,C1,(dr./dr2)'); + end + end + end + + + %== RESHAPE ======================================================% % A simple function to reshape a vector based on the grid. % Note: If the grid is partial, missing grid points are From e2743aca51fa90f1fd49ab308a3af90ae7cf3d2f Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Mon, 9 Mar 2020 13:56:43 -0700 Subject: [PATCH 17/56] Bug fix to plot2d_marg_b --- +tools/plot2d_marg_b.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/+tools/plot2d_marg_b.m b/+tools/plot2d_marg_b.m index 2a4fb14..a6dc0b6 100644 --- a/+tools/plot2d_marg_b.m +++ b/+tools/plot2d_marg_b.m @@ -38,7 +38,7 @@ %-- Plot marginal SP2 data ----------------------------% -if and(nargin>3, exist('d','var')) +if and(nargin>2, exist('d','var')) hold on; stairs(d,grid.edges{1, 1},'b'); hold off; From 359bb99e44360840b9e07502fa2be8bbf4cedc1b Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Mon, 9 Mar 2020 18:30:04 -0700 Subject: [PATCH 18/56] Bug fix for Grid.ray_sum This bug fix only applies for partial grids. + Added 1D kernel generation for DMA. --- +kernel/gen_1d_dma.m | 56 ++++++++++++++++++++++++++++++++ +kernel/tfer_dma.m | 24 +++++++------- +tools/plot2d_marg_b.m | 10 +++--- @Grid/Grid.m | 74 ++++++++++++++++++++++++++++-------------- 4 files changed, 123 insertions(+), 41 deletions(-) create mode 100644 +kernel/gen_1d_dma.m diff --git a/+kernel/gen_1d_dma.m b/+kernel/gen_1d_dma.m new file mode 100644 index 0000000..6364916 --- /dev/null +++ b/+kernel/gen_1d_dma.m @@ -0,0 +1,56 @@ + +% GEN_1D_DMA Evaluates the transfer function of a differential mobility analyzer. +% Author: Timothy Sipkens, 2020-03-09 +% +% Inputs: +% d_star Particle diameter, measurement set point for DMA [m] +% d Particle diameter, points in integral, can be vector [m] + +% varargin{1} = +% prop DMA properties, struct, generated using prop_DMA function (optional) +% +% varargin{2} = +% opts.diffusion Indicates whether to include diffusion, boolean (optional) +% .solver Indicates the method by which diffusion is calculated (optional) +% .param String indicated which parameter set to use (see prop_DMA.m) +% +% Outputs: +% Omega Transfer function +% Zp_tilde Non-dimensional electrical mobility, vector +%=========================================================================% + +function [Omega] = gen_1d_dma(d_star,d,varargin) + +n_b = length(d_star); +n_i = length(d); + +%== Evaluate particle charging fractions =================================% +z_vec = (1:3)'; +f_z = sparse(kernel.tfer_charge(d.*1e-9,z_vec)); % get fraction charged for d +n_z = length(z_vec); + + +%== Evaluate DMA transfer function =======================================% +disp('Computing DMA kernel...'); +Omega = sparse(n_b,n_i); +for kk=1:n_z + t0 = sparse(n_b,n_i); % pre-allocate for speed + + for ii=1:n_b % loop over d_star + t0(ii,:) = kernel.tfer_dma(... + d_star(ii).*1e-9,... + d.*1e-9,... + z_vec(kk),varargin{:}); + end + + t0(t0<(1e-7.*max(max(t0)))) = 0; + % remove numerical noise in kernel + + Omega = Omega+f_z(kk,:).*t0; +end +disp('Completed DMA contribution.'); +disp(' '); + + + +end diff --git a/+kernel/tfer_dma.m b/+kernel/tfer_dma.m index df857bd..8c59f84 100644 --- a/+kernel/tfer_dma.m +++ b/+kernel/tfer_dma.m @@ -1,8 +1,8 @@ -% TFER_DMA Evaluates the transfer function of a differential mobility analyzer. -% Author: Timothy Sipkens, 2018-12-27 -% Adapted: Buckley et al. (2017) and Olfert group -%-------------------------------------------------------------------------% +% TFER_DMA Evaluates the transfer function of a differential mobility analyzer. +% Author: Timothy Sipkens, 2018-12-27 +% Adapted: Buckley et al. (2017) and Olfert group +% % Inputs: % d_star Particle diameter, measurement set point for DMA [m] % d Particle diameter, points in integral, can be vector [m] @@ -31,7 +31,7 @@ %-- Physical constants ---------------------------------------------------% -kB = 1.38064852e-23; % Boltzmann constant [m^2 kg s^-2 K^-1] +kb = 1.38064852e-23; % Boltzmann constant [m^2 kg s^-2 K^-1] e = 1.6022E-19; % electron charge [C] @@ -49,18 +49,18 @@ %-- Evaluate transfer function -------------------------------------------% if opts.diffusion switch opts.solver - case 'Buckley' % evaluation from Buckley et al. + case 'buckley' % evaluation from Buckley et al. D = prop.D(B).*z; % diffusion sigma = (prop.G_DMA*2*pi*prop.L.*D./prop.Q_c).^0.5; - + case 'plug' % Plug flow, Stolzenburg, 2018 V = (prop.Q_c/(2*pi*Zp_star*prop.L))*log(prop.R2/prop.R1); % Classifier Voltage (TSI DMA 3080 Manual Equation B-5) - sigma_star = sqrt(((kB*prop.T)/(z*e*V))*prop.G_DMA); % Stolzenburg Manuscript Equation 20 + sigma_star = sqrt(((kb*prop.T)/(z*e*V))*prop.G_DMA); % Stolzenburg Manuscript Equation 20 sigma = sqrt(sigma_star^2.*Zp_tilde); % Stolzenburg Manuscript Equation 19 - - case {'Olfert','fullydeveloped'} % Olfert laboratory; Fully developed flow, Stolzenburg, 2018 + + case {'olfert','fullydeveloped'} % Olfert laboratory; Fully developed flow, Stolzenburg, 2018 V = (prop.Q_c/(2*pi.*Zp_star.*prop.L)).*log(prop.R2/prop.R1); % Classifier Voltage (TSI DMA 3080 Manual Equation B-5) - sigma_star = sqrt(((kB*prop.T)./(z.*e*V))*prop.G_DMA); % Stolzenburg Manuscript Equation 20 + sigma_star = sqrt(((kb*prop.T)./(z.*e*V))*prop.G_DMA); % Stolzenburg Manuscript Equation 20 sigma = sqrt(sigma_star^2.*Zp_tilde); % Stolzenburg Manuscript Equation 19 otherwise @@ -85,8 +85,8 @@ abs(Zp_tilde-1-prop.bet)-... abs(Zp_tilde-1+prop.bet*prop.del)-... abs(Zp_tilde-1-prop.bet*prop.del)); - end +%-------------------------------------------------------------------------% %-- Remove negative and small values --% diff --git a/+tools/plot2d_marg_b.m b/+tools/plot2d_marg_b.m index a6dc0b6..181b5e6 100644 --- a/+tools/plot2d_marg_b.m +++ b/+tools/plot2d_marg_b.m @@ -38,10 +38,12 @@ %-- Plot marginal SP2 data ----------------------------% -if and(nargin>2, exist('d','var')) - hold on; - stairs(d,grid.edges{1, 1},'b'); - hold off; +if nargin>2 + if ~isempty(d) + hold on; + stairs(d,grid.edges{1, 1},'b'); + hold off; + end end diff --git a/@Grid/Grid.m b/@Grid/Grid.m index 4c3f937..54c4489 100644 --- a/@Grid/Grid.m +++ b/@Grid/Grid.m @@ -464,6 +464,8 @@ %-- Added processing for partial grids -----------------------% if obj.ispartial==1 + dr0 = obj.full2partial(dr); % used if lower cut is employed + [~,r_min,r_max] = obj.ray_sum([0,obj.cut(1)],obj.cut(2),0); t0 = (r_min(:,1)-log10(obj.nelements(:,1))).*... (log10(obj.nelements(:,2))-log10(obj.nelements(:,1))); @@ -476,7 +478,7 @@ % upper, left triangle dr = t0+t1+t2; - if length(obj.cut)==4 % Note: Ignores if both rays pass through element + if length(obj.cut)==4 % consider cutting lower triangle [~,r_min,r_max] = obj.ray_sum([0,obj.cut(3)],obj.cut(4),0); t0 = (log10(obj.nelements(:,2))-r_max(:,1)).*... (log10(obj.nelements(:,2))-log10(obj.nelements(:,1))); @@ -487,7 +489,7 @@ t2 = 1/2.*(r_max(:,1)-r_min(:,1)).*... (r_max(:,2)-r_min(:,2)); % lower, right triangle - dr = t0+t1+t2; + dr = dr.*(t0+t1+t2)./dr0; % accounts for element that are discected twice end end @@ -524,7 +526,7 @@ %== MARGINALIZE_OP ===============================================% % A marginalizing operator, C1, to act on 2D distributions. % Author: Timothy Sipkens, Arash Naseri, 2020-03-09 - function C1 = marginalize_op(obj,dim) + function [C1,dr0] = marginalize_op(obj,dim) if ~exist('dim','var'); dim = []; end if isempty(dim); dim = 1; end @@ -545,11 +547,14 @@ switch dim % determine which dimension to sum over case 2 % integrate in column direction dr1 = obj.full2partial(dr1(:)); - C1 = bsxfun(@times,C1,(dr./dr1)'); + dr0 = dr./dr1; % ~dr2 case 1 % integrate in row direction dr2 = obj.full2partial(dr2(:)); - C1 = bsxfun(@times,C1,(dr./dr2)'); + dr0 = dr./dr2; % ~dr1 end + C1 = bsxfun(@times,C1,dr0'); + else + dr0 = ones(obj.Ne,1); end end @@ -573,7 +578,7 @@ %== VECTORIZE ====================================================% % A simple function to vectorize 2D gridded data. - %-----------------------------------------------------------------% + % % Outputs: % x Vectorized data % t1 Vectorized element centers for first dimension @@ -595,7 +600,7 @@ % and can accommodate partial grids. % Based on: Code from Samuel Grauer % Author: Timothy Sipkens, 2019-07-14 - %-----------------------------------------------------------------% + % % Inputs: % logr0 A single point on the line in log-log space, r0 = log10([dim1,dim2]) % slope Slope of the line @@ -618,32 +623,35 @@ for ii=1:m % loop over multiple rays %-- Ray vector -------------% - dv = [slope,1]; % convert slope to step vector along line + dv = [1,slope]; % convert slope to step vector along line dv = dv/norm(dv); dv(dv == 0) = 1e-10; % for stability during division %-- Line intersections -----% - % Use parametric representation of the line and find two - % intersections or each element. + % Use parametric representation of the line and finds two + % intersections for each element. % Note: Assumes a logarithmic grid. - ttmp = (log10(obj.nelements(:,[1,3]))-... + tmin = (log10(obj.nelements(:,[3,1]))-... logr0)./dv; % minimum of element - tmax = (log10(obj.nelements(:,[2,4]))-... + tmax = (log10(obj.nelements(:,[4,2]))-... logr0)./dv; % maximum of element %-- Corrections ------------% % Decide which points would correspond to transecting the % pixel. - tmin = max(min(ttmp,tmax),[],2); - tmax = min(max(ttmp,tmax),[],2); - tmax(tmax(t0(:,2).*slope0+b0); + f_missing = tup(:,1)>(tup(:,2).*slope0+b0); t1 = 1:prod(obj.ne); obj.cut = [b0,slope0]; %-- Consider cutting lower triangle --------% + if strcmp(obj.discrete,'logarithmic') + tlow = log10(obj.nelements(:,[2,3])); + else + tlow = obj.nelements(:,[2,3]); + end + tlow = tlow+1e-10.*tlow.*[1,-1]; + % avoids minimially overlapping elements + if ~isinf(r1) - f_missing1 = t0(:,1)<(t0(:,2).*slope1+b1); + f_missing1 = tlow(:,1)<(tlow(:,2).*slope1+b1); f_missing = or(f_missing,f_missing1); obj.cut = [obj.cut,b1,slope1]; end From 4a2328659797d4e1ff2d254009d916bbffe271e5 Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Mon, 9 Mar 2020 19:32:28 -0700 Subject: [PATCH 19/56] Bug fixes to Grid.dr function --- @Grid/Grid.m | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/@Grid/Grid.m b/@Grid/Grid.m index 54c4489..d52047b 100644 --- a/@Grid/Grid.m +++ b/@Grid/Grid.m @@ -466,28 +466,28 @@ if obj.ispartial==1 dr0 = obj.full2partial(dr); % used if lower cut is employed - [~,r_min,r_max] = obj.ray_sum([0,obj.cut(1)],obj.cut(2),0); - t0 = (r_min(:,1)-log10(obj.nelements(:,1))).*... - (log10(obj.nelements(:,2))-log10(obj.nelements(:,1))); + [~,rmin,rmax] = obj.ray_sum([0,obj.cut(1)],obj.cut(2),0); + t0 = (rmin(:,1)-log10(obj.nelements(:,1))).*... + (log10(obj.nelements(:,4))-log10(obj.nelements(:,3))); % lower rectangle - t1 = (log10(obj.nelements(:,4))-r_max(:,2)).*... - (log10(obj.nelements(:,2))-r_min(:,1)); + t1 = (log10(obj.nelements(:,4))-rmax(:,2)).*... + (log10(obj.nelements(:,2))-rmin(:,1)); % right rectangle - t2 = 1/2.*(r_max(:,1)-r_min(:,1)).*... - (r_max(:,2)-r_min(:,2)); + t2 = 1/2.*(rmax(:,1)-rmin(:,1)).*... + (rmax(:,2)-rmin(:,2)); % upper, left triangle dr = t0+t1+t2; if length(obj.cut)==4 % consider cutting lower triangle - [~,r_min,r_max] = obj.ray_sum([0,obj.cut(3)],obj.cut(4),0); - t0 = (log10(obj.nelements(:,2))-r_max(:,1)).*... - (log10(obj.nelements(:,2))-log10(obj.nelements(:,1))); + [~,rmin,rmax] = obj.ray_sum([0,obj.cut(3)],obj.cut(4),0); + t0 = (log10(obj.nelements(:,2))-rmax(:,1)).*... + (log10(obj.nelements(:,4))-log10(obj.nelements(:,3))); % upper rectangle - t1 = (r_min(:,2)-log10(obj.nelements(:,3))).*... - (r_max(:,1)-log10(obj.nelements(:,1))); + t1 = (rmin(:,2)-log10(obj.nelements(:,3))).*... + (rmax(:,1)-log10(obj.nelements(:,1))); % left rectangle - t2 = 1/2.*(r_max(:,1)-r_min(:,1)).*... - (r_max(:,2)-r_min(:,2)); + t2 = 1/2.*(rmax(:,1)-rmin(:,1)).*... + (rmax(:,2)-rmin(:,2)); % lower, right triangle dr = dr.*(t0+t1+t2)./dr0; % accounts for element that are discected twice end @@ -732,7 +732,11 @@ 'correct proportions for non-uniform grids.']); end - x = obj.reshape(x.*dr./obj.full2partial(dr0)); + mod = min(obj.full2partial(dr0)./dr,1e2); + % rewieght according to element size (for partial grids only) + % limit modification limit to 100x + + x = obj.reshape(x.*mod); % reshape, multiplied factor accounts for size of elements %-- Plot -------------------------------% @@ -1003,7 +1007,7 @@ else tup = obj.nelements(:,[1,4]); end - tup = tup+1e-10.*tup.*[-1,1]; + tup = tup+abs(1e-3.*mean(tup(2:end,:)-tup(1:(end-1),:))).*[1,0]; % avoids minimially overlapping elements f_missing = tup(:,1)>(tup(:,2).*slope0+b0); @@ -1017,7 +1021,7 @@ else tlow = obj.nelements(:,[2,3]); end - tlow = tlow+1e-10.*tlow.*[1,-1]; + tlow = tlow+abs(1e-3.*mean(tlow(2:end,:)-tlow(1:(end-1),:))).*[-1,0]; % avoids minimially overlapping elements if ~isinf(r1) From 90502a8cd927048943913a77640c4ddaf2566c18 Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Mon, 9 Mar 2020 19:36:21 -0700 Subject: [PATCH 20/56] Update overlay_ellipse.m --- +tools/overlay_ellipse.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/+tools/overlay_ellipse.m b/+tools/overlay_ellipse.m index edb2788..6e3d769 100644 --- a/+tools/overlay_ellipse.m +++ b/+tools/overlay_ellipse.m @@ -27,9 +27,9 @@ hold on; if strcmp(get(gca,'XScale'),'log') - h = loglog(10.^(a(1, :)+mu(1)), 10.^(a(2, :)+mu(2)), varargin{:}); + h = loglog(10.^(a(1,:)+mu(1)), 10.^(a(2,:)+mu(2)), varargin{:}); else - h = plot(a(1, :)+mu(1), a(2, :)+mu(2), varargin{:}); + h = plot(a(1,:)+mu(1), a(2,:)+mu(2), varargin{:}); end hold off; From e6186486c4144d084a93cb397bcc97632582492b Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Thu, 12 Mar 2020 10:10:54 -0700 Subject: [PATCH 21/56] Reverted plot2d mod based on element size --- @Grid/Grid.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/@Grid/Grid.m b/@Grid/Grid.m index d52047b..ec29aa8 100644 --- a/@Grid/Grid.m +++ b/@Grid/Grid.m @@ -732,8 +732,8 @@ 'correct proportions for non-uniform grids.']); end - mod = min(obj.full2partial(dr0)./dr,1e2); - % rewieght according to element size (for partial grids only) + mod = 1; % min(obj.full2partial(dr0)./dr,1e2); + % used to rewieght according to element size (for partial grids only) % limit modification limit to 100x x = obj.reshape(x.*mod); From 247ca996498af92c37e825d0eda4cc87698bb131 Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Tue, 17 Mar 2020 21:29:44 -0700 Subject: [PATCH 22/56] Update to io.import_a + Increased the number of contours. + Added fgreen colormap. --- +io/import_a.m | 148 +++++++++++++++++++++++++-------------------- +kernel/prop_pma.m | 15 ++++- .gitignore | 2 + @Grid/Grid.m | 2 +- cmap/fgreen.mat | Bin 0 -> 659 bytes 5 files changed, 97 insertions(+), 70 deletions(-) create mode 100644 cmap/fgreen.mat diff --git a/+io/import_a.m b/+io/import_a.m index 005221a..877ac1c 100644 --- a/+io/import_a.m +++ b/+io/import_a.m @@ -5,75 +5,73 @@ function [data,d_star,sp,prop_dma,prop_pma] = import_a(fn_smps,fn_cpma) -fid_smps = fopen(fn_smps); -t0 = textscan(fid_smps,'%s','delimiter','\t'); -prop_dma = struct(); % properties structure require for analysis -prop_dma_alt = struct(); % alternate properties read directly from file - -n = length(t0{1}); - - -%== Read SMPS file ============================================% -for ii=1:2:n % read DMA properties - t1 = t0{1}{ii}; - if strcmp(t0{1}{ii},'Sample #'); break; end - % finished reading DMA properties - - t1 = strrep(t1,' ',''); % remove invalid field name characters - t1 = strrep(t1,'(','_'); - t1 = strrep(t1,')',''); - t1 = strrep(t1,'*','_'); - t1 = strrep(t1,'/','_per_'); - t1 = strrep(t1,'#','No'); - - prop_dma_alt.(t1) = t0{1}{ii+1}; -end - - -%-- Determine the number of samples in the file ----% -for jj=ii+1:1:n - if strcmp(t0{1}{jj},'Date'); break; end -end -n_samples = jj-ii-1; - - -%-- Read time and data -----------------------------% -for kk=1:n_samples - smps_time(kk) = datetime(... - [t0{1}{kk+jj},' ',... - t0{1}{kk+jj+n_samples+1}],... - 'InputFormat','MM/dd/yy HH:mm:ss',... - 'PivotYear',2000); -end - -%-- Read CPC data ---------------------------------------------% -kk = 0; -for jj=(jj+2*(n_samples+1)+1):(n_samples+1):n - if strcmp(t0{1}{jj},'Scan Up Time(s)'); break; end - % finished reading data - - kk = kk+1; - d_star(kk) = str2double(t0{1}{jj}); +%== Read SMPS/CPC file =========================================% +n = linecount(fn_smps); +opts = detectImportOptions(fn_smps); +opts.Whitespace = '\b '; +opts.Delimiter = {'\t'}; + +optsc = opts; +for ii=1:40 + optsc.DataLines = [ii,ii]; + for jj=1:length(optsc.VariableTypes) + optsc.VariableTypes{jj} = 'string'; + end - for ll=1:n_samples - data(kk,ll) = str2double(t0{1}{jj+ll}); + ta = readtable(fn_smps,optsc); + if or(table2array(ta(1,1))=="DMA Inner Radius(cm)",table2array(ta(1,1))=="DMA Inner Radius (cm)"); + nR1 = ii; end + if table2array(ta(1,1))=="Reference Gas Temperature (K)"; nT = ii; end + if table2array(ta(1,1))=="Date"; nDate = ii; end end -%-- Read in flow (assume the same over all runs in file) ------% -prop_dma.Q_c = str2double(t0{1}{jj+5*(n_samples+1)+1})/60/1000; % sheath flow [m^3/s] -prop_dma.Q_a = str2double(t0{1}{jj+6*(n_samples+1)+1})/60/1000; % aerosol flow [m^3/s] -prop_dma.Q_s = prop_dma.Q_a; % sample flow [m^3/s] (assume equal flow) -prop_dma.Q_m = prop_dma.Q_c; % exhaust flow [m^3/s] (assume equal flow) +opts.DataLines = [40,40]; +ta = readtable(fn_smps,opts); +ta = table2array(ta); +ta(isnan(ta)) = []; +ncol = length(ta)-1; % number of data samples/scans + +opts.DataLines = [nR1,nR1+2]; +ta = readtable(fn_smps,opts); +ta = table2array(ta); +prop_dma.R1 = ta(1,2); +prop_dma.R2 = ta(2,2); +prop_dma.L = ta(3,2); % some DMA properties + +opts.DataLines = [12,12]; % this is unreliable +ta = readtable(fn_smps,opts); +ta = table2array(ta); +prop_dma.Q_a = ta(1,2)/60/1000; +prop_dma.Q_s = prop_dma.Q_a; % equal flow assumption +prop_dma.Q_c = ta(1,4)/60/1000; +prop_dma.Q_m = prop_dma.Q_c; % equal flow assumption + +opts.DataLines = [nT,nT+1]; +ta = readtable(fn_smps,opts); +ta = table2array(ta); +prop_dma.T = ta(1,2); % more DMA properties +prop_dma.p = ta(2,2); + + +optsb = opts; +for ii=1:length(optsb.VariableTypes); optsb.VariableTypes{ii} = 'string'; end +optsb.DataLines = [nDate,nDate+1]; +ta = readtable(fn_smps,optsb); +ta = table2array(ta); +for ii=1:ncol + date_smps(ii,1) = datetime(ta(1,ii+1),'InputFormat','MM/dd/yy')+... + duration(ta(2,ii+1),'InputFormat','hh:mm:ss'); +end -%-- Reassign properties in alternate struct -------------------% -prop_dma.R1 = str2double(prop_dma_alt.DMAInnerRadius_cm); -prop_dma.R2 = str2double(prop_dma_alt.DMAOuterRadius_cm); -prop_dma.T = str2double(prop_dma_alt.ReferenceGasTemperature_K); -prop_dma.p = str2double(prop_dma_alt.ReferenceGasPressure_kPa)/101.325; -fclose(fid_smps); +opts.DataLines = [nDate+3,n-26]; +t0 = readtable(fn_smps,opts); +t0 = table2array(t0); +data = t0(:,2:(ncol+1)); +d_star = t0(:,1); +%==============================================================% @@ -84,17 +82,15 @@ % fclose(fid_cpma); t0 = readtable(fn_cpma); -t1 = t0.Date_Time; +date_cpma = datetime(t0.Date_Time); -[gr1,gr2] = ndgrid(smps_time,t1); +[gr1,gr2] = ndgrid(date_smps,date_cpma); [~,ind] = max(gr14+z)&%Vk%8&|Up^rB z2O|Rm8<6GzVh{}itUwIn3j;AT5Cau6B2=%w_oF>ePQ2w(MkHT`73+jcRIBbHodk# zq_~75IhS3lp{tKEw@ZL{^AeX3W-j*_tV_FwmX+?qb^y?u3`Sft^LaC^#?BQe`i1G<;&jy D1@sFj literal 0 HcmV?d00001 From a7bfcf72e01ad2a783421edcce1c10cf7c9bd5c5 Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Wed, 18 Mar 2020 14:39:16 -0700 Subject: [PATCH 23/56] Added Phantom.fit2 method This method allows for fitting of multimodal distributions to data/reconstructions. + Separated both fit methods from the single Phantom class definition. The methods are now externally defined to make them more apparent in the file tree. This is also to emphasize these methods as alternate routes to make Phantom objects (i.e. they create an instance of the Phantom class without requiring the user to call the constructor). + Minor updates to the rest of the Phantom class. --- @Phantom/Phantom.m | 86 ++++++++++++++-------------------------------- @Phantom/fit.m | 53 ++++++++++++++++++++++++++++ @Phantom/fit2.m | 78 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 156 insertions(+), 61 deletions(-) create mode 100644 @Phantom/fit.m create mode 100644 @Phantom/fit2.m diff --git a/@Phantom/Phantom.m b/@Phantom/Phantom.m index 4042e9b..9b3c454 100644 --- a/@Phantom/Phantom.m +++ b/@Phantom/Phantom.m @@ -30,7 +30,7 @@ methods %== PHANTOM ======================================================% % Intialize phantom object. - %-----------------------------------------------------------------% + % % Inputs: % type The type of phantom specified as a string % (e.g. 'standard', 'mass-mobility', '1') @@ -59,13 +59,13 @@ case {'standard'} n_modes = length(mu_p); if ~iscell(mu_p); n_modes = 1; end - + obj.mu = mu_p; obj.Sigma = Sigma_modes; obj.R = obj.sigma2r(obj.Sigma); - - [obj.modes{1:n_modes}] = 'logn'; - + + for ii=1:n_modes; obj.modes{ii} = 'logn'; end + p = obj.cov2p(obj.mu,obj.Sigma,obj.modes); % mass-mobility equivlanet params. obj.p = obj.fill_p(p); % get mg as well @@ -76,7 +76,7 @@ obj.type = 'mass-mobility'; obj.modes = Sigma_modes; obj.p = mu_p; - + if ~any(strcmp('cond-norm',Sigma_modes)) [obj.mu,obj.Sigma] = obj.p2cov(obj.p,obj.modes); obj.R = obj.sigma2r(obj.Sigma); @@ -175,8 +175,9 @@ %== EVAL =========================================================% % Generates a distribution from the phantom mean and covariance. % Author: Timothy Sipkens, 2019-10-29 - % NOTE: Does not work for conditionally-normal distributions - % (which cannot be defined with mu and Sigma). + % + % Note: This method does not work for conditionally-normal distributions + % (which cannot be defined with mu and Sigma). function [x] = eval(obj,mu,Sigma,grid) if ~exist('grid','var'); grid = []; end @@ -258,12 +259,24 @@ methods (Static) - %== PRESET_PHANTOMS ==============================================% + %== PRESET_PHANTOMS (External definition) ========================% % Returns a set of parameters for preset/sample phantoms. - % Definition is in an external function. [p,modes,type] = presets(obj,type); %=================================================================% - + + %== FIT (External definition) ====================================% + % Fits a phantom to a given set of data, x, defined on a given grid, + % or vector of elements. Outputs a fit phantom object. + [phantom,N] = fit(x,vec_grid); + %=================================================================% + + %== FIT2 (External definition) ===================================% + % Fits a multimodal phantom object to a given set of data, x, + % defined on a given grid or vector of elements. Outputs a fit phantom object. + [phantom,N] = fit2(x,vec_grid,nmodes,logr0); + %=================================================================% + + %== FILL_P =======================================================% % Generates the remainder of the components of p. @@ -404,56 +417,7 @@ end end %=================================================================% - - - - %== FIT ==========================================================% - % Fits a phantom to a given set of data, x, defined on a given - % grid. Outputs a fit phantom object. - %-----------------------------------------------------------------% - % Inputs: - % x - input data (2D distribution data) - % grid - 'Grid' object on which input data is evaluated - %-----------------------------------------------------------------% - function [phantom,N] = fit(x,vec1_grid,vec2) - - disp('Fitting phantom object...'); - - %-- Parse inputs ---------------------------------------------% - if isa(vec1_grid,'Grid') - grid = vec1_grid; - [~,vec1,vec2] = grid.vectorize(); - else - vec1 = vec1_grid(:); - vec2 = vec2(:); - grid = [min(vec1),max(vec1);min(vec2),max(vec2)]; - % specify a span for a grid - end - %-------------------------------------------------------------% - - - corr2cov = @(sigma,R) diag(sigma)*R*diag(sigma); - - fun_pha = @(y) y(1).*mvnpdf(log10([vec1,vec2]),[y(2),y(3)],... - corr2cov([y(4),y(5)],[1,y(6);y(6),1])); - y0 = [max(x),0,2.3,0.6,0.25,0.97]; - % [C,log10(mg),log10(dg),log10(sm),log10(sg),corr] - - y1 = lsqnonlin(@(y) fun_pha(y)-x, y0, ... - [0,-10,-10,0,0,-1],[inf,10,10,10,3,1]); - - mu = [y1(2),y1(3)]; - sigma = [y1(4),y1(5)]; - Sigma = corr2cov(sigma,[1,y1(6);y1(6),1]); - - phantom = Phantom('standard',grid,mu,Sigma); - phantom.type = 'standard-fit'; - - N = y1(1); % scaling parameter denoting total number of particles - disp('Complete.'); - disp(' '); - end - %=================================================================% + end end diff --git a/@Phantom/fit.m b/@Phantom/fit.m new file mode 100644 index 0000000..7794951 --- /dev/null +++ b/@Phantom/fit.m @@ -0,0 +1,53 @@ + +% FIT Fits a phantom to a given set of data, x, defined on a given grid. +% Outputs a fit phantom object. +% +% Inputs: +% x Input data (2D distribution data) +% vec_grid A `Grid` object on which input data is defined or a vector +% of the coordiantes of the elements +% +% Outputs: +% phantom Output phantom object representing a fit bivariate lognormal. +% N Scaling parameter that acts to scale the phantom to the data. +%=============================================================% + +function [phantom,N] = fit(x,vec_grid) + +disp('Fitting phantom object...'); + +%-- Parse inputs ---------------------------------------------% +if isa(vec_grid,'Grid') + grid = vec_grid; + [~,vec1,vec2] = grid.vectorize(); +else + vec1 = vec_grid(:,1); + vec2 = vec_grid(:,2); + grid = [min(vec1),max(vec1);min(vec2),max(vec2)]; + % specify a span for a grid +end +%-------------------------------------------------------------% + + +corr2cov = @(sigma,R) diag(sigma)*R*diag(sigma); + +fun_pha = @(y) y(1).*mvnpdf(log10([vec1,vec2]),[y(2),y(3)],... + corr2cov([y(4),y(5)],[1,y(6);y(6),1])); +y0 = [max(x),0,2.3,0.6,0.25,0.97]; + % [C,log10(mg),log10(dg),log10(sm),log10(sg),corr] + +y1 = lsqnonlin(@(y) fun_pha(y)-x, y0, ... + [0,-10,-10,0,0,-1],[inf,10,10,10,3,1]); + +mu = [y1(2),y1(3)]; +sigma = [y1(4),y1(5)]; +Sigma = corr2cov(sigma,[1,y1(6);y1(6),1]); + +phantom = Phantom('standard',grid,mu,Sigma); +phantom.type = 'standard-fit'; + +N = y1(1); % scaling parameter denoting total number of particles +disp('Complete.'); +disp(' '); + +end diff --git a/@Phantom/fit2.m b/@Phantom/fit2.m new file mode 100644 index 0000000..e27faf9 --- /dev/null +++ b/@Phantom/fit2.m @@ -0,0 +1,78 @@ + +% FIT2 Fits a multimodal phantom to a given set of data, x, defined on a given grid. +% Outputs a fit phantom object. +% +% Inputs: +% x Input data (2D distribution data) +% vec_grid A `Grid` object on which input data is defined or a vector +% of the coordiantes of the elements +% n_modes Number of modes to fit to the data. +% +% Outputs: +% phantom Output phantom object representing a fit bivariate lognormal. +% N Scaling parameter that acts to scale the phantom to the data. +%=============================================================% + +function [phantom,N] = fit2(x,vec_grid,n_modes,logr0) + +disp('Fitting phantom object...'); + +%-- Parse inputs ---------------------------------------------% +if isa(vec_grid,'Grid') + grid = vec_grid; + [~,vec1,vec2] = grid.vectorize(); +else + vec1 = vec_grid(:,1); + vec2 = vec_grid(:,2); + grid = [min(vec1),max(vec1);min(vec2),max(vec2)]; + % specify a span for a grid +end + +if ~exist('logr0','var'); logr0 = []; end +%-------------------------------------------------------------% + + +corr2cov = @(sigma,R) diag(sigma)*R*diag(sigma); + +y0 = []; +yup = []; +ylow = []; +for ii=1:n_modes + y0 = [y0,max(x),-0.5+ii/n_modes,1.8+ii/n_modes,0.4,0.2,0.97]; + ylow = [ylow,0,-10,-10,0,0,-1]; + yup = [yup,inf,10,10,10,3,1]; + % [C,log10(mg),log10(dg),log10(sm),log10(sg),corr] +end +if ~isempty(logr0) % update centers of distributions + y0(2:6:end) = logr0(1:2:end); + y0(3:6:end) = logr0(2:2:end); +end + +y1 = lsqnonlin(@(y) fun_pha(y,vec1,vec2,n_modes,corr2cov)-x, ... + y0, ylow, yup); + +for ii=0:(n_modes-1) + mu{ii+1} = [y1(6*ii+2),y1(6*ii+3)]; + sigma{ii+1} = [y1(6*ii+4),y1(6*ii+5)]; + Sigma{ii+1} = corr2cov(sigma{ii+1},[1,y1(6*ii+6);y1(6*ii+6),1]); +end + +phantom = Phantom('standard',grid,mu,Sigma); +phantom.type = 'standard-fit'; + +N = y1(1:6:end); % scaling parameter denoting total number of particles +disp('Complete.'); +disp(' '); + +end + + +function x = fun_pha(y,vec1,vec2,nmodes,corr2cov) + +x = 0; +for ii=0:(nmodes-1) + x = x+y(6*ii+1).*mvnpdf(log10([vec1,vec2]),[y(6*ii+2),y(6*ii+3)],... + corr2cov([y(6*ii+4),y(6*ii+5)],[1,y(6*ii+6);y(6*ii+6),1])); +end + +end \ No newline at end of file From 33445a840b485132c748e4eff5eb54da3da25e15 Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Wed, 18 Mar 2020 14:42:11 -0700 Subject: [PATCH 24/56] Updated Phantom.mass2rho method for multimodal --- @Phantom/Phantom.m | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/@Phantom/Phantom.m b/@Phantom/Phantom.m index 9b3c454..fc7d3b2 100644 --- a/@Phantom/Phantom.m +++ b/@Phantom/Phantom.m @@ -246,10 +246,17 @@ function [phantom] = mass2rho(obj,grid_rho) A = [1,-3;0,1]; % corresponds to mass-mobility relation - - mu_rhod = (A*obj.mu'+[log10(6/pi)+9;0])'; - Sigma_rhod = A*obj.Sigma*A'; - + + if obj.n_modes==1 % for unimodal phantom + mu_rhod = (A*obj.mu'+[log10(6/pi)+9;0])'; + Sigma_rhod = A*obj.Sigma*A'; + else% for a multimodal phantom + for ii=1:obj.n_modes + mu_rhod{ii} = (A*obj.mu{ii}'+[log10(6/pi)+9;0])'; + Sigma_rhod{ii} = A*obj.Sigma{ii}*A'; + end + end + phantom = Phantom('standard',grid_rho,mu_rhod,Sigma_rhod); end From cc3aabb5701bc5e1104e9b554102a52d0523f3b5 Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Fri, 20 Mar 2020 12:43:51 -0700 Subject: [PATCH 25/56] Added tools for fitting distributions Specifically, modified Phantom.fit2 in a continued effort to improve multimodal fitting. + Added `logr0` arguement to Phantom.fit + Added sin(corr) durign fitting in an attempt to better span the space between -1 and 1 --- @Phantom/Phantom.m | 4 ++-- @Phantom/fit.m | 11 ++++++++--- @Phantom/fit2.m | 42 +++++++++++++++++++++++++++--------------- 3 files changed, 37 insertions(+), 20 deletions(-) diff --git a/@Phantom/Phantom.m b/@Phantom/Phantom.m index fc7d3b2..ed4b8fb 100644 --- a/@Phantom/Phantom.m +++ b/@Phantom/Phantom.m @@ -274,13 +274,13 @@ %== FIT (External definition) ====================================% % Fits a phantom to a given set of data, x, defined on a given grid, % or vector of elements. Outputs a fit phantom object. - [phantom,N] = fit(x,vec_grid); + [phantom,N] = fit(x,vec_grid,logr0); %=================================================================% %== FIT2 (External definition) ===================================% % Fits a multimodal phantom object to a given set of data, x, % defined on a given grid or vector of elements. Outputs a fit phantom object. - [phantom,N] = fit2(x,vec_grid,nmodes,logr0); + [phantom,N] = fit2(x,vec_grid,n_modes,logr0); %=================================================================% diff --git a/@Phantom/fit.m b/@Phantom/fit.m index 7794951..ff69372 100644 --- a/@Phantom/fit.m +++ b/@Phantom/fit.m @@ -12,7 +12,7 @@ % N Scaling parameter that acts to scale the phantom to the data. %=============================================================% -function [phantom,N] = fit(x,vec_grid) +function [phantom,N] = fit(x,vec_grid,logr0) disp('Fitting phantom object...'); @@ -26,22 +26,27 @@ grid = [min(vec1),max(vec1);min(vec2),max(vec2)]; % specify a span for a grid end +if ~exist('logr0','var'); logr0 = []; end %-------------------------------------------------------------% corr2cov = @(sigma,R) diag(sigma)*R*diag(sigma); fun_pha = @(y) y(1).*mvnpdf(log10([vec1,vec2]),[y(2),y(3)],... - corr2cov([y(4),y(5)],[1,y(6);y(6),1])); + corr2cov([y(4),y(5)],[1,sin(y(6));sin(y(6)),1])); y0 = [max(x),0,2.3,0.6,0.25,0.97]; % [C,log10(mg),log10(dg),log10(sm),log10(sg),corr] +if ~isempty(logr0) % update centers of distributions, if specified + y0(2:3) = logr0; +end + y1 = lsqnonlin(@(y) fun_pha(y)-x, y0, ... [0,-10,-10,0,0,-1],[inf,10,10,10,3,1]); mu = [y1(2),y1(3)]; sigma = [y1(4),y1(5)]; -Sigma = corr2cov(sigma,[1,y1(6);y1(6),1]); +Sigma = corr2cov(sigma,[1,sin(y1(6));sin(y1(6)),1]); phantom = Phantom('standard',grid,mu,Sigma); phantom.type = 'standard-fit'; diff --git a/@Phantom/fit2.m b/@Phantom/fit2.m index e27faf9..bc9db6d 100644 --- a/@Phantom/fit2.m +++ b/@Phantom/fit2.m @@ -35,44 +35,56 @@ corr2cov = @(sigma,R) diag(sigma)*R*diag(sigma); y0 = []; +y1 = []; yup = []; ylow = []; for ii=1:n_modes - y0 = [y0,max(x),-0.5+ii/n_modes,1.8+ii/n_modes,0.4,0.2,0.97]; - ylow = [ylow,0,-10,-10,0,0,-1]; - yup = [yup,inf,10,10,10,3,1]; + y0 = [y0,log(max(x))-3*(ii-1)/n_modes-3.5,... + 3+ii/n_modes,1.8+ii/n_modes,0.15,0.15,-0.9]; + ylow = [ylow,-inf,-3,-3,0,0,-pi]; + yup = [yup,inf,5,5,0.2,0.3,pi]; % [C,log10(mg),log10(dg),log10(sm),log10(sg),corr] end -if ~isempty(logr0) % update centers of distributions +if ~isempty(logr0) % update centers of distributions, if specified y0(2:6:end) = logr0(1:2:end); y0(3:6:end) = logr0(2:2:end); end +for ii=1:n_modes % prior std. dev. + y1 = [y1,inf,1e-2.*y0(6*(ii-1)+[2,3]),y0(6*(ii-1)+[4,5]),10]; + % [C,log10(mg),log10(dg),log10(sm),log10(sg),corr] +end -y1 = lsqnonlin(@(y) fun_pha(y,vec1,vec2,n_modes,corr2cov)-x, ... - y0, ylow, yup); +% opts = optimoptions(@lsqnonlin,'MaxFunctionEvaluations',1e4,'MaxIterations',1e3); +% y2 = lsqnonlin(@(y) fun_pha(y,vec1,vec2,n_modes,corr2cov)-... +% x, y0, ylow, yup); +y2 = lsqnonlin(@(y) [max(log(fun_pha(y,vec1,vec2,n_modes,corr2cov)),max(log(x)-4))-... + max(log(x),max(log(x)-4));... + (y-y0)'./y1'], y0, ylow, yup); for ii=0:(n_modes-1) - mu{ii+1} = [y1(6*ii+2),y1(6*ii+3)]; - sigma{ii+1} = [y1(6*ii+4),y1(6*ii+5)]; - Sigma{ii+1} = corr2cov(sigma{ii+1},[1,y1(6*ii+6);y1(6*ii+6),1]); + mu{ii+1} = [y2(6*ii+2),y2(6*ii+3)]; + sigma{ii+1} = [y2(6*ii+4),y2(6*ii+5)]; + Sigma{ii+1} = corr2cov(sigma{ii+1},[1,sin(y2(6*ii+6));sin(y2(6*ii+6)),1]); end phantom = Phantom('standard',grid,mu,Sigma); phantom.type = 'standard-fit'; -N = y1(1:6:end); % scaling parameter denoting total number of particles +N = exp(y2(1:6:end)); % scaling parameter denoting total number of particles disp('Complete.'); disp(' '); end -function x = fun_pha(y,vec1,vec2,nmodes,corr2cov) +function x = fun_pha(y,vec1,vec2,n_modes,corr2cov) x = 0; -for ii=0:(nmodes-1) - x = x+y(6*ii+1).*mvnpdf(log10([vec1,vec2]),[y(6*ii+2),y(6*ii+3)],... - corr2cov([y(6*ii+4),y(6*ii+5)],[1,y(6*ii+6);y(6*ii+6),1])); +for ii=0:(n_modes-1) + x = x + exp(y(6*ii+1)).*mvnpdf(log10([vec1,vec2]),[y(6*ii+2),y(6*ii+3)],... + corr2cov([y(6*ii+4),y(6*ii+5)],[1,sin(y(6*ii+6));sin(y(6*ii+6)),1])); + % cos() correlation is to better span -1 to 1 +end + end -end \ No newline at end of file From 5b586c9d73663263385c1bbc404a1fde2fa8f8f0 Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Fri, 20 Mar 2020 13:52:57 -0700 Subject: [PATCH 26/56] Added tools.overlay_phantom_marg Overlays phantom ellipses for multimodal phantoms and adds marginal distributions to appropriate panels. This function expects a figure previously produced by `Grid.plot2d_marg`. + Updates to inputs of tools.overlay_phantom to allow for multimodal cases. + Updates to commenting in tools.overlay_phantom. --- +tools/overlay_phantom.m | 31 ++++++++---- +tools/overlay_phantom_marg.m | 88 +++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 10 deletions(-) create mode 100644 +tools/overlay_phantom_marg.m diff --git a/+tools/overlay_phantom.m b/+tools/overlay_phantom.m index 67fadc7..fb90505 100644 --- a/+tools/overlay_phantom.m +++ b/+tools/overlay_phantom.m @@ -1,13 +1,11 @@ -% OVERLAY_PHANTOM Fit a distribution and overlay circles for std. dev. isolines +% OVERLAY_PHANTOM Overlay circles for std. dev. isolines for phantom (optionally, fits a unimodal phantom). % Author: Timothy Sipkens, 2019-10-31 %=========================================================================% -function [pha,N] = overlay_phantom(x_pha,grid,varargin) - -ylim_st = ylim; -xlim_st = xlim; +function [pha,N] = overlay_phantom(x_pha,grid,iso_levels,varargin) +%-- Parse inputs ---------------------------------------------------------% if isa(x_pha,'Phantom') pha = x_pha; grid = pha.grid; @@ -16,16 +14,29 @@ [pha,N] = Phantom.fit(x_pha,grid); end +if ~exist('iso_levels','var'); iso_levels = []; end +if isempty(iso_levels); iso_levels = [1,2,3]; end % plot 1, 2, and 3 sigma + if isempty(varargin); varargin = {'Color',[1,1,0,1]}; end % specify line properties (default: yellow, no transparency) +%-------------------------------------------------------------------------% + + +ylim_st = ylim; +xlim_st = xlim; %-- Proceed with plotting ----------------% -tools.overlay_ellipse(pha.mu,pha.Sigma,1,varargin{:}); -tools.overlay_ellipse(pha.mu,pha.Sigma,2,varargin{:}); -tools.overlay_ellipse(pha.mu,pha.Sigma,3,varargin{:}); +mu = pha.mu; % local copies of parameters +Sigma = pha.Sigma; +p = pha.p; +if ~iscell(mu); mu = {mu}; Sigma = {Sigma}; end % handle unimodal case +for ii=1:pha.n_modes + for jj=1:length(iso_levels) + tools.overlay_ellipse(mu{ii},Sigma{ii},iso_levels(jj),varargin{:}); + end + tools.overlay_line(grid,fliplr(mu{ii}),p(ii).Dm,varargin{:}); +end -tools.overlay_line(grid,fliplr(pha.mu),... - pha.p.Dm,varargin{:}); ylim(ylim_st); % restore original plot bounds xlim(xlim_st); diff --git a/+tools/overlay_phantom_marg.m b/+tools/overlay_phantom_marg.m new file mode 100644 index 0000000..567e02f --- /dev/null +++ b/+tools/overlay_phantom_marg.m @@ -0,0 +1,88 @@ + +% OVERLAY_PHANTOM_MARG Overlay ellipses and plot on marginal panels (optionally, fits a unimodal phantom). +% Author: Timothy Sipkens, 2020-03-20 +%=========================================================================% + +function [pha,N] = overlay_phantom_marg(x_pha,N,grid,iso_levels,varargin) + +% Parse inputs: Perform initial ellipse overlays for phantom + optionally fit unimodal phantom +if isa(x_pha,'Phantom') % don't fit phantom + pha = x_pha; + tools.overlay_phantom(pha,grid,iso_levels,varargin{:}); + +else % fit phantom + [pha,N] = tools.overlay_phantom(x_pha,grid,iso_levels,varargin{:}); +end + + +%-- Generate higher resolution plot vectors ------------------------------% +subplot(4,4,[1,3]); % marginal panel for dim2 +xlim_st = xlim; +vec_dim2 = logspace(log10(xlim_st(1)),log10(xlim_st(2)),200); + +subplot(4,4,[8,16]); % marginal panel for dim1 +ylim_st = ylim; +vec_dim1 = logspace(log10(ylim_st(1)),log10(ylim_st(2)),199); + +pha_dim1 = zeros(size(vec_dim1)); +pha_dim2 = zeros(size(vec_dim2)); + + +%-- Evaluate phantom marginals -------------------------------------------% +for ii=1:pha.n_modes + pha_dim1 = pha_dim1 + ... + N(ii).*normpdf(log10(vec_dim1),... + log10(pha.p(ii).mg),log10(pha.p(ii).sm)); + + pha_dim2 = pha_dim2 + ... + N(ii).*normpdf(log10(vec_dim2),... + log10(pha.p(ii).dg),log10(pha.p(ii).sg)); +end + + +%-- Plot on marginal panels ----------------------------------------------% +subplot(4,4,[1,3]); % add phantom to mobility (or dim2) marginal dist. +hold on; +semilogx(vec_dim2,pha_dim2); +hold off; + +subplot(4,4,[8,16]); % add phantom to mass (or dim1) marginal dist. +hold on; +semilogx(pha_dim1,vec_dim1); +hold off; + + + +subplot(4,4,[5,15]); % refocus center panel + +if nargout==0; clear pha N; end % prevent unnecessary output + +%-------------------------% +%{ +if isa(x_pha,'Phantom') + pha = x_pha; + grid = pha.grid; + N = []; +else + [pha,N] = Phantom.fit(x_pha,grid); +end + +if isempty(varargin); varargin = {'Color',[1,1,0,1]}; end + % specify line properties (default: yellow, no transparency) + +%-- Proceed with plotting ----------------% +tools.overlay_ellipse(pha.mu,pha.Sigma,1,varargin{:}); +tools.overlay_ellipse(pha.mu,pha.Sigma,2,varargin{:}); +tools.overlay_ellipse(pha.mu,pha.Sigma,3,varargin{:}); + +tools.overlay_line(grid,fliplr(pha.mu),... + pha.p.Dm,varargin{:}); + +ylim(ylim_st); % restore original plot bounds +xlim(xlim_st); + +if nargout==0; clear pha N; end % prevent unnecessary output +%} + +end + From d003ee7c14f3b84646c688caff5d233fbd2c949f Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Fri, 20 Mar 2020 15:36:49 -0700 Subject: [PATCH 27/56] Bug fixes to Phantom.fill_p + REAMDE update README updates consistute a sweeping overhaul of the Phantom class description, which now includes more detail and code details. --- @Phantom/Phantom.m | 15 ++--- README.md | 142 +++++++++++++++++++++++++-------------------- 2 files changed, 86 insertions(+), 71 deletions(-) diff --git a/@Phantom/Phantom.m b/@Phantom/Phantom.m index ed4b8fb..297922e 100644 --- a/@Phantom/Phantom.m +++ b/@Phantom/Phantom.m @@ -287,6 +287,7 @@ %== FILL_P =======================================================% % Generates the remainder of the components of p. + % Requires a minimum of: % Author: Timothy Sipkens, 2019-10-30 function p = fill_p(p) @@ -294,15 +295,15 @@ %-- Assign other parameters of distribution ------------------% for ll=1:n_modes % loop through distribution modes + if ~isfield(p(ll),'rhog'); p(ll).rhog = []; end + if isempty(p(ll).rhog) + p(ll).rhog = p(ll).mg/(1e-9*pi/6*p(ll).dg^3); + end + p(ll).mg = 1e-9*p(ll).rhog*pi/6*... (p(ll).dg^3); % geometric mean mass in fg - - if ~isfield(p,'rhog'); p.rho = []; end - - if ~isempty(p(ll).rhog) % use effective density at dg - p(ll).rho_100 = p(ll).rhog*(100/p(ll).dg)^(p(ll).Dm-3); - end - + + p(ll).rho_100 = p(ll).rhog*(100/p(ll).dg)^(p(ll).Dm-3); p(ll).m_100 = 1e-9*p(ll).rho_100*pi/6*100^3; p(ll).rhog = p(ll).rho_100*((p(ll).dg/100)^(p(ll).Dm-3)); p(ll).k = p(ll).m_100/(100^p(ll).Dm); diff --git a/README.md b/README.md index 6b9f3ad..2beb925 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ The `main*` scripts in the top directory of the program constitute the primary c that can be called to demonstrate use of the code. The are generally composed of four parts. -#### 2.1.1 General structure: Four parts +##### 2.1.1 General structure: Four parts - **STEP 1**: Optionally, one can define a phantom used to generate synthetic data and a ground truth. The `Phantom` class, described in Section [3.2](#32-phantom-class), is designed to @@ -136,7 +136,7 @@ by calling the `plot2d_marg` method of this class. This plots both the retrieved distribution as well as the marginalized distribution on each of the axes, taking the reconstruction (e.g. `x_tk1`, `x_lsq`) as an input. -#### 2.1.2 Script associated with the original J. Aerosol Sci. paper +##### 2.1.2 Script associated with the original J. Aerosol Sci. paper Of particular note, the `main_jas20a.m` script is designed to replicate the results in the associated paper [Sipkens et al. (2020a)][1_JAS1], as noted above. Minor @@ -180,12 +180,11 @@ Grid is a class developed to discretize a parameter space (e.g. mass-mobility sp This is done using a simple rectangular grid that can have linear, logarithmic or custom spaced elements along the edges. Methods are designed to make it easier to deal with gridded data, allowing users to reshape -vectorized data back to a 2D grid (`reshape` method) or vice versa. Other -methods allow for plotting the 2D representation of vector data (`plot2d` method) or -calculate the gradient of vector data (`grad` method). +vectorized data back to a 2D grid (`Grid.reshape` method) or vice versa. Other +methods allow for plotting the 2D representation of vector data (`Grid.plot2d` method) or calculate the gradient of vector data (`Grid.grad` method). Instances of the Grid class can primarily be constructed in two ways. First, -one can specify a `span` for the grid to cover in the parameter space. The span +one can specify a `Grid.span` for the grid to cover in the parameter space. The span is specified using a 2 x 2 matrix, where the first row corresponds to the span for the first dimension of the parameter space (e.g. mass) and the second row corresponds to the span for the second dimension of the parameter space (e.g. mobility). @@ -225,31 +224,26 @@ by default) and then with increasing the second size dimension. Vectorizing the 2D gridded data can be done using the colon operand, i.e. `x(:)`, or using the `vectorize` method. -#### 3.1.1 Support for partial grids +##### 3.1.1 Support for partial grids -The current program also supports creating partial grids, made up of a regular -grid where certain elements are ignored or missing. This allows practitioners -to ignore certain regions in the grid that may have higher uncertainties or -are otherwise unphysical. Quantities defined on partial grids have a -reduced dimension, often speeding inversion. +The current program also supports creating partial grids, made up of a regular grid where certain elements are ignored or missing. This allows practitioners to ignore certain regions in the grid that may have higher uncertainties or are otherwise unphysical. Quantities defined on partial grids have a reduced dimension, often speeding inversion. -For mass-mobility measurements, this can be used to -block out particles with extraordinarily high densities. +For mass-mobility measurements, this can be used to block out particles with extraordinarily high or low densities. These partial grids are also useful for PMA-SP2 inversion, where part of the -grid will be unphysical. +grid will be unphysical. Partial grids can be created using the `Grid.partial`, which cuts the grid about a line specified by a y-intercept and slope. All of the grid points with a center above this line is removed from the grid. For example, if one wanted -to create a grid where all of the points above the 1-1 line shoudl be removed +to create a grid where all of the points above the 1-1 line should be removed (as is relevant for PMA-SP2 inversion), one can call ```Matlab grid = grid.partial(0,1); ``` -For partial grids: +which replaces the current grid with a partial grid with the same span. For partial grids: 1. `Grid.ispartial` will be 1 (i.e. true), 2. `Grid.missing` contains a list of the global indices for the missing pixels, @@ -268,55 +262,71 @@ Methods specific to partial grids include: Most other Grid methods will operate on partial grids. For example, the `Grid.marginalization` method will only sum the pixels that are still on the grid (exploiting the fact that the other pixels are necessarily zero), including accounting for the fact that pixels on the diagonal are only half pixels (i.e. they are triangles rather than rectangular elements). The `Grid.plot2d` method, equally, will plot zeros in the upper left triangle. The `Grid.l1` method will generate the first-order Tikhonov L matrix for the modified grid (only considering pixels that are adjacent on the partial grid). The `Grid.ray_sum` method will sum the distribution along some ray (i.e. along some line), assuming missing pixels are zero and do not contribute to the ray-sum. The list goes on. - ### 3.2 Phantom class -Phantom is a class developed to contain the parameters and other information -for the phantom distributions that are used in testing the different inversion -methods. Currently, the phantom class is programmed to primarily produce -bivariate lognormal distributions and secondarily distributions -that are lognormal with mobility and conditional normal for mass -following [Buckley et al. (2017)][3_Buck]. - -The Phantom class parameterizes the aerosol distribution in two -possible ways: - -- **OPTION 1**: Most generally, the class parameterized the distribution -using a mean, `mu`, and covariance matrix, `Sigma`. For lognormal-lognormal -distributions, the mean and covariance are given in -[log10*m*, log10*d*]T -space. For phantoms of the form provided by [Buckley et al. (2017)][3_Buck] -are lognormal in mobility diameter space and conditionally normally -distributed in mass space. - -- **OPTION 2**: The distribution is parameterized using the typical -mass-mobility parameters, stored in the `p` field. This includes -parameters, such as the geometric mean diameter, `dg`; -mass-mobility exponent, `Dm`; and the effective density of particles -with a mobility diameter of 100 nm `rho_100`. - -- **OPTION 3**: Use a preset or sample distribution, which are loaded using -a string and the `preset_phantoms` function. For example, the four sample phantoms from -[Sipkens et al. (2020a)][1_JAS1] can be called using strings encompassing -the distribution numbers or names from that work (e.g. the demonstration phantom -can be generated using `'1'` or `'demonstration'`). The demonstration phantom -is indicated in the image below. +Phantom is a class developed to contain the parameters and other information for the phantom distributions that are used in testing the different inversion methods. Currently, the phantom class is programmed to primarily produce bivariate lognormal distributions and secondarily distributions that are lognormal with mobility and conditionally normal for mass following [Buckley et al. (2017)][3_Buck]. Bivariate normal distributions can also be represented by the class but will suffer from a loss of support from some of the class's methods. - +Instances of the Phantom class can be created in three ways. The first two represent different parameterziations of the Phantoms. + +##### 3.2.2 OPTION 1: 'standard' parameterization + +Using the '**standard**' representation explicitly for bivariate lognormal distributions. In this case, the used specifies a mean, `Phantom.mu`, and covariance, `Phantom.Sigma`, defined in [log10*a*, log10*b*]T space, where, as before, *a* and *b* are two aerosol size parameters (e.g. *a* = *m* and *b* = *m*). This is representation applied more generally beyond just mass-mobility distributions preferred and received more support across this program. For the scenario, instances of the class are given by calling: + +```Matlab +phantom = Phantom('standard',grid,mu,Sigma); +``` + +where `grid` is an instance of the Grid class described above. For example, for a mass-mobility distribution, + +```Matlab +phantom = Phantom('standard',grid,[0,2],... % distribution mean + [0.205,0.0641;0.0641,0.0214]); % covariance information +``` + +will produce a bivariate lognormal distribution centered at *m* = 1 fg and *d* = 100 nm and with covariance information corresponding to geometric standard deviations of σd = 10sqrt(0.0214) = 1.4 and σm = 10sqrt(0.205) = 2.84 and a correlation of *R* = 0.968. Similarly, for a bimodal, bivariate lognormal phantom, + +```Matlab +phantom = Phantom('standard',grid,... + {[0,2],[1,2.5]},... % distribution means + {[0.205,0.0641;0.0641,0.0214],... % covaraince of mode no. 1 + [0.205,0.0641;0.0641,0.0214]}); % covaraince of mode no. 2 +``` + +adds a second mode with identical covariance information at *m* = 10 fg and *d* = 316 nm to the distribution produced in the first example. -Conversion between the `mu` and `Sigma` parameterization and the -`p` structure parameterization can be accomplished using the `cov2p` -method of the Phantom class and vice versa using the `p2cov` -method of the Phantom class. +##### 3.2.1 OPTION 2: 'mass-mobility' parameterization -For experimental data, the Phantom class can also be used to derive -morphological parameters from the reconstructions. Of particular note, -the `fit` method of the Phantom class, takes a reconstruction, `x` and -the grid on which it is defined and creates a bivariate lognormal -phantom that most resembles the data. This done using least squares -analysis. The `p` properties of the Phantom class then contains many of the -morphological parameters of interest to practitioners measuring -mass-mobility distributions. +The 'mass-mobility' parameterization uses a `p` structured array, which is built specifically for mass-mobility distributions. The required fields for this structure are: + +| Field | Description | +| ------- | ----------- | +| `dg` | Mean mobility diameter | +| `sg` | Standard deviation of the mobility diameter | +| `sm` | Standard deviation of the particle mass | +| `Dm` | Mass-mobility exponent | +| `smd` | Conditional mass distribution standard deviation | +| | *And either:* | +| `mg` | Mean particle mass | +| `rhog` | Effective density of at the mean mobility diameter | +| | * For lognomral modes, means should be geometric means and standard deviations should be geometric standard deviations.| + +##### 3.2.3 Converting between parameterizations + +In both cases, creating an instance of the class will fill that class instance with the information corresponding to the other creation method (e.g. using a '**p**' structure, the class constructor will determined the corresponding mean and covariance information and store this in `Phantom.mu` and `Phantom.Sigma`). Conversion between the '**standard**' parameterization and the '**p**' structure parameterization can be accomplished using the `Phantom.cov2p` method of the Phantom class and vice versa using the `Phantom.p2cov` method of the Phantom class. + +##### 3.2.4 OPTION 3: Preset phantoms + +Instances of the Phantom class can be generated in multiple ways: + +- **OPTION 1**: Most generally, the class parameterized the distribution using a mean, `Phantom.mu`, and covariance matrix, `Phantom.Sigma`. For lognormal-lognormal distributions, the mean and covariance are given in [log10*m*, log10*d*]T space. For phantoms of the form provided by [Buckley et al. (2017)][3_Buck] are lognormal in mobility diameter space and conditionally normally distributed in mass space. + +- **OPTION 2**: The distribution is parameterized using the typical mass-mobility parameters, stored in the `Phantom.p` field. This includes parameters, such as the geometric mean diameter, `dg`; mass-mobility exponent, `Dm`; and the effective density of particles with a mobility diameter of 100 nm `rho_100`. + +- **OPTION 3**: Use a preset or sample distribution, which are loaded using a string and the `preset_phantoms` function. For example, the four sample phantoms from [Sipkens et al. (2020a)][1_JAS1] can be called using strings encompassing the distribution numbers or names from that work (e.g. the demonstration phantom can be generated using `'1'` or `'demonstration'`). The demonstration phantom is indicated in the image below. + + + +- **OPTION 4**: For experimental data, the Phantom class can also be used to derive morphological parameters from the reconstructions. Of particular note, the `Phantom.fit` method, which is defined external to the main definition of the Phantom class, takes a reconstruction, `x` and the grid on which it is defined and creates a bivariate lognormal phantom that most resembles the data. This done using least squares analysis. The `p` properties of the Phantom class then contains many of the morphological parameters of interest to practitioners measuring mass-mobility distributions. ## 4. Packages @@ -338,7 +348,7 @@ Evaluation proceeds using the analytical expressions of [Sipkens et al. (2020b)][2_AST] and the `tfer_pma` package provided with that work. The package uses a `sp` structure to define the PMA setpoints. -#### 4.1.1 sp +##### 4.1.1 sp The `sp` or setpoint structure is a structured array containing the information necessary to define the PMA setpoints. Defining the quantity requires a pair of parameters and a property structure defining the physical dimensions of the PMA. Pairings can be converted into a `sp` structured array using the `get_setpoint` function included with the `tfer_pma` package described below. Generally, this function can be placed inside a loop that generates an entry in `sp` for each available setpoint. The output structure will contain all of the relevant parameters that could be used to specify that setpoint, including @@ -362,7 +372,7 @@ sp = tfer_pma.get_setpoint(prop_pma,... 'm_star',m_star,'Rm',10); % get PMA setpoints ``` -#### 4.1.2 grid_b +##### 4.1.2 grid_b Alternatively, one can generate a grid corresponding to the data points. This can speed transfer function evaluation be exploiting the structure of the setpoints @@ -442,7 +452,7 @@ The `overlay*` functions produce overlay to be placed on top of plots in mass-mobility space. For example, `overlay_phantom` will plot the line corresponding to the least-squares line representative of the phantom (equivalent to the mass-mobility relation for mass-mobility phantoms) and ellipses representing -isolines one, two, and three standard deviations from the center of the distribution. +isolines. By default, the function plots one, two, and three standard deviations from the center of the distribution, accounting for the correlation encoded in the distribution. ---------------------------------------------------------------------- @@ -521,3 +531,7 @@ README in the `cmap` folder. [7_CC_Lcurve]: https://arxiv.org/abs/1608.04571 [Stolz18]: https://www.tandfonline.com/doi/full/10.1080/02786826.2018.1514101 [code_v11]: https://github.com/tsipkens/mat-2d-aerosol-inversion/releases/tag/v1.1 + +``` + +``` \ No newline at end of file From 66ace3503c946e01016da9a23dc50814b1402995 Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Fri, 20 Mar 2020 16:28:35 -0700 Subject: [PATCH 28/56] Improved Phantom.fill_p method Now can take rhog or mg and smd or sm. + Further updates to the description of the Phantom class in the README. --- @Phantom/Phantom.m | 15 +++++- @Phantom/presets.m | 8 +-- README.md | 126 ++++++++++++++++++++++++++++++++++----------- 3 files changed, 109 insertions(+), 40 deletions(-) diff --git a/@Phantom/Phantom.m b/@Phantom/Phantom.m index 297922e..6560852 100644 --- a/@Phantom/Phantom.m +++ b/@Phantom/Phantom.m @@ -75,7 +75,7 @@ % sepcified using a p structure obj.type = 'mass-mobility'; obj.modes = Sigma_modes; - obj.p = mu_p; + obj.p = obj.fill_p(mu_p); % fill out p structure if ~any(strcmp('cond-norm',Sigma_modes)) [obj.mu,obj.Sigma] = obj.p2cov(obj.p,obj.modes); @@ -295,14 +295,25 @@ %-- Assign other parameters of distribution ------------------% for ll=1:n_modes % loop through distribution modes + + %-- Handle mg/rhog ------------% if ~isfield(p(ll),'rhog'); p(ll).rhog = []; end if isempty(p(ll).rhog) p(ll).rhog = p(ll).mg/(1e-9*pi/6*p(ll).dg^3); end - p(ll).mg = 1e-9*p(ll).rhog*pi/6*... (p(ll).dg^3); % geometric mean mass in fg + %-- Handle sm/smd -------------% + if ~isfield(p(ll),'smd'); p(ll).smd = []; end + if isempty(p(ll).smd) + p(ll).smd = 10^sqrt(log10(p(ll).sm)^2 - ... + p(ll).Dm^2*log10(p(ll).sg)^2); + end + p(ll).sm = 10^sqrt(log10(p(ll).smd)^2+... + p(ll).Dm^2*log10(p(ll).sg)^2); + + %-- Other parameters ----------% p(ll).rho_100 = p(ll).rhog*(100/p(ll).dg)^(p(ll).Dm-3); p(ll).m_100 = 1e-9*p(ll).rho_100*pi/6*100^3; p(ll).rhog = p(ll).rho_100*((p(ll).dg/100)^(p(ll).Dm-3)); diff --git a/@Phantom/presets.m b/@Phantom/presets.m index 2efe060..10dca8b 100644 --- a/@Phantom/presets.m +++ b/@Phantom/presets.m @@ -70,16 +70,10 @@ p.Dm = 1; type{1} = 'logn'; - otherwise + otherwise % create empty phantom p = []; type = []; end -sm_fun = @(ii) 10^sqrt(log10(p(ii).smd)^2+... - p(ii).Dm^2*log10(p(ii).sg)^2); -for ii=1:length(p) - p(ii).sm = sm_fun(ii); -end - end diff --git a/README.md b/README.md index 2beb925..2a7f59f 100644 --- a/README.md +++ b/README.md @@ -21,16 +21,7 @@ base of the program and will be detailed in this README. ## 1. General description and problem definition -Size characterization is critical to understanding the role of aerosols in various -roles, ranging from climate change to novel nanotechnologies. Aerosol size -distributions have typically been resolved only with respect to a single quantity -or variable. With the increasing frequency of tandem measurements, this program -is designed to move towards inferring two-dimensional distributions of aerosol -particle size. This kind of analysis requires a double deconvolution, that is -the inversion of a double integral. While this may complicate the process, the -information gained is quite valuable, including the distribution of aerosol -quantities and the identification of multiple particle types which may be challenging -from one-dimensional analyses or when simply computing summary parameters. +Size characterization is critical to understanding the role of aerosols in various roles, ranging from climate change to novel nanotechnologies. Aerosol size distributions have typically been resolved only with respect to a single quantity or variable. With the increasing frequency of tandem measurements, this program is designed to move towards inferring two-dimensional distributions of aerosol particle size. This kind of analysis requires a double deconvolution, that is the inversion of a double integral. While this may complicate the process, the information gained is quite valuable, including the distribution of aerosol quantities and the identification of multiple particle types which may be challenging from one-dimensional analyses or when simply computing summary parameters. Mathematically, the problem to be solved here is of the form @@ -266,11 +257,13 @@ Most other Grid methods will operate on partial grids. For example, the `Grid.ma Phantom is a class developed to contain the parameters and other information for the phantom distributions that are used in testing the different inversion methods. Currently, the phantom class is programmed to primarily produce bivariate lognormal distributions and secondarily distributions that are lognormal with mobility and conditionally normal for mass following [Buckley et al. (2017)][3_Buck]. Bivariate normal distributions can also be represented by the class but will suffer from a loss of support from some of the class's methods. -Instances of the Phantom class can be created in three ways. The first two represent different parameterziations of the Phantoms. +Instances of the Phantom class can be created in four ways. We explicitly note that *the first two options are unique in that they represent different parameterizations of the phantom*. -##### 3.2.2 OPTION 1: 'standard' parameterization +##### 3.2.2 OPTION 1: The 'standard' parameterization -Using the '**standard**' representation explicitly for bivariate lognormal distributions. In this case, the used specifies a mean, `Phantom.mu`, and covariance, `Phantom.Sigma`, defined in [log10*a*, log10*b*]T space, where, as before, *a* and *b* are two aerosol size parameters (e.g. *a* = *m* and *b* = *m*). This is representation applied more generally beyond just mass-mobility distributions preferred and received more support across this program. For the scenario, instances of the class are given by calling: +Using the '**standard**' representation explicitly for bivariate lognormal distributions. In this case, the used specifies a mean, `Phantom.mu`, and covariance, `Phantom.Sigma`, defined in [log10*a*, log10*b*]T space, where, as before, *a* and *b* are two aerosol size parameters (e.g. *a* = *m* and *b* = *m*). This is representation applied more generally beyond just mass-mobility distributions preferred and received more support across this program. + +For this scenario, instances of the class are given by calling: ```Matlab phantom = Phantom('standard',grid,mu,Sigma); @@ -279,22 +272,34 @@ phantom = Phantom('standard',grid,mu,Sigma); where `grid` is an instance of the Grid class described above. For example, for a mass-mobility distribution, ```Matlab -phantom = Phantom('standard',grid,[0,2],... % distribution mean +span = [0.01,100; 10,1000]; % span of grid +ne = [550,600]; % elements in grid +grid = Grid(span,ne,'logarithmic'); % create instance of Grid + +phantom = Phantom('standard',grid,[-0.105,1.70],... % distribution mean [0.205,0.0641;0.0641,0.0214]); % covariance information + +grid.plot2d_marg(phantom.x); % plot the resultant phantom ``` -will produce a bivariate lognormal distribution centered at *m* = 1 fg and *d* = 100 nm and with covariance information corresponding to geometric standard deviations of σd = 10sqrt(0.0214) = 1.4 and σm = 10sqrt(0.205) = 2.84 and a correlation of *R* = 0.968. Similarly, for a bimodal, bivariate lognormal phantom, - +will produce and plot a bivariate lognormal distribution centered at *m* = 0.785 fg and *d* = 50 nm and with covariance information corresponding to geometric standard deviations of σd = 10sqrt(0.0214) = 1.4 and σm = 10sqrt(0.205) = 2.84 and a correlation of *R* = 0.968. Similarly, for a bimodal, bivariate lognormal phantom, + ```Matlab +span = [0.01,100; 10,1000]; % span of grid +ne = [550,600]; % elements in grid +grid = Grid(span,ne,'logarithmic'); % create instance of Grid + phantom = Phantom('standard',grid,... {[0,2],[1,2.5]},... % distribution means {[0.205,0.0641;0.0641,0.0214],... % covaraince of mode no. 1 - [0.205,0.0641;0.0641,0.0214]}); % covaraince of mode no. 2 + [0.126,0.0491;0.0491,0.0214]}); % covaraince of mode no. 2 + +grid.plot2d_marg(phantom.x); % plot the resultant phantom ``` -adds a second mode with identical covariance information at *m* = 10 fg and *d* = 316 nm to the distribution produced in the first example. +adds a second mode at *m* = 2.09 fg and *d* = 200 nm to the distribution produced in the first example. We note that this latter example is Phantom no. 1 (i.e. the demonstration phantom) from [Sipkens et al. (2020a)][1_JAS1]. -##### 3.2.1 OPTION 2: 'mass-mobility' parameterization +##### 3.2.1 OPTION 2: The 'mass-mobility' parameterization The 'mass-mobility' parameterization uses a `p` structured array, which is built specifically for mass-mobility distributions. The required fields for this structure are: @@ -302,31 +307,90 @@ The 'mass-mobility' parameterization uses a `p` structured array, which is built | ------- | ----------- | | `dg` | Mean mobility diameter | | `sg` | Standard deviation of the mobility diameter | -| `sm` | Standard deviation of the particle mass | | `Dm` | Mass-mobility exponent | -| `smd` | Conditional mass distribution standard deviation | -| | *And either:* | +| | *Either:* | +| `sm` | Standard deviation of the particle mass | +| `smd` | Standard deviation of the conditional mass distribution | +| | *Either:* | | `mg` | Mean particle mass | | `rhog` | Effective density of at the mean mobility diameter | -| | * For lognomral modes, means should be geometric means and standard deviations should be geometric standard deviations.| -##### 3.2.3 Converting between parameterizations +For lognormal modes, means should be geometric means and standard deviations should be geometric standard deviations. Remaining entries of the `p` structure will be filled using the `Phantom.fill_p` method. (We note that `p = Phantom.fill_p(p);` can be used to fill out the `p` structure without the need to create an instance of the Phantom class.) + +For this scenario, instances of the class are generated by calling: + +```Matlab +phantom = Phantom('mass-mobility',grid,p,modes); +``` + +where, as before, `grid` is an instance of the Grid class described above, `p` is the structured array containing the mass-mobility properties, and, `modes` indicates the type of mode that the `p` structure represents. The final argument is a cell of strings, with one cell entry per mode and where each string can be either: + +1. `'logn'` - indicating a bivariate lognormal mode and +2. `'norm'` - indicating a conditionally-normal distribution, where the mobility diameter distribution is lognormal and the conditional mass distribution is normal. This mode type represents the type of phantoms defined by [Buckley et al. (2017)][3_Buck]. + +To exemplify this procedure, the unimodal phantom from the previous section can generated by + +```Matlab +span = [0.01,100; 10,1000]; % span of grid +ne = [550,600]; % elements in grid +grid = Grid(span,ne,'logarithmic'); % create instance of Grid + +p.dg = 50; p.rhog = 12000; % geometric means +p.sg = 1.4; p.smd = 1.3; % geometric standard deviations +p.Dm = 3; % mass-mobility exponent +phantom = Phantom('mass-mobility',grid,p,{'logn'}); % create phantom + +grid.plot2d_marg(phantom.x); % plot the resultant phantom +``` + +noting that the final entry must still be enclosed by curly braces. One can generate a mutlmodal phantom by stacking multiple entries in the `p` structure and adding the same number of entrie to the `modes` cell. For example, to produce Phantom no. 1, -In both cases, creating an instance of the class will fill that class instance with the information corresponding to the other creation method (e.g. using a '**p**' structure, the class constructor will determined the corresponding mean and covariance information and store this in `Phantom.mu` and `Phantom.Sigma`). Conversion between the '**standard**' parameterization and the '**p**' structure parameterization can be accomplished using the `Phantom.cov2p` method of the Phantom class and vice versa using the `Phantom.p2cov` method of the Phantom class. +```Matlab +span = [0.01,100; 10,1000]; % span of grid +ne = [550,600]; % elements in grid +grid = Grid(span,ne,'logarithmic'); % create instance of Grid + +% distribution parameters: +% mode 1 mode 2 +p(1).dg = 50; p(2).dg = 200; +p(1).rhog = 12000; p(2).rhog = 500; +p(1).sg = 1.4; p(2).sg = 1.4; +p(1).smd = 1.3; p(2).smd = 1.3; +p(1).Dm = 3; p(2).Dm = 2.3; + +phantom = Phantom('mass-mobility',grid,p,{'logn','logn'}); + % create phantom + +grid.plot2d_marg(phantom.x); % plot the resultant phantom +``` + +where the distribution parameters match those from [Sipkens et al. (2020a)][1_JAS1]. + +##### 3.2.3 Converting between the 'standard' and 'mass-mobility' parameterizations + +In both cases, creating an instance of the class will fill that class instance with the information corresponding to the other creation method (e.g. using a `p` structure, the class constructor will determined the corresponding mean and covariance information and store this in `Phantom.mu` and `Phantom.Sigma`). This can be demonstrated by investigating the examples provided in the proceeding sections, which generate the same phantom, svae for rounding errors. Conversion between the '**standard**' parameterization and the '**mass-mobility**' parameterizations can be accomplished using the `Phantom.cov2p` method of the Phantom class and vice versa using the `Phantom.p2cov` method of the Phantom class. ##### 3.2.4 OPTION 3: Preset phantoms -Instances of the Phantom class can be generated in multiple ways: +Use a preset or sample distribution, which are loaded using a string and the `presets` function, which is defined external to the main Phantom class definition for easier access. For example, the four sample phantoms from [Sipkens et al. (2020a)][1_JAS1] can be called using strings encompassing the distribution numbers or names from that work (e.g. the demonstration phantom can be generated using `'1'` or `'demonstration'`). The demonstration phantom is indicated in the image below. + + + +Notably, Phantom no. 3, that is the phantom produced by + +```Matlab +phantom = Phantom('3'); +``` -- **OPTION 1**: Most generally, the class parameterized the distribution using a mean, `Phantom.mu`, and covariance matrix, `Phantom.Sigma`. For lognormal-lognormal distributions, the mean and covariance are given in [log10*m*, log10*d*]T space. For phantoms of the form provided by [Buckley et al. (2017)][3_Buck] are lognormal in mobility diameter space and conditionally normally distributed in mass space. +corresponds to the one used by [Buckley et al. (2017)][3_Buck] and demonstrates a scenerio with a conditionally-normal mass distribution. -- **OPTION 2**: The distribution is parameterized using the typical mass-mobility parameters, stored in the `Phantom.p` field. This includes parameters, such as the geometric mean diameter, `dg`; mass-mobility exponent, `Dm`; and the effective density of particles with a mobility diameter of 100 nm `rho_100`. +##### 3.2.4 OPTION 4: Using the Phantom class's fit methods -- **OPTION 3**: Use a preset or sample distribution, which are loaded using a string and the `preset_phantoms` function. For example, the four sample phantoms from [Sipkens et al. (2020a)][1_JAS1] can be called using strings encompassing the distribution numbers or names from that work (e.g. the demonstration phantom can be generated using `'1'` or `'demonstration'`). The demonstration phantom is indicated in the image below. +For experimental data, the Phantom class can also be used to derive morphological parameters from the reconstructions. - +Of particular note, the `Phantom.fit` method, which is defined external to the main definition of the Phantom class, takes a reconstruction, `x` and the grid on which it is defined and creates a bivariate lognormal phantom that most resembles the data. This done using least squares analysis. The `p` properties of the Phantom class then contains many of the morphological parameters of interest to practitioners measuring mass-mobility distributions. -- **OPTION 4**: For experimental data, the Phantom class can also be used to derive morphological parameters from the reconstructions. Of particular note, the `Phantom.fit` method, which is defined external to the main definition of the Phantom class, takes a reconstruction, `x` and the grid on which it is defined and creates a bivariate lognormal phantom that most resembles the data. This done using least squares analysis. The `p` properties of the Phantom class then contains many of the morphological parameters of interest to practitioners measuring mass-mobility distributions. +The `Phantom.fit2` method can be used in an attempt to derive multimodal phantoms for the data. This task is often challenging, such that the method may need tuning in order to get distributions that appropriately resemble the data. ## 4. Packages From dcc47bc9274b8c7ad1280b45a81ba250194bb5ce Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Fri, 20 Mar 2020 16:29:59 -0700 Subject: [PATCH 29/56] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2a7f59f..6ec7a64 100644 --- a/README.md +++ b/README.md @@ -374,7 +374,7 @@ In both cases, creating an instance of the class will fill that class instance w Use a preset or sample distribution, which are loaded using a string and the `presets` function, which is defined external to the main Phantom class definition for easier access. For example, the four sample phantoms from [Sipkens et al. (2020a)][1_JAS1] can be called using strings encompassing the distribution numbers or names from that work (e.g. the demonstration phantom can be generated using `'1'` or `'demonstration'`). The demonstration phantom is indicated in the image below. - + Notably, Phantom no. 3, that is the phantom produced by From 2acc8b00f6bd948877d5d6c1d2c65f7b7a971ae6 Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Fri, 20 Mar 2020 21:13:37 -0700 Subject: [PATCH 30/56] Update README.md --- README.md | 97 +++++++++++++++++++++---------------------------------- 1 file changed, 37 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index 6ec7a64..b60353f 100644 --- a/README.md +++ b/README.md @@ -82,13 +82,13 @@ four parts. ##### 2.1.1 General structure: Four parts -- **STEP 1**: Optionally, one can define a phantom used to generate synthetic data and a +**STEP 1**: Optionally, one can define a phantom used to generate synthetic data and a ground truth. The `Phantom` class, described in Section [3.2](#32-phantom-class), is designed to perform this task. The results is an instance of the `Grid` class, which is described in Section [3.1](#31-grid-class), and a vector, `x_t`, that contains a vectorized form of the phantom distribution, defined on the output grid. -- **STEP 2A**: One must now generate a model matrix, `A`, which relates the distribution, +**STEP 2A**: One must now generate a model matrix, `A`, which relates the distribution, `x`, to the data, `b`, such that **Ax** = **b**. This requires one to compute the transfer functions of all of the devices involved in the measurement as well as the grids on which `x` and `b` are to be defined. @@ -96,32 +96,25 @@ For phantom distributions, the grid for `x` can generated using the `Phantom` class. In all other cases, the grid for `x` and `b` can be generated by creating an instance of the `Grid` class described below. -- **STEP 2b**: One must also define some set of data in an appropriate format. For -simulated data, this is straightforward: `b = A*x;`. For experimental data, the data -should be imported along with either (i) a grid on which the data is defined or -(ii) using a series of setpoints for the DMA and PMA. For experimental data, -one must have knowledge of the setpoint before computing `A`. Accordingly, -the data may first be imported prior to Step 2A. -Also in this step, one should include some definition of the -expected uncertainties in each point in `b`, encoded in the matrix -`Lb`. For those cases involving simple counting noise, this can be -approximated as - - ```Matlab - Lb = theta*diag(sqrt(b)); - ``` - - where `theta` is related to the total number of particle counts as described in +**STEP 2b**: One must also define some set of data in an appropriate format. For simulated data, this is straightforward: `b = A*x;`. For experimental data, the data should be imported along with either (i) a grid on which the data is defined or +(ii) using a series of setpoints for the DMA and PMA. For experimental data, one must have knowledge of the setpoint before computing `A`. Accordingly, +the data may first be imported prior to Step 2A. Also in this step, one should include some definition of the expected uncertainties in each point in `b`, encoded in the matrix `Lb`. For those cases involving simple counting noise, this can beapproximated as + +```Matlab + Lb = theta*diag(sqrt(b)); +``` + + where `theta` is related to the total number of particle counts as described in [Sipkens et al. (2020a)][1_JAS1]. The function `get_noise` is included in the `+tools` package to help with noise creation, and more information on the noise model is provided in [Sipkens et al. (2017)][6_AO17]. -- **STEP 3**: With this information, one can proceed to implement various inversion +**STEP 3**: With this information, one can proceed to implement various inversion approaches, such as those available in the `invert` package described below. Preset groupings on inversion approaches are available in the `run_inversions*` scripts, also described below. -- **STEP 4**: Finally, one can post-process and visualize the results as desired. The +**STEP 4**: Finally, one can post-process and visualize the results as desired. The `Grid` class allows for a simple visualization of the inferred distribution by calling the `plot2d_marg` method of this class. This plots both the retrieved distribution as well as the marginalized distribution on each of @@ -145,19 +138,18 @@ inversion methods into a single line of code in the `main*` scripts. The lettered scripts roughly perform as follows: -- `run_inversions_a` - Attempts to optimize the regularization parameter in -the Tikhonov, MART, Twomey, and Twomey-Markowski approaches. +1. `run_inversions_a` - Attempts to optimize the regularization parameter in + the Tikhonov, MART, Twomey, and Twomey-Markowski approaches.` +2. `run_inversions_b` - Re-runs inversion at the set of optimal parameters + produced by *run_inversions_a.m*. Can be modified to adjust the optimization + approach used in the Tikhonov solvers (e.g. specifying the `'non-neg'` option). -- `run_inversions_b` - Re-runs inversion at the set of optimal parameters -produced by *run_inversions_a.m*. Can be modified to adjust the optimization -approach used in the Tikhonov solvers (e.g. specifying the `'non-neg'` option). +3. `run_inversions_c` - A simple set of the Tikhonov and Twomey approaches where + the user must explicitly set the regularization parameter of the Tikhonov + schemes. -- `run_inversions_c` - A simple set of the Tikhonov and Twomey approaches where -the user must explicitly set the regularization parameter of the Tikhonov -schemes. - -- `run_inversions_d` - Run the inversion methods multiple times and time the -length of time required to produce a reconstruction. +4. `run_inversions_d` - Run the inversion methods multiple times and time the + length of time required to produce a reconstruction. Other methods are available to combine other sets of inversion techniques. @@ -303,17 +295,15 @@ adds a second mode at *m* = 2.09 fg and *d* = 200 nm to the distribution produce The 'mass-mobility' parameterization uses a `p` structured array, which is built specifically for mass-mobility distributions. The required fields for this structure are: -| Field | Description | -| ------- | ----------- | -| `dg` | Mean mobility diameter | -| `sg` | Standard deviation of the mobility diameter | -| `Dm` | Mass-mobility exponent | -| | *Either:* | -| `sm` | Standard deviation of the particle mass | -| `smd` | Standard deviation of the conditional mass distribution | -| | *Either:* | -| `mg` | Mean particle mass | -| `rhog` | Effective density of at the mean mobility diameter | +1. `dg` - Mean mobility diameter +2. `sg` - Standard deviation of the mobility diameter +3. `Dm` - Mass-mobility exponent +4. *Either:* + `sm` - Standard deviation of the particle mass + `smd` - Standard deviation of the conditional mass distribution +5. *Either:* + `mg` - Mean particle mass + `rhog` - Effective density of at the mean mobility diameter For lognormal modes, means should be geometric means and standard deviations should be geometric standard deviations. Remaining entries of the `p` structure will be filled using the `Phantom.fill_p` method. (We note that `p = Phantom.fill_p(p);` can be used to fill out the `p` structure without the need to create an instance of the Phantom class.) @@ -343,7 +333,7 @@ phantom = Phantom('mass-mobility',grid,p,{'logn'}); % create phantom grid.plot2d_marg(phantom.x); % plot the resultant phantom ``` -noting that the final entry must still be enclosed by curly braces. One can generate a mutlmodal phantom by stacking multiple entries in the `p` structure and adding the same number of entrie to the `modes` cell. For example, to produce Phantom no. 1, +noting that the final entry must still be enclosed by curly braces. One can generate a multimodal phantom by stacking multiple entries in the `p` structure and adding the same number of entire to the `modes` cell. For example, to produce Phantom no. 1, ```Matlab span = [0.01,100; 10,1000]; % span of grid @@ -382,7 +372,7 @@ Notably, Phantom no. 3, that is the phantom produced by phantom = Phantom('3'); ``` -corresponds to the one used by [Buckley et al. (2017)][3_Buck] and demonstrates a scenerio with a conditionally-normal mass distribution. +corresponds to the one used by [Buckley et al. (2017)][3_Buck] and demonstrates a scenario which uses a conditionally-normal mass distribution. ##### 3.2.4 OPTION 4: Using the Phantom class's fit methods @@ -396,21 +386,11 @@ The `Phantom.fit2` method can be used in an attempt to derive multimodal phantom ### 4.1 +kernel -This package is used to evaluate the transfer function of the different -instruments, such as the differential mobility analyzer (DMA), -particle mass analyzer (such as the CPMA or APM), and charging fractions. -Various functions within this package can generate the matrix `A` -that acts as the forward model. +This package is used to evaluate the transfer function of the different instruments, such as the differential mobility analyzer (DMA), particle mass analyzer (such as the CPMA or APM), and charging fractions. Various functions within this package can generate the matrix `A` that acts as the forward model. -The transfer function for the DMA uses the analytical -expressions of [Stozenburg et al. (2018)][Stolz18]. +The transfer function for the DMA uses the analytical expressions of [Stozenburg et al. (2018)][Stolz18]. -Transfer function evaluation for a PMA can -proceed using one of two inputs either (i) a `sp` structure or (ii) an -instance of the `Grid` class defined for the data setpoints. -Evaluation proceeds using the analytical expressions -of [Sipkens et al. (2020b)][2_AST] and the `tfer_pma` package provided with -that work. The package uses a `sp` structure to define the PMA setpoints. +Transfer function evaluation for a PMA can proceed using one of two inputs either (i) a `sp` structure or (ii) an instance of the `Grid` class defined for the data setpoints. Evaluation proceeds using the analytical expressions of [Sipkens et al. (2020b)][2_AST] and the `tfer_pma` package provided with that work. The package uses a `sp` structure to define the PMA setpoints. ##### 4.1.1 sp @@ -596,6 +576,3 @@ README in the `cmap` folder. [Stolz18]: https://www.tandfonline.com/doi/full/10.1080/02786826.2018.1514101 [code_v11]: https://github.com/tsipkens/mat-2d-aerosol-inversion/releases/tag/v1.1 -``` - -``` \ No newline at end of file From 57338842241af7e29887652a818aa6887dc4aa7a Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Fri, 20 Mar 2020 21:16:33 -0700 Subject: [PATCH 31/56] Update README.md --- README.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b60353f..005c1b5 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,7 @@ is designed to invert tandem measurements of aerosol size distributions. This includes the inversion of particle mass analyzer-differential mobility analyzer (PMA-DMA) data to find the two-dimensional mass-mobility distribution. -The results of the aforementioned paper can be produced by running `main_jas20a` -in [v1.1][code_v11] of this code. See Section -[2.1.2](#212-script-associated-with-the-original-j-aerosol-sci-paper) -of this README for more details. +The results of the aforementioned paper can be produced by running `main_jas20a` in [v1.1][code_v11] of this code. See Section [2.1.2](#212-script-associated-with-the-original-j-aerosol-sci-paper) of this README for more details. This program is organized into several: classes (folders starting with the `@` symbol), packages (folders starting with the `+` symbol), and scripts that form the @@ -101,10 +98,10 @@ generated by creating an instance of the `Grid` class described below. the data may first be imported prior to Step 2A. Also in this step, one should include some definition of the expected uncertainties in each point in `b`, encoded in the matrix `Lb`. For those cases involving simple counting noise, this can beapproximated as ```Matlab - Lb = theta*diag(sqrt(b)); +Lb = theta*diag(sqrt(b)); ``` - where `theta` is related to the total number of particle counts as described in +where `theta` is related to the total number of particle counts as described in [Sipkens et al. (2020a)][1_JAS1]. The function `get_noise` is included in the `+tools` package to help with noise creation, and more information on the noise model is provided in [Sipkens et al. (2017)][6_AO17]. From 668e93adcc7e58d9b7e0b197594ba3a589be9521 Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Fri, 20 Mar 2020 22:20:21 -0700 Subject: [PATCH 32/56] Update README.md --- README.md | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 005c1b5..8df2695 100644 --- a/README.md +++ b/README.md @@ -392,17 +392,15 @@ Transfer function evaluation for a PMA can proceed using one of two inputs eithe ##### 4.1.1 sp The `sp` or setpoint structure is a structured array containing the information -necessary to define the PMA setpoints. Defining the quantity requires a pair of parameters and a property structure defining the physical dimensions of the PMA. Pairings can be converted into a `sp` structured array using the `get_setpoint` function included with the `tfer_pma` package described below. Generally, this function can be placed inside a loop that generates an entry in `sp` for each available setpoint. The output structure will contain all of the relevant parameters that could be used to specify that setpoint, including -mass setpoint (assuming a singly charged particle), `m_star`; the resolution, `Rm`; -the voltage, `V`; and the electrode speeds, `omega*`. A sample `sp` is shown below. - -| Fields | m_star | V | Rm | omega | omega1 | omega2 | alpha | beta | m_max | -| ------- | --------- | ------ | -- | ----- | ------ | ------ | ----- | ----- | -------- | -| 1 | 4.51e-19 | 81.638 | 3 | 692.6 | 703.4 | 682.0 | 47.91 | 2.359 | 6.01e-19 | -| 2 | 7.67e-19 | 110.68 | 3 | 618.3 | 627.9 | 608.9 | 42.77 | 2.106 | 1.02e-18 | -| 3 | 1.30e-18 | 148.76 | 3 | 549.5 | 558.1 | 541.2 | 38.01 | 1.872 | 1.74e-18 | -| 4 | 2.22e-18 | 198.02 | 3 | 486.1 | 493.7 | 478.7 | 33.63 | 1.656 | 2.96e-18 | -| ... | +necessary to define the PMA setpoints. Defining the quantity requires a pair of parameters and a property structure defining the physical dimensions of the PMA. Pairings can be converted into a `sp` structured array using the `get_setpoint` function included with the `tfer_pma` package described below. Generally, this function can be placed inside a loop that generates an entry in `sp` for each available setpoint. The output structure will contain all of the relevant parameters that could be used to specify that setpoint, including mass setpoint (assuming a singly charged particle), `m_star`; the resolution, `Rm`; the voltage, `V`; and the electrode speeds, `omega*`. A sample `sp` is shown below. + +| Fields | m_star | V | Rm | omega | omega1 | omega2 | alpha | beta | m_max | +| ------- | :-------: | :----: | :-: | :---: | :----: | :----: | :---: | :---: | :------: | +| 1 | 4.51×10-19 | 81.638 | 3 | 692.6 | 703.4 | 682.0 | 47.91 | 2.359 | 6.01×10-18 | +| 2 | 7.67×10-19 | 110.68 | 3 | 618.3 | 627.9 | 608.9 | 42.77 | 2.106 | 1.02×10-18 | +| 3 | 1.30×10-18 | 148.76 | 3 | 549.5 | 558.1 | 541.2 | 38.01 | 1.872 | 1.74×10-18 | +| 4 | 2.22×10-18 | 198.02 | 3 | 486.1 | 493.7 | 478.7 | 33.63 | 1.656 | 2.96×10-18 | +| ... |||||||||| As an example, the array can be generated from a vector of mass setpoints assuming a resolution of *R*m = 10 and PMA properties specified in `prop_pma` using: From fdb992e4d9c6204a43f585ce0d62e46d69193a79 Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Fri, 20 Mar 2020 22:22:59 -0700 Subject: [PATCH 33/56] Update README.md --- README.md | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 8df2695..354d176 100644 --- a/README.md +++ b/README.md @@ -48,10 +48,7 @@ e.g. particle mobility diameter). In this case, we define a global index for the ![](https://latex.codecogs.com/svg.latex?x_j=p(a_j,b_j)) -This results is a vector with *n*a x *n*b total entries. -This vectorized form is chosen over a two-dimensional **x** so that the problem can be -represented as a linear system of equations. -Here, the solution is assumed to be uniform within each element, in which case +This results is a vector with *n*a x *n*b total entries. This vectorized form is chosen over a two-dimensional **x** so that the problem can be represented as a linear system of equations. Here, the solution is assumed to be uniform within each element, in which case ![](https://latex.codecogs.com/svg.latex?N_i(a_i*,b_i*){\approx}N_{\text{tot}}\sum_{j=1}^{n_a\cdot{n_b}}{p(a_j,b_j)\int_{a_j}{\int_{b_j}{K(a_i*,b_i*,a_j,b_j)\cdot\text{d}a\cdot\text{d}b}}}) @@ -60,13 +57,11 @@ in [*a*,*b*]T space). This results is a linear system of equations of ![](https://latex.codecogs.com/svg.latex?{\mathbf{b}}={\mathbf{Ax}}+{\mathbf{e}}) -where **b** is the data vector (i.e. *bi* = *Ni*); -**A** is a discrete form of the kernel, +where **b** is the data vector (i.e. *bi* = *Ni*); **A** is a discrete form of the kernel, ![](https://latex.codecogs.com/svg.latex?A_{i,j}=\int_{a_j}{\int_{b_j}{K(a_i*,b_i*,a_j,b_j)\cdot\text{d}a\cdot\text{d}b}}}) -and **e** is a vector of measurement errors that -corrupt the results of **Ax**. This is the problem that the current code is designed to solve. +and **e** is a vector of measurement errors that corrupt the results of **Ax**. This is the problem that the current code is designed to solve. ## 2. Scripts in upper directory @@ -530,18 +525,11 @@ who used a Twomey-type approach to derive two-dimensional mass-mobility distributions. Much of the code from that work has been significantly modified in this distribution. -Also included is a reference to code designed to quickly evaluate -the transfer function of particle mass analyzers (e.g. APM, CPMA) by -[Sipkens et al. (2020b)][2_AST]. See the parallel repository -parallel repository [https://github.com/tsipkens/mat-tfer-pma](https://github.com/tsipkens/mat-tfer-pma) -for more details. +Also included is a reference to code designed to quickly evaluate the transfer function of particle mass analyzers (e.g. APM, CPMA) by [Sipkens et al. (2020b)][2_AST]. See the parallel repository parallel repository [https://github.com/tsipkens/mat-tfer-pma](https://github.com/tsipkens/mat-tfer-pma) for more details. -The authors would also like to thank @sgrauer -for consulting on small pieces of this code (such as the MART code -and the `textbar` function). +The authors would also like to thank [@sgrauer][https://github.com/sgrauer] for consulting on small pieces of this code (such as the MART code and the `tools.textbar` function). -Information on the provided colormaps can be found in an associated -README in the `cmap` folder. +Information on the provided colormaps can be found in an associated README in the `cmap` folder. #### References From 5de4f78ca6492ee69c3f1a4f371c94ef2dbfc8b9 Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Fri, 20 Mar 2020 22:24:01 -0700 Subject: [PATCH 34/56] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 354d176..bb02628 100644 --- a/README.md +++ b/README.md @@ -527,7 +527,7 @@ modified in this distribution. Also included is a reference to code designed to quickly evaluate the transfer function of particle mass analyzers (e.g. APM, CPMA) by [Sipkens et al. (2020b)][2_AST]. See the parallel repository parallel repository [https://github.com/tsipkens/mat-tfer-pma](https://github.com/tsipkens/mat-tfer-pma) for more details. -The authors would also like to thank [@sgrauer][https://github.com/sgrauer] for consulting on small pieces of this code (such as the MART code and the `tools.textbar` function). +The authors would also like to thank [@sgrauer](https://github.com/sgrauer) for consulting on small pieces of this code (such as the MART code and the `tools.textbar` function). Information on the provided colormaps can be found in an associated README in the `cmap` folder. From a028a7076983c3f2310fa3a3343ac59a48ecef3f Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Sat, 21 Mar 2020 09:36:00 -0700 Subject: [PATCH 35/56] Update README.md --- README.md | 200 +++++++++++++++--------------------------------------- 1 file changed, 54 insertions(+), 146 deletions(-) diff --git a/README.md b/README.md index bb02628..dc277c2 100644 --- a/README.md +++ b/README.md @@ -37,14 +37,7 @@ and the total sampling time; is a kernel containing device transfer functions or other discretization information; and *p*(*a*,*b*) is a two-dimensional size distribution. -Inversion refers to finding *p*(*a*,*b*) from some set of measurements, -{*N*1,*N*2,...}. For computation, the -two-dimensional size distribution is discretized, most -simply by representing the quantity on a regular rectangular grid with *n*a -discrete points for the first type of particle size (that is for *a*, e.g. particle mass) -and *n*b for the second type of particle size (that is for *b*, -e.g. particle mobility diameter). In this case, we define a global index for the grid, -*j*, and vectorize the distribution, such that +Inversion refers to finding *p*(*a*,*b*) from some set of measurements, {*N*1,*N*2,...}. For computation, the two-dimensional size distribution is discretized, most simply by representing the quantity on a regular rectangular grid with *n*a discrete points for the first type of particle size (that is for *a*, e.g. particle mass) and *n*b for the second type of particle size (that is for *b*, e.g. particle mobility diameter). In this case, we define a global index for the grid, *j*, and vectorize the distribution, such that ![](https://latex.codecogs.com/svg.latex?x_j=p(a_j,b_j)) @@ -74,81 +67,33 @@ four parts. ##### 2.1.1 General structure: Four parts -**STEP 1**: Optionally, one can define a phantom used to generate synthetic data and a -ground truth. The `Phantom` class, described in Section [3.2](#32-phantom-class), is designed to -perform this task. The results is an instance of the `Grid` class, which is -described in Section [3.1](#31-grid-class), and a vector, `x_t`, that contains a vectorized form of -the phantom distribution, defined on the output grid. +**STEP 1**: Optionally, one can define a phantom used to generate synthetic data and a ground truth. The `Phantom` class, described in Section [3.2](#32-phantom-class), is designed to perform this task. The results is an instance of the `Grid` class, which is described in Section [3.1](#31-grid-class), and a vector, `x_t`, that contains a vectorized form of the phantom distribution, defined on the output grid. -**STEP 2A**: One must now generate a model matrix, `A`, which relates the distribution, -`x`, to the data, `b`, such that **Ax** = **b**. This requires one to compute -the transfer functions of all of the devices involved in the measurement -as well as the grids on which `x` and `b` are to be defined. -For phantom distributions, the grid for `x` can generated using -the `Phantom` class. In all other cases, the grid for `x` and `b` can be -generated by creating an instance of the `Grid` class described below. +**STEP 2A**: One must now generate a model matrix, `A`, which relates the distribution, `x`, to the data, `b`, such that **Ax** = **b**. This requires one to compute the transfer functions of all of the devices involved in the measurement as well as the grids on which `x` and `b` are to be defined. For phantom distributions, the grid for `x` can generated using the `Phantom` class. In all other cases, the grid for `x` and `b` can be generated by creating an instance of the `Grid` class described below. -**STEP 2b**: One must also define some set of data in an appropriate format. For simulated data, this is straightforward: `b = A*x;`. For experimental data, the data should be imported along with either (i) a grid on which the data is defined or -(ii) using a series of setpoints for the DMA and PMA. For experimental data, one must have knowledge of the setpoint before computing `A`. Accordingly, -the data may first be imported prior to Step 2A. Also in this step, one should include some definition of the expected uncertainties in each point in `b`, encoded in the matrix `Lb`. For those cases involving simple counting noise, this can beapproximated as +**STEP 2b**: One must also define some set of data in an appropriate format. For simulated data, this is straightforward: `b = A*x;`. For experimental data, the data should be imported along with either (i) a grid on which the data is defined or (ii) using a series of setpoints for the DMA and PMA. For experimental data, one must have knowledge of the setpoint before computing `A`. Accordingly, the data may first be imported prior to Step 2A. Also in this step, one should include some definition of the expected uncertainties in each point in `b`, encoded in the matrix `Lb`. For those cases involving simple counting noise, this can be approximated as ```Matlab -Lb = theta*diag(sqrt(b)); +Lb = diag(1./sqrt(1/Ntot.*b)); ``` -where `theta` is related to the total number of particle counts as described in -[Sipkens et al. (2020a)][1_JAS1]. The function `get_noise` is -included in the `+tools` package to help with noise creation, and more -information on the noise model is provided in [Sipkens et al. (2017)][6_AO17]. +where `Ntot` is the total number of particle counts as described in [Sipkens et al. (2020a)][1_JAS1]. The function `tools.get_noise` is included to help with noise creation and more information on the noise model is provided in [Sipkens et al. (2017)][6_AO17]. -**STEP 3**: With this information, one can proceed to implement various inversion -approaches, such as those available in the `invert` package described below. -Preset groupings on inversion approaches are available in the -`run_inversions*` scripts, also described below. +**STEP 3**: With this information, one can proceed to implement various inversion approaches, such as those available in the `invert` package described below. Preset groupings on inversion approaches are available in the `run_inversions*` scripts, also described below. -**STEP 4**: Finally, one can post-process and visualize the results as desired. The -`Grid` class allows for a simple visualization of the inferred distribution -by calling the `plot2d_marg` method of this class. This plots both the -retrieved distribution as well as the marginalized distribution on each of -the axes, taking the reconstruction (e.g. `x_tk1`, `x_lsq`) as an input. +**STEP 4**: Finally, one can post-process and visualize the results as desired. The `Grid` class allows for a simple visualization of the inferred distribution by calling the `Grid.plot2d_marg` method of this class. This plots both the retrieved distribution as well as the marginalized distribution on each of the axes, taking the reconstruction (e.g. `x_tk1`, `x_lsq`) as an input. ##### 2.1.2 Script associated with the original J. Aerosol Sci. paper -Of particular note, the `main_jas20a.m` script is designed to replicate the results -in the associated paper [Sipkens et al. (2020a)][1_JAS1], as noted above. Minor -differences in the Euclidean error stem from using a smaller search space when -optimizing the regularization parameter for Tikhonov regularization. The narrower -range in the updated code provides a better optimized regularization parameter -and thus a slightly smaller Euclidean error. +Of particular note, the `main_jas20a.m` script is designed to replicate the results in the associated paper [Sipkens et al. (2020a)][1_JAS1], as noted above. Minor differences in the Euclidean error stem from using a smaller search space when optimizing the regularization parameter for Tikhonov regularization. The narrower +range in the updated code provides a better optimized regularization parameter and thus a slightly smaller Euclidean error. ### 2.2 Scripts to run a series of inversion methods (run_inversions*.m) -As noted above, these scripts are intend to bundle a series of -inversion methods into a single line of code in the `main*` scripts. - This can include optimization routines, included in the `+invert` - package, which run through several values of the regularization parameters. - -The lettered scripts roughly perform as follows: - -1. `run_inversions_a` - Attempts to optimize the regularization parameter in - the Tikhonov, MART, Twomey, and Twomey-Markowski approaches.` -2. `run_inversions_b` - Re-runs inversion at the set of optimal parameters - produced by *run_inversions_a.m*. Can be modified to adjust the optimization - approach used in the Tikhonov solvers (e.g. specifying the `'non-neg'` option). - -3. `run_inversions_c` - A simple set of the Tikhonov and Twomey approaches where - the user must explicitly set the regularization parameter of the Tikhonov - schemes. - -4. `run_inversions_d` - Run the inversion methods multiple times and time the - length of time required to produce a reconstruction. - -Other methods are available to combine other sets of inversion techniques. +As noted above, these scripts are intend to bundle a series of inversion methods into a single line of code in the `main*` scripts. This can include optimization routines, included in the `+optimize` package, which run through several values of the regularization parameters. The lettered scripts each denote different combinations of techniques. The `run_inversions_a` script, for example, attempts to optimize the regularization parameter in the Tikhonov, MART, Twomey, and Twomey-Markowski approaches. Other lettered scripts combine other sets of inversion techniques. ## 3. Classes -Classes are contained in folders starting with the `@` symbol. - ### 3.1 Grid class Grid is a class developed to discretize a parameter space (e.g. mass-mobility space). @@ -205,13 +150,9 @@ The current program also supports creating partial grids, made up of a regular g For mass-mobility measurements, this can be used to block out particles with extraordinarily high or low densities. -These partial grids are also useful for PMA-SP2 inversion, where part of the -grid will be unphysical. +These partial grids are also useful for PMA-SP2 inversion, where part of the grid will be unphysical. -Partial grids can be created using the `Grid.partial`, which cuts the grid -about a line specified by a y-intercept and slope. All of the grid points with -a center above this line is removed from the grid. For example, if one wanted -to create a grid where all of the points above the 1-1 line should be removed +Partial grids can be created using the `Grid.partial`, which cuts the grid about a line specified by a y-intercept and slope. All of the grid points with a center above this line is removed from the grid. For example, if one wanted to create a grid where all of the points above the 1-1 line should be removed (as is relevant for PMA-SP2 inversion), one can call ```Matlab @@ -228,8 +169,7 @@ are left in the grid (i.e. have indices that are not listed in `Grid.ismissing`) 5. `Grid.nelements` similarly only lists the edges of pixels that remain in the partial grid, and 6. `Grid.adj` is updated to only list the elements/pixels that are adjacent on the partial grid. -The `Grid.edges` and `Grid.nodes` properties remain unchanged and refer to the -underlying grid structure prior to any of the grid points being removed. +The `Grid.edges` and `Grid.nodes` properties remain unchanged and refer to the underlying grid structure prior to any of the grid points being removed. Methods specific to partial grids include: 1. `Grid.partial2full(x)` will convert a partial x (resolved with only the remaining points on the grid) to one resolved on the original grid, filling the missing points with zeros. I use this to plot and marginalize the distributions (i.e. plots will have zeros in all of the missing elements/pixels) and @@ -245,9 +185,9 @@ Instances of the Phantom class can be created in four ways. We explicitly note t ##### 3.2.2 OPTION 1: The 'standard' parameterization -Using the '**standard**' representation explicitly for bivariate lognormal distributions. In this case, the used specifies a mean, `Phantom.mu`, and covariance, `Phantom.Sigma`, defined in [log10*a*, log10*b*]T space, where, as before, *a* and *b* are two aerosol size parameters (e.g. *a* = *m* and *b* = *m*). This is representation applied more generally beyond just mass-mobility distributions preferred and received more support across this program. +The '**standard**' parameterization is explicitly for bivariate lognormal distributions (though it can equally be used for standard bivariate normal distributions). In this case, the user specifies a mean, `Phantom.mu`, and covariance, `Phantom.Sigma`, defined in [*a*, *b*]T space, where, as before, *a* and *b* are two aerosol size parameters. The bivariate lognormal form, such as when *a* = log10*m* and *b* = log10*d*, generally receives more support across this program. -For this scenario, instances of the class are given by calling: +For this scenario, instances of the class are created by calling: ```Matlab phantom = Phantom('standard',grid,mu,Sigma); @@ -285,16 +225,21 @@ adds a second mode at *m* = 2.09 fg and *d* = 200 nm to the distribution produce ##### 3.2.1 OPTION 2: The 'mass-mobility' parameterization -The 'mass-mobility' parameterization uses a `p` structured array, which is built specifically for mass-mobility distributions. The required fields for this structure are: +The '**mass-mobility**' parameterization uses a `p` structured array, which is built specifically for mass-mobility distributions. The required fields for this structure are: 1. `dg` - Mean mobility diameter 2. `sg` - Standard deviation of the mobility diameter 3. `Dm` - Mass-mobility exponent 4. *Either:* + `sm` - Standard deviation of the particle mass + `smd` - Standard deviation of the conditional mass distribution -5. *Either:* + +5. *Either:* + `mg` - Mean particle mass + `rhog` - Effective density of at the mean mobility diameter For lognormal modes, means should be geometric means and standard deviations should be geometric standard deviations. Remaining entries of the `p` structure will be filled using the `Phantom.fill_p` method. (We note that `p = Phantom.fill_p(p);` can be used to fill out the `p` structure without the need to create an instance of the Phantom class.) @@ -350,7 +295,7 @@ where the distribution parameters match those from [Sipkens et al. (2020a)][1_JA ##### 3.2.3 Converting between the 'standard' and 'mass-mobility' parameterizations -In both cases, creating an instance of the class will fill that class instance with the information corresponding to the other creation method (e.g. using a `p` structure, the class constructor will determined the corresponding mean and covariance information and store this in `Phantom.mu` and `Phantom.Sigma`). This can be demonstrated by investigating the examples provided in the proceeding sections, which generate the same phantom, svae for rounding errors. Conversion between the '**standard**' parameterization and the '**mass-mobility**' parameterizations can be accomplished using the `Phantom.cov2p` method of the Phantom class and vice versa using the `Phantom.p2cov` method of the Phantom class. +In both cases, creating an instance of the class will also contain the information corresponding to the other creation method (e.g. using a `p` structure, the class constructor will determined the corresponding mean and covariance information and store this in `Phantom.mu` and `Phantom.Sigma`). This can be demonstrated by investigating the examples provided in the proceeding sections, which generate the same phantom, save for rounding errors. Conversion between the '**standard**' parameterization and the '**mass-mobility**' parameterizations can be accomplished using the `Phantom.cov2p` method of the Phantom class and vice versa using the `Phantom.p2cov` method of the Phantom class. ##### 3.2.4 OPTION 3: Preset phantoms @@ -370,7 +315,7 @@ corresponds to the one used by [Buckley et al. (2017)][3_Buck] and demonstrates For experimental data, the Phantom class can also be used to derive morphological parameters from the reconstructions. -Of particular note, the `Phantom.fit` method, which is defined external to the main definition of the Phantom class, takes a reconstruction, `x` and the grid on which it is defined and creates a bivariate lognormal phantom that most resembles the data. This done using least squares analysis. The `p` properties of the Phantom class then contains many of the morphological parameters of interest to practitioners measuring mass-mobility distributions. +Of particular note, the `Phantom.fit` method, which is defined external to the main definition of the Phantom class, takes a reconstruction, `x` and the grid on which it is defined and creates a bivariate lognormal phantom that most resembles the data. This done using least squares analysis. The `p` structure of the Phantom class then contains many of the morphological parameters of interest to practitioners measuring mass-mobility distributions. The `Phantom.fit2` method can be used in an attempt to derive multimodal phantoms for the data. This task is often challenging, such that the method may need tuning in order to get distributions that appropriately resemble the data. @@ -387,7 +332,7 @@ Transfer function evaluation for a PMA can proceed using one of two inputs eithe ##### 4.1.1 sp The `sp` or setpoint structure is a structured array containing the information -necessary to define the PMA setpoints. Defining the quantity requires a pair of parameters and a property structure defining the physical dimensions of the PMA. Pairings can be converted into a `sp` structured array using the `get_setpoint` function included with the `tfer_pma` package described below. Generally, this function can be placed inside a loop that generates an entry in `sp` for each available setpoint. The output structure will contain all of the relevant parameters that could be used to specify that setpoint, including mass setpoint (assuming a singly charged particle), `m_star`; the resolution, `Rm`; the voltage, `V`; and the electrode speeds, `omega*`. A sample `sp` is shown below. +necessary to define the PMA setpoints. Defining the quantity requires a pair of parameters and a property structure defining the physical dimensions of the PMA. Pairings can be converted into a `sp` structured array using the `tfer_pma.get_setpoint` function described below. Generally, this function can be placed inside a loop that generates an entry in `sp` for each available setpoint. The output structure will contain all of the relevant parameters that could be used to specify that setpoint, including mass setpoint (assuming a singly charged particle), `m_star`; the resolution, `Rm`; the voltage, `V`; and the electrode speeds, `omega*`. A sample `sp` is shown below. | Fields | m_star | V | Rm | omega | omega1 | omega2 | alpha | beta | m_max | | ------- | :-------: | :----: | :-: | :---: | :----: | :----: | :---: | :---: | :------: | @@ -398,7 +343,7 @@ necessary to define the PMA setpoints. Defining the quantity requires a pair of | ... |||||||||| As an example, the array can be generated from a vector of mass setpoints assuming -a resolution of *R*m = 10 and PMA properties specified in `prop_pma` using: +a resolution of *R*m = 10 and PMA properties specified in `kernel.prop_pma` using: ```Matlab m_star = 1e-18.*logspace(log10(0.1),log10(100),25); % mass setpoints @@ -406,11 +351,11 @@ sp = tfer_pma.get_setpoint(prop_pma,... 'm_star',m_star,'Rm',10); % get PMA setpoints ``` -##### 4.1.2 grid_b +##### 4.1.2 Exploiting the gridded structure of that data Alternatively, one can generate a grid corresponding to the data points. This can speed transfer function evaluation be exploiting the structure of the setpoints -to minimize the number of function evaluations (using the `gen_A_grid` function). +to minimize the number of function evaluations (using the `kernel.gen_grid` function). ### 4.2 +tfer_pma @@ -421,14 +366,9 @@ analytical transfer functions derived by [Sipkens et al. (2020b)][2_AST], includ different approximations for the particle migration velocity and options for transfer functions that include diffusion. For more details on the theory, one is referred to the referenced work. The package also contains some standard reference -functions used in evaluating the DMA transfer function, i.e. in `tfer_dma`. +functions used in evaluating the DMA transfer function, i.e. in `kernel.tfer_dma`. -This is imported from a package distributed with [Sipkens et al. (2020b)][2_AST] -and is available in a parallel repository -[https://github.com/tsipkens/mat-tfer-pma](https://github.com/tsipkens/mat-tfer-pma) -and the associated archive [(Sipkens et al., 2019)][5_code]. - -The current implementation corresponds to v1.3 of that code. +This is imported from a package distributed with [Sipkens et al. (2020b)][2_AST] and is available in a parallel repository [https://github.com/tsipkens/mat-tfer-pma](https://github.com/tsipkens/mat-tfer-pma) and the associated archive [(Sipkens et al., 2019)][5_code]. The current implementation corresponds to v2.0 of that code. ### 4.3 +invert @@ -438,52 +378,33 @@ least-squares, Tikhonov regularization, Twomey, Twomey-Markowski (including usin the method of [Buckley et al. (2017)][3_Buck]), and the multiplicative algebraic reconstruction technique (MART). -An important note in connection with these methods is that they do not have the -matrix `Lb` as an input. This is done for two reasons: +An important note in connection with these methods is that they do not have the matrix `Lb` as an input. This is done for two reasons: -1. to allow for the case where the data noise is entirely unknown, thereby -considering a traditional, unweighted least-squares analysis -(though, this is not recommended) and +1. to allow for the case where the data noise is entirely unknown, thereby considering a traditional, unweighted least-squares analysis (though, this is not recommended) and 2. to avoid unnecessary repeat computation of the products `Lb*A` and `Lb*b`. -To incorporate `Lb`, use `Lb*A` and `Lb*b` when calling the inversion -functions for the input `A` and `b` arguments. +To incorporate `Lb`, use `Lb*A` and `Lb*b` when calling the inversion functions for the input `A` and `b` arguments. -Details on the available approaches to inversion are provided in the -associated paper, [Sipkens et al. (2020a)][1_JAS1]. +Details on the available approaches to inversion are provided in the associated paper, [Sipkens et al. (2020a)][1_JAS1]. -Development is underway on the use of an exponential -distance covariance function to correlate pixel values and reduce -reconstruction errors [Sipkens et al. (Under preparation)][4]. +Development is underway on the use of an exponential distance covariance function to correlate pixel values and reduce reconstruction errors [Sipkens et al. (Under preparation)][4]. ### 4.4 +optimize -This package mirrors the content of the +invert package but -aims to determine the optimal number of -iterations for the Twomey and MART schemes or the optimal prior -parameter set for the other methods. -This includes some methods aimed to optimize the prior/regularization -parameters used in the reconstructions, without knowledge of the data. - -Of particular note are a subset of the methods that implement -evaluation of the Bayes factor for a range of methods, namely the -`bayesf*.m` methods. The functions have inputs that mirror the functions -in the `invert` package, this means that data uncertainties can be included -in the procedure by giving `Lb*A` as an input to the program in the place of `A`. -The methods general take `lambda` as a separate parameter, to promote the -stability of the algorithm. +This package mirrors the content of the +invert package but aims to determine the optimal number of +iterations for the Twomey and MART schemes or the optimal prior parameter set for the other methods. This includes some methods aimed to optimize the prior/regularization parameters used in the reconstructions, without knowledge of the data. + +Of particular note are a subset of the methods that implement evaluation of the Bayes factor for a range of methods, namely the `optimize.bayesf*.m` methods. The functions have inputs that mirror the functions in the `invert` package, this means that data uncertainties can be included +in the procedure by giving `Lb*A` as an input to the program in the place of `A`. The methods general take `lambda` as a separate parameter, to promote the stability of the algorithm. ### 4.5 +tools A series of utility functions that serve various purposes, including printing -a text-based progress bar (based on code from -[Samuel Grauer](https://www.researchgate.net/profile/Samuel_Grauer)) -and a function to convert mass-mobility distributions to effective -density-mobility distributions. +a text-based progress bar (based on code from [Samuel Grauer](https://www.researchgate.net/profile/Samuel_Grauer)) and a function to convert mass-mobility distributions to effective density-mobility distributions. -The `overlay*` functions produce overlay to be placed on top of plots in -mass-mobility space. For example, `overlay_phantom` will plot the line +The `tools.overlay*` functions produce overlay to be placed on top of plots in +mass-mobility space. For example, `tools.overlay_phantom` will plot the line corresponding to the least-squares line representative of the phantom (equivalent to the mass-mobility relation for mass-mobility phantoms) and ellipses representing isolines. By default, the function plots one, two, and three standard deviations from the center of the distribution, accounting for the correlation encoded in the distribution. @@ -492,40 +413,27 @@ isolines. By default, the function plots one, two, and three standard deviations #### License -This software is licensed under an MIT license (see the corresponding file -for details). +This software is licensed under an MIT license (see the corresponding file for details). #### How to cite This work can be cited in two ways. -1. If the methods are used, but the code is not, -please cite [Sipkens et al. (2020a)][1_JAS1]. -Note that if the Twomey-Markowski approach is used -one should also cite [Buckley et al. (2017)][3_Buck], and if particle mass analyzer -transfer function evaluation is discussed, one should cite [Sipkens et al. (2020b)][2_AST]. +1. If the methods are used, but the code is not, please cite [Sipkens et al. (2020a)][1_JAS1]. +Note that if the Twomey-Markowski approach is used one should also cite [Buckley et al. (2017)][3_Buck], and if particle mass analyzer transfer function evaluation is discussed, one should cite [Sipkens et al. (2020b)][2_AST]. -2. If this code is used directly, cite: (*i*) this [code][5_code] -(including the DOI, included at the top) and (*ii*) the associated paper describing -the methods, [Sipkens et al. (2020a)][1_JAS1]. Also note that additional references -to [Buckley et al. (2017)][3_Buck] and [Sipkens et al. (2020b)][2_AST] -should also be considered as per above. +2. If this code is used directly, cite: (*i*) this [code][5_code] (including the DOI, included at the top) and (*ii*) the associated paper describing the methods, [Sipkens et al. (2020a)][1_JAS1]. Also note that additional references +to [Buckley et al. (2017)][3_Buck] and [Sipkens et al. (2020b)][2_AST] should also be considered as per above. #### Contact information and acknowledgements -This program was largely written and compiled by Timothy Sipkens -([tsipkens@mail.ubc.ca](mailto:tsipkens@mail.ubc.ca)) while at the -University of British Columbia. Code excerpts were contributed by @ArashNaseri -from the University of Alberta, including implementation of the -L-curve optimization method of [Cultrera and Callegaro (2016)][7_CC_Lcurve]. +This program was largely written and compiled by Timothy Sipkens ([tsipkens@mail.ubc.ca](mailto:tsipkens@mail.ubc.ca)) while at the +University of British Columbia. Code excerpts were contributed by [@ArashNaseri](https://github.com/ArashNaseri) from the University of Alberta, including implementation of the L-curve optimization method of [Cultrera and Callegaro (2016)][7_CC_Lcurve] among other optimizations. -This distribution includes code snippets from the code provided with -the work of [Buckley et al. (2017)][3_Buck], -who used a Twomey-type approach to derive two-dimensional mass-mobility -distributions. Much of the code from that work has been significantly -modified in this distribution. +Also included is a reference to code designed to quickly evaluate the transfer function of particle mass analyzers (e.g. APM, CPMA) by [Sipkens et al. (2020b)][2_AST]. See the parallel repository parallel repository [https://github.com/tsipkens/mat-tfer-pma](https://github.com/tsipkens/mat-tfer-pma) for more details. -Also included is a reference to code designed to quickly evaluate the transfer function of particle mass analyzers (e.g. APM, CPMA) by [Sipkens et al. (2020b)][2_AST]. See the parallel repository parallel repository [https://github.com/tsipkens/mat-tfer-pma](https://github.com/tsipkens/mat-tfer-pma) for more details. +This distribution includes code snippets from the code provided with the work of [Buckley et al. (2017)][3_Buck], +who used a Twomey-type approach to derive two-dimensional mass-mobility distributions. Much of the code from that work has been significantly modified in this distribution. The authors would also like to thank [@sgrauer](https://github.com/sgrauer) for consulting on small pieces of this code (such as the MART code and the `tools.textbar` function). From 0988ed689279c3b451fdd865e0a56c2025b7767a Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Sat, 21 Mar 2020 22:13:04 -0700 Subject: [PATCH 36/56] Limited support for uncertainty analysis of Phantom fitting + Added tools.redistribute --- +tools/redistribute.m | 15 +++++++++++++++ @Grid/Grid.m | 12 ++++++------ @Phantom/Phantom.m | 6 +++--- @Phantom/fit.m | 14 ++++++++++---- @Phantom/fit2.m | 39 +++++++++++++++++++++++---------------- README.md | 2 +- 6 files changed, 58 insertions(+), 30 deletions(-) create mode 100644 +tools/redistribute.m diff --git a/+tools/redistribute.m b/+tools/redistribute.m new file mode 100644 index 0000000..d4473e4 --- /dev/null +++ b/+tools/redistribute.m @@ -0,0 +1,15 @@ + +% REDISTRIBUTE Estimate the distribution width of a transformed random variable. +% Author: Timothy Sipkens, 2020-03-21 +%=========================================================================% + +function s = redistribute(s0,fun,mu) + +if ~exist('mu','var'); mu = []; end +if isempty(mu); mu = 0; end + +n = 6000; +s = std(fun(mu+s0.*randn([size(s0),n])),[],ndims(s0)+1); + +end + diff --git a/@Grid/Grid.m b/@Grid/Grid.m index 6bd68b6..881e2f0 100644 --- a/@Grid/Grid.m +++ b/@Grid/Grid.m @@ -1,13 +1,13 @@ % GRID Responsible for discretizing space as a grid and related operations. -% Author: Timothy Sipkens, 2019-02-03 +% Author: Timothy Sipkens, 2019-02-03 % -% The grid class is currently used when a simple discretization of -% two-dimensional space is required. It then takes either the span -% of spcae to be covered or pre-defined edge vectors to form a grid. +% The grid class is currently used when a simple discretization of +% two-dimensional space is required. It then takes either the span +% of spcae to be covered or pre-defined edge vectors to form a grid. % -% See constructor method for list of other variables required -% for creation. +% See constructor method for list of other variables required +% for creation. %=========================================================================% classdef Grid diff --git a/@Phantom/Phantom.m b/@Phantom/Phantom.m index 6560852..29bd306 100644 --- a/@Phantom/Phantom.m +++ b/@Phantom/Phantom.m @@ -29,7 +29,7 @@ methods %== PHANTOM ======================================================% - % Intialize phantom object. + % Intialize a phantom object. % % Inputs: % type The type of phantom specified as a string @@ -274,13 +274,13 @@ %== FIT (External definition) ====================================% % Fits a phantom to a given set of data, x, defined on a given grid, % or vector of elements. Outputs a fit phantom object. - [phantom,N] = fit(x,vec_grid,logr0); + [phantom,N,y_out,J] = fit(x,vec_grid,logr0); %=================================================================% %== FIT2 (External definition) ===================================% % Fits a multimodal phantom object to a given set of data, x, % defined on a given grid or vector of elements. Outputs a fit phantom object. - [phantom,N] = fit2(x,vec_grid,n_modes,logr0); + [phantom,N,y_out,J] = fit2(x,vec_grid,n_modes,logr0); %=================================================================% diff --git a/@Phantom/fit.m b/@Phantom/fit.m index ff69372..20dc1bf 100644 --- a/@Phantom/fit.m +++ b/@Phantom/fit.m @@ -6,13 +6,17 @@ % x Input data (2D distribution data) % vec_grid A `Grid` object on which input data is defined or a vector % of the coordiantes of the elements +% logr0 Initial guess for center of each mode, expressed as a vector: +% logr0 = [dim1_mode1,dim2_mode1,dim1_mode2,dim2_mode2,...] % % Outputs: -% phantom Output phantom object representing a fit bivariate lognormal. -% N Scaling parameter that acts to scale the phantom to the data. +% phantom Output phantom object representing a fit bivariate lognormal +% N Scaling parameter that acts to scale the phantom to the data +% y_out Direct output from the fitting procedure +% J Jacobian of fitting procedure %=============================================================% -function [phantom,N] = fit(x,vec_grid,logr0) +function [phantom,N,y_out,J] = fit(x,vec_grid,logr0) disp('Fitting phantom object...'); @@ -41,7 +45,7 @@ y0(2:3) = logr0; end -y1 = lsqnonlin(@(y) fun_pha(y)-x, y0, ... +[y1,~,~,~,~,~,J] = lsqnonlin(@(y) fun_pha(y)-x, y0, ... [0,-10,-10,0,0,-1],[inf,10,10,10,3,1]); mu = [y1(2),y1(3)]; @@ -52,6 +56,8 @@ phantom.type = 'standard-fit'; N = y1(1); % scaling parameter denoting total number of particles +y_out = y1; + disp('Complete.'); disp(' '); diff --git a/@Phantom/fit2.m b/@Phantom/fit2.m index bc9db6d..30d4a02 100644 --- a/@Phantom/fit2.m +++ b/@Phantom/fit2.m @@ -3,17 +3,21 @@ % Outputs a fit phantom object. % % Inputs: -% x Input data (2D distribution data) -% vec_grid A `Grid` object on which input data is defined or a vector -% of the coordiantes of the elements -% n_modes Number of modes to fit to the data. +% x Input data (2D distribution data) +% vec_grid A `Grid` object on which input data is defined or a vector +% of the coordiantes of the elements +% n_modes Number of modes to fit to the data +% logr0 Initial guess for center of each mode, expressed as a vector: +% logr0 = [dim1_mode1,dim2_mode1,dim1_mode2,dim2_mode2,...] % % Outputs: -% phantom Output phantom object representing a fit bivariate lognormal. -% N Scaling parameter that acts to scale the phantom to the data. +% phantom Output phantom object representing a fit bivariate lognormal +% N Scaling parameter that acts to scale the phantom to the data +% y_out Direct output from the fitting procedure +% J Jacobian of fitting procedure %=============================================================% -function [phantom,N] = fit2(x,vec_grid,n_modes,logr0) +function [phantom,N,y_out,J] = fit2(x,vec_grid,n_modes,logr0) disp('Fitting phantom object...'); @@ -35,7 +39,7 @@ corr2cov = @(sigma,R) diag(sigma)*R*diag(sigma); y0 = []; -y1 = []; +sy = []; yup = []; ylow = []; for ii=1:n_modes @@ -50,27 +54,30 @@ y0(3:6:end) = logr0(2:2:end); end for ii=1:n_modes % prior std. dev. - y1 = [y1,inf,1e-2.*y0(6*(ii-1)+[2,3]),y0(6*(ii-1)+[4,5]),10]; + sy = [sy,inf,1e-2.*y0(6*(ii-1)+[2,3]),y0(6*(ii-1)+[4,5]),10]; % [C,log10(mg),log10(dg),log10(sm),log10(sg),corr] end % opts = optimoptions(@lsqnonlin,'MaxFunctionEvaluations',1e4,'MaxIterations',1e3); -% y2 = lsqnonlin(@(y) fun_pha(y,vec1,vec2,n_modes,corr2cov)-... +% y1 = lsqnonlin(@(y) fun_pha(y,vec1,vec2,n_modes,corr2cov)-... % x, y0, ylow, yup); -y2 = lsqnonlin(@(y) [max(log(fun_pha(y,vec1,vec2,n_modes,corr2cov)),max(log(x)-4))-... +[y1,~,~,~,~,~,J] = ... + lsqnonlin(@(y) [max(log(fun_pha(y,vec1,vec2,n_modes,corr2cov)),max(log(x)-4))-... max(log(x),max(log(x)-4));... - (y-y0)'./y1'], y0, ylow, yup); + (y-y0)'./sy'], y0, ylow, yup); for ii=0:(n_modes-1) - mu{ii+1} = [y2(6*ii+2),y2(6*ii+3)]; - sigma{ii+1} = [y2(6*ii+4),y2(6*ii+5)]; - Sigma{ii+1} = corr2cov(sigma{ii+1},[1,sin(y2(6*ii+6));sin(y2(6*ii+6)),1]); + mu{ii+1} = [y1(6*ii+2),y1(6*ii+3)]; + sigma{ii+1} = [y1(6*ii+4),y1(6*ii+5)]; + Sigma{ii+1} = corr2cov(sigma{ii+1},[1,sin(y1(6*ii+6));sin(y1(6*ii+6)),1]); end phantom = Phantom('standard',grid,mu,Sigma); phantom.type = 'standard-fit'; -N = exp(y2(1:6:end)); % scaling parameter denoting total number of particles +N = exp(y1(1:6:end)); % scaling parameter denoting total number of particles +y_out = y1; + disp('Complete.'); disp(' '); diff --git a/README.md b/README.md index dc277c2..1a23e56 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![MIT license](https://img.shields.io/badge/License-MIT-blue.svg)](https://lbesson.mit-license.org/) [![Version](https://img.shields.io/badge/Version-2.1+-blue.svg)]() -This program, originally released with [Sipkens et al. (2020a)][1_JAS1], +This program, originally released with [Sipkens et al. (2020a)][1_JAS1], is designed to invert tandem measurements of aerosol size distributions. This includes the inversion of particle mass analyzer-differential mobility analyzer (PMA-DMA) data to find the two-dimensional mass-mobility distribution. From 9cb385615cb8a5a7dd67e1aa5c2b0d9cf04eb5c9 Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Sat, 21 Mar 2020 22:40:53 -0700 Subject: [PATCH 37/56] Update to colormaps Added CMasher colormaps. --- cmap/README.md | 40 +++++++++++++++++++++++++++------------ cmap/cividis.mat | Bin 0 -> 5674 bytes cmap/docs/cividis.jpg | Bin 0 -> 2045 bytes cmap/docs/eclipse.jpg | Bin 0 -> 2042 bytes cmap/docs/ember.jpg | Bin 0 -> 2073 bytes cmap/docs/fgreen.jpg | Bin 0 -> 2088 bytes cmap/docs/lavender.jpg | Bin 0 -> 2019 bytes cmap/docs/nuclear.jpg | Bin 0 -> 2013 bytes cmap/docs/ocean.jpg | Bin 0 -> 2043 bytes cmap/docs/rainforest.jpg | Bin 0 -> 2158 bytes cmap/eclipse.mat | Bin 0 -> 5980 bytes cmap/ember.mat | Bin 0 -> 5986 bytes cmap/lavender.mat | Bin 0 -> 5981 bytes cmap/nuclear.mat | Bin 0 -> 5993 bytes cmap/ocean.mat | Bin 0 -> 5971 bytes cmap/plot.m | 7 +++++++ cmap/rainforest.mat | Bin 0 -> 5989 bytes 17 files changed, 35 insertions(+), 12 deletions(-) create mode 100644 cmap/cividis.mat create mode 100644 cmap/docs/cividis.jpg create mode 100644 cmap/docs/eclipse.jpg create mode 100644 cmap/docs/ember.jpg create mode 100644 cmap/docs/fgreen.jpg create mode 100644 cmap/docs/lavender.jpg create mode 100644 cmap/docs/nuclear.jpg create mode 100644 cmap/docs/ocean.jpg create mode 100644 cmap/docs/rainforest.jpg create mode 100644 cmap/eclipse.mat create mode 100644 cmap/ember.mat create mode 100644 cmap/lavender.mat create mode 100644 cmap/nuclear.mat create mode 100644 cmap/ocean.mat create mode 100644 cmap/plot.m create mode 100644 cmap/rainforest.mat diff --git a/cmap/README.md b/cmap/README.md index 1a9be72..ac59109 100644 --- a/cmap/README.md +++ b/cmap/README.md @@ -2,22 +2,20 @@ *Last updated: November 19, 2019* -These `.mat` files contain the colormaps from four primary sources: +These `.mat` files contain the colormaps from multiple primary sources: 1. *matplotlib* - Colormaps designed by Stéfan van der Walt and Nathaniel Smith. (More information is available at https://bids.github.io/colormap/). - 2. *cmocean* - Kristen M. Thyng, Chad A. Greene, Robert D. Hetland, Heather M. Zimmerle, and Steven F. DiMarco. True colors of oceanography: Guidelines for effective and accurate colormap selection. Oceanography, September 2016. http://dx.doi.org/10.5670/oceanog.2016.66 (More information is available at https://matplotlib.org/cmocean/). - 3. *colorbrewer2* - Colormaps by Cynthia Brewer and Mark Harrower. (More information available at http://colorbrewer2.org/). - -4. *turbo* - A. Mikhailov. Turbo, An Improved Rainbow Colormap for Visualization. -(More information is available at https://ai.googleblog.com/2019/08/turbo-improved-rainbow-colormap-for.html). +4. CMasher - A collection of scientific colormaps for making accessible, informative and *cmashing* plots in Python. (More information available at https://github.com/1313e/CMasher and https://cmasher.readthedocs.io/). +5. *turbo* - A. Mikhailov. Turbo, An Improved Rainbow Colormap for Visualization. + (More information is available at https://ai.googleblog.com/2019/08/turbo-improved-rainbow-colormap-for.html). When loaded directly, the colormaps will appear as the variable `cm` in the workspace. Otherwise `load_cmap` can be used to load the colormap specified @@ -33,7 +31,7 @@ The colormaps, and swages indicating their color progression, are included below ## Sequantial colormaps -#### From mpl colormaps: +##### From matplotlib/mpl colormaps: ![viridis](docs/viridis.jpg) *viridis* @@ -43,7 +41,9 @@ The colormaps, and swages indicating their color progression, are included below ![magma](docs/magma.jpg) *magma* -#### From cmocean: +![cividis](docs/cividis.jpg) *cividis* + +##### From cmocean: ![thermal](docs/thermal.jpg) *thermal* @@ -61,7 +61,7 @@ The colormaps, and swages indicating their color progression, are included below ![speed](docs/speed.jpg) *speed* -#### From colorbrewer2: +##### From colorbrewer2: ![YlGnBu](docs/YlGnBu.jpg) *YlGnBu* @@ -69,15 +69,31 @@ The colormaps, and swages indicating their color progression, are included below ![RdPu](docs/RdPu.jpg) *RdPu* -#### Custom: +##### From CMasher: + +![eclipse](docs/eclipse.jpg) *eclipse* + +![rainforest](docs/rainforest.jpg) *rainforest* + +![ember](docs/ember.jpg) *ember* + +![nuclear](docs/nuclear.jpg) *nuclear* + +![lavender](docs/lavender.jpg) *lavender* + +![ocean](docs/ocean.jpg) *ocean* + +##### Custom: ![fblue](docs/fblue.jpg) *fblue* ![fred](docs/fred.jpg) *fred* +![fgreen](docs/fgreen.jpg) *fgreen* + ## Divergent colormaps -#### From cmocean: +##### From cmocean: ![balance](docs/balance.jpg) *balance* @@ -85,7 +101,7 @@ The colormaps, and swages indicating their color progression, are included below ![curl](docs/curl.jpg) *curl* -#### From colorbrewer2: +##### From colorbrewer2: ![PuOr](docs/PuOr.jpg) *PuOr* diff --git a/cmap/cividis.mat b/cmap/cividis.mat new file mode 100644 index 0000000000000000000000000000000000000000..78aeb57e5d0a7cce600bd5572192804494e79e94 GIT binary patch literal 5674 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 P{lB^AGBlPFfvNrhbcDUC literal 0 HcmV?d00001 diff --git a/cmap/docs/eclipse.jpg b/cmap/docs/eclipse.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bace5ea855849066e19c015746aa39646280b28c GIT binary patch literal 2042 zcmbW0c~nzZ9>;G;LIANENGMP=Ws|UpgaC@xuv;QK0VJo&AW$J4%fXVO13d}yNj~NkhXV^ zp^?$YX8SEZHnA}^GNL05viWPsU;0fCl>{;@%GMe&E#b1fn`gLsM%96i9aiH5?wVM!*w^ z1OilFgx>*yOw=*9cT?95iXxdLQW$ADB^svgjUCkB;T1E7)92DPwe&vJH!w8cXF<2L zVmdlGA3WsZ;pydl>{B0h2q!cw{6qvdIwtn>GjZ{}uafv*C!fFY&E+ds|B~_7OhInm z&HRGGZ-qsrWp~OeDyt;bP4}9m_gfycwsm%OKkDi2>mL{y9UGr`Jo(Gi%*)xi`CnxV zi@&Y@vG)4S`i6XSOT~o)_;*GAL${bu#PY2|<(gu###OK20wSxyz_fQoL<|OQXKjv$ zN=BMJn+6`vF1N^nYn(T-Wr22V;TN7cHbS2_5)S8ha2+p~kr$J8thn-Ok!!#ZF}7+w(%YY#3xwG zljU4tN|^7slVj4h-R4>g2HHIoRgqZv1-@N6RskSf` zfq`$&ik0%n6-lAz2(n-hi-Ai^C^gC|rq2rF0+uj_QSVL2g0-(li7y}Es(NMIA zQe$~Z^%vMDhhF9cCGGqOP1B>vwZ$_bb{Wl)Tlhr`sG_iuO75@U%|BhZ9)7w=p6r2v zVkg+@B?(Gp$P?UQxc_~~=ke=(#>8Jo+N4klyIgiDam;B2AsfQ4^k%@n5-X4wX2?u< zp44ADWF=(@m2(ez8{LlB&dg%qz#cmc0QjOMO0}HnWX<8qkrzhD3|BLxlHD0vjW}%o zSWcbXyxiUNNsB<5l9C)(B3O^Paqy>cuMb?o2LLanOzRwIrIHb9 z>5~gfS@G*5L}UxDh)0x6wC;=Z7gh7CbX99*Vi0AOv>0ga55?ZWM^CMuQQUBmDX&;y zU^vTbuMGxrdLiT8D;U_auWD#7Qyknf9ZLC*v*P=h(e%ie*iW#O6~(oP(ewQnNG+n* z5q$3+$G{OWy6MPxWT99~7!X*Ek{M;Qubk(Dt6Me#=Dnyq--1jIjh*jD36$%S?+CGdBUUKgFn|y# z94BtN`APRd6tTs?@&X17h5V0bn@W|TYEWiS;4qIabOu<6) zP))C$vNZ`EZ=T-L;398lg#0WAG~G$Op+tHF*3N{u%)*F~UPX5;tiOeUEZ?^0c^Ggz zY^TtOTrCy!k9t>o4%WxhUQ;3B4{&i|2fx$M^a+-Hw|LfzdcCT9$|0tvVPX39hj*dk zrb?0OQHp`TM+dx*>9yt=*BnD581ic>=lle%lI8<@mFKCPGx08D27UHNr^iF75>-EjI=aXkx-KDo z2pcB#!6bpT<)7j=kjH%)LE^*m0_WD%u!h7hvuqg8e_ZX?e_CJOPat;?l1l~Fi;|%# LvvZCT?3aH6x*)ne literal 0 HcmV?d00001 diff --git a/cmap/docs/ember.jpg b/cmap/docs/ember.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e7a19be2e5dd4350871e75cce3e84dc201b37e2f GIT binary patch literal 2073 zcmbW0c~nz(7RP@f2@u56KoE^!SY%0bw1l7%!IUKe0l|Q5sg{GlAfOg(K&F-R1Pd+5 zB0@lDY1jgUMV3lc4u{YPA}}2YplIzG6r?OVB@niR=_IO#n#E?7~T1h1B1Th1VbPY^mpj*-nnx(-NMv@ z{y!Vi1Bkl74r7VMSOP5~21~>sy?}z|iN|ak_?Ka{usFQ7jxIrO2P)|J5NKhrSS=hD zkH_Iqbt<|Ka74VJg}tk`5jR4|GLb}2E2z}9a%=m^I6x-f?{NM~Izexj$?iR5Ya3fT zDvjZI zBGJf_o~BE3E2wPyiD2arAU8gLMW(lFKjXFa(iYk_*?$L?{(q7E0rqdMUw}RqgBA}< z1S}xWnk~H?9aQ`=_7;fjXUGy+>a{{x#ds;)P?cyD>iMec2b&boa0mjrbA8(f5a2>l zG&BZ^E@a+3V14KPPanTKJHC%e(LL9rkSj0;{n(dLTQz6!BS@)dr<=x^& zrm&Ou5ur{NEhU%rKq7FAb7kk%ySZHVq^z4P*XdRx;QM&xX{uCIo>2FJbo)OR0oO*q zi9~m{zOoZU#`^!L<4^`OmG;mu15Js2%-ux2?7#UYVC-3Y!3#ZfO_2Ji`0u8WI3HFT zGkGtC$_NvUoSHS-WXpL||DvT;q;CA(v^cpRK39o4z=!(mbw|LVR%o)#OAx^f+37v> zpqv~&*!}9%i6;mcs-djEgEIWelCls`asb*~k-X*zA`WDVyw{_c@52T9wz^!rBv~9q zK!$Hw@ORLRbOKhlY@8_=pFbN!8;4`oOFOrvy*@Ruk`;q{2#_3rjhS<*a1Hh%0v4rP zCk4!;yJ5j%_3$EHK!XOmr;KI%7L|4wG?{?_y>h;b6`YnFzVU(Zb)YDe(>KwGD(wY! z$?T*NKBU4uN5Cs4nrSyh2F(#85O93}HjbuA1J+idQq8Qh_)$B}_Un5n>_@3A1Yio) zG-=A`ZY%|UOd>aCNt^59T8qB?Lh@HwQI?Q@{`N+^D*`HPzC|60(Ogp^KV~VgV=TEL zOWr6B4iMhoitl8*d+o&KB~t#SsY}7SW>uNT2MVm*JA&MA0c-`fgEUNnp1f>Az{Cmy zRQB~PBlR`&{jz;NzfN6-rWeB3hZVKiNyF+?A6N|DoI>i$t#U3 zXz~sg@v`EA8jfVq#?z~Lw@&kW3gTS3&0bWq)238u*;d08_3LrHsBHJ+u{ZsB(Jt{+ zvsN2Fw37y(kdCOYWu;83HyCqL`Zs)KD@<%{5Ia@={msii_@B8Gti*nxydV8M*egH?EExT}E z2&bTEpI<_XoJJz4ky|ET&!coo9}F%#&cbdVb0SR>6EO`{(ITy%KJ-7F%Zh4Exb(HBnS$ zhcF|v%1AyG^@{)ILeZ)1_Z*k)!;Hu&o6AsCq4R}ycdA}O*(5dMog*d+|ty|0ZbV#Kv6wS1vvvCJnYzsD2jX*== zlyy0meZ?;)v|AF=4tGb**M}J>zq|p%220{GZX0EJ^D_iMA?gcN4`VWKEDWTm%1c%i zwx|4hlDdCHXQL5zq{nq8piVc#H`S79w(-x-W=Efvb|v|3#XHnei+~ZgF&F`dH^$#K z#X>8FKcL6n!VK=&<({$M5zta#us5*X_FOy%?cwqg4FbZK#1m&nS##8A1f&sAWms2t z$yLp@9MMf-lwapsJ_20hDQaDRRM$&Btiiuy$*ozBv#C(b=(fY0Hivgvg}qF31l)Nn zM4eD!#|UT7N&{tX?BoVXIL#*gA#Hq8dd;T-P4Y9ewmmfnAeTG)R~RVsVuc$flnoi1 z^-gtea&_5hCfHZ*PdvGP!scmA8aXtot8~<3ub}9mc{(%wxQuPQ%KvTLaQPT{9C`6i DlBLbq literal 0 HcmV?d00001 diff --git a/cmap/docs/fgreen.jpg b/cmap/docs/fgreen.jpg new file mode 100644 index 0000000000000000000000000000000000000000..cb04824b05a5b2ef3c962477a61671b3173027a5 GIT binary patch literal 2088 zcmbW0c~nzZ9>;Gsl0XpIm%#=D43G-QRurWf6#@kcC2Sgp;UE+RM2-SNtM~>4sSF+l zF(CCIni3$U(c%yhIyEe^%E-PHoFd373JD+tvfO#mndv$6*UbFhdH0;(yWjhJ-_O0j z3&-F~u+xwE853YI0ASDuzzJ{!kTf)i8UzxNNFWV3!&7qg`g^ z<`z~CHWsu!mgeTR?sj|jI=Q;KQf>Bo?sGoq;Nt46E`lMG$rLS$iMFk{;KI~~%{4~{0%&Kfvp72PFS9qnLfUs`Gtgrg-0Ac#*T^oGVbK5_=J?ywDaj1UwwT!C-+L;H&^qo7vJEN zl-?}+mit{Lud2F+fA8U6>O_y~A2&3%v_5TX@96C69vB=N9vL0`d3FV10yNylR*0!1p18^U((D4JJ{=f2rg=d@xx2$Z(``h%k4tW1BLtu1eeejMO6e#I8R4;iFQ_dRgltv(!gO7t zZmrcrAof=oS`40M%y&?KBYlN@pU7on`HT_c#3tOQ;qcIK2UB& zN%-Ma>~Q88QP3Rn6?V}hS&4hgSm}l!H=vPO2Ep%^g5(nMRET{b$DWq?LA5X*rNo_v zV5J3>`87IlP}ws_1u;q@AcqM3eLdgHj=6eTK+xmx3PglhX?pW~Q}HPR0dG{r)oS$@%zi}X=Zv+vWo@`RSqx+F6Zqf9yLCYieuq{ymU zQ4oS8{LxmnjuPWKsZIn}N4~fB_wl0lZk2f-bh$&_%32i5$W&SA(iEa2g+R~V1@&Qx z{@|~i=nJ9cU+_Pj`&t&!DqhD7Kp@${KofX$eFM zMJF_-3A9+xZ|d`o{$V(h-m<~>SS%JE^}8>MrXQOKC{IYOF{<|SNpqQ5F+n=tK0wBp zuOMK$8DzAoe3CAxTHLAIPBoS3!UD>!j>{vrZMi=Y!p3HA=*Qm4FFG1iqgdb;CiIkB z`P~=)LM``wXtO!G#?AK>xTK!&d)NVir@m_2kk)Igco!sJ2=;bFwWRC9r;N36hI}FP zPboyjW}!-M5bRSIm#mEqPD@6YOhfRb&A0yi$-y!XlW}G~QY6AIH(lof-6%jHB!Tk1w3Ia#jkl^0pq@(0Z-_X6(zAw zte_Ms64In6^N5Nbvj~-jkIFUJck!ADH%*#+MZ6F_fJi=JsOVKu#oul_@ywvMlWX6x|$Mwu_$;tB>vjt}&LM(S4GT-U5pPO&%Q3!wj2Tpj+PXGV_ literal 0 HcmV?d00001 diff --git a/cmap/docs/lavender.jpg b/cmap/docs/lavender.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ec420d78cd3b450de07076120f5ee309e61bad0f GIT binary patch literal 2019 zcmbW0c~nz(7RP@f2_Y8Vj3_Q5Q!n|t%sIP7Ad0@MIFtNVNu)& zD4>j_gkXS(h(Hm0G$IIy$|6Y7c4SpRv=G9U?Y()_o|!ZKt26hV_x||(?z{JX?)_eP z5FQ4r{d|0V015>F3V8thGw=p_y1F=BtR4=B!{hbz4T(mE1_p-fOiYc5>&?j<)|*>c zSlYO3w6u1jSXkI^b#QXsOsCVy8@GA7)3&>8q0?4^pzwISp@HFABcru6D+?>y|7`F> zK+psBC~Gvz8t4#EXaWju1!Sa7Eb2D{zZ*&ijlt^T^zixyh(p6_po2oAbuefw7K1^& zPa^jKL%^C?ZD#74a=*b@A0*OF3rh7UUbW4nz}{J#Em4Qk@%n4b)~++RwcBX#K&88V zw$*)`hxbmF&n~vFA1^34Bs45MB045EZhw42;&+Erj~q=qcKq)dnOWIq&gSIhU%paM zSakK8s7zdbLsC&$b*HYr;fKb%P4^zPv_5WY?|9PrqObquz)yp(UXP59jlX?2F*)^K zKBt&pSX^3Gs#dsA0R0II`F;ZX0~Z0|(!pTR7~BdMO6Mr@q6rwR)n;82CKvb3K~rnm zX+5HsptQCbPuUVUONu(wtG~vEK4L3hLHmvD&w-`?Uu6FV`w!P^V2DN`!$T7Q1I(VC zxcaSaaKRVoToBz(?>)%SEapQh`p?2;Rk2o~nMyw`>L^K3zK6k5Z{?Z<{zMrJhJ2*e ze)p-p;@t*LLPf94BTWE<2SeAr?o(OSQG)tFyH$hgc7-G>Wro}(TJJD=&r(nybrwzL zaRN^!ZVq%l(wA|w|h56+`gVv>g9RnAE8mPIkZ!wP_RdfnP!fo z9uG|_Dl-|Ukzaz8L}$N-RfG?R{FzB9FxYO5l!0YCJIf~C zY*G*N3dE%1j1v}j4t!nr0}O=m{F&2%29A-+#cW8MlU9k18QajlU91+I`wK_8uyQ6i za6k@&4Y~ZGS`LXifAg2n=yd`6Ix;hb8Wj@;r`nf!ZNgX>>^Oq7V~YQ%JR&#bTgTcA ziOV=8pcYB-IbS_58{wEIU(zVQL>g5N1O2NBqov~SI>qWcs2hX)?kkO2RphM1h}HuA zEm=160tT63#F6m&iwQ4<3fkR1It-L@q+HoBCL9P~8Ass9;@opb|?2FpXzv`$s^H-v-CHP$5!40W-?E*)oE`)(MJ+zm%TX3Ar zHO{n1?$(^#11X`y&739}T&k1}F}jKzy?Uwr(iftLE60P&*m*U6f<(8eK<6aW_`Fq@ z<^C&r`8tI#DvmfGbZ%e$b1p4aiXV_lRuocBpmnBJ4tyWb;L+3Nn~zN2q+hzXYg=A5 zo9b4Hjq3`})6Wfx5|i?`iMIzKIaa3lDkJ;%>t{Be38O{z^6O5TaL7-`wyk>C9<+2mG2NKJrU)IpPSl-iD6KZtmL((XxlUuqWZwN9RFNO^smCs5|Zp5pK4!S z#w}xUFY2E(@?_47a;7dMP*}lvrOi^k_r`C?*NGud3D&XPJE;Y(r&Lg?$8$5y0!KZ` zWs$=K-437mHLiw1cRUPcN&^jw7!&pUr5;gE;Tb`=6(INv{I(J|N3v4e zi2N>v=-j>Up2cQmA8}6%AuH6$FyM!35?Xk-k#N%~YL#q^MOx+G7REjroh6YVNDGZx zZKPasfyY5S)RAh0>yWp#Ng(Knadl!_E)lt`<30IY_M+%R I9s_>$FHuUkq5uE@ literal 0 HcmV?d00001 diff --git a/cmap/docs/nuclear.jpg b/cmap/docs/nuclear.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a2c7897d6a9f2ecb175f927729ef1204e6b6af2a GIT binary patch literal 2013 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 QQ(H*Kt~-`xN#Nsu0srf(N&o-= literal 0 HcmV?d00001 diff --git a/cmap/docs/rainforest.jpg b/cmap/docs/rainforest.jpg new file mode 100644 index 0000000000000000000000000000000000000000..67c409e8a17bf8a47c4bbaa52bab88882933d6c8 GIT binary patch literal 2158 zcmbVMc~nzp7XL!R5#2sT2DSq|9UWaAZ9QFGT_RCW-;lJ-(7?dZ+{E+~(kcsz)hY{1 zOKW>qM{8RbJ4;KajcZ)iyV2=%ilf&SPugZzcRFn;364l48X6d`Shj2h&BoG(_CFhT z4;bqKC!8%FXA889ad=}K)&wZ9Pi@>s1HTzg3s2D2(bXgB8$f`}2x#H(cr5~6Tbn?D z=yVtd1Y>Oz8@Fvbrks7cw#P`cvw5X@c0M)t$-&(!d-wgx8AN@vPgj^*IIMPbT0^C8 z@Yv|-#n`@sQVIe^*m{_9rk=Y+3mhpcv`xmi)^BM$(cpO|F-WV`} zDo0uPr})rLky%#{a$+uy1R zH@7BfgKi~-AK~AH0WCC?5E}1%^w9FwQL+E^77T1wp!1{&!`P`d=KDOK-i$iF=j`U& zJ#jmXU~zj`oaMRg6U`(KqJ{&>S*2Dj--RWQTQ_7f-^MLP+4cPxn0m18!_a4pUP%Po z?Oie>fBgQDwQ}Wi=nRn@`Wyq^u=ES9Y*E3jmwB#&4>e}Z3N(Em2ITw8qYaqz)0yZZ zPbJ*j#|XFYqUMh`1u&X;>Zt|@*oBfAi3Pgoo+I-2a2(lQjL@uVeQ8lM%#tHaY(iSH zuPx5Uszi#NiXctnFU;v=jZTnCLLBZe#K4cA24dh|2nO`)C{Z#BXKyyOZu~KyJRrVe z(G?V98UUkkYY(a_vNhhvFkrIQ9oDgf_y$T+eX*ol=yl4Of6}QU`wZh)F@6_>dwzsx z{osMTj_r}F^qDG2_pBtb=Z;12!Q@{(GfN7ZN{pTyToY^Qqh74mh z0SUrLp77+omqp&}iICc;)W+!g`jcPk9aVniS+*&Z<@1VXSs~pZ{lT6?QQ=| zx?9#ki=@qpdcI%Uzr$8m)>xQ{q=J2&uk71!b|2igc&w+&;q->bgRCr(Lb{dDNV#S} zO1oiT+}HZ{a!NAel_2d#J7-41nIVUH%aXjW-m&o8y^8}UH7NEQlvX57i%5|?S#%&j z*t_otBV6eJnDwp1ZCczG7B?2tJ}p;XD4A=!nyZr&W{xQH;q_i!9wBI1sMl0pm7S*t+Z)U0R!)kvHqHgT zVv!YrWxnFWX%gfF8cNk-x!lqn;aRNju#IeW+ntYZ2bmT7s7kT;(b2}d=9 z0gJ*EsWX~!5}8{>nsc4or5P1#pKTJcY*S}C>{I$&qnwAS(5iEU3J6c|uBFq^9!&rQ2wz)iXd{^9arV}G~F%6Qws_l*oPd1F`I zP>zA2J;-Pn_n{SK1qO1)3t>hm!HcBu1M4SPfA)!J0N;-!XS%^LTC#>=9wg3PC literal 0 HcmV?d00001 diff --git a/cmap/eclipse.mat b/cmap/eclipse.mat new file mode 100644 index 0000000000000000000000000000000000000000..c2e729a837cde3d8605ed9849091a60287bd5be6 GIT binary patch literal 5980 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 HaqxctdvBuV literal 0 HcmV?d00001 diff --git a/cmap/ember.mat b/cmap/ember.mat new file mode 100644 index 0000000000000000000000000000000000000000..1c5389a0699eb6e833f9349e810db9b4cac95fd6 GIT binary patch literal 5986 zcmV-o7oF%$K~zjZLLfCRFd$7qR4ry{Y-KDUP;6mzW^ZzBIv`L(S4mDbG%O%Pa%Ew3 zWn>_4ZaN@SVRRr(VR9fcF(5KBIx{mmH8~(MFfuS8ARr(hARr(hARr(hARr(hARr(h zARr(hARr(hARr(hARr(hARr(B00000000000ZB~{0002m761TvoIRFzI2G>u$7N)F zkW@xmMjBR0pNzbzl&FX@Dxt`ZjEoX0BS|tVNrmjakG(hNSaEE}<{XtsRDQm{_5Aa? z?)$!;`?|09bv@7f=A@&eljftN+q^**y8q#S4%7d}berht82%gdbhmBk=ve=gik zlb3D~rdYZk+UGnE^_icBH8Y1!Our5U>=~`$dCe)Pwa}QH{TsJd^ z-G@VcA#eHILri&BaSv>LjFi+0Q)zKu{MKspBX?+J!<6b8ZTV>2=4^J?$P#GSbkeP^7X-Id>`jMAt#*#M-I!d z?D-T3ELsRW%uI*g-;+OWN;6Sxz()%f&4z}J-BpX=94tMqh|T+xi$xK^)0(#V;4=K{ zz&Tt1p>1rdo!Uik3Jq!UZ6M%AD}VP^sT!Uje<;h24YjI=y zPxd_XI=D%gDSi{LN2iO!5aoM4JglXP1Bx2Zr!QGS@oGfO?fK<-wI)oh`*_81H{*z% z^(&VCW>^y%EqLQw;NY04-EP+kzN2-qk_v5*2+GQoVQz=VC)19B&UTpla>|5h5#gGx zY#dNP1V6o$AaOSdCzM75O?^q&{Bm#7SsDrFw3+gpbvy9tWbgFXvJRNHzrJ1|)`>8o z>D>*%ov`6pJ}I=?i3CT1q?+4Z$Qgd@pWfPqn&s5Cp?|xfzgl2%*+GogvF^3c8-#iJr70efAxz#I+S?N{1U*y5h@4Hs zkcoObl4w5+@6c1m*~7zF7#0z{BQ^rmsEPf$IfBVG{~O{l}e^j ztT=kp>7E>gn-_!mxYH<%PG z3`t_iMa-3Bkji_1g=1w56OO#gG)W3}M#~>eG^4;Swme4W9R>G1qYIvQP!PeFt(U?w zj_~j+gU^-5aoLQzyx(aY{)helI46yx#cN&2VQ3sI+)8acJQH}hTTWl++yq$r1-cGA zn1D6ggROz-6FBr-CEaOs0;emY1jsy-2)U`1pQAbnMhAo0mHU&J;pt=im@o;K)QHH~ zFO%@@hGVyuXigI=hSBqaA#}I9ordvR3UyZd^v+n7qEURYX%Mwm;Kj#$rvdM1Co!*3QAm(r4ZVrMb&e(xET@>#61 z)GzRoXVFhO@crhGSvZen?AggVhh`VuWi7Ee5sGGYqk0|CaM=vHS z#5X--TiZqjFGbl}RD=rJ7#n5%C>0*p33UM~RM1JW$JJ?4Ar`l7CC8A8hdmuqI@VMu zOm`>8xl$3bPl}M}Lq)X;$0wr@DwK)(QH}4ZID9>bG?`4r@-weH1^HB{v=rMbR8irO zQGUd+m5N=gyA+Ljsd#pEVEXSE6;13r4{KAYaJ%WVp#PJK=zujNo=r4V`$y?NW}{)( z=zKsN4-FQ%J&kUHG!)2PUZ5ABZ_ ze=4a#L#p-Gpqa}wfGi=WtG^LzvQnLH(y*DrFs5os!{a`Q$a+f}bXSs_*6z~K9llgF z>_~$?lOgkcR~oE8oz*RVNQ01xvTL>%4L5j*{Q6I6$kq_7P4}b0iJ?0#Cx8Z*j7LTm zFKE!KQaIK8l7^h;>FxusXprHfXM7h*1HFtdD@!;H<<@1nLJ>5`MqBNi+2EjtXWRA2 zjXwHZ?d}^~u$WXnv%%)Sj2uN9^=)CT+j}?e*}lF@_aTf1vBw2L+#CI#r}4TT-st}z zDcN{Fhz7Q_BFig*Gh&2anKh8_n}uI%vLsE?`n+CHYi;GyJM%SSXsKM2~v>`sHu zwdOumXBzyq9Y2WJZ|L|d)FIZ21`QR>-0}@w{PyMZx^C!{S?iAKbmL_`vT*+R3M|IJ9QO ze%>D;f0eAP!UJ(oyhOG7MKI=<)xJv3hCn-(M=JkDIJAZ&gKfql@q&_bqQ>TQ~ii(G2X_^#7}s0QGL?{R~#-&Q}-G;$0LAJkuuTt89hPo z^YwQpf^srRM94G=slvM=Qa>akyyqmd5Vn|hXG>}p2JTkxI|RPp%xu zOEZslJ<0)7mTFegYz`Jr95iOs$;F^UcQvgb7j+yhwf6jZcZ1ronPT9f(gxT&>ux`Yn1<6YF&iq zK5{bQVMTb-6 z)19`~-pC)Ty#3UO04_5TZ6-?sPO9@5Oxz{lZ0`3CfqMj4`pnC`vm@Z`*~@lDRs^^u zQ`QVj2vFHkJmPhQ0PaU@jrFGqm^PJ=6qX`DJnDh6-wpy!eT#OP`c(v$-?~f3`-)H= zZ5CpYTZETed57*kFM|4MzDM#mH|EWnF)tS{Li$A4=)!U#2BVlym6a62(&YF*yB-yS zS2UY%UA_=5SFV@qEf(PYV;8QS2?bEoS>aI9-Ow*!8)E@o0raAd^m}~H$JHIG&jZip zL!m_F{V*jDR)cKkogU<&*x?fK0%smd`1Y+hzRN|6;Ngj%qPds~vm;ZJbHKAQly7x7 z2TCh~yWb{cBUryxD0fdb^eLwqzlLVvwO3?o0%I0fwPP1m9WqgJQn$~akb&RIelqU^ z)1k25oO}948jPry8BGtTqK8>X`10>$NF~2njUgpL!GYMDm7a(-^}PeSkqO}Inqs00 z{)`JA79|DI@!)nSGE}dN!|L=kzB0}@L@$vWGrVK5bc|bluShH=dvz;TRzD#$i*!eu zJr)P@Sex%&iv^|LB`u8_i+ydrpJkfjFiqRdH8vLy-}>z<*T0ec_sQh917x=6x?LT9p}KuFYmo0fw-b6Hzmu{?C=G3-5& zkq5q2|I>l;2RMr^`xsosfFq+7UN4X2htw9Z)euTBMalq_9U zm#Kt#=rZ4_ZB;m`fAy;GauqalUn*4hRpY*)%InP18idf|SGYgbf=kI%D#y1D5(G-Y zE$ezb-5nD9SF-`p8C&#Ck2S)}e*3aJUlWvu%;gULZUWo*+0g3{LxPBTU4XnA7HRp+tl-?#$1kCm|&I!j7JMBp~ml zX2J*wTZn!s`!ze@y)c-6rL+Sp#`ABE9`1xgob!d6*PU2htgd9=+=Y{F=RH^)yRhx- z_Z^kPUAQ&Vcl6kWZoHJ}-DFYNjZ@IcxGvHIMMbmm?ZG`b>0{j$w$_7gE`y4pJ72IE z_rY?R^acFYDpO>|UL4J-k_$=bh3(+s+TC0GP|AF9_VL3$FsOX)ET8PdQLeFuUM(^x zreXC&0vQ6H$->DZ{h;vHiJNci&k^*)2iR8n!PE9YFWh7R&xG7)Y|R7s{#Z)T_Shiw zX139nMGt~>o~3Ag^AM`Cl(df74`Hn4XM54>62 zM?q(}$uTm06cv}&ejeJ`qfOfVC#!hJVE;3V!T7=$Ed5uj7`(?I;_~=RTHY9@%k#}| z&W_=b-)?o@JrqPPlf`;gvjs4l6-1PI!-P3T~_q4$7$~4A13$BF)Od~@l z_jyajG>is}KJfjVhI^x2u$#mTlKjYx3%6%b+$R0o{p}3Aj>p^-B+WoMO8tHp^DI73 zdZxuH&7$_@ji1k)XWqVrG)-UWXp%SA-MS!>mF z%`Xc7tny~kd3`{K{cNrBqgV*C-BP#y{266`ng&swgu7#bbEciCaH02n`K>zxiw4qX zh0U_COH}Iwdv7*GZ_T|(KAj80jQU!h#60|vo0YX;DnJFZQ18_BLX4?7JS$Gw@Db{* zCZQz)!lZwdYM&^99B-@DQ|D6rxOZ7+CbbO0OvV>8hRR`Jp~>gvQvu=M++;fbO88SP z8fKF!p)~liUgJa+Oyzd8AE~JVgBz=1;>Bu2k@ph|yQ<;*Z)ci~K@Hw-rr+D#Ujxl# zr5^J2T3nGgEd1D4i$duWcSWw&K~0p#ZQfP~?>;USTh)44pR4MYBh;gVm9V#6yaB=% zmaCG{4RGkwsK3VEh|%Uxk9Axc5hkJE%s<+QqC4jOX=+Wdo7RoyN^e3#(tLsvcQa~| z-A>6nG(*Ls29BIKo;!ySv*;a_#h1W+v+u(P7 z5$66@9B0y}mps%4@(8~ZmrEP|gfeO+R<=Rqk{Om*+Y#_yz2v)QJF2$q9eEep4o|W` z+(K77)HFo2sC7 z2umYEE%WE`=QTuV@r02DI>o92%c_%$kHtNh60O9Y}}@v+gKz zA>ji;pibK7x~$L?#rSv!6hi?03P zX@`lJ*oWk@HY_S?=`WkLA$6w!*9>bLJnA&`Ba>V4@bJx#{A#Uusr71m;8+XJn$fRw zxVOOLd)wz)&K9_8c+d5JXvU}Xt3Dpm&G^lwNJlPhf-<9rwdnaKDDsA|M0PYHVQ^61 z{8}TnZhCf^IMRT{<8RD3?leGYIiOE}wjLve9`;n*dNi(du`ztB!}wA0OKpyIP|;q3 z_i`<&3_@+x-D>e5H?iRA?;5yt%A3`B*5KHJ=4c5+4cNM6ldkwzqbXucCXS;TZ{8KL zmxfkhKzjF`bb%`D`X^|~`%@*xX5t3r4p&0#V!BXIZUuUj*i#Kr0ZyCRH-6#e7?sM{ z$B|x!+gWmFWg1E$kdZ_8jZ%VJOd18dHkDv+9U(4hPcaS$?%d}A0vL-gci9;iA&J9V z*7R{9I9A_WRfsP@?K79{-3|F5TrRx5g_?){a5ej8?mXNaH?Pr}+2NjkN23HaJ*X{H)srHzOqaY>t73d0X&|5_u4*I=75{l#k#g&$kXD`bDYhg8|lfmDZ&sE>cnSp6~}3s(vdVwh)D|01l!a{-KhL5P~Ewf zvj?-`{E2*EnJE_`J4f3Mr1DU}-Tz|LARj-aSrYwy3h?WTQ;}wNAtEQE2ak^w!E{7) zP=Tu$q`b}Y#Ge{Bt8o4DMxeCpU#L4Ke4jOExW(B5{KPd zUrrQP;!khq&oR9!I8~ZIzx$&KEn!O4ey^&Lw!U3VQmO`mo{PVXTWfIo5wF=En_B3Y zwar+t*TM9FLYZPp9TxpX9p7lwgJV-n?4$X5jJm#antRcJ?vvKzLsE@kIOA!rxA9*4 zJ5JVLac;ss|IokPe9hoFJ9Ly>(hT{AOY=r1EePiB>$tPI6;2Mes>}Tw8S$|o+*a2!^Ivs8F|y_PgbW&)x|0Jz}eCdP4NGo z9SbTXvlo=#dd3aBpg|=;YFH(-k$GRMdeE)Wzypf-rU$-Olopo&c2E9)#-RdV;t^BtSrx$*B)s7yKv#Rjc>CT27U4St&R?&VPMjZD8f?HLoY=sA3=k) z?y0(NemsBt)JRg|Z(@V#SSxW^wO;Ie+3JncE0RI28x^`AoLw?06rS3Cga=w31|)9! zrPaa^_MuIo2HFgeA`BwBzQlo)DYj-mTCve(X&%O~RSF^%L1R;VQYWT-;`VObcujo7 zeeJdq!nYq6Y0RwhHdcGj1S`>i!c#$?3BEVp@rpbqr{UW*%2wHyjYfxs;f398jJdHT zZXs84@MV%wGk7B;VUJ+*rb!sUy}fe!-1=GDi%;G;|w(x*~Xcz23H{`5Y< zdlq%$A|uAm^;pwNwP>Yg1tg+vVuk&MA>|5H2|r)|GOk-2z(8&MbyO>zjkQ#QYr1<^ z!TCF#x%?%H7NGdcnmq8dX=L>^$E{!E%^(*@D0SXVU}FyWsT~yI z)n`o(%>r*mC;~htD_@nuhK5PC(EOgcWQeEo#EX#%3QO@(VSfT_I{=%i?E()88SQ-Yh8_fvnjc{Yqp-f zU+ZUmzI0TI<;$-%&|z2WN{l-KH^W2IvtV%4?t!Y|1(@Wa|C+OlJgffHDJHw+pbhLe zi!$i>M4E1xP}sItv;NLI;=FOCBCl+o??y_tuemRnx-mezPwzMYikwcn$o{UUUr6PD z7minnuM{Y))@sP??rIdPh0#_td+(RTsLxDfi0XNa-aatoRUIVz4K+F8!{N?*9=2aX zj&pH@K;SB9e5+zPG^D9Aaa4tsXwj;c;bl2?F&}%O# z)w$lin~;@QL?-GpUyGbwFZOY24hkS^Q~z%2k0a~& zr*ydYP*FQB@!0xt`>_fe_J zyq5ySPsxfN=j{4{oAYY5k76ai&T+lFo}!;XC5jxn93rsK>)+FZu@0|aXZ^BjD!Rrx zC*3Mk!wSJLFsvN8p!z-juSoA_WUkBmp$U3SRPOHhJhZPrT#o(xUdHH84JAjKd zL*AhY`h&lZPhUdCXoUDXzP)6%MvnSA7NarxqZC?s-n>W5jpumyR4-5C+Xy0_s~Ul2 z*dn-p)Ap&X7nS9V@MdpT$ELaU|*B8`%M4Q(WHJ!c95qP!+_;-0B* zdNG%ER-aKXeGRH0HdtaC!KO*Pi^5x^*BU1csTNkaZ;UV(=eya1-66Xi@U^;ObRMGbylVai4*%q=T?K3fBvw|=X!Igol9t2i@LAs&T3L-E_f)wGDIeBUc*BJK0Oy>vI} zLLOa?3#>wGcTrq_PHk_&e2}qpoDkTLi6#58Nr4&k2DGixWkmn_fc{E}KpIvc*!nnH zpn??W-IDrSM=U&W^3hdk@5i4E5JfIi+`znRUe5lx`FKTaj!-i#PJn^lLAWNKX8*Hu z3}-X~?OyOO-BCG~@L^rcpfG~k=T-%|ce6tC!`78aW(>#MC|nsbc?LEXbR1#)g#I3w z&dE?@FwHI)mqPKYSsU$b^SpG&%cTc!`yK5^bFGL=XhMPcb($B^k+b=Mi>BQS6^1H{0(Te4p$rcSViXCDdRMC>9)e^M z?K?g-xM;)8bQ3~K%~-&)wdHp^pHD{*18!m=FaYx9*Qt!-gngDKW|bjKnl;S96S)9v z>a>f|$-k)G#a8KV6i;SQ)~I=%p-0HFyJIqG9&=vUVFnwb}E>|!cVrmhDR*& zUrg$>Gt5GR8Np6Nd<2}rigLOfg2L67@y&3_Us}+=UOfZ+onU!o+`W3=n9Ys-xzd`Y zg)$%UCv|nY%XnZ^g|4-f^DjoXY?Aj}?oN(m>6^lter~$>Lab;PRk5aJ>SN~gQn<7L zI8xI7OKVL=l(1Cv)9tB3Iz^UcS{<$E2F4pog7Pvnb%9$h+Zud2`aYb7xplxKgEHhPWJ_Js#0d_*D+{pjJU^j~UQ zCy2;Z`Z8ZbWB*kvCg=nvE?hb4o#TnB#i`{VDf!Au1X`XGq}Ag!T>tcXqC5*NDw=Py zu^{yAz^t0VG5kq0LSD+~ZqD|VIrY|vU(Ely=VE08y=cZrTeSeOjA=B`wM|r)@fkNz zyc!W!^)Zi3_i~VLa;{h}tJh|c%c~9*645OkV((E;7>N`oOT>1J$snoE*r9OlZEE)?I9>GDwA8vSdHQLUzg&UT2 z)UoH01z1@YGxMNiZev!pKDXAkGj1) zmKMxWc%YI+U*K(4VUlH!cFL#ryz-b3;#v~GyJCYT**_EWBsx~W`$EIP6o{69_ekr= zN;+)k-r~*WfmT%;iI|!>W{Q)!tAtG|r&{=c3@&VEnWNQc+xtqdn6?1XZJ<<)_u}@u{ zO(C;TL_?4Dg+sQ^&WxH=)q>+jxK713X~^dKw(bJvXe=;~*GW%RsQY!m5YjaTk+|~> zVSGVkwJ@B3OMl;O%KgT2XPlt7Rkf+N%y54s9a2aeBQTt7gU7`ir7DX5QKU6v$Z)dI zIJr-&rL4TC26w=+yJ1Vz2qDQL%$FtABC~$`WlLr+ zLN;p8kG@f&*dl|~`;B2EP}GB@CPLp@jbHg-v%q#?Ynf+@z5RW~h(9dzwbM=Kk2Uy8 zH>AYZA7L<%MzQuDZJ(bHx&CDqJbstI-&jHnhwu_9ls;Rq!R{(-Yl5)0+>!tNBS+#< z(dTxIwfRbO=i{|?i^bL2KCQFWTB9KKHe%hApsc-!;o;E=`^0nck;9h2&dW1ldpuJn zm%;gqJ!)^zHvnV_#IpSU+~@o(93z{&l$&jkuU;tEX#M*M?qI21WlCFYk5_+x-n>`s z$%?$_pY1(zjbfuA+hzWMtY=<7{?!Te9qMcXndz`VCfk0ox>(OAA=32h|K7KTG}m4h z@ZGXa7Cwfr8a)g*XDP3pKcedZ`N*yv+$}6{Ny|3VcZ^zF6F{6?54-pL$0zSli{Y=> z2f?U2oL;N5zg6ll*v&9T!RHqupo-DQTbBUEmss?iN|w2#nPuh$-)mY5o7eswj#mQth~TjnWoHTC@j6u={J znxxjRpSl#QZ*jf?VBR}1q2BlXKBeX z&KiST6M0z1ex8dg`KF`fFu$Z{1~%Z}mZ95L+E)!!j=&ezF?pAVFIi|-U1xIJT}64J z8@V5mds}M8_;SxE2`PGv_mnwxD-=*j@V7U)aPSqdaG$?LbRtxo+ilH>FC*m|2ErT( zWpT7YPMMQNtw2e9og#JiA;49`m~`~o1`hqPpvGSG!cnL88-$HL*W~BXby9L~2cRpb zP3vbB`p17W;hsZJ;hj)g7%p9ch;Rm53m_^v8=inNOr>^qc5zDRh_|&P<|do&j~;B$ zb3N*Y7+a=_#&)6!{#ttCiNrWgt8G&oqR+_1v=5=5X+1k%-=j4F-jF z3bPCYZnyIt(TO5cFuEt<4v?HC#2}~-WP>AaRbv!0ao|Z1OgSHS*Q^sh?bJ%FS;nhh zpBTTPIDSI@(R=}Q3mx4<3p4&fW}sImZiNYhrHG}ml+4E`LC}x1XeFyx1za*7UHdxx zT~@hadOM&*Wo(8LnW#?8<1IsxoMu9lt*3U?K&NaScASD z0{T*7jidIYpjud+qVD-A3i>2j&Vwo$+~V|kP7XX$MjzG<7?)msnuqa>Rc+u!>9=k? zS4chT)FrDOT%=dN9+`*&81#O4C1VK7N1NUb5t1A5+#E_xKn|g#f0%X>Q(JQ;yxE9| zfeXC7(An|$^>x+iS*7$FoMO3HIKM9LkG~bmd>zUrN@bSvUe+ zXbxZLEwHnkG_nM2eHKOu@Cx@BlahaRM{%Gb?w8Q4Qo${2)Hepq z5XAa)Ach<6llI5i#oP=W^=ULbYA4d2A$#lHhV&6TX>r}C!ft&r4Tm!gI2Mzhi4W^a z%+V4&t6DG_)F+V`H{cRO^Fa0ucFN4DEcy(I2T|{)$AapI_@(F=zZLA!d9Jr~Cejlr z5$EJ$z)Rb43=-Q*5I1dfMPxsZ$jW{6(z{CWoZxDxg1C)|ot|%IyZrF$-@2Rzc#;xT zO??^hrK_jEdTQy!B&lrY>K@qzdzi)py}3Lj1|C8DyW~X@TBEmN#_XcvKReSD5^awI zHB$f1OAcdMM(;_Jqn})h8xy824H&&?LCmSpM+rWp%A0J!F7oPafl6TpCN-kNs3aof z(4B1xrQwN)unCz28*qHeKgVu0Dd;5tBW07P=XLP)=$w~i2ub{a%buj}u6H(`r;I8> zAaBPdI#224J=q;z{?0t&kTSQzLfADVMLa*jhjz^2 zfGL!0w_^^BMY+z#gJG!tJw5x0V`%D_m|-RCf&dJx9#aYrNdO@vChT z3|ZStnh(U%b&1MWzwZE5bL%w1cNoEkSuWW*kWWUpD+xaoFb3YW7Xdnc;v^TIE|EH+Adkw7Hc1H%Ka@*C#=xG;f`k_zv^^SbGFJmq0BX@=~1Nnud%FOx+Djqo9*u9 z-tT+*080AWO3KXQ0z%9HC4GK72Pa!TW{{Jmmz}GJvoteE#YjU(LX3}D#lzOp%hrb3 z)kT_F&(ez-VClguBFrozA}uN=EiTC{A|xWj{Qvad|9PPSApYNPQT_LyZ;qUipRE2d zIz3FCCzZ_XU9z(M@QR^vqs`B#WC>;6Fc*!*G!BYF{mTch8uhVwuJX3F3rngd@Y@c@$!6rc}$d>91B2o)ct|J;P~m zK#3okID>%xS|edX6|W^OH1UCUHp2Muu*~?(am;E=bYci(e(yu{{91js%`9$ae2`Ol zgRu>AfjlfjWso0lNCXY<+nM^5j06rN`%n2@l^LQ5Z3@c*d&z z+Pj{r-7K~x6BkfNzES`Bsm)^gfy{pVaCxnk_k}Csne6`8hYAMW9pEhUH?h|h#psl8 zRo2);#bl#r_h6BGCU@3VuvASRm{oa9bT0e~7Aa9Z*4QL#S{8R8cbMwJT)-~&yI4y(687ZH|$uN0Wbk>*k_!CXeeG|3$79GpQhW)*|+n4EF^=IqO?ovY;7aP8ocxI$n2d%q?Z?F#oryt?wHFw)@;( z0&BHB&mS6?21;%F1y{Aq2;)nl+zi)>xzo~h0er_9h#(Vfhx58)f9r<%BuYfcSeILFtV3t zu%c(A#VG&drTzrZqZlk?!dUvksg}Z(p{9-DFW{SX+TFc#$B)1SJ{u$XH=CE$>gYW_ z48)P?sOB&(*SZN>+wL>%RbvXWx7Q@%7C8KuXC0E^gj9zphuwvZc8LGRb z6r2t3%=`;v@51X9IX8rbt#v$vt59pSZviyMZ4VzMb`Vj)&*~r3JI*ci#c*ZmF1>Dn z?AqF=KN%I7x4?t*EB65Q0B#!N>_f{M(dH^v@%QkBQ9#4chDQB@LwutdGt2D7flVeF1 zcG}(xQMIet$S&|GiNI{p+l^7dVkb$E-AYhrQ4)QQEgaNZcxs^)uTf*q*(Lb$saHoi zJRG@W;!K6UaP(J#YWS+qYjdwChnT^D$&;^1f!Hq10j`{lJ^9psw%@9*l4;pX-6uyD z#O*1k`9ujd`u@07g5t=JC6GlV^X0G2RzZHf57T0dqA&+V{N<|&PG=Yu^11HW!go)b zJoPvv?Z9o9UR)f%3HeV>L@p*E{#)Q@@?^&)-1f`Tmi4rccya$Fw-p+3%kIw5`{eNt zYM=3v8lgTQMk^QAB|st9I&*}yZ#kxnvI8zfOE5!^Lsl+MVFh38IzQFg;tss`m87Ii z+nnP@i5I0&%`zXDNP$yZ`<-uktzL5Rw{x;sxZdRUEFQF@R)!V%z_*x)!nis*)LUQ7nyJx+y zt-D`x-x)0(fa|JHu2naD21GJ=*U^zAF(Ug2oWe5VfWXfdy7*tDmX2F zJxTPwL+4+-x@59wxCd{moRgo=5rCdO44J9`a#1UA?LV(L2OmJ3^8(IlUqXp)Pe|KW zqY|o?FM5(~a>is)kbo!x@U0`c+GD31+qPS+tF`1sAi{HU5#-kQb}!PLf%4O>6=UgBo)%E~lk1-2L-=v#R;t@fsssmq5>qD-2&;08mNoF{bC4X3^dB^P<&<9r%T zrK0%EgiY57E!f>ke|GE_qH@I6Vf-mYP+au-tICm(2Uzp-vZN$I&nN_V$~#+6W6j&J zsio4*C}&^rJp!kC+did+O$bkX(ceZQr12+7)mSzum7u8iSRSd9Ax=-JfC!$^KhZ7e zwBq*E4DgD$KSgn~bBXRKm-(A+YRy$0sFm-`+9z^pyHxUVo6j@CskY|KGwM)%rlzrU zM*=^XRa>t3&{^Hi#1wae@ROmxM1#Ii#Or^_bE$uY5y9oB61;!Dl`48a9C*Jn9HQrV zvZlG~JG`ps4gHQ1Aq2icO0n}(W{HXqlXki3DjuFo%{bmw=k3MzcSyQ#$n$DZSwtcEEKI;ye`}*HkZEz!!ia;q>EI8K^rB%Y9cLuVAgkwWo7F^mA z!bl<3+2gB@H1QAm#9i?bu+;2NIfF7`J(GohPIMIKgJ!Hgd9kvx_7X&&0weUYS5ra8 zcta)88M9>$G=JN(GF5=XkQlpu_#^EBt)84)(D}=Q>oiv!#!TNp_kV*h7;vctycvJl zFL=hyUmqSuC33vKIP(Kfy=DIR~&VWCC^5VEHD@Gnk3zo0C z$Cn_)wA|ehG9VRtR~PK}<3I5u2kPZAyu?zMfqRa7UaJk+TY`E%%;76VlWGgn+L1P2 z&t|H=f_M8Kmg>T<2vE%LXaf2@DQjl;FgPq!p^k*tFvSo-fp@Y^6f@mb){X0!Pf`vJ zY+uYGy8JH65*{ND41+7J>9xXIMK==C%-HXKqsC_~uJKtPOS2qi{z+NQ`7?jJ?yX0y zTzx6j#rlvT;{7v)@@@~bcg9yimaJ6#P6S%D>E6gK(^87}h_036Ju9Hw;Av;S40lXC zLl*WcXnpp*54*Y35cum^;FHxpyE|s(qcC_BcYn`bR#o^F( zUMm)cNdAWk$Ca5EZ-46%)YarBrqiGiRx?pMGuh^j@4?DfKR#S58*%a(l~9`FrjWLN zqGYyHdi8v1*ypfH2+gm1L={e$BCf?3stLJ4yT1<{X8Cf9RseC~aW?=M+tQyLkmwusTV}+CEaV@xg zA0a$yC(P?5dB@k4fMdTkJTp9D3#A!xSJox0AJAZk z$C03fTr3`~4Hqt%5pN|pvDjRwj+)ygC!iT-pkDKidf{O97^N31JVi6b+;9=`UEObu za44JrCtu`q@`;^=qxJb)WIn-JE{mBo%daY#A_bzHR3>br?qi0P%N^gv%Ye5N;MX5I zU*cbM4Y?nhKvEsAq$&&+{X`pwEmc=8AU=Pj$ZQ##sjp*qj>Y+5bCvF0BU~T5&LFRA z-}--FyvvC6+vc|6936Oz>eY565_RXcM@>#vX~+6=s=gMp+J?gI8fF(WN8CR8Z9IR& zjcI}m?>}d5GSOKEQ}u@#fRmt$)NhEKmd%Ans(dEii_^1mYkv}gkW$P1QT;s?Jwot^ zDW7o+c*Fx*tn^^ijWv?n>t}U(039*br%g+=_}-i?jI|oyqcCsmqZ-l zFl-0yYGR*obNv0C%57#bSu>JQeZ}OPhIv0(T1CD~y-5FThLLh0MRqCs*^Uf&vRKy1ULZN=iZ$}HeZ_J}w3W>=51!6QgdzT1oJ>v0XcX2%aI2zjeD2#JMf+c614bDgC4n>V*~)L+xF1mk6Yr#z+?Oc2EnFynH!-HhcqkqasgFk(s+}E$&gzqw(DYjA|Yudk$ z=D6rY8qPrE60@lcSmeskCPr(zMfDluD_x%i;Z_hzeA?4dA38RsZ)*siVLn?_i%D!^ zQWU6L<)%X8=FHCt7UdbMLyASJ`O;tZzKHVlVEeg=SVn8gOEZCq>ikv#y+xUYLdESh zIIFkv?rL5XY_)@N*>`1_aL#bO$6C@&2$wON8#d|9hRBuvlKNd=>ebqWG&bdOr?Dnz z^crqQU}1-%tIW3k4IZ8EMDLfslr`F_4xv>9N$yb zKjaD+=-j%NGH!z$DLjj^0OYi8yxCN(!*tpQCAJn7LoSMU?J$@0PfhQZLhuIL>xp6v z54;-CWHevHFWkfY*T1Sn+sw5*L`fv7x;W$ce1+c0xn3V%o`4o*(o(mVUp!hv*UM0d z|De^tOZgUXRAJ19Titiwe$++r zyNY9=)`c@(R*P!&c-{w4@uiBKLQV*_&l1!-g?lPq)6h2$(DzF*fv+VM<4BlOgLdH-u zA1s7%)ivAz5=uAr3PPl&OX%y9@ObKmi(~)j)9{N^c;kb%Mg(*DI#QzdI4bFwgn7ac z0tqfj1x5Q^C+q$WJfEU%$}j&_eC#&)Fz1(w_YQZFo!gcNTuqy8;|MA|EE)0n42>?& zaRsi66&1yp!S0_}DZw^=-dU8C8ms4E2?_l!iQnrSo9gfGlfBvh!0;aPcuM0r^_S)z z<8L2y8w`FN^ef{~1{2{gM!wfQm-1QhUX$Xf+N0F_G*cn_9ND`tq`pS1D$P(Jd$`CvbPAsTV+ zyNo*QX`nT0+&u);A-PktlE$cSXznZhOW8wh#?+OnH73~p=YYS9f72JFt>F_u(>{gT zv8M@~Y>xGXxJ+(m8Qgfwd?cc~oHFk-0&|QKHkRF%iwg&QsQ@ve+bR-u28S9opf!A` z1Yg!r!PbTgbfDsFT{P^r8<82Q%=7Rd>(wO>>if~IF%?~Cb-6~kQK_UaJbPiN|1?p*6DAJ#j&wE0ud+yjK52(9_?ueK6k{sgrbvo5C!A~q1) zrTlX8UzAtDz}9lIYoLtz^f;nuZF$RSyQ9q;C<8CG_L8bL4s6(+>z4yNODUS(FmAi_ zM|e4d@8=Bl*HXBu^c@EG{L+6Qx%o4E@rq$pc40pONUGQNaDjdI_`AyYcN!CIpfB`^ z@>9ofiv>27w(_>hYN8Ff8oV6*ukQZ*5`9a(ueNQFS1*_6X&Ut;=*=rIvmtaWJU=k@ z@hL>H!$;lUCk6usVj^2dh?_TXODk{<$tZTX-uELIzw>2c#FE*wmOXlW4E%Qqn+nae z_q`g}vIOEGn)WA{^nu?{Skr0Cr@s0% zTQe~XYsTbd-Q)Sd^JIkK+6yVVlXNo^+F#^m=irse&?T-$$KL!^l8c^K5QTFlc;X2r z;Hw*sP6C%w&v1ki4^ZTWn05#h=9oWmAmkN4iVw>#Dl{?ZaJ}Im9GwPB#|qQc@;qB$ V@+MeRUC<=41z@7FA~uP;{s$_4ZaN@SVRRr(VR9fcF(5KBIx{#rGdLhJFfuS8ARr(hARr(hARr(hARr(hARr(h zARr(hARr(hARr(hARr(hARr(B00000000000ZB~{0002X761TvoJCi8RE^QwO^7rj z6q2MQq`4>?N#>+UWoR+7XRWjMmJ|{a+P+FiNJM~(h5i@+`&;;bwa`2vq51yGTSa3tVr`Z>oSz&gJb2 z{0huWX%%Ek5#V5!+gP}bfa=N88NDL}$Rw_o-F}gPrFHjzytzXF*CUDZB$0q&ZUAOK z60mE9#4xQ^fR>*dj|>uUvkg85-wD{~+4CrU9uXqr3vJ%Y5HVa;WUZ!1#9UWi8MYGP znYgisV?e~eth=2WCPetFaK6eMAtEI2xp15%5q*~}#Om#c2s$|-o^yr>kMU`#BhEy; zEq>yc;Y!3KU)k!8b3}|CIBwe}z^sB#mRW-S0sktQYXV+zymjs7vqaDpk}lBgi8yFl zy)x692s8VdeG%pY{tt2O*aJjNHm)%2-A#mZ72%qTU_W}gF{FJOL{P{T3nMoYp+*)B zv0P1rMp9M4TWKPehkY)MT1Z6u@}vzqGX%KRNR%gz5ioPtQ)7@yz(2_~!V=vCES5DL z+|xk7=Zf1tk>vy=)QI?q7Z9*$YW71*8UZPjqQ+w{2?#GZydgM>fM}zZu=sESJU^OG zTizm|TYIlrrH^3V4Qp0aTw;DmVI4ekX3oQIOP z?-8u8XOmKV!Jhz&oI^eCVFX|u<)KqF0h(fxndT{ieFrB!*jFf6*T4PGp?U&rr&E`@ zlLhtsn|Jf43CJu~_`Go$5doePFY5*82f6)YsmF-O=_%GATp^++yJT)nl;FNt^M4e7 zA%eR;CgmlWh}KqAR4u85=^OX^+WM7va+N8;JXeVZU4@qZm`eP)Sn_gdeI?|VzIb)- zUnLp`%l!29tMKqi`wj)KDqK)j3)9c4LaO)bj+@*nwC`PZm9Jh6IIFA-^{9r^>Ty^7 z{AxVf7ru1COf{}8ii@G|tpVllkrVBYYOr-)*`NBp8q{0g)2~*mg;aZ4QMrFDre~S{ zADe0+7J0EYUa1a^hd%H<{puiP_Ond8tqvO97ww0()MM*xl8SqHJ$!;5P7I9HgZ|^r zpUnpvAT8OHYL(G|ifpEM_Aj@t%b7l@H$gu}F|tpW0BN-iGEp z3+dIKZAfsPtBd&5hT1hhcdYr-hVvvP-xia0@J+hrWks~(ON~$S*RFQ_*z@Sne5DRF zB*5gMM+dg|WaK)Obim23QY=WU6M5qqtEL?~k#ee4vn#6;gVkGJD9-D`JJF4UWa}>2 zUp1>z{LqC~*SK#uqR&#v|! z2wSepT&&v*%ID$q__$u&IP9-EHP?&Z{PQ1`&-KAe=}Y{E=04b`(6>(*^h4En*GS~M zevp3Xm`yAjfO$g`mW2+WBkzPg_16FzuI1R7yA8tYcY%iK&>$L3<>oT1hETyXDRpfe z!o>R;2TRjoxJ`LkTbB>xXqVW>4Qt70p_Em#FL#bkJQ=D{qS-U2XjPB8}qUgwv+DaO#EyeyU6Q*M>pipBC=vW{rn@kR% zqpmHu^z0Wpw%K_m#Q&rtPs!loBR|g^o zt)$V9`)4?Ka@^f~xh5Co!;Usp7q~EPUR`OQ#6`ec-3Of=T==aEkJJ+zMWU44>zjK< z5hE|YGs{PStjIxP)+mhC3N{gGqj)&ryCzePhxrwG$^Pa%{NOKD)V|BZZr!+Z?ZrG4 za|^BQzw;11t)No7aSUR+u1d=}j3Iq0=F-t8V<67H;hwD;Lv{3SP4k&C4Dkw-e*TdgYTI5bW8hPz5^x}8T*?R`r_Kah)`eAUEI9< z>)?mzNJyPbF>joV#ARnxCt^KV51qiZqlxG)x z!qQZW<8LPl5g#mjDn#uIY!sSQW*mxel1jF@^spG@=SPpJe))=k;pe%&)DmcIz7?Ic zqzpdW4)wHdFUON&p|sbVzd`Ju)f<#g{{ zgINM3XN!C@!ikW#+ia7uvJwwpHE-Mdt`g3NBj5XLRUxZg^0h;G6^IWDWkwHIqtDuH zk##cX0k!ZIR{hf~Qim45=gpj_bvSWxvaw@LJ)-7Y zt3FAshj7$}LIte`{9`V9f4igsX0MMP|GK{sxBlbwtD&_K+*If80{bR>HcpASL2p9s z*H@cmJenc3De>yvsb-u_FllbP)q!`WfGcHb@NWdE*BC0{#5YZo|gZe*wKl#J|hDfE4x7FGYOe? zUFfrU_D$+V7p#O8Cmn{mkaD0pVb&*6A~-42fykw)q;6F@OiJUEu_(l>3dh~E_?QZX4t)`GOrhRWsS-g zP4uE??H;neP9Ng01x{Ld_d$np)ctH>AEwG5SJ_VWVXND!Te}4BbIjbWP0RiIAt}Ys z=`HHViL$=!k7xRk>$>x$_Raw?m0m8a4;a9n6TKXZk^x8^scUNZGk_Ov`!x)V2cf%Q z^OM-1L5MA=3FDLw;+}TUP0haoRCeQC-Zg}pDaErdZw|pE*Gz|4HUzWOLf+8dAsDAN zO@7}sjP>~%xxa4?vA=^zDL<6Zo0=O{R7Ie0{VEfqa3 zO7D7&sIb5O$LaYgD!%VLec)&y6_15-wdTE`!j*HmHTNqOOWC#k4*gWzxt|~^JWEAn z!Upymc^cfz2Gn-#ph2uH!nECv1`BbY1}8rnD!txsX2;Q>Cturm@e2)6kEKM&-83}X z=59VVMT3sG$n}pa==iz9LQGtbj_TF2Qufw#+}1dCKk+&pj@M6~Yl)&GS==c4=SMoO zI;ISYw$ai5VcCqtI31Baowxrx;^LSHaay8S{fUWh;n!~y+nA_ZB~tg8&&0_e7q+R0kKjW5y?ysL zjld##Ej@qV2y%^7_oO+FK*ga~_FUiy7Fu4q!-*XM$=Su&P@r#jj_!SYqIm?48fT0R z*dw@Nnvyg+pM|1u!-%U2EVwJrvuHA4VP-%nO30E0DS0s~X?H=b^EXH69t*W8+GCxs zS$IIET1OYN;L%#QYIg?e$GjIz&D`yLzNrs>k8*KFiO@ALOBW@E>gbeCoa8v&bUWPbAm|MiFWw=Loz zDd^71^7R}vX+B(0v6F)>_vgHO1$ruBTeV?{CkH$HBuJJIIB;-S`|fiJ2b%qaJiT%b zShNWqsaL@BXx4r3lY=9)NpnXjEms)31`1>wNiGJeM<~mcYm0 zxt)crU-@v}PvQJSq7EH(%V4rs+$Rw4U(RVxPKl-L)gNci9>>MR?&gD7&D&E;!ZSJ*+?WG z>xAUS5U-5rPB<(P89v(AiQsjWq4(Bx3H;}#KucGFPfsYZ%1-M-TGpfc(W706>U>!u zyuBOsQ<@KdUF}Afc~!RCr*1etuxoUf>PDv*Ex*>F2g$9rx>+_qglL7LhP`MgkFm}6>4n*yv%OzG^+H#>HdaHR9~UmwZd{Z$W6OivAFqliSi$&kvzswaNOM&xgQ?%k#`64uRtM z#8Y#82r658Xfg`Jcp>^F>)4@TJZ>vn!|@)*FHzI8%-6&48kVA)HVmWdLbSQquVHlL zE{<9wMTUl%j*8m`G7d}6tGldEhSaLrxlM=32=3R>dUb{j1-qs5NnT{^a~+r~4;AoQ z`*Yl5$;eNbhEUa#9I+BIE@wL${c9q_T)mw8d6VnCoGt}6@Ae;lW=g>^J+Fd(L4Mi4Y^Y=M90evtujCls6dZn6 z%TK*af&7=cy4}wycx;leATNc2Tk)E6zw;^J21?AXAyQz=9^0{6kaxy0ZC!_2pE^Nwmx}XrzL5=t9SiUctPxxW#5B6&LA!Jdp`1NG^Ou(myILOD=W@ z7pEaG#BEALj)r_S*;7>;X^1x(A3CT-!~X564W+wiAo-I2v;Hs*R6_VXZ(AC=6~;(k zTxjsl5L(Q;LPL6X%aBYU4TcAg-B!I%!?Aa<|F%ZckV#Go-S}FN=j3d6N`9arak2G5 z)@K@A`tr;2i8Oqpr+@Wqp~2uiLuy?=4RMoWV&zPMPH*|sa{LEPa8J5>I_7Awy^?VD zpeP+5tnveLm(#H>`=Ga|A{|FObT3$`(=p%uZ}L-tZh!SCYx%Z^j;XxA@2N)w`keA6 zZ_<{I#^qUG=bQ!kk-j-$%AF3U6aymNmk!oa$0jwXqlbuRIOzJK^J zr4%|`W-C&=vgsI^Zb-acM8{0tcFKlII-=O_xu+|g&cTlUfs7P#arjYh}vx%4HT zV{`;Z=AN0IrbFwdcJ#gZ44mV(=&Y1vK=1yG;SZ}Acs8gXdUyi^CxX;;rnWL5Yb#@# zZ@|E5w=#aH2?GY|7Xv(xF<>6nzB15`0cFDsdX_6g@cu{#iF-02Z#DDiRR9C&LVWWp z_ZV2Ox-T^}ih*Qr6H)dn2E6Zt-+uprfjhlhV{3{SND<{~+6w$eK)HvjV6 zXbe;qa6gGnFc4}sV?Y#U;!u`iqQWvJyss^-R#9f+RMxUsnl2NeT?-2w4lprPaNRN5 zfr*-vF+oqSFcEM4IM_OriIfU=cS<}H5fN_lbaR=wkn!uwu^J}UscHvo8D^rkD9x*W ziiwGYZIj9}BaoC`Af~TAg15A>7qg}UpW^raw7b&?4&A&H6&5st#rlE4dlCdbg%#l( z_jv@*GM>5L?Hob;1CvqVpCeFM9PBN&jD@`Z3(w=VSeVm1v?1*T3x6M_t4me(Pe`@|%UdVU%+>*RWCNd(9|Pkkj>t#Z~`0vmv%K zy0|8cjno%z$*c^vARo+rJKe&@{X>+4%0Joo6}o?mp&SRDd575XyEssPdb-iXm4o){ zVzD{`pV7LeGTJSdgB9k+ZR9QvKHOJWA2-K=tJB%FkCeH%_lrAGdYB9UHocNf*92%E z8`O}%#rvB>Rw)E7_B%{_0-bjVxsIZ}PVI7I#3=Ue zm7XGg8b$RvM+wHzC>#r1DO(rwAe&ja?}a80Ati4@w>j{TlwFWT4do$Ltu(nXn}>kZ zZ=}>79@Ynqoh=X^L&0-WYVVdY@Ru9REVmuQ-Stt6U2h9~fw%Z+V&)h!$J3j)ca1@{ zVs%@Z5Fetelg_BA^YLM+PH>JjA9kTBW;<^25#WE_s4jz#KorHC>HL5F|1ZSuLR&87 Bye_4ZaN@SVRRr(VR9fcF(5KBIx{mmFgGAFFfuS8ARr(hARr(hARr(hARr(hARr(h zARr(hARr(hARr(hARr(hARr(B00000000000ZB~{0002p761TvoMo43JeBX)_Eic= zBx56~hz#i$$#5tQN`oP#q)3LX6d95zlzAR1BvXlm49T2%p66*}?`_!IxZ`IiqI>`E z`+lD1#k1ZVpS7;*I?waPwa(+awlXm>$p|npZDJ@Z)BosyV&nhjOzW7KHvG4&XEL^A zVq*JGO@QhD2656lLr+{Drn@woN4D#N<@5s$>nc;|EyYUCwcDWLq*PUB{BvwJ|MRhP z#vSr?kJtIt_+wE!_;3B=P&k$h8FG9IhuY9~2~pQ*>{sV0zVI?0!7X2JjB|d1NP{nz z!u!wouzS+-kVq<=62J5DYNf+@^L!9EGGVc6RZgTT3(Du-Mg4e|jYhVF&+DXeP`*01 zy_uK`!;;FVRnL5+TOGJyBwdKBf6v~iXe)x=N_Jd_bum7NLSjdr7=jqg2ba1ePtj-s{rO88SfQdWzSi}EtwdUX&D-~N@0R);80 zW=mn~daRsQ&C>t39uK-${^&n!zypCyhdgovY%K(|H(hGPfHK#Q=C6%7KCZhnnym?8 zuQKcxZZv_g(0=t=Y!jY2`u3<0n^2W=<;-D@X7qZz8Hti@2J0Onf%{@J(vEb+@!V)e z%`UgmqYUN0A;GbgQQx1_cUkL5Gn%?}`HESa5x~l1Dc#nD1Jgy4ul<{_t}dlv?L-p- z4c1S;?P~X?piEHWT}km)!@@0TS}W>HI~;KotDb4!jGAYdke{xn3`XI;wW<^ zbeX6^YWpg%VEjTLUa1@{D}P+tbjq-vv!KVxycF{n&o;6@D}krin@CQ9Vq9L?TAeFj zgv!-`XU)0=&<)OC+Gdjn-Zz1v(m^>eceaZ{m4l>tBNw&OOO z=}?>WsLdp#AX>w_@oGU5SY`w>#Ca1SN&dc33G1KaHO3X9peBA{2GVELgVDnfsG=84&&O8(Ps{nkfF4zK-D z&v_rCyodcQatL5wnG#KMx{sIEacz+-R=DJQ>%q5R8%Vs~p8QwA0res~=uSjuTrkz( zv^wmDqelDw{;hFGc-#5-u5537^z=;(AM=N!cx|4xSTKG{UA_9!RiuXIZU&E9` zTUGvII2OikPJ6UQqH2fhh6uCwsH0A}m$Jm-%~=hRVv{(Ss*n1zAduePA zw7)BSHDu0%;fLFrM8|yig|9a=W-f$!H-7_LToE44%k|W17Q^TJvCMMs66~kw&iUGw zf*ueY5NlqBwL<&rB$IN?eiXfV=s^Xfcl#-7KdZz7SDltZXcb)Uw_o^{RSg*rPKS76 z4Nf(@tRiyMVRK|=s`|Nlu${<1SM1e*+d_oABW;X+vrt`oF4BzYWx*JAl@<_LwT9AW zT0mt_4Vs8=#r^Kkp?`0-fjHNmUd7iAsgJw<-05n^J&9w4qKFQ>%PSir8g?S=QOxc_ zi7v2Y^wjGwci|+zrEft)H_q=fEo_SKLB;ba*Kns^O!MsH-EgxHarS0xTNL~8aWn1* z?ImJ;hx~vQ%K(@Zl@7Yh4Ir^z@8=8BARGl*0(Z9#!85Y%{gM$036-*j%0v<_(k>2h zTqmP%bI1X%HZm4EHm&QwNC8FYR1Z@%1vK@wN_N#@oZ>L~ML4;4`+{|(V}XbCm$8Q%k9#= zwp6szG@mQ9P$76EQnp--2G;!bPeY#5aC48smdaKd{wVV~KHE2rD5p`DW~*^z@~Y_# z7LLP6OY%?IrU~=}E4YPToCH)!=tpz+IN-FrJG(Y!cam40&)0zVH# zoQ{};WhPsnMAsy2s`Iq1w@sm-;^2cl8dD%T@dTcDIR(dCBXae5Q{Z!pZRnVpLc@<= zK{xkJqtHwIUy(Y5_w*}+%Sh;QK)?9ZCU$)w!ZTI6ZeYTa#9=9qzMa-l<{{0y32 z%(566&fweCx@2$98OYopRA*0}!9cike1FRfMt%33q|eP@SYhUa!ge}N$vnEAdV~%y zt|Kf;m+2T1a~*YB~q~kuPf_oa34yI&grq-W~=Xab>rY_M@VzaLD@*g_-)_+s2TcblvB`QSeFCA)} zYiiA_bnxmtY0Fxsqx;XJo05xk7+(}FhUqxJ>arTj*k>#A zhNCfEbVLbC#$Rruqs!W7#if;wg+unc9}m%C`$7I!)HEGA)yg8WzZmEF@A!%T<^-X#*Cg8)F1Wwt5%o~N8?NlVc)iOnH!TZ}1>T@1#Bf+HtBF9+d7&Ly6j!a&5iiMfbs z_`{#`acs$FAG~?K_(^Zr6FPd*BEg&7;qAh_%~`|^k3Yz+Dn>oWv;oy^tHM*1$b!(Q zZ3~^-5^)9lt#E#Wby47sy~SMT&6h@60c5uOXegDXbGoGL}bS+tM2O+S@ckj+Guko!n!F_{Y6x>z%zL5y=_@jIO zJ-bvQI4ni?(RfoZ+uPz{kQH<=Z^YCz^KXL3ua#RqS0-IH~7DD}7)eVts7$99cvxeTu8Qd6`wUui^? z0Q0!@S`$hpJna4~H=|(7!jn*k7QB)2Ukdx#f~o7zmEM`OVm$fIiHh!4Os7fGq|dd% zXS>1FYE~O0`jUe$?`ek_eTUI0k9MHWzvA~~JLF?ctHZ8zKy)>bD4Wp%hXk*zIj&Bu zGs)OH_plR-H?OQ*s_#VNThT?~gI##9Y5(5yMHl#n?ubNoccCG6oc*y>H=L|WQVzW8 zhFRi8-Nv47jJ(#;bUxgJrgJZJG+y*zr*rk?3#~oy(g~sZ@9V{yrsob(n_d{WPOcLx z?}dE#>|hLcA4cNUlA?|JU@UC8Ae-KY<@?-ugtb0&Q@va@FZN@6$5)kqBl>ab_uGtH z)PC%toN<2p4-t9azDcLL60whV?B1s)BIr^dB7JrafDkXnWMn>ol%u=iq_YRG_4|=e zE9(Y9D)!f{y*vm7_mk{V?*hc7gE<>%bg;_;^?G6ggF_vf1Pri2q0rKl_nvYO@=w;nU+RB83E*LW7_K|C@yy> zR}iHjyFoEyh2dL0Y-y@tk12S4#s=%*2|@@({?S2FT@H?V7T1mtIw z)QkHeT$LrZ2WMQyV4vW7-!*Uy=LFope0a~O7x)?GCXZo@Ly}+1_c0tL zOY?6m8-ug&xP@)Q80Z#9m({w)u)5>(eALhweD|C;{6ZT;&8)NLyV)_!Z{GUp&GHxo z`UQShY@lM>q*~)y4l2Y04%sX4Q$hMm`jjP1g^tFC?yAF7>@h5}b2&~$EW7T~_-QKY z+l4*Yw5eG7!vAde4i%ic4{|s@q=J=0<3ocp74vQUc|?CI415}T5~HawE%husoK6Lq z{>{sxjEY=gyZv`MsZh4u{f%{;ioLg_i%eFjcoKhM(w3Ws>a|m%#~6H(QNli!phCl; zwA_R$T^i&nK5cw!NyGj^%ai;bG@NZi^&W*G%ufAY{s7!r_aoQ?Dk@tEqhS z-hCIu{<{UOE{_mm(wMSj)z(eTGjpiVIiF~_f~ zH|j^A_66;=l|l^0=Z%fa=i)KFd9&P$_)qZTnD=rwNrq9do#lc;8oYVV`;xgbFw0rP zXUv&}WJ`&11Ho*tniYn}OXuJ$NiT6;I}cA4td>|_72xN)%JpI8MKD~T^X+3Rfq8x3 zK2?TqDIN^TwtZBFjNsc@q_lG6__HM?Fnnw6giIFsY!zc}=~W4jsfI(C5l=2tE$q$2 zckH)d%cK`GtPc?B#iMJEi=y+rP`;vKE?3qEf=ttGX8(Q^GRM1j=@4No${ISwJAf}A zr}kowsTfwFz);6+ zQpUmd6ukQ-6GdgbM_9JX3pAymK~jpP?INCkx;w1+)yN=>5ND1^l2M$t=HbgvM%bt@QF=WYJX$|3 z#ivP_4>J*R=^>#_$fH@Vf&{7Ugsr;iBQXf!kV!1o`}kqAz<%_RWhA&8A4+z{pH2GLj7g(w`gff;AZ`lJw`0(OI zyT_&>R35n09Y-BRvE7MCvzkHd&Ug@0kTeKqu4X1z#{ck$J2x}ZY!D*m4f`j~41$}O zZKowNh_#O5ADMp#a6I#)($BsDB(OewI`wS;EH5SmeEbJcMN837xi^6L!{5c^PYgiH zdu%)B)&WT0)v^{O5#iR%lBt+Z1k=<`L4)T+Z1*}FYI1>yqH*p~Wga3NdbSic_xGc( zd8;^cTt9RSY?Z5x`yp)Vkg;)pKaS+Ze@P+t;iwuHds0*%l4>3w8oSzu(r>rRu5a#x zf5iv(slr~A8{HEPc+iVqubNDc3ie{v>rzu)Sr3xc6+~1CJ$Sdsmh7{o2d7DgH9}Il z@wM!n&873*=pPP?NuYEgl+2|c;LwF8mR9wd&0Wy3m2)hJ=){zdWM#+xPKdKC9h6Mz zfKvDLqg3e*2r+wy9Q)dip6x~6$>QzEr+l-TjcY@_{g@ezrwxkhM2jq5wnFCja<=Al z3szlb%pYmDK(*F$#}SieYu;n~9{m7pfZIIDYKbG1GcHJiYKmb-5Nt@0~cm z>{^5IsqKee|EU5`Y06eFLM4*kyt3!1DTn>>{0mR`%g~XcT|a%T1e1)>}8Y zF<`2Gvlhx7>wn(RuZL@oEMJ#iBc9a8i)-sNgE%l7^dX@IHCc=D?_wGE<@coWn#eY! z5`+S7g|&mMb3xF}zXPt3VMc}?ofxhgPUU0p_<)s7ZuygLIJ(pqx?A-iQE9Hgme7l+ z({ev!@Ae^`7G1jYRzD1=ZOp%QiHLF8qotxbfTmYfe7`RYB9`BYMds`fSX6wB=Y$yj zNm#Pq8bbnq@rn&$H-l5-f7xw_B7=8gm8F|;-%wptR7nvOh_~+<u z*=kkN(;zC&42paW{c5?Ys6B~G0as7$FP+5vncuJYC8yAqV@qp#J%u-U#u7X$Q;;KdTBhBa zhTSob`3L#as3kbBJHRu8%d1WSafBHtPK4$8<B!9#xsmQh T$KKV5f>Wjc&;S1m0C@{-Cj-l9 literal 0 HcmV?d00001 From 52f27441615ded58e011b8f7dc887ed6535f47d8 Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Mon, 23 Mar 2020 12:09:52 -0700 Subject: [PATCH 38/56] Updates to commenting and headers --- +invert/exp_dist.m | 39 ++++++++++++++++---------------- +invert/exp_dist_lpr.m | 29 ++++++++++++++++-------- +optimize/exp_dist_op.m | 44 ++++++++++++++++++++---------------- +optimize/exp_dist_op1d.m | 16 ++++++++++--- +optimize/exp_dist_opbf.m | 1 + +optimize/exp_dist_opx.m | 1 + +optimize/tikhonov_op.m | 2 +- cmap/{plot.m => plot_cmap.m} | 0 main_bayes.m | 3 +-- run_inversions_g.m | 4 ++-- run_inversions_i.m | 7 ++---- run_inversions_j.m | 16 ++++++------- 12 files changed, 92 insertions(+), 70 deletions(-) rename cmap/{plot.m => plot_cmap.m} (100%) diff --git a/+invert/exp_dist.m b/+invert/exp_dist.m index 65d9d85..e220f18 100644 --- a/+invert/exp_dist.m +++ b/+invert/exp_dist.m @@ -1,35 +1,32 @@ % EXP_DIST Regularization based on the exponential of the distance between elements/pixels. -% Author: Timothy Sipkens, 2018-10-22 -%-------------------------------------------------------------------------% +% Author: Timothy Sipkens, 2018-10-22 +% % Inputs: -% A Model matrix -% b Data -% grid_d Position of elements in mobility space -% m Position of elements in mass space -% lambda Regularization parameter -% Gd Mass-mobility covariance matrix, used to calculate -% Mahalanobis distance (Optional, default: identity matrix) -% xi Initial guess for solver (Optional, default: empty) -% solver Least-squares solver to use (e.g. 'interior-point', see 'lsq' function) +% A Model matrix or Lb*A +% b Data or Lb*b +% lambda Regularization parameter +% Gd Mass-mobility covariance matrix, used to calculate +% Mahalanobis distance (Optional, default: identity matrix) +% grid_vec2 Position of elements in mobility space, grid.elements(:,2) +% vec1 Position of elements in mass space, grid.elements(:,1) +% xi Initial guess for solver (Optional, default: empty) +% solver Least-squares solver to use (e.g. 'interior-point', see 'invert.lsq' function) % % Outputs: -% x Regularized estimate -% D Inverse operator (x = D*[b;0]) -% Lpr0 Cholesky factorization of prior covariance -% Gpo_inv Inverse of the posterior covariance +% x Regularized estimate +% D Inverse operator (x = D*[b;0]) +% Lpr0 Cholesky factorization of prior covariance +% Gpo_inv Inverse of the posterior covariance %=========================================================================% -function [x,D,Lpr0,Gpo_inv] = exp_dist(A,b,lambda,Gd,grid_d,m,xi,solver) +function [x,D,Lpr0,Gpo_inv] = exp_dist(A,b,lambda,Gd,grid_vec2,vec1,xi,solver) x_length = length(A(1,:)); %-- Parse inputs ---------------------------------------------% -if ~exist('solver','var'); solver = []; end - % if computation method not specified - if ~exist('Gd','var'); Gd = []; end if isempty(Gd); Gd = speye(2); end % if not specified, use an identity matrix if Gd(1,2)/sqrt(Gd(1,1)*Gd(2,2))>=1 % check if correlation is unphysical @@ -37,13 +34,15 @@ end if ~exist('xi','var'); xi = []; end % if no initial x is given +if ~exist('solver','var'); solver = []; end % if computation method not specified %--------------------------------------------------------------% -Lpr0 = invert.exp_dist_lpr(Gd,grid_d,m); +Lpr0 = invert.exp_dist_lpr(Gd,grid_vec2,vec1); % use external function to evaluate prior covariance Lpr = lambda.*Lpr0; + %-- Choose and execute solver --------------------------------------------% [x,D] = invert.lsq(... [A;Lpr],[b;sparse(x_length,1)],xi,solver); diff --git a/+invert/exp_dist_lpr.m b/+invert/exp_dist_lpr.m index 280099c..24ff4fa 100644 --- a/+invert/exp_dist_lpr.m +++ b/+invert/exp_dist_lpr.m @@ -1,21 +1,30 @@ -% EXP_DIST_LPR A helper function to 'exp_dist' to compute prior covariance. -% Author: Timothy Sipkens, 2019-12-11 +% EXP_DIST_LPR A helper function for 'exp_dist' to compute prior covariance. +% Author: Timothy Sipkens, 2019-12-11 %=========================================================================% -function [Lpr,D,Gpr] = exp_dist_lpr(Gd,grid_d,m) +function [Lpr,D,Gpr] = exp_dist_lpr(Gd,grid_vec2,vec1) + +%-- Parse inputs ---------------------------------------------------------% +if isa(grid_vec2,'Grid') % if a grid is supplied (`vec1` is unused) + vec2 = grid_vec2.elements(:,2); + vec1 = grid_vec2.elements(:,1); +else % if vectors of elements are supplied (`vec1` is used unchanged) + vec2 = grid_vec2; +end +%-------------------------------------------------------------------------% %-- Compute distances between elements -----------------------------------% -[vec_d1,vec_d2] = ndgrid(grid_d,grid_d); -[vec_m1,vec_m2] = ndgrid(m,m); +[vec2_a,vec2_b] = ndgrid(vec2,vec2); +[vec1_a,vec1_b] = ndgrid(vec1,vec1); Gd_inv = inv(Gd); -drm = log10(vec_m1)-log10(vec_m2); -drd = log10(vec_d1)-log10(vec_d2); -D = sqrt(drm.^2.*Gd_inv(1,1)+... - 2.*drd.*drm.*Gd_inv(1,2)+... - drd.^2.*Gd_inv(2,2)); % distance +dr1 = log10(vec1_a)-log10(vec1_b); +dr2 = log10(vec2_a)-log10(vec2_b); +D = sqrt(dr1.^2.*Gd_inv(1,1)+... + 2.*dr2.*dr1.*Gd_inv(1,2)+... + dr2.^2.*Gd_inv(2,2)); % distance %-- Compute prior covariance matrix --------------------------------------% diff --git a/+optimize/exp_dist_op.m b/+optimize/exp_dist_op.m index c8ff6e4..f006cb2 100644 --- a/+optimize/exp_dist_op.m +++ b/+optimize/exp_dist_op.m @@ -1,26 +1,30 @@ -% EXP_DIST_OP Finds optimal lambda for exponential distance solver. +% EXP_DIST_OP Approximates optimal lambda for exponential distance solver. +% Uses a brute force / simple discretization method over the supplied +% 'span' for lambda. % Author: Timothy Sipkens, 2019-12-19 -%-------------------------------------------------------------------------% +% % Inputs: -% A Model matrix -% b Data -% n Length of first dimension of solution -% lambda Regularization parameter -% span Range for 1/Sf, two entry vector -% x_ex Exact distribution project to current basis -% Lex Transformation to rotate space (Optional, default is indentity matrix) -% xi Initial guess for solver (Optional, default is zeros) -% solver Solver (Optional, default is interior-point) +% A Model matrix or Lb*A +% b Data or Lb*b +% lambda Regularization parameter +% Gd Mass-mobility covariance matrix, used to calculate +% Mahalanobis distance (Optional, default: identity matrix) +% grid_vec2 Position of elements in mobility space, grid.elements(:,2) +% vec1 Position of elements in mass space, grid.elements(:,1) +% x_ex Exact distribution project to current basis (Optional, default is empty) +% xi Initial guess for solver (Optional, default is ones) +% solver Solver (Optional, default is interior-point) % % Outputs: -% x Regularized estimate -% lambda Semi-optimal regularization parameter -% (against exact solution if x_ex is specified or using Bayes factor) -% output Output structure with information for a range of the regularization parameter +% x Regularized estimate +% lambda Semi-optimal regularization parameter +% (against exact solution if x_ex is specified or using Bayes factor) +% output Output structure with information for a range of the +% regularization parameter %=========================================================================% -function [x,lambda,output] = exp_dist_op(A,b,span,Gd,d_vec,m_vec,x_ex,xi,solver,n) +function [x,lambda,output] = exp_dist_op(A,b,span,Gd,grid_vec2,vec1,x_ex,xi,solver,n) %-- Parse inputs ---------------------------------------------% @@ -41,10 +45,10 @@ lambda = logspace(log10(span(1)),log10(span(2)),n); -Lpr = invert.exp_dist_lpr(Gd,d_vec,m_vec); % same structure throughout +Lpr = invert.exp_dist_lpr(Gd,grid_vec2,vec1); % same structure throughout [~,~,~,S1,S2] = gsvd(full(A),full(Lpr)); % pre-compute GSVD -disp('Optimizing exponential distance regularization:'); +disp('Optimizing exp. dist. regularization for lambda...'); tools.textbar(0); for ii=length(lambda):-1:1 % reverse loop to pre-allocate %-- Store case parameters ----------------------% @@ -55,7 +59,7 @@ %-- Perform inversion --------------------------% output(ii).x = invert.exp_dist(... - A,b,lambda(ii),Gd,d_vec,m_vec,xi,solver); + A,b,lambda(ii),Gd,grid_vec2,vec1,xi,solver); %-- Store ||Ax-b|| and Euclidean error ---------% if ~isempty(x_ex); output(ii).chi = norm(output(ii).x-x_ex); end @@ -70,6 +74,8 @@ tools.textbar((length(lambda)-ii+1)/length(lambda)); end + +%-- Specify output ----------------------------------% if ~isempty(x_ex) [~,ind_min] = min([output.chi]); else diff --git a/+optimize/exp_dist_op1d.m b/+optimize/exp_dist_op1d.m index 29fc574..d010ede 100644 --- a/+optimize/exp_dist_op1d.m +++ b/+optimize/exp_dist_op1d.m @@ -1,8 +1,11 @@ % EXP_DIST_OP1D Single parameter sensitivity study for exponential distance regularization. +% Optimized with respect to parameters other than lambda (which is +% is optimized by 'exp_dist_op'). +% Author: Timothy Sipkens, 2019-12-20 %=========================================================================% -function [output] = exp_dist_op1d(A,b,lambda,Gd,d_vec,m_vec,x_ex,xi,solver,type) +function [x,output] = exp_dist_op1d(A,b,lambda,Gd,grid_vec2,vec1,x_ex,xi,solver,type) %-- Parse inputs ---------------------------------------------% if ~exist('solver','var'); solver = []; end @@ -33,7 +36,7 @@ %-------------------------------------% -disp('Optimizing exponential distance regularization:'); +disp(['Optimizing exp. dist. regularization for ',type,'...']); tools.textbar(0); for ii=length(beta_vec):-1:1 @@ -57,7 +60,7 @@ %-- Perform inversion --% [output(ii).x,~,Lpr] = invert.exp_dist(... - A,b,lambda,Gd_alt,d_vec,m_vec,xi,solver); + A,b,lambda,Gd_alt,grid_vec2,vec1,xi,solver); %-- Store ||Ax-b|| and Euclidean error --% if ~isempty(x_ex); output(ii).chi = norm(output(ii).x-x_ex); end @@ -71,5 +74,12 @@ end +if ~isempty(x_ex) + [~,ind_min] = min([output.chi]); +else + [~,ind_min] = max([output.B]); +end +x = output(ind_min).x; + end diff --git a/+optimize/exp_dist_opbf.m b/+optimize/exp_dist_opbf.m index 61c2bc4..fa755fd 100644 --- a/+optimize/exp_dist_opbf.m +++ b/+optimize/exp_dist_opbf.m @@ -1,5 +1,6 @@ % EXP_DIST_OPBF Approximates optimal prior parameter set using the brute force method. +% Brute force method is applied over multiple parameters. % Author: Timothy Sipkens, 2019-12 % % Inputs: diff --git a/+optimize/exp_dist_opx.m b/+optimize/exp_dist_opx.m index b566afb..3b034d6 100644 --- a/+optimize/exp_dist_opx.m +++ b/+optimize/exp_dist_opx.m @@ -1,5 +1,6 @@ % EXP_DIST_OPX Uses fminsearch to find optimal regularization parameters +% Author: Timothy Sipkens, 2019-11-14 % % Inputs: % A Model matrix diff --git a/+optimize/tikhonov_op.m b/+optimize/tikhonov_op.m index 2f41317..cb0ba3f 100644 --- a/+optimize/tikhonov_op.m +++ b/+optimize/tikhonov_op.m @@ -38,7 +38,7 @@ Lpr0 = invert.tikhonov_lpr(order,n_grid,x_length); % get Tikhonov matrix -disp('Pre-computing GSV...'); +disp('Pre-computing generalized SVD...'); [~,~,~,S1,S2] = gsvd(full(A),full(Lpr0)); % pre-compute gsvd for Bayes factor calculation disp('Complete.'); diff --git a/cmap/plot.m b/cmap/plot_cmap.m similarity index 100% rename from cmap/plot.m rename to cmap/plot_cmap.m diff --git a/main_bayes.m b/main_bayes.m index 6d7d134..2aa7fad 100644 --- a/main_bayes.m +++ b/main_bayes.m @@ -135,7 +135,7 @@ %-{ -%-- Plot posterior uncertainties ---------% +%-- Plot posterior uncertainties ------------------------------------% %-- Tikhonov --% % [~,spo] = tools.get_posterior(... % A,Lb,out_tk1(ind).lambda.*out_tk1(1).Lpr); @@ -145,7 +145,6 @@ % colorbar; - %-- Exponential distance --% %{ if iscell(phantom.Sigma) diff --git a/run_inversions_g.m b/run_inversions_g.m index f8a62b6..d93a4d0 100644 --- a/run_inversions_g.m +++ b/run_inversions_g.m @@ -1,6 +1,6 @@ -% RUN_INVERSIONS_G Optimize exponential rotated and Tikhonov regularization. -% Author: Timothy Sipkens, 2019-05-28 +% RUN_INVERSIONS_G Optimize exponential distance and 1st-order Tikhonov regularization. +% Author: Timothy Sipkens, 2019-05-28 %=========================================================================% %-- Tikhonov (1st order) -----% diff --git a/run_inversions_i.m b/run_inversions_i.m index 312fdcf..367488f 100644 --- a/run_inversions_i.m +++ b/run_inversions_i.m @@ -1,6 +1,6 @@ -% RUN_INVERSIONS_I Optimize exponential dist. reg. wrt lambda. -% Author: Timothy Sipkens, 2019-05-28 +% RUN_INVERSIONS_I Optimize exponential distance regularization w.r.t. lambda. +% Author: Timothy Sipkens, 2019-05-28 %=========================================================================% if iscell(phantom.Sigma) @@ -9,9 +9,6 @@ Gd = phantom.Sigma; end -% [~,Gd] = phantom.p2cov(phantom.p,phantom.modes); -% Gd = Gd{2}; - [x_ed_lam,lambda_ed_lam,out_ed_lam] = ... optimize.exp_dist_op(... Lb*A,Lb*b,[0.1,10],Gd,... diff --git a/run_inversions_j.m b/run_inversions_j.m index d9fca31..d660e01 100644 --- a/run_inversions_j.m +++ b/run_inversions_j.m @@ -1,14 +1,14 @@ -% RUN_INVERSIONS_J Optimize exponential dist. reg. w.r.t. a range of parameters. -% Author: Timothy Sipkens, 2020-02-22 +% RUN_INVERSIONS_J Optimize exponential distance regularization w.r.t. a range of parameters. +% Author: Timothy Sipkens, 2020-02-22 %=========================================================================% %{ guess = [1.3,1/4,log10(1.8),0.84]; % [lambda, ratio, ld, corr] disp('Optimizing exponential distance regularization (least-sqaures)...'); [x_ed_opt,lambda_ed_opt,out_ed_opt] = optimize.exp_dist_opx(... - Lb*A,Lb*b,grid_x.elements(:,2),grid_x.elements(:,1),... + Lb*A,Lb*b,grid_x,[],... guess,x0); disp('Inversion complete.'); disp(' '); @@ -18,7 +18,7 @@ %{ disp('Parametric study of exponential distance regularization (brute force)...'); [x_ed_par,lambda_ed_par,out_ed_par] = optimize.exp_dist_opbf(... - Lb*A,Lb*b,grid_x.elements(:,2),grid_x.elements(:,1),... + Lb*A,Lb*b,grid_x,[],... x0); disp('Inversion complete.'); disp(' '); @@ -31,14 +31,14 @@ else Gd = phantom.Sigma; end -[out_ed_corr] = ... +[x_ed_corr,out_ed_corr] = ... optimize.exp_dist_op1d(Lb*A,Lb*b,lambda_ed_lam,Gd,... - grid_x.elements(:,2),grid_x.elements(:,1),x0,... + grid_x,[],x0,... [],[],'corr'); -[out_ed_lmld] = ... +[x_ed_lmld,out_ed_lmld] = ... optimize.exp_dist_op1d(Lb*A,Lb*b,lambda_ed_lam,Gd,... - grid_x.elements(:,2),grid_x.elements(:,1),x0,... + grid_x,[],x0,... [],[],'lmld'); %} From eb8cc846fe2f9aab765a1820a791266244bea8fa Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Tue, 24 Mar 2020 15:10:35 -0700 Subject: [PATCH 39/56] Moved fit2 to fit2_rho The Phantom.fit2_rho method is tuned for effective density-mobility distributions + New Phantom.fit method tuned for mass-mobility distributions + Updated Phantom.eval* methods to take more flexible or clear arguments + Working updates --- +optimize/exp_dist_op.m | 2 +- +optimize/exp_dist_op1d.m | 2 +- +optimize/tikhonov_op.m | 2 +- @Phantom/Phantom.m | 83 +++++++++++++++++++++--------- @Phantom/fit2.m | 19 ++++--- @Phantom/fit2_rho.m | 104 ++++++++++++++++++++++++++++++++++++++ main_bayes.m | 3 ++ main_jas20a_noise.m | 1 + run_inversions_g.m | 72 ++++++++++++++++++++++++-- 9 files changed, 252 insertions(+), 36 deletions(-) create mode 100644 @Phantom/fit2_rho.m diff --git a/+optimize/exp_dist_op.m b/+optimize/exp_dist_op.m index f006cb2..b2f0145 100644 --- a/+optimize/exp_dist_op.m +++ b/+optimize/exp_dist_op.m @@ -48,7 +48,7 @@ Lpr = invert.exp_dist_lpr(Gd,grid_vec2,vec1); % same structure throughout [~,~,~,S1,S2] = gsvd(full(A),full(Lpr)); % pre-compute GSVD -disp('Optimizing exp. dist. regularization for lambda...'); +disp('Optimizing exp. dist. regularization w.r.t. lambda...'); tools.textbar(0); for ii=length(lambda):-1:1 % reverse loop to pre-allocate %-- Store case parameters ----------------------% diff --git a/+optimize/exp_dist_op1d.m b/+optimize/exp_dist_op1d.m index d010ede..78420a8 100644 --- a/+optimize/exp_dist_op1d.m +++ b/+optimize/exp_dist_op1d.m @@ -36,7 +36,7 @@ %-------------------------------------% -disp(['Optimizing exp. dist. regularization for ',type,'...']); +disp(['Optimizing exp. dist. regularization w.r.t. ',type,'...']); tools.textbar(0); for ii=length(beta_vec):-1:1 diff --git a/+optimize/tikhonov_op.m b/+optimize/tikhonov_op.m index cb0ba3f..e541897 100644 --- a/+optimize/tikhonov_op.m +++ b/+optimize/tikhonov_op.m @@ -44,7 +44,7 @@ disp('Complete.'); disp(' '); -disp('Optimizing Tikhonov regularization:'); +disp('Optimizing Tikhonov regularization w.r.t lambda...'); tools.textbar(0); for ii=length(lambda):-1:1 % reverse loop to pre-allocate output(ii).lambda = lambda(ii); % store regularization parameter diff --git a/@Phantom/Phantom.m b/@Phantom/Phantom.m index 29bd306..d5667c7 100644 --- a/@Phantom/Phantom.m +++ b/@Phantom/Phantom.m @@ -7,18 +7,19 @@ %-- Phantom properties -----------------------------------------------% properties - type = []; % optional name for the phantom - modes = {}; % types of distribution for each mode - n_modes = []; % number of modes + type = []; % optional name for the phantom + modes = {}; % types of distribution for each mode, e.g. {'logn','logn'} + n_modes = []; % number of modes + w = []; % weighting for each mode - mu = []; % center of bivariate distribution - Sigma = []; % covariance of bivariate distribution - R = []; % correlation matrix + mu = []; % center of bivariate distribution + Sigma = []; % covariance of bivariate distribution + R = []; % correlation matrix - x = []; % phantom evaluated on default grid - grid = []; % default grid the phantom is to be represented - % on generally a high resolution instance of the - % Grid class + x = []; % phantom evaluated on default grid + grid = []; % default grid the phantom is to be represented + % on generally a high resolution instance of the + % Grid class p struct = struct(); % parameters relevant to mass-mobility distributions @@ -42,14 +43,18 @@ % Sigma_modes Either the covariance matrix for the distribution % or the number of modes in the distribution % (e.g. 'logn','cond-norm') + % w Weights for each mode + % (Optional: default is ones(n_modes,1);) %-----------------------------------------------------------------% - function [obj] = Phantom(type,span_grid,mu_p,Sigma_modes) + function [obj] = Phantom(type,span_grid,mu_p,Sigma_modes,w) %-- Parse inputs ---------------------------------------------% if nargin==0; return; end % return empty phantom if ~exist('span_grid','var'); span_grid = []; end if isempty(span_grid); span_grid = [10^-1.5,10^1.5;20,10^3]; end + + if ~exist('w','var'); w = []; end %-------------------------------------------------------------% %== Assign parameter values - 3 options ======================% @@ -99,7 +104,8 @@ end obj.n_modes = length(obj.modes); % get number of modes - + if isempty(w); obj.w = ones(obj.n_modes,1); else; obj.w = w; end + % assign node weightings %-- Generate a grid to evaluate phantom on -------------------% if isa(span_grid,'Grid') % if grid is specified @@ -116,7 +122,7 @@ obj.x = obj.eval_p(obj.p); % special evaluation for conditional normal conditions else - obj.x = obj.eval(obj.mu,obj.Sigma); + obj.x = obj.eval; end end %=================================================================% @@ -178,13 +184,20 @@ % % Note: This method does not work for conditionally-normal distributions % (which cannot be defined with mu and Sigma). - function [x] = eval(obj,mu,Sigma,grid) + function [x] = eval(obj,grid) if ~exist('grid','var'); grid = []; end if isempty(grid); grid = obj.grid; end - if ~iscell(mu); mu = {mu}; end - if ~iscell(Sigma); Sigma = {Sigma}; end + if ~iscell(obj.mu); mu0 = {obj.mu}; + else; mu0 = obj.mu; + end + + if ~iscell(obj.Sigma); Sigma0 = {obj.Sigma}; + else; Sigma0 = obj.Sigma; + end + + if ~exist('w','var'); end m_vec = grid.elements(:,1); @@ -193,7 +206,7 @@ %-- Assign other parameters of distribution ------------------% x = zeros(size(m_vec)); for ll=1:obj.n_modes % loop through distribution modes - x = x + mvnpdf(log10([m_vec,d_vec]),mu{ll},Sigma{ll}); + x = x + mvnpdf(log10([m_vec,d_vec]),mu0{ll},Sigma0{ll}); end %-- Reweight modes -------------------------------------------% @@ -207,10 +220,22 @@ % Generates a distribution from p as required for conditionally- % normal modes. % Author: Timothy Sipkens, 2019-10-29 - function [x] = eval_p(obj,p) + function [x] = eval_p(obj,p,grid_vec) + + if ~exist('grid','var'); grid_vec = []; end + if isempty(grid_vec); grid_vec = obj.grid; end % use phantom grid + if isempty(grid_vec) % if an empty phantom (e.g. Phantom.eval_p(p);) + error(['For an empty Phantom object, one must specify ',... + 'a grid or set of element centers.']); + end + + if isa(grid_vec,'Grid'); vec = grid_vec.elements; % get element centers from grid + else; vec = grid_vec; % if a set of element centers was provided directly + end + + m_vec = vec(:,1); + d_vec = vec(:,2); - m_vec = obj.grid.elements(:,1); - d_vec = obj.grid.elements(:,2); m_fun = @(d,ll) log(p(ll).m_100.*((d./100).^p(ll).Dm)); % geometric mean mass in fg as a function of d @@ -237,8 +262,8 @@ end %=================================================================% - - + + %== MASS2RHO =====================================================% % Convert a mass-mobility phantom to an effective density-mobility % phanatom. Output is a new phantom in the transformed space. @@ -273,16 +298,26 @@ %== FIT (External definition) ====================================% % Fits a phantom to a given set of data, x, defined on a given grid, - % or vector of elements. Outputs a fit phantom object. + % or vector of elements. Outputs a fit phantom object. [phantom,N,y_out,J] = fit(x,vec_grid,logr0); %=================================================================% %== FIT2 (External definition) ===================================% % Fits a multimodal phantom object to a given set of data, x, - % defined on a given grid or vector of elements. Outputs a fit phantom object. + % defined on a given grid or vector of elements. + % Outputs a fit phantom object. [phantom,N,y_out,J] = fit2(x,vec_grid,n_modes,logr0); %=================================================================% + %== FIT2_RHO (External definition) ===============================% + % Fits a multimodal phantom object to a given set of data, x, + % defined on a given grid or vector of elements. + % Outputs a fit phantom object. + % Tuned specifically for effective density-mobility distributions + % (e.g. for negative or nearly zero correlation) + [phantom,N,y_out,J] = fit2_rho(x,vec_grid,n_modes,logr0); + %=================================================================% + %== FILL_P =======================================================% diff --git a/@Phantom/fit2.m b/@Phantom/fit2.m index 30d4a02..a867dca 100644 --- a/@Phantom/fit2.m +++ b/@Phantom/fit2.m @@ -1,6 +1,8 @@ % FIT2 Fits a multimodal phantom to a given set of data, x, defined on a given grid. -% Outputs a fit phantom object. +% Optimized for effective density-mobility distributions. +% Outputs a fit phantom object. +% Author: Timothy Sipkens, 2020-03-18 % % Inputs: % x Input data (2D distribution data) @@ -9,6 +11,7 @@ % n_modes Number of modes to fit to the data % logr0 Initial guess for center of each mode, expressed as a vector: % logr0 = [dim1_mode1,dim2_mode1,dim1_mode2,dim2_mode2,...] +% (Optional) % % Outputs: % phantom Output phantom object representing a fit bivariate lognormal @@ -24,7 +27,8 @@ %-- Parse inputs ---------------------------------------------% if isa(vec_grid,'Grid') grid = vec_grid; - [~,vec1,vec2] = grid.vectorize(); + vec1 = grid.elements(:,1); + vec2 = grid.elements(:,2); else vec1 = vec_grid(:,1); vec2 = vec_grid(:,2); @@ -43,10 +47,13 @@ yup = []; ylow = []; for ii=1:n_modes - y0 = [y0,log(max(x))-3*(ii-1)/n_modes-3.5,... - 3+ii/n_modes,1.8+ii/n_modes,0.15,0.15,-0.9]; + y0 = [y0,... + log(max(x))-3*(ii-1)/n_modes-3.5,... % scaling of mode + 0+ii/n_modes,... % center of mode, dim1 + 1.8+ii/n_modes,... % center of mode, dim2 + 0.4,0.15,0.9]; % std. devs. and correlation ylow = [ylow,-inf,-3,-3,0,0,-pi]; - yup = [yup,inf,5,5,0.2,0.3,pi]; + yup = [yup,inf,5,5,0.8,0.3,pi]; % [C,log10(mg),log10(dg),log10(sm),log10(sg),corr] end if ~isempty(logr0) % update centers of distributions, if specified @@ -54,7 +61,7 @@ y0(3:6:end) = logr0(2:2:end); end for ii=1:n_modes % prior std. dev. - sy = [sy,inf,1e-2.*y0(6*(ii-1)+[2,3]),y0(6*(ii-1)+[4,5]),10]; + sy = [sy,inf,y0(6*(ii-1)+[4,5]),y0(6*(ii-1)+[4,5]),4]; % [C,log10(mg),log10(dg),log10(sm),log10(sg),corr] end diff --git a/@Phantom/fit2_rho.m b/@Phantom/fit2_rho.m new file mode 100644 index 0000000..d40cf86 --- /dev/null +++ b/@Phantom/fit2_rho.m @@ -0,0 +1,104 @@ + +% FIT2_RHO Fits a multimodal phantom to a given set of data, x, defined on a given grid. +% Optimized for effective density-mobility distributions. +% Outputs a fit phantom object. +% Author: Timothy Sipkens, 2020-03-18 +% +% Inputs: +% x Input data (2D distribution data) +% vec_grid A `Grid` object on which input data is defined or a vector +% of the coordiantes of the elements +% n_modes Number of modes to fit to the data +% logr0 Initial guess for center of each mode, expressed as a vector: +% logr0 = [dim1_mode1,dim2_mode1,dim1_mode2,dim2_mode2,...] +% (Optional) +% +% Outputs: +% phantom Output phantom object representing a fit bivariate lognormal +% N Scaling parameter that acts to scale the phantom to the data +% y_out Direct output from the fitting procedure +% J Jacobian of fitting procedure +%=============================================================% + +function [phantom,N,y_out,J] = fit2_rho(x,vec_grid,n_modes,logr0) + +disp('Fitting phantom object...'); + +%-- Parse inputs ---------------------------------------------% +if isa(vec_grid,'Grid') + grid = vec_grid; + vec1 = grid.elements(:,1); + vec2 = grid.elements(:,2); +else + vec1 = vec_grid(:,1); + vec2 = vec_grid(:,2); + grid = [min(vec1),max(vec1);min(vec2),max(vec2)]; + % specify a span for a grid +end + +if ~exist('logr0','var'); logr0 = []; end +%-------------------------------------------------------------% + + +corr2cov = @(sigma,R) diag(sigma)*R*diag(sigma); + +y0 = []; +sy = []; +yup = []; +ylow = []; +for ii=1:n_modes + y0 = [y0,... + log(max(x))-3*(ii-1)/n_modes-3.5,... % scaling of mode + 3+ii/n_modes,... % center of mode, dim1 + 1.8+ii/n_modes,... % center of mode, dim2 + 0.15,0.15,-0.9]; % std. devs. and correlation + ylow = [ylow,-inf,-3,-3,0,0,-pi]; + yup = [yup,inf,5,5,0.2,0.3,pi]; + % [C,log10(mg),log10(dg),log10(sm),log10(sg),corr] +end +if ~isempty(logr0) % update centers of distributions, if specified + y0(2:6:end) = logr0(1:2:end); + y0(3:6:end) = logr0(2:2:end); +end +for ii=1:n_modes % prior std. dev. + sy = [sy,inf,y0(6*(ii-1)+[4,5]),y0(6*(ii-1)+[4,5]),10]; + % [C,log10(mg),log10(dg),log10(sm),log10(sg),corr] +end + +% opts = optimoptions(@lsqnonlin,'MaxFunctionEvaluations',1e4,'MaxIterations',1e3); +% y1 = lsqnonlin(@(y) fun_pha(y,vec1,vec2,n_modes,corr2cov)-... +% x, y0, ylow, yup); +[y1,~,~,~,~,~,J] = ... + lsqnonlin(@(y) [max(log(fun_pha(y,vec1,vec2,n_modes,corr2cov)),max(log(x)-4))-... + max(log(x),max(log(x)-4));... + (y-y0)'./sy'], y0, ylow, yup); + +for ii=0:(n_modes-1) + mu{ii+1} = [y1(6*ii+2),y1(6*ii+3)]; + sigma{ii+1} = [y1(6*ii+4),y1(6*ii+5)]; + Sigma{ii+1} = corr2cov(sigma{ii+1},[1,sin(y1(6*ii+6));sin(y1(6*ii+6)),1]); +end + +phantom = Phantom('standard',grid,mu,Sigma); +phantom.type = 'standard-fit'; + +N = exp(y1(1:6:end)); % scaling parameter denoting total number of particles +y_out = y1; + +disp('Complete.'); +disp(' '); + +end + + +function x = fun_pha(y,vec1,vec2,n_modes,corr2cov) + +x = 0; +for ii=0:(n_modes-1) + x = x + exp(y(6*ii+1)).*mvnpdf(log10([vec1,vec2]),[y(6*ii+2),y(6*ii+3)],... + corr2cov([y(6*ii+4),y(6*ii+5)],[1,sin(y(6*ii+6));sin(y(6*ii+6)),1])); + % cos() correlation is to better span -1 to 1 +end + +end + diff --git a/main_bayes.m b/main_bayes.m index 2aa7fad..3572d6b 100644 --- a/main_bayes.m +++ b/main_bayes.m @@ -102,6 +102,9 @@ grid_b.plot2d_sweep(b,cm_b); +[pha_b,Nb] = Phantom.fit2(b,grid_b,2,[0,1.7,0.1,2.3]); + + %% %== STEP 3: Perform inversions ===========================================% diff --git a/main_jas20a_noise.m b/main_jas20a_noise.m index 5c59e2d..e3ec8cd 100644 --- a/main_jas20a_noise.m +++ b/main_jas20a_noise.m @@ -1,5 +1,6 @@ % Evaluates the JAS19 cases for a varying amount of noise. +%=========================================================================% clear; clc; diff --git a/run_inversions_g.m b/run_inversions_g.m index d93a4d0..6d09dd4 100644 --- a/run_inversions_g.m +++ b/run_inversions_g.m @@ -15,7 +15,7 @@ chi.tk1 = norm(x0-x_tk1); - +%% %-- Expectation maximization ----% % Get initial guess b_init = b; @@ -29,10 +29,76 @@ chi.init = norm(x0-x_init); x_init_m = grid_x.marginalize(x_init); -disp('Performing expectation-maximization...'); -x_em = invert.em(Lb*A,Lb*b,x_init,4,x0); +disp('Performing expectation-maximization (attempt no. 1)...'); +x_em = invert.em(Lb*A,Lb*b,x_init,3,x0); disp('Inversion complete.'); disp(' '); chi.em = norm(x0-x_em); + + +%-- Expectation maximization (attempt 2) ----% +% Get initial guess +n2 = [18,21]; +grid2 = Grid([grid_t.span],... + n2,'logarithmic'); +B2 = grid2.transform(grid_t); % evaluate matrix modifier to transform kernel +A2 = A_t*B2; +x02 = grid2.project(grid_t,x_t); % project into basis for x + +x_init2 = interp2(grid_b.edges{2}',grid_b.edges{1}',... + reshape(full(b_init)./(A2*ones(size(x02))),grid_b.ne),... + grid2.elements(:,2),grid2.elements(:,1)); +x_init2(isnan(x_init)) = 0; +x_init2(isinf(x_init2)) = 0; +x_init2 = sparse(max(0,x_init2)); +chi.init2 = norm(x02-x_init2); + +disp('Performing expectation-maximization (attempt no. 2)...'); +x_em2 = invert.em(Lb*A2,Lb*b,x_init2,250); +disp('Inversion complete.'); +disp(' '); + +chi.em2 = norm(x02-x_em2); + + + + +%-- Expectation maximization (attempt 3) ----% +% Get initial guess +x_init3 = ones(size(x02)); + +disp('Performing expectation-maximization (attempt no. 3)...'); +x_em3 = invert.em(Lb*A2,Lb*b,x_init3,250); +disp('Inversion complete.'); +disp(' '); + +chi.em3 = norm(x02-x_em3); + + + +%-- Expectation maximization (attempt 4) ----% +disp('Performing expectation-maximization (attempt no. 1)...'); +x_em4 = invert.em(Lb*A,Lb*b,ones(size(x_init)),3,x0); +disp('Inversion complete.'); +disp(' '); + +chi.em4 = norm(x0-x_em4); + + + +%-- Plot EM results ------------------------% +figure(31); +grid_x.plot2d(x_em); +colorbar; +figure(32); +grid2.plot2d(x_em2); +colorbar; +figure(33); +grid2.plot2d(x_em3); +colorbar; + + + + From e47961b5de702ff4591734b7fa10b9a5a58c0944 Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Tue, 24 Mar 2020 15:41:03 -0700 Subject: [PATCH 40/56] Updated Phantom for mode weighting The Phantom class can now take an additional arguement, `w`, that will rewieght the modes in the Phantom. This allows for appropriate Phantom instances when using Phantom.fit2*. --- @Phantom/Phantom.m | 68 ++++++++++++++++++++++++++++++--------------- @Phantom/fit2.m | 7 ++--- @Phantom/fit2_rho.m | 7 ++--- 3 files changed, 51 insertions(+), 31 deletions(-) diff --git a/@Phantom/Phantom.m b/@Phantom/Phantom.m index d5667c7..48839a6 100644 --- a/@Phantom/Phantom.m +++ b/@Phantom/Phantom.m @@ -104,8 +104,13 @@ end obj.n_modes = length(obj.modes); % get number of modes - if isempty(w); obj.w = ones(obj.n_modes,1); else; obj.w = w; end - % assign node weightings + + if isempty(w) % assign mode weightings + obj.w = ones(obj.n_modes,1)./obj.n_modes; % evely distribute modes + else + obj.w = w./sum(w); % normalize weights and assign + end + %-- Generate a grid to evaluate phantom on -------------------% if isa(span_grid,'Grid') % if grid is specified @@ -184,10 +189,21 @@ % % Note: This method does not work for conditionally-normal distributions % (which cannot be defined with mu and Sigma). - function [x] = eval(obj,grid) + function [x] = eval(obj,grid_vec,w) - if ~exist('grid','var'); grid = []; end - if isempty(grid); grid = obj.grid; end + %-- Parse inputs ---------------------------------------------% + if ~exist('w','var'); w = []; end + if isempty(w); w = ones(obj.n_modes,1)./obj.n_modes; end + % weight modes evenly + + if ~exist('grid','var'); grid_vec = []; end + if isempty(grid_vec); grid_vec = obj.grid; end % use phantom grid + if isempty(grid_vec); error('For an empty Phantom, grid_vec is required.'); end + % if an empty phantom (e.g. Phantom.eval_p(p);) + + if isa(grid_vec,'Grid'); vec = grid_vec.elements; % get element centers from grid + else; vec = grid_vec; % if a set of element centers was provided directly + end if ~iscell(obj.mu); mu0 = {obj.mu}; else; mu0 = obj.mu; @@ -196,21 +212,18 @@ if ~iscell(obj.Sigma); Sigma0 = {obj.Sigma}; else; Sigma0 = obj.Sigma; end - - if ~exist('w','var'); end + %-------------------------------------------------------------% - m_vec = grid.elements(:,1); - d_vec = grid.elements(:,2); + m_vec = vec(:,1); % element centers in mass + d_vec = vec(:,2); % element centers in mobility %-- Assign other parameters of distribution ------------------% x = zeros(size(m_vec)); for ll=1:obj.n_modes % loop through distribution modes - x = x + mvnpdf(log10([m_vec,d_vec]),mu0{ll},Sigma0{ll}); + x = x + w(ll).*... % add new mode, reweighting accordingly + mvnpdf(log10([m_vec,d_vec]),mu0{ll},Sigma0{ll}); end - - %-- Reweight modes -------------------------------------------% - x = x./obj.n_modes; end %=================================================================% @@ -220,22 +233,31 @@ % Generates a distribution from p as required for conditionally- % normal modes. % Author: Timothy Sipkens, 2019-10-29 - function [x] = eval_p(obj,p,grid_vec) + function [x] = eval_p(obj,p,grid_vec,w) + + %-- Parse inputs ---------------------------------------------% + if ~exist('w','var'); w = []; end + if isempty(w); w = ones(length(p),1)./obj.n_modes; end + % weight modes evenly + + if ~exist('p','var'); p = []; end + if isempty(p); p = obj.p; end % use p values from given phantom + if isempty(p); error('For an empty Phantom, p is required.'); end + % if an empty phantom (e.g. Phantom.eval_p;) if ~exist('grid','var'); grid_vec = []; end if isempty(grid_vec); grid_vec = obj.grid; end % use phantom grid - if isempty(grid_vec) % if an empty phantom (e.g. Phantom.eval_p(p);) - error(['For an empty Phantom object, one must specify ',... - 'a grid or set of element centers.']); - end + if isempty(grid_vec); error('For an empty Phantom, grid_vec is required.'); end + % if an empty phantom (e.g. Phantom.eval_p(p);) if isa(grid_vec,'Grid'); vec = grid_vec.elements; % get element centers from grid else; vec = grid_vec; % if a set of element centers was provided directly end + %-------------------------------------------------------------% - m_vec = vec(:,1); - d_vec = vec(:,2); + m_vec = vec(:,1); % element centers in mass + d_vec = vec(:,2); % element centers in mobility m_fun = @(d,ll) log(p(ll).m_100.*((d./100).^p(ll).Dm)); % geometric mean mass in fg as a function of d @@ -251,12 +273,12 @@ exp(m_fun(d_vec,ll))); end - p_temp = lognpdf(d_vec,log(p(ll).dg),log(p(ll).sg)).*p_m; - x = x+p_temp; + x = x + ... + w(ll).*p_m.*... + lognpdf(d_vec,log(p(ll).dg),log(p(ll).sg)); end %-- Reweight modes and transform to log-log space ------------% - x = x./obj.n_modes; x = x.*(d_vec.*m_vec).*log(10).^2; % convert to [log10(m),log10(d)]T space end diff --git a/@Phantom/fit2.m b/@Phantom/fit2.m index a867dca..1214ee0 100644 --- a/@Phantom/fit2.m +++ b/@Phantom/fit2.m @@ -72,6 +72,8 @@ lsqnonlin(@(y) [max(log(fun_pha(y,vec1,vec2,n_modes,corr2cov)),max(log(x)-4))-... max(log(x),max(log(x)-4));... (y-y0)'./sy'], y0, ylow, yup); +N = exp(y1(1:6:end)); % scaling parameter denoting total number of particles +y_out = y1; for ii=0:(n_modes-1) mu{ii+1} = [y1(6*ii+2),y1(6*ii+3)]; @@ -79,12 +81,9 @@ Sigma{ii+1} = corr2cov(sigma{ii+1},[1,sin(y1(6*ii+6));sin(y1(6*ii+6)),1]); end -phantom = Phantom('standard',grid,mu,Sigma); +phantom = Phantom('standard',grid,mu,Sigma,N); phantom.type = 'standard-fit'; -N = exp(y1(1:6:end)); % scaling parameter denoting total number of particles -y_out = y1; - disp('Complete.'); disp(' '); diff --git a/@Phantom/fit2_rho.m b/@Phantom/fit2_rho.m index d40cf86..138a467 100644 --- a/@Phantom/fit2_rho.m +++ b/@Phantom/fit2_rho.m @@ -72,6 +72,8 @@ lsqnonlin(@(y) [max(log(fun_pha(y,vec1,vec2,n_modes,corr2cov)),max(log(x)-4))-... max(log(x),max(log(x)-4));... (y-y0)'./sy'], y0, ylow, yup); +N = exp(y1(1:6:end)); % scaling parameter denoting total number of particles +y_out = y1; for ii=0:(n_modes-1) mu{ii+1} = [y1(6*ii+2),y1(6*ii+3)]; @@ -79,12 +81,9 @@ Sigma{ii+1} = corr2cov(sigma{ii+1},[1,sin(y1(6*ii+6));sin(y1(6*ii+6)),1]); end -phantom = Phantom('standard',grid,mu,Sigma); +phantom = Phantom('standard',grid,mu,Sigma,N); phantom.type = 'standard-fit'; -N = exp(y1(1:6:end)); % scaling parameter denoting total number of particles -y_out = y1; - disp('Complete.'); disp(' '); From eb112300fcc67d72028bab00107bb4ef20cb6be7 Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Tue, 24 Mar 2020 16:34:23 -0700 Subject: [PATCH 41/56] Added Phantom.plus method Users can now use the `+` operator to add two phantoms together. --- @Phantom/Phantom.m | 44 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/@Phantom/Phantom.m b/@Phantom/Phantom.m index 48839a6..6cceacc 100644 --- a/@Phantom/Phantom.m +++ b/@Phantom/Phantom.m @@ -75,6 +75,7 @@ % mass-mobility equivlanet params. obj.p = obj.fill_p(p); % get mg as well + %-- OPTION 2: Using a mass-mobility parameter set (p) ----% case {'mass-mobility'} % for custom mass-mobility phantom % sepcified using a p structure @@ -87,6 +88,7 @@ obj.R = obj.sigma2r(obj.Sigma); end + %-- OPTION 3: Use a preset or sample distribution --------% otherwise % check if type is a preset phantom [p,modes,type] = obj.presets(type); @@ -193,6 +195,7 @@ %-- Parse inputs ---------------------------------------------% if ~exist('w','var'); w = []; end + if isempty(w); w = obj.w; end if isempty(w); w = ones(obj.n_modes,1)./obj.n_modes; end % weight modes evenly @@ -237,7 +240,8 @@ %-- Parse inputs ---------------------------------------------% if ~exist('w','var'); w = []; end - if isempty(w); w = ones(length(p),1)./obj.n_modes; end + if isempty(w); w = obj.w; end + if isempty(w); w = ones(obj.n_modes,1)./obj.n_modes; end % weight modes evenly if ~exist('p','var'); p = []; end @@ -283,7 +287,43 @@ % convert to [log10(m),log10(d)]T space end %=================================================================% - + + + + %== PLUS =========================================================% + % Adds two phantoms. + % Author: Timothy Sipkens, 2020-03-24 + function objn = plus(obj1,obj2,w) + + if ~exist('w','var'); w = []; end + if isempty(w); w = [1,1]./2; else; w = w./sum(w); end + w = [w(1).*obj1.w(:);w(2).*obj2.w(:)]; + + span = [min(obj1.grid.span(:,1),obj2.grid.span(:,1)),... + max(obj1.grid.span(:,2),obj2.grid.span(:,2))]; + % update span to incorporate both Phantoms + + + if and(~isempty(obj1.mu),~isempty(obj2.mu)) % bivariate lognormal phantoms + if ~iscell(obj1.mu); obj1.mu = {obj1.mu}; end + if ~iscell(obj1.Sigma); obj1.Sigma = {obj1.Sigma}; end + if ~iscell(obj2.mu); obj2.mu = {obj2.mu}; end + if ~iscell(obj2.Sigma); obj2.Sigma = {obj2.Sigma}; end + + objn = Phantom('standard',span,... + [obj1.mu,obj2.mu],... + [obj1.Sigma,obj2.Sigma],... + w); + + + else % at least one mode is not bivariate lognormal + objn = Phantom('mass-mobility',span,... + [obj1.p(:);obj2.p(:)]',... + [obj1.modes,obj2.modes],... + w); + end + end + %== MASS2RHO =====================================================% From d5b6c2664cf2e31e24e50caa40b64ee391f84c82 Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Wed, 25 Mar 2020 14:49:00 -0700 Subject: [PATCH 42/56] Updated chi > err in run_inversions* This better reflects the Euclidean error nature of this quantity. --- main_bayes.m | 7 +++++++ run_inversions_a.m | 16 ++++++++-------- run_inversions_b.m | 16 ++++++++-------- run_inversions_c.m | 10 +++++----- run_inversions_d.m | 16 ++++++++-------- run_inversions_e.m | 24 ++++++++++++------------ run_inversions_g.m | 14 +++++++------- run_inversions_h.m | 4 ++-- run_inversions_i.m | 9 ++++++++- 9 files changed, 65 insertions(+), 51 deletions(-) diff --git a/main_bayes.m b/main_bayes.m index 3572d6b..0e26fc2 100644 --- a/main_bayes.m +++ b/main_bayes.m @@ -104,6 +104,13 @@ [pha_b,Nb] = Phantom.fit2(b,grid_b,2,[0,1.7,0.1,2.3]); +%-- pha_b.Sigma properties --------% +s1b = sqrt(pha_b.Sigma{1}(1,1)); +s2b = sqrt(pha_b.Sigma{1}(2,2)); +R12b = pha_b.Sigma{1}(1,2)/(s1b*s2b); +Dmb = pha_b.Sigma{1}(1,2)/pha_b.Sigma{1}(2,2); % also s1*R12/s2 +%----------------------------------% + %% diff --git a/run_inversions_a.m b/run_inversions_a.m index b73cc29..60ed8fd 100644 --- a/run_inversions_a.m +++ b/run_inversions_a.m @@ -12,7 +12,7 @@ x_init(isnan(x_init)) = 0; x_init(isinf(x_init)) = 0; x_init = sparse(max(0,x_init)); -chi.init = norm(x0-x_init); +err.init = norm(x0-x_init); x_init_m = grid_x.marginalize(x_init); @@ -23,7 +23,7 @@ disp('Inversion complete.'); disp(' '); -chi.lsq = norm(x0-x_lsq); +err.lsq = norm(x0-x_lsq); %% Tikhonov (0th) implementation @@ -35,7 +35,7 @@ disp('Inversion complete.'); disp(' '); -chi.tk0 = norm(x0-x_tk0); +err.tk0 = norm(x0-x_tk0); %% Tikhonov (1st) implementation @@ -47,7 +47,7 @@ disp('Inversion complete.'); disp(' '); -chi.tk1 = norm(x0-x_tk1); +err.tk1 = norm(x0-x_tk1); %% Tikhonov (2nd) implementation @@ -59,7 +59,7 @@ disp('Inversion complete.'); disp(' '); -chi.tk2 = norm(x0-x_tk2); +err.tk2 = norm(x0-x_tk2); %% MART, Maximum entropy regularized solution @@ -72,7 +72,7 @@ disp('Inversion complete.'); disp(' '); -chi.mart = norm(x0-x_mart); +err.mart = norm(x0-x_mart); %% Twomey @@ -85,7 +85,7 @@ disp('Completed Twomey.'); disp(' '); -chi.two = norm(x0-x_two); +err.two = norm(x0-x_two); %% Twomey-Markowski-Buckley @@ -96,7 +96,7 @@ x_init,35,[1e2,1e-5],x0,'Buckley'); t.two_mh = toc; -chi.two_mh = norm(x0-x_two_mh); +err.two_mh = norm(x0-x_two_mh); diff --git a/run_inversions_b.m b/run_inversions_b.m index eeca21c..f96b206 100644 --- a/run_inversions_b.m +++ b/run_inversions_b.m @@ -12,7 +12,7 @@ x_init(isnan(x_init)) = 0; x_init(isinf(x_init)) = 0; x_init = sparse(max(0,x_init)); -chi.init = norm(x0-x_init); +err.init = norm(x0-x_init); x_init_m = grid_x.marginalize(x_init); @@ -30,7 +30,7 @@ disp(' '); % chi.lsq = norm(x0-x_lsq); -chi.lsq = norm(x0-x_lsq_nn); +err.lsq = norm(x0-x_lsq_nn); %% Tikhonov (0th) implementation @@ -52,7 +52,7 @@ disp(' '); % diff.tk0 = norm(x_tk0-x_tk0_nn); -chi.tk0 = norm(x0-x_tk0_nn); +err.tk0 = norm(x0-x_tk0_nn); % chi.tk0_hr = norm(x0-x_tk0_hr); @@ -75,7 +75,7 @@ disp(' '); % diff.tk1 = norm(x_tk1-x_tk1_nn); -chi.tk1 = norm(x0-x_tk1_nn); +err.tk1 = norm(x0-x_tk1_nn); % chi.tk1_hr = norm(x0-x_tk1_hr); @@ -98,7 +98,7 @@ disp(' '); % diff.tk2 = norm(x_tk2-x_tk2_nn); -chi.tk2 = norm(x0-x_tk2_nn); +err.tk2 = norm(x0-x_tk2_nn); % chi.tk2_hr = norm(x0-x_tk2_hr); @@ -109,7 +109,7 @@ disp('Inversion complete.'); disp(' '); -chi.mart = norm(x0-x_MART); +err.mart = norm(x0-x_MART); %% Twomey @@ -118,7 +118,7 @@ disp('Completed Twomey.'); disp(' '); -chi.two = norm(x0-x_Two); +err.two = norm(x0-x_Two); %% Twomey-Markowski-Buckley @@ -127,7 +127,7 @@ x_init,35,'Buckley',Sf_two_mh); disp('Completed Twomey-Markowski.'); -chi.two_mh = norm(x0-x_two_mh); +err.two_mh = norm(x0-x_two_mh); diff --git a/run_inversions_c.m b/run_inversions_c.m index aac2e23..67e20d3 100644 --- a/run_inversions_c.m +++ b/run_inversions_c.m @@ -10,7 +10,7 @@ x_init(isnan(x_init)) = 0; x_init(isinf(x_init)) = 0; x_init = sparse(max(0,x_init)); -chi.init = norm(x0-x_init); +err.init = norm(x0-x_init); %% Tikhonov (0th) implementation @@ -21,7 +21,7 @@ disp('Inversion complete.'); disp(' '); -chi.tk0 = norm(x0-x_tk0); +err.tk0 = norm(x0-x_tk0); %% Tikhonov (1st) implementation @@ -32,7 +32,7 @@ disp('Inversion complete.'); disp(' '); -chi.tk1 = norm(x0-x_tk1); +err.tk1 = norm(x0-x_tk1); %% Tikhonov (2nd) implementation @@ -43,7 +43,7 @@ disp('Inversion complete.'); disp(' '); -chi.tk2 = norm(x0-x_tk2); +err.tk2 = norm(x0-x_tk2); %% Twomey @@ -54,5 +54,5 @@ disp('Completed Twomey.'); disp(' '); -chi.two = norm(x0-x_two); +err.two = norm(x0-x_two); diff --git a/run_inversions_d.m b/run_inversions_d.m index 95bf555..94d2097 100644 --- a/run_inversions_d.m +++ b/run_inversions_d.m @@ -12,7 +12,7 @@ x_init(isnan(x_init)) = 0; x_init(isinf(x_init)) = 0; x_init = sparse(max(0,x_init)); -chi.init = norm(x0-x_init); +err.init = norm(x0-x_init); x_init_m = grid_x.marginalize(x_init); @@ -26,7 +26,7 @@ disp('Inversion complete.'); disp(' '); -chi.LSQ = norm(x0-x_LSQ); +err.LSQ = norm(x0-x_LSQ); %% Tikhonov (0th) implementation @@ -37,7 +37,7 @@ disp('Inversion complete.'); disp(' '); -chi.tk0(ii) = norm(x0-x_tk0); +err.tk0(ii) = norm(x0-x_tk0); %% Tikhonov (1st) implementation @@ -48,7 +48,7 @@ disp('Inversion complete.'); disp(' '); -chi.tk1(ii) = norm(x0-x_tk1); +err.tk1(ii) = norm(x0-x_tk1); %% Tikhonov (2nd) implementation @@ -60,7 +60,7 @@ disp('Inversion complete.'); disp(' '); -chi.tk2(ii) = norm(x0-x_tk2); +err.tk2(ii) = norm(x0-x_tk2); %% MART, Maximum entropy regularized solution @@ -72,7 +72,7 @@ disp('Inversion complete.'); disp(' '); -chi.mart = norm(x0-x_mart); +err.mart = norm(x0-x_mart); %% Twomey @@ -83,7 +83,7 @@ disp('Completed Twomey.'); disp(' '); -chi.two = norm(x0-x_two); +err.two = norm(x0-x_two); %% Twomey-Markowski-Buckley @@ -94,7 +94,7 @@ t.two_mh(ii) = toc; disp('Completed Twomey-Markowski.'); -chi.two_mh = norm(x0-x_two_mh); +err.two_mh = norm(x0-x_two_mh); end diff --git a/run_inversions_e.m b/run_inversions_e.m index d530980..8de8f78 100644 --- a/run_inversions_e.m +++ b/run_inversions_e.m @@ -12,7 +12,7 @@ x_init(isnan(x_init)) = 0; x_init(isinf(x_init)) = 0; x_init = sparse(max(0,x_init)); -chi.init = 100*norm(x0-x_init)/norm(x0); +err.init = 100*norm(x0-x_init)/norm(x0); x_init_m = grid_x.marginalize(x_init); @@ -23,7 +23,7 @@ disp('Inversion complete.'); disp(' '); -chi.lsq = 100*norm(x0-x_lsq)/norm(x0); +err.lsq = 100*norm(x0-x_lsq)/norm(x0); %% Tikhonov (0th) implementation @@ -36,7 +36,7 @@ disp('Inversion complete.'); disp(' '); -chi.tk0 = 100*norm(x0-x_tk0)/norm(x0); +err.tk0 = 100*norm(x0-x_tk0)/norm(x0); %% Tikhonov (1st) implementation @@ -49,7 +49,7 @@ disp('Inversion complete.'); disp(' '); -chi.tk1 = 100*norm(x0-x_tk1)/norm(x0); +err.tk1 = 100*norm(x0-x_tk1)/norm(x0); %% Tikhonov (2nd) implementation @@ -62,7 +62,7 @@ disp('Inversion complete.'); disp(' '); -chi.tk2 = 100*norm(x0-x_tk2)/norm(x0); +err.tk2 = 100*norm(x0-x_tk2)/norm(x0); %% Tikhonov (0th) implementation L-curve @@ -74,7 +74,7 @@ t.tk0_lc = toc; disp('Inversion complete.'); disp(' '); -chi.tk0_lcurve = 100*norm(x0-x_tk0_lc)/norm(x0); +err.tk0_lcurve = 100*norm(x0-x_tk0_lc)/norm(x0); %% Tikhonov (1st) implementation L-curve @@ -87,7 +87,7 @@ t.tk1_lc = toc; disp('Inversion complete.'); disp(' '); -chi.tk1_lcurve = 100*norm(x0-x_tk1_lc)/norm(x0); +err.tk1_lcurve = 100*norm(x0-x_tk1_lc)/norm(x0); %% Tikhonov (2nd), L-curve @@ -99,7 +99,7 @@ t.tk2_lc = toc; disp('Inversion complete.'); disp(' '); -chi.tk2_lc = 100*norm(x0-x_tk2_lc)/norm(x0); +err.tk2_lc = 100*norm(x0-x_tk2_lc)/norm(x0); %% MART, Maximum entropy regularized solution @@ -112,7 +112,7 @@ disp('Inversion complete.'); disp(' '); -chi.mart = 100*norm(x0-x_mart)/norm(x0); +err.mart = 100*norm(x0-x_mart)/norm(x0); %% MART with smoothing function @@ -122,7 +122,7 @@ A,b,Lb,grid_x,x_init,35,[1e2,1e-5],x0,'Buckley'); t.mart_mh = toc; -chi.mart_mh = 100*norm(x0-x_mart_mh)/norm(x0); +err.mart_mh = 100*norm(x0-x_mart_mh)/norm(x0); disp('Completed MART with smoothing function.'); disp(' '); @@ -138,7 +138,7 @@ disp('Completed Twomey.'); disp(' '); -chi.two = 100*norm(x0-x_two)/norm(x0); +err.two = 100*norm(x0-x_two)/norm(x0); %% Twomey-Markowski-Buckley @@ -148,7 +148,7 @@ A,b,Lb,grid_x,x_init,35,[1e2,1e-5],x0,'Buckley'); t.two_mh = toc; -chi.two_mh = 100*norm(x0-x_two_mh)/norm(x0); +err.two_mh = 100*norm(x0-x_two_mh)/norm(x0); diff --git a/run_inversions_g.m b/run_inversions_g.m index 6d09dd4..5e40920 100644 --- a/run_inversions_g.m +++ b/run_inversions_g.m @@ -12,7 +12,7 @@ disp('Inversion complete.'); disp(' '); -chi.tk1 = norm(x0-x_tk1); +err.tk1 = norm(x0-x_tk1); %% @@ -26,7 +26,7 @@ x_init(isnan(x_init)) = 0; x_init(isinf(x_init)) = 0; x_init = sparse(max(0,x_init)); -chi.init = norm(x0-x_init); +err.init = norm(x0-x_init); x_init_m = grid_x.marginalize(x_init); disp('Performing expectation-maximization (attempt no. 1)...'); @@ -34,7 +34,7 @@ disp('Inversion complete.'); disp(' '); -chi.em = norm(x0-x_em); +err.em = norm(x0-x_em); @@ -53,14 +53,14 @@ x_init2(isnan(x_init)) = 0; x_init2(isinf(x_init2)) = 0; x_init2 = sparse(max(0,x_init2)); -chi.init2 = norm(x02-x_init2); +err.init2 = norm(x02-x_init2); disp('Performing expectation-maximization (attempt no. 2)...'); x_em2 = invert.em(Lb*A2,Lb*b,x_init2,250); disp('Inversion complete.'); disp(' '); -chi.em2 = norm(x02-x_em2); +err.em2 = norm(x02-x_em2); @@ -74,7 +74,7 @@ disp('Inversion complete.'); disp(' '); -chi.em3 = norm(x02-x_em3); +err.em3 = norm(x02-x_em3); @@ -84,7 +84,7 @@ disp('Inversion complete.'); disp(' '); -chi.em4 = norm(x0-x_em4); +err.em4 = norm(x0-x_em4); diff --git a/run_inversions_h.m b/run_inversions_h.m index 30b095d..4961139 100644 --- a/run_inversions_h.m +++ b/run_inversions_h.m @@ -14,7 +14,7 @@ disp('Inversion complete.'); disp(' '); -chi.tk1_nn = norm(x0-x_tk1_a); +err.tk1_nn = norm(x0-x_tk1_a); %-- Exponential rotated ------% @@ -34,6 +34,6 @@ disp('Inversion complete.'); disp(' '); -chi.exp_a = norm(x0-x_exp_rot_a); +err.exp_a = norm(x0-x_exp_rot_a); diff --git a/run_inversions_i.m b/run_inversions_i.m index 367488f..26c7b29 100644 --- a/run_inversions_i.m +++ b/run_inversions_i.m @@ -9,6 +9,13 @@ Gd = phantom.Sigma; end +%-- Gd properties -----------------% +l1 = sqrt(Gd(1,1)); +l2 = sqrt(Gd(2,2)); +R12 = Gd(1,2)/(l1*l2); +Dm = Gd(1,2)/Gd(2,2); % s1*R12/s2 +%----------------------------------% + [x_ed_lam,lambda_ed_lam,out_ed_lam] = ... optimize.exp_dist_op(... Lb*A,Lb*b,[0.1,10],Gd,... @@ -16,6 +23,6 @@ disp('Process complete.'); disp(' '); -chi.ed = norm(x_ed_lam-x0); +err.ed = norm(x_ed_lam-x0); From b468caf55f945ea463b2bbfe9108124f3056125d Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Thu, 26 Mar 2020 13:43:06 -0700 Subject: [PATCH 43/56] Added Dm, l1, and l2 output from cov2p --- @Phantom/Phantom.m | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/@Phantom/Phantom.m b/@Phantom/Phantom.m index 6cceacc..bf7f8f4 100644 --- a/@Phantom/Phantom.m +++ b/@Phantom/Phantom.m @@ -442,10 +442,12 @@ %== COV2P ========================================================% % Function to convert covariance matrix and mean to p. % Author: Timothy Sipkens, 2019-10-29 - function [p] = cov2p(mu,Sigma,modes) + function [p,Dm,l1,l2] = cov2p(mu,Sigma,modes) if ~iscell(mu); mu = {mu}; end if ~iscell(Sigma); Sigma = {Sigma}; end + if ~exist('modes','var'); modes = [];end + if isempty(modes); modes = repmat({'logn'},[1,length(mu)]); end p = []; for ll=length(mu):-1:1 % loop through modes @@ -475,8 +477,11 @@ p(ll).rhog = p(ll).mg/(pi*p(ll).dg^3/6)*1e9; end - + p = Phantom.fill_p(p); + Dm = [p.Dm]; + l1 = log10([p.sm]); + l2 = log10([p.sg]); end %=================================================================% From d4a870ead7b921d08c639435c87afa9d73ecd045 Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Fri, 27 Mar 2020 11:48:52 -0700 Subject: [PATCH 44/56] Updated `err` or `chi` -> `eps` This variable name change is consistent with the this quantity uniformly representing the Euclidean error (Euclidean distance to exact solution). --- +invert/exp_dist.m | 1 + +optimize/exp_dist_op.m | 4 +-- +optimize/exp_dist_op1d.m | 6 ++-- +optimize/exp_dist_opbf.m | 6 ++-- +optimize/mart_op.m | 4 +-- +optimize/martmark_op.m | 4 +-- +optimize/tikhonov_op.m | 6 ++-- +optimize/tikhonov_op2d_bf.m | 6 ++-- +optimize/twomark_op.m | 4 +-- +optimize/twomey_op.m | 4 +-- main_bayes.m | 3 +- run_inversions_a.m | 16 +++++----- run_inversions_b.m | 16 +++++----- run_inversions_c.m | 10 +++--- run_inversions_d.m | 16 +++++----- run_inversions_e.m | 24 +++++++-------- run_inversions_g.m | 14 ++++----- run_inversions_h.m | 39 ----------------------- run_inversions_i.m | 10 ++++-- run_inversions_j.m | 6 ++++ run_inversions_k.m | 60 ++++++++++++++++++++++++++++++++++++ 21 files changed, 147 insertions(+), 112 deletions(-) delete mode 100644 run_inversions_h.m create mode 100644 run_inversions_k.m diff --git a/+invert/exp_dist.m b/+invert/exp_dist.m index e220f18..c2cb6b2 100644 --- a/+invert/exp_dist.m +++ b/+invert/exp_dist.m @@ -22,6 +22,7 @@ function [x,D,Lpr0,Gpo_inv] = exp_dist(A,b,lambda,Gd,grid_vec2,vec1,xi,solver) +if ~exist('vec1','var'); vec1 = []; end x_length = length(A(1,:)); diff --git a/+optimize/exp_dist_op.m b/+optimize/exp_dist_op.m index b2f0145..fad6a5c 100644 --- a/+optimize/exp_dist_op.m +++ b/+optimize/exp_dist_op.m @@ -62,7 +62,7 @@ A,b,lambda(ii),Gd,grid_vec2,vec1,xi,solver); %-- Store ||Ax-b|| and Euclidean error ---------% - if ~isempty(x_ex); output(ii).chi = norm(output(ii).x-x_ex); end + if ~isempty(x_ex); output(ii).eps = norm(output(ii).x-x_ex); end output(ii).Axb = norm(A*output(ii).x-b); %-- Compute credence, fit, and Bayes factor ----% @@ -77,7 +77,7 @@ %-- Specify output ----------------------------------% if ~isempty(x_ex) - [~,ind_min] = min([output.chi]); + [~,ind_min] = min([output.eps]); % use Euclidean error else [~,ind_min] = max([output.B]); end diff --git a/+optimize/exp_dist_op1d.m b/+optimize/exp_dist_op1d.m index 78420a8..77f9c43 100644 --- a/+optimize/exp_dist_op1d.m +++ b/+optimize/exp_dist_op1d.m @@ -63,7 +63,7 @@ A,b,lambda,Gd_alt,grid_vec2,vec1,xi,solver); %-- Store ||Ax-b|| and Euclidean error --% - if ~isempty(x_ex); output(ii).chi = norm(output(ii).x-x_ex); end + if ~isempty(x_ex); output(ii).eps = norm(output(ii).x-x_ex); end output(ii).Axb = norm(A*output(ii).x-b); %-- Compute credence, fit, and Bayes factor --% @@ -75,9 +75,9 @@ if ~isempty(x_ex) - [~,ind_min] = min([output.chi]); + [~,ind_min] = min([output.eps]); % use Euclidean error else - [~,ind_min] = max([output.B]); + [~,ind_min] = max([output.B]); % use Bayes factor end x = output(ind_min).x; diff --git a/+optimize/exp_dist_opbf.m b/+optimize/exp_dist_opbf.m index fa755fd..fc76883 100644 --- a/+optimize/exp_dist_opbf.m +++ b/+optimize/exp_dist_opbf.m @@ -48,13 +48,13 @@ vec_corr = vec_corr(:); tools.textbar(0); -output(length(vec_lambda)).chi = []; +output(length(vec_lambda)).eps = []; for ii=1:length(vec_lambda) y = [vec_lambda(ii),vec_ratio(ii),vec_ld(ii),vec_corr(ii)]; output(ii).x = invert.exp_dist(... A,b,y(1),Gd_fun(y),d_vec,m_vec,xi,solver); - output(ii).chi = norm(output(ii).x-x_ex); + output(ii).eps = norm(output(ii).x-x_ex); % Euclidean error output(ii).lambda = vec_lambda(ii); output(ii).ratio = vec_ratio(ii); @@ -69,7 +69,7 @@ tools.textbar(1); -[~,ind_min] = min([output(ii).chi]); +[~,ind_min] = min([output(ii).eps]); x = out(ind_min).x; lambda = out(ind_min).lambda; diff --git a/+optimize/mart_op.m b/+optimize/mart_op.m index e3155ac..4002016 100644 --- a/+optimize/mart_op.m +++ b/+optimize/mart_op.m @@ -27,13 +27,13 @@ output(1).iter_vec = iter_vec(1); output(1).x = invert.mart(A,b,xi,iter_vec(1)); -output(1).chi = norm(output(1).x-x_ex); +output(1).eps = norm(output(1).x-x_ex); % Euclidean error tools.textbar(1/length(iter_vec)); for ii=2:length(iter_vec) output(ii).iter_vec = ii; output(ii).x = invert.mart(A,b,output(ii-1).x,iter_vec(ii)-iter_vec(ii-1)); - output(ii).chi = norm(output(ii).x-x_ex); + output(ii).eps = norm(output(ii).x-x_ex); % Euclidean error if any(isnan(output(ii).x)); break; end % if NaN is encountered exit function (warning thrown my MART) diff --git a/+optimize/martmark_op.m b/+optimize/martmark_op.m index 6360c40..9f68401 100644 --- a/+optimize/martmark_op.m +++ b/+optimize/martmark_op.m @@ -41,7 +41,7 @@ for ii=length(Sf):-1:1 % loop through values of Sf output(ii).Sf = Sf(ii); output(ii).x = x_fun(output(ii).Sf); - if ~isempty(x_ex); output(ii).chi = norm(output(ii).x-x_ex); end + if ~isempty(x_ex); output(ii).eps = norm(output(ii).x-x_ex); end output(ii).Axb = norm(A*output(ii).x-b); disp(' '); @@ -52,7 +52,7 @@ end if ~isempty(x_ex) - [~,ind_min] = min([output.chi]); + [~,ind_min] = min([output.eps]); % use Euclidean error else ind_min = []; end diff --git a/+optimize/tikhonov_op.m b/+optimize/tikhonov_op.m index e541897..506483e 100644 --- a/+optimize/tikhonov_op.m +++ b/+optimize/tikhonov_op.m @@ -54,7 +54,7 @@ A,b,lambda(ii),Lpr0,[],xi,solver); %-- Store ||Ax-b|| and Euclidean error --% - if ~isempty(x_ex); output(ii).chi = norm(output(ii).x-x_ex); end + if ~isempty(x_ex); output(ii).eps = norm(output(ii).x-x_ex); end output(ii).Axb = norm(A*output(ii).x-b); %-- Compute credence, fit, and Bayes factor --% @@ -66,9 +66,9 @@ end if ~isempty(x_ex) % if exact solution is supplied - [~,ind_min] = min([output.chi]); + [~,ind_min] = min([output.eps]); % use Euclidean error else - [~,ind_min] = max([output.B]); + [~,ind_min] = max([output.B]); % use Bayes factor end lambda = output(ind_min).lambda; x = output(ind_min).x; diff --git a/+optimize/tikhonov_op2d_bf.m b/+optimize/tikhonov_op2d_bf.m index 229610a..4c30aa1 100644 --- a/+optimize/tikhonov_op2d_bf.m +++ b/+optimize/tikhonov_op2d_bf.m @@ -3,7 +3,7 @@ % Author: Arash Naseri, Timothy Sipkens, 2020-02-28 %=========================================================================% -function [x,lambda,alpha,out,chi] = tikhonov_op2d_bf(A,b,C,d,span1,span2,order,n,x_ex,xi,solver) +function [x,lambda,alpha,out,eps] = tikhonov_op2d_bf(A,b,C,d,span1,span2,order,n,x_ex,xi,solver) %-- Parse inputs ---------------------------------------------------------% @@ -34,7 +34,7 @@ [out(ii).x,~,Lpr0] = invert.tikhonov(... [param(ii,2).*A;C],[param(ii,2).*b;d],param(ii,1),Lpr0,[],xi,solver); %-- Store ||Ax-b|| and Euclidean error --% - if ~isempty(x_ex); out(ii).chi = norm(out(ii).x-x_ex); end + if ~isempty(x_ex); out(ii).eps = norm(out(ii).x-x_ex); end out(ii).Axb = norm(A*out(ii).x-b); %-- Compute credence, fit, and Bayes factor --% @@ -69,7 +69,7 @@ x = invert.tikhonov(... [alpha*A;C],[alpha*b;d],lambda,Lpr0,[],xi,solver); -chi = norm(x-x_ex); +eps = norm(x-x_ex); % Euclidean error end diff --git a/+optimize/twomark_op.m b/+optimize/twomark_op.m index 9672282..0f2b59e 100644 --- a/+optimize/twomark_op.m +++ b/+optimize/twomark_op.m @@ -41,7 +41,7 @@ for ii=length(Sf):-1:1 % loop through values of Sf output(ii).Sf = Sf(ii); output(ii).x = x_fun(output(ii).Sf); - if ~isempty(x_ex); output(ii).chi = norm(output(ii).x-x_ex); end + if ~isempty(x_ex); output(ii).eps = norm(output(ii).x-x_ex); end output(ii).Axb = norm(A*output(ii).x-b); disp(' '); @@ -52,7 +52,7 @@ end if ~isempty(x_ex) - [~,ind_min] = min([output.chi]); + [~,ind_min] = min([output.eps]); % use Euclidean error else ind_min = []; end diff --git a/+optimize/twomey_op.m b/+optimize/twomey_op.m index 582c3f6..3d0f5e1 100644 --- a/+optimize/twomey_op.m +++ b/+optimize/twomey_op.m @@ -25,13 +25,13 @@ out(1).iter_vec = iter_vec(1); out(1).x = invert.twomey(A,b,xi,iter_vec(1)); -out(1).chi = norm(out(1).x-x_ex); +out(1).eps = norm(out(1).x-x_ex); tools.textbar(1/length(iter_vec)); for ii=2:length(iter_vec) out(ii).iter_vec = ii; out(ii).x = invert.twomey(A,b,out(ii-1).x,1); - out(ii).chi = norm(out(ii).x-x_ex); + out(ii).eps = norm(out(ii).x-x_ex); % Euclidean error tools.textbar(ii/length(iter_vec)); end diff --git a/main_bayes.m b/main_bayes.m index 0e26fc2..2f91e9e 100644 --- a/main_bayes.m +++ b/main_bayes.m @@ -25,7 +25,7 @@ % grid to generate x. span_t = [10^-1.5,10^1.5;20,10^3]; % range of mobility and mass -phantom = Phantom('1',span_t); +phantom = Phantom('4',span_t); x_t = phantom.x; grid_t = phantom.grid; nmax = max(x_t); @@ -118,6 +118,7 @@ run_inversions_g; run_inversions_i; run_inversions_j; +run_inversions_k; % time methods diff --git a/run_inversions_a.m b/run_inversions_a.m index 60ed8fd..0a1737a 100644 --- a/run_inversions_a.m +++ b/run_inversions_a.m @@ -12,7 +12,7 @@ x_init(isnan(x_init)) = 0; x_init(isinf(x_init)) = 0; x_init = sparse(max(0,x_init)); -err.init = norm(x0-x_init); +eps.init = norm(x0-x_init); x_init_m = grid_x.marginalize(x_init); @@ -23,7 +23,7 @@ disp('Inversion complete.'); disp(' '); -err.lsq = norm(x0-x_lsq); +eps.lsq = norm(x0-x_lsq); %% Tikhonov (0th) implementation @@ -35,7 +35,7 @@ disp('Inversion complete.'); disp(' '); -err.tk0 = norm(x0-x_tk0); +eps.tk0 = norm(x0-x_tk0); %% Tikhonov (1st) implementation @@ -47,7 +47,7 @@ disp('Inversion complete.'); disp(' '); -err.tk1 = norm(x0-x_tk1); +eps.tk1 = norm(x0-x_tk1); %% Tikhonov (2nd) implementation @@ -59,7 +59,7 @@ disp('Inversion complete.'); disp(' '); -err.tk2 = norm(x0-x_tk2); +eps.tk2 = norm(x0-x_tk2); %% MART, Maximum entropy regularized solution @@ -72,7 +72,7 @@ disp('Inversion complete.'); disp(' '); -err.mart = norm(x0-x_mart); +eps.mart = norm(x0-x_mart); %% Twomey @@ -85,7 +85,7 @@ disp('Completed Twomey.'); disp(' '); -err.two = norm(x0-x_two); +eps.two = norm(x0-x_two); %% Twomey-Markowski-Buckley @@ -96,7 +96,7 @@ x_init,35,[1e2,1e-5],x0,'Buckley'); t.two_mh = toc; -err.two_mh = norm(x0-x_two_mh); +eps.two_mh = norm(x0-x_two_mh); diff --git a/run_inversions_b.m b/run_inversions_b.m index f96b206..bf2ab81 100644 --- a/run_inversions_b.m +++ b/run_inversions_b.m @@ -12,7 +12,7 @@ x_init(isnan(x_init)) = 0; x_init(isinf(x_init)) = 0; x_init = sparse(max(0,x_init)); -err.init = norm(x0-x_init); +eps.init = norm(x0-x_init); x_init_m = grid_x.marginalize(x_init); @@ -30,7 +30,7 @@ disp(' '); % chi.lsq = norm(x0-x_lsq); -err.lsq = norm(x0-x_lsq_nn); +eps.lsq = norm(x0-x_lsq_nn); %% Tikhonov (0th) implementation @@ -52,7 +52,7 @@ disp(' '); % diff.tk0 = norm(x_tk0-x_tk0_nn); -err.tk0 = norm(x0-x_tk0_nn); +eps.tk0 = norm(x0-x_tk0_nn); % chi.tk0_hr = norm(x0-x_tk0_hr); @@ -75,7 +75,7 @@ disp(' '); % diff.tk1 = norm(x_tk1-x_tk1_nn); -err.tk1 = norm(x0-x_tk1_nn); +eps.tk1 = norm(x0-x_tk1_nn); % chi.tk1_hr = norm(x0-x_tk1_hr); @@ -98,7 +98,7 @@ disp(' '); % diff.tk2 = norm(x_tk2-x_tk2_nn); -err.tk2 = norm(x0-x_tk2_nn); +eps.tk2 = norm(x0-x_tk2_nn); % chi.tk2_hr = norm(x0-x_tk2_hr); @@ -109,7 +109,7 @@ disp('Inversion complete.'); disp(' '); -err.mart = norm(x0-x_MART); +eps.mart = norm(x0-x_MART); %% Twomey @@ -118,7 +118,7 @@ disp('Completed Twomey.'); disp(' '); -err.two = norm(x0-x_Two); +eps.two = norm(x0-x_Two); %% Twomey-Markowski-Buckley @@ -127,7 +127,7 @@ x_init,35,'Buckley',Sf_two_mh); disp('Completed Twomey-Markowski.'); -err.two_mh = norm(x0-x_two_mh); +eps.two_mh = norm(x0-x_two_mh); diff --git a/run_inversions_c.m b/run_inversions_c.m index 67e20d3..7c1841a 100644 --- a/run_inversions_c.m +++ b/run_inversions_c.m @@ -10,7 +10,7 @@ x_init(isnan(x_init)) = 0; x_init(isinf(x_init)) = 0; x_init = sparse(max(0,x_init)); -err.init = norm(x0-x_init); +eps.init = norm(x0-x_init); %% Tikhonov (0th) implementation @@ -21,7 +21,7 @@ disp('Inversion complete.'); disp(' '); -err.tk0 = norm(x0-x_tk0); +eps.tk0 = norm(x0-x_tk0); %% Tikhonov (1st) implementation @@ -32,7 +32,7 @@ disp('Inversion complete.'); disp(' '); -err.tk1 = norm(x0-x_tk1); +eps.tk1 = norm(x0-x_tk1); %% Tikhonov (2nd) implementation @@ -43,7 +43,7 @@ disp('Inversion complete.'); disp(' '); -err.tk2 = norm(x0-x_tk2); +eps.tk2 = norm(x0-x_tk2); %% Twomey @@ -54,5 +54,5 @@ disp('Completed Twomey.'); disp(' '); -err.two = norm(x0-x_two); +eps.two = norm(x0-x_two); diff --git a/run_inversions_d.m b/run_inversions_d.m index 94d2097..1545ed8 100644 --- a/run_inversions_d.m +++ b/run_inversions_d.m @@ -12,7 +12,7 @@ x_init(isnan(x_init)) = 0; x_init(isinf(x_init)) = 0; x_init = sparse(max(0,x_init)); -err.init = norm(x0-x_init); +eps.init = norm(x0-x_init); x_init_m = grid_x.marginalize(x_init); @@ -26,7 +26,7 @@ disp('Inversion complete.'); disp(' '); -err.LSQ = norm(x0-x_LSQ); +eps.LSQ = norm(x0-x_LSQ); %% Tikhonov (0th) implementation @@ -37,7 +37,7 @@ disp('Inversion complete.'); disp(' '); -err.tk0(ii) = norm(x0-x_tk0); +eps.tk0(ii) = norm(x0-x_tk0); %% Tikhonov (1st) implementation @@ -48,7 +48,7 @@ disp('Inversion complete.'); disp(' '); -err.tk1(ii) = norm(x0-x_tk1); +eps.tk1(ii) = norm(x0-x_tk1); %% Tikhonov (2nd) implementation @@ -60,7 +60,7 @@ disp('Inversion complete.'); disp(' '); -err.tk2(ii) = norm(x0-x_tk2); +eps.tk2(ii) = norm(x0-x_tk2); %% MART, Maximum entropy regularized solution @@ -72,7 +72,7 @@ disp('Inversion complete.'); disp(' '); -err.mart = norm(x0-x_mart); +eps.mart = norm(x0-x_mart); %% Twomey @@ -83,7 +83,7 @@ disp('Completed Twomey.'); disp(' '); -err.two = norm(x0-x_two); +eps.two = norm(x0-x_two); %% Twomey-Markowski-Buckley @@ -94,7 +94,7 @@ t.two_mh(ii) = toc; disp('Completed Twomey-Markowski.'); -err.two_mh = norm(x0-x_two_mh); +eps.two_mh = norm(x0-x_two_mh); end diff --git a/run_inversions_e.m b/run_inversions_e.m index 8de8f78..c2297f5 100644 --- a/run_inversions_e.m +++ b/run_inversions_e.m @@ -12,7 +12,7 @@ x_init(isnan(x_init)) = 0; x_init(isinf(x_init)) = 0; x_init = sparse(max(0,x_init)); -err.init = 100*norm(x0-x_init)/norm(x0); +eps.init = 100*norm(x0-x_init)/norm(x0); x_init_m = grid_x.marginalize(x_init); @@ -23,7 +23,7 @@ disp('Inversion complete.'); disp(' '); -err.lsq = 100*norm(x0-x_lsq)/norm(x0); +eps.lsq = 100*norm(x0-x_lsq)/norm(x0); %% Tikhonov (0th) implementation @@ -36,7 +36,7 @@ disp('Inversion complete.'); disp(' '); -err.tk0 = 100*norm(x0-x_tk0)/norm(x0); +eps.tk0 = 100*norm(x0-x_tk0)/norm(x0); %% Tikhonov (1st) implementation @@ -49,7 +49,7 @@ disp('Inversion complete.'); disp(' '); -err.tk1 = 100*norm(x0-x_tk1)/norm(x0); +eps.tk1 = 100*norm(x0-x_tk1)/norm(x0); %% Tikhonov (2nd) implementation @@ -62,7 +62,7 @@ disp('Inversion complete.'); disp(' '); -err.tk2 = 100*norm(x0-x_tk2)/norm(x0); +eps.tk2 = 100*norm(x0-x_tk2)/norm(x0); %% Tikhonov (0th) implementation L-curve @@ -74,7 +74,7 @@ t.tk0_lc = toc; disp('Inversion complete.'); disp(' '); -err.tk0_lcurve = 100*norm(x0-x_tk0_lc)/norm(x0); +eps.tk0_lcurve = 100*norm(x0-x_tk0_lc)/norm(x0); %% Tikhonov (1st) implementation L-curve @@ -87,7 +87,7 @@ t.tk1_lc = toc; disp('Inversion complete.'); disp(' '); -err.tk1_lcurve = 100*norm(x0-x_tk1_lc)/norm(x0); +eps.tk1_lcurve = 100*norm(x0-x_tk1_lc)/norm(x0); %% Tikhonov (2nd), L-curve @@ -99,7 +99,7 @@ t.tk2_lc = toc; disp('Inversion complete.'); disp(' '); -err.tk2_lc = 100*norm(x0-x_tk2_lc)/norm(x0); +eps.tk2_lc = 100*norm(x0-x_tk2_lc)/norm(x0); %% MART, Maximum entropy regularized solution @@ -112,7 +112,7 @@ disp('Inversion complete.'); disp(' '); -err.mart = 100*norm(x0-x_mart)/norm(x0); +eps.mart = 100*norm(x0-x_mart)/norm(x0); %% MART with smoothing function @@ -122,7 +122,7 @@ A,b,Lb,grid_x,x_init,35,[1e2,1e-5],x0,'Buckley'); t.mart_mh = toc; -err.mart_mh = 100*norm(x0-x_mart_mh)/norm(x0); +eps.mart_mh = 100*norm(x0-x_mart_mh)/norm(x0); disp('Completed MART with smoothing function.'); disp(' '); @@ -138,7 +138,7 @@ disp('Completed Twomey.'); disp(' '); -err.two = 100*norm(x0-x_two)/norm(x0); +eps.two = 100*norm(x0-x_two)/norm(x0); %% Twomey-Markowski-Buckley @@ -148,7 +148,7 @@ A,b,Lb,grid_x,x_init,35,[1e2,1e-5],x0,'Buckley'); t.two_mh = toc; -err.two_mh = 100*norm(x0-x_two_mh)/norm(x0); +eps.two_mh = 100*norm(x0-x_two_mh)/norm(x0); diff --git a/run_inversions_g.m b/run_inversions_g.m index 5e40920..f1bf42a 100644 --- a/run_inversions_g.m +++ b/run_inversions_g.m @@ -12,7 +12,7 @@ disp('Inversion complete.'); disp(' '); -err.tk1 = norm(x0-x_tk1); +eps.tk1 = norm(x0-x_tk1); %% @@ -26,7 +26,7 @@ x_init(isnan(x_init)) = 0; x_init(isinf(x_init)) = 0; x_init = sparse(max(0,x_init)); -err.init = norm(x0-x_init); +eps.init = norm(x0-x_init); x_init_m = grid_x.marginalize(x_init); disp('Performing expectation-maximization (attempt no. 1)...'); @@ -34,7 +34,7 @@ disp('Inversion complete.'); disp(' '); -err.em = norm(x0-x_em); +eps.em = norm(x0-x_em); @@ -53,14 +53,14 @@ x_init2(isnan(x_init)) = 0; x_init2(isinf(x_init2)) = 0; x_init2 = sparse(max(0,x_init2)); -err.init2 = norm(x02-x_init2); +eps.init2 = norm(x02-x_init2); disp('Performing expectation-maximization (attempt no. 2)...'); x_em2 = invert.em(Lb*A2,Lb*b,x_init2,250); disp('Inversion complete.'); disp(' '); -err.em2 = norm(x02-x_em2); +eps.em2 = norm(x02-x_em2); @@ -74,7 +74,7 @@ disp('Inversion complete.'); disp(' '); -err.em3 = norm(x02-x_em3); +eps.em3 = norm(x02-x_em3); @@ -84,7 +84,7 @@ disp('Inversion complete.'); disp(' '); -err.em4 = norm(x0-x_em4); +eps.em4 = norm(x0-x_em4); diff --git a/run_inversions_h.m b/run_inversions_h.m deleted file mode 100644 index 4961139..0000000 --- a/run_inversions_h.m +++ /dev/null @@ -1,39 +0,0 @@ - -% RUN_INVERSIONS_H Re-run select exponential rotated and Tikhonov regularization -% Author: Timothy Sipkens, 2019-05-28 -%=========================================================================% - -%-- Tikhonov (1st order) -----% -lambda_tk1 = 1.4; - -disp('Performing Tikhonov (1st) regularization...'); -tic; -x_tk1_a = invert.tikhonov(... - Lb*A,Lb*b,lambda_tk1,1,grid_x,[],'non-neg'); -t.tk1 = toc; -disp('Inversion complete.'); -disp(' '); - -err.tk1_nn = norm(x0-x_tk1_a); - - -%-- Exponential rotated ------% -lambda_exp_rot = 1.4; -s1 = 0.4; -s2 = s1; -R12 = 0.96; % correlation -Gd2 = [s1^2,R12*(s1*s2);R12*(s1*s2),s2^2]; - -[V,~] = eig(Gd2); -the = atan(V(1,2)/V(2,2))/pi*180; - -disp('Performing exponential distance regularization...'); -x_exp_rot_a = invert.exp_dist(... - Lb*A,Lb*b,lambda_exp_rot,Gd2,... - grid_x.elements(:,2),grid_x.elements(:,1)); -disp('Inversion complete.'); -disp(' '); - -err.exp_a = norm(x0-x_exp_rot_a); - - diff --git a/run_inversions_i.m b/run_inversions_i.m index 26c7b29..09f84e6 100644 --- a/run_inversions_i.m +++ b/run_inversions_i.m @@ -5,6 +5,12 @@ if iscell(phantom.Sigma) Gd = phantom.Sigma{1}; +elseif isempty(phantom.Sigma) + p = phantom.p; ll = 2; + Gd = inv([(1/log10(p(ll).smd))^2,... + -p(ll).Dm/log10(p(ll).smd)^2;... + -p(ll).Dm/log10(p(ll).smd)^2,... + 1/log10(p(ll).sg)^2+p(ll).Dm^2/log10(p(ll).smd)^2]); else Gd = phantom.Sigma; end @@ -19,10 +25,10 @@ [x_ed_lam,lambda_ed_lam,out_ed_lam] = ... optimize.exp_dist_op(... Lb*A,Lb*b,[0.1,10],Gd,... - grid_x.elements(:,2),grid_x.elements(:,1),x0); + grid_x,[],x0); disp('Process complete.'); disp(' '); -err.ed = norm(x_ed_lam-x0); +eps.ed = norm(x_ed_lam-x0); diff --git a/run_inversions_j.m b/run_inversions_j.m index d660e01..712cb48 100644 --- a/run_inversions_j.m +++ b/run_inversions_j.m @@ -28,6 +28,12 @@ %-{ if iscell(phantom.Sigma) Gd = phantom.Sigma{1}; +elseif isempty(phantom.Sigma) + p = phantom.p; ll = 2; + Gd = inv([(1/log10(p(ll).smd))^2,... + -p(ll).Dm/log10(p(ll).smd)^2;... + -p(ll).Dm/log10(p(ll).smd)^2,... + 1/log10(p(ll).sg)^2+p(ll).Dm^2/log10(p(ll).smd)^2]); else Gd = phantom.Sigma; end diff --git a/run_inversions_k.m b/run_inversions_k.m new file mode 100644 index 0000000..2539955 --- /dev/null +++ b/run_inversions_k.m @@ -0,0 +1,60 @@ + +% RUN_INVERSIONS_K Time Bayesian and Twomey-Markowski optimization routines. +% Author: Timothy Sipkens, 2020-02-22 +%=========================================================================% + +clear t; + +[x_two_mh,Sf_two_mh,out_two_mh] = ... + optimize.twomark_op(A,b,Lb,grid_x,... + x_init,35,[1e2,1e-5],x0,'Buckley'); +eps.two_mh = norm(x0-x_two_mh); + +%% +nt = 25; +disp('Performing time repeats...'); +tools.textbar(0); +for tt=1:nt + tic; + x_two_mh = invert.twomark(A,b,Lb,grid_x,... + x_init,35,'Buckley',1/Sf_two_mh); + t(tt).two_mh = toc; + disp('Completed Twomey-Markowski.'); + + eps.two_mh = norm(x0-x_two_mh); + + + tic; + invert.em(Lb*A,Lb*b,x_init,3,x0); + t(tt).em = toc; + + + tic; + invert.tikhonov(... + Lb*A,Lb*b,lambda_tk1,1,grid_x,[]); + t(tt).tk1 = toc; + + + if iscell(phantom.Sigma) + Gd = phantom.Sigma{1}; + else + Gd = phantom.Sigma; + end + + tic; + invert.exp_dist(... + Lb*A,Lb*b,lambda_ed_lam,Gd,... + grid_x); + t(tt).ed = toc; + + disp('Performing time repeats...'); + tools.textbar(0); + tools.textbar(tt/nt); +end + +tmean.two_mh = mean([t.two_mh]); +tmean.em = mean([t.em])./tmean.two_mh; +tmean.tk1 = mean([t.tk1])./tmean.two_mh; +tmean.ed = mean([t.ed])./tmean.two_mh; +tmean.two_mh = 1; + From acb753ff5cfd97a14adff3705db7bce369e8c7ce Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Sat, 28 Mar 2020 00:00:40 -0700 Subject: [PATCH 45/56] Update to display from Phantom.fit* --- @Phantom/fit.m | 6 ++++-- @Phantom/fit2.m | 6 ++++-- @Phantom/fit2_rho.m | 6 ++++-- main_bayes.m | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/@Phantom/fit.m b/@Phantom/fit.m index 20dc1bf..477d6d9 100644 --- a/@Phantom/fit.m +++ b/@Phantom/fit.m @@ -18,7 +18,7 @@ function [phantom,N,y_out,J] = fit(x,vec_grid,logr0) -disp('Fitting phantom object...'); +disp('[ Fitting phantom object... -------------]'); %-- Parse inputs ---------------------------------------------% if isa(vec_grid,'Grid') @@ -58,7 +58,9 @@ N = y1(1); % scaling parameter denoting total number of particles y_out = y1; -disp('Complete.'); +disp(' '); +disp('[ Complete ------------------------------]'); +disp(' '); disp(' '); end diff --git a/@Phantom/fit2.m b/@Phantom/fit2.m index 1214ee0..f48d43f 100644 --- a/@Phantom/fit2.m +++ b/@Phantom/fit2.m @@ -22,7 +22,7 @@ function [phantom,N,y_out,J] = fit2(x,vec_grid,n_modes,logr0) -disp('Fitting phantom object...'); +disp('[ Fitting phantom object... -------------]'); %-- Parse inputs ---------------------------------------------% if isa(vec_grid,'Grid') @@ -84,7 +84,9 @@ phantom = Phantom('standard',grid,mu,Sigma,N); phantom.type = 'standard-fit'; -disp('Complete.'); +disp(' '); +disp('[ Complete ------------------------------]'); +disp(' '); disp(' '); end diff --git a/@Phantom/fit2_rho.m b/@Phantom/fit2_rho.m index 138a467..15662e3 100644 --- a/@Phantom/fit2_rho.m +++ b/@Phantom/fit2_rho.m @@ -22,7 +22,7 @@ function [phantom,N,y_out,J] = fit2_rho(x,vec_grid,n_modes,logr0) -disp('Fitting phantom object...'); +disp('[ Fitting phantom object... -------------]'); %-- Parse inputs ---------------------------------------------% if isa(vec_grid,'Grid') @@ -84,7 +84,9 @@ phantom = Phantom('standard',grid,mu,Sigma,N); phantom.type = 'standard-fit'; -disp('Complete.'); +disp(' '); +disp('[ Complete ------------------------------]'); +disp(' '); disp(' '); end diff --git a/main_bayes.m b/main_bayes.m index 2f91e9e..0975878 100644 --- a/main_bayes.m +++ b/main_bayes.m @@ -25,7 +25,7 @@ % grid to generate x. span_t = [10^-1.5,10^1.5;20,10^3]; % range of mobility and mass -phantom = Phantom('4',span_t); +phantom = Phantom('1',span_t); x_t = phantom.x; grid_t = phantom.grid; nmax = max(x_t); From 37eaf1c077211bafc5d416454ec6930a4f69f509 Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Sat, 28 Mar 2020 09:32:55 -0700 Subject: [PATCH 46/56] Working updates --- main_bayes.m | 2 +- run_inversions_g.m | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/main_bayes.m b/main_bayes.m index 0975878..df7f6c8 100644 --- a/main_bayes.m +++ b/main_bayes.m @@ -177,7 +177,7 @@ %-- Plot regularization parameter selection schemes ----------------------% %-{ figure(13); -loglog([out.lambda],[out.chi]); % plot absolute Euclidean error +loglog([out.lambda],[out.eps]); % plot absolute Euclidean error hold on; loglog([out.lambda],-([out.B])); % plot Bayes factor loglog([out.lambda],-([out.F]),'--'); % plot fit diff --git a/run_inversions_g.m b/run_inversions_g.m index f1bf42a..3c5b3ab 100644 --- a/run_inversions_g.m +++ b/run_inversions_g.m @@ -89,15 +89,26 @@ %-- Plot EM results ------------------------% +%{ figure(31); grid_x.plot2d(x_em); +colormap(cm); colorbar; +title('EM, attempt 1 (x_{init} = interp(b_{i}/a_{i}x)'); + figure(32); grid2.plot2d(x_em2); +colormap(cm); colorbar; +title('EM, attempt 2 (x_{init} = interp(b_{i}/a_{i}x)'); + figure(33); grid2.plot2d(x_em3); +colormap(cm); colorbar; +title('EM, attempt 3 (x_{init} = 1'); +%} + From 664bb57e33bc122de924fa888833ada76f340551 Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Sat, 28 Mar 2020 09:34:24 -0700 Subject: [PATCH 47/56] Update run_inversions_g.m --- run_inversions_g.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run_inversions_g.m b/run_inversions_g.m index 3c5b3ab..a00979d 100644 --- a/run_inversions_g.m +++ b/run_inversions_g.m @@ -106,7 +106,7 @@ grid2.plot2d(x_em3); colormap(cm); colorbar; -title('EM, attempt 3 (x_{init} = 1'); +title('EM, attempt 3 (x_{init} = 1)'); %} From 427237911819887a268c03f1ac55354d000c063f Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Sat, 28 Mar 2020 11:16:11 -0700 Subject: [PATCH 48/56] Working updates --- @Phantom/Phantom.m | 3 ++- main_bayes.m | 9 ++------- run_inversions_i.m | 6 +----- run_inversions_j.m | 6 +----- run_inversions_k.m | 5 ----- 5 files changed, 6 insertions(+), 23 deletions(-) diff --git a/@Phantom/Phantom.m b/@Phantom/Phantom.m index bf7f8f4..3b35f88 100644 --- a/@Phantom/Phantom.m +++ b/@Phantom/Phantom.m @@ -504,7 +504,8 @@ -p(ll).Dm/log10(p(ll).smd)^2;... -p(ll).Dm/log10(p(ll).smd)^2,... 1/log10(p(ll).sg)^2+p(ll).Dm^2/log10(p(ll).smd)^2]); - else + + else % for non-bivariate lognormal distributions, approximate Sigma{ll} = inv([(1/p(ll).smd)^2,... -p(ll).Dm/p(ll).smd^2;... -p(ll).Dm/p(ll).smd^2,... diff --git a/main_bayes.m b/main_bayes.m index df7f6c8..0523c04 100644 --- a/main_bayes.m +++ b/main_bayes.m @@ -25,7 +25,7 @@ % grid to generate x. span_t = [10^-1.5,10^1.5;20,10^3]; % range of mobility and mass -phantom = Phantom('1',span_t); +phantom = Phantom('3',span_t); x_t = phantom.x; grid_t = phantom.grid; nmax = max(x_t); @@ -118,7 +118,7 @@ run_inversions_g; run_inversions_i; run_inversions_j; -run_inversions_k; % time methods +% run_inversions_k; % time methods @@ -158,11 +158,6 @@ %-- Exponential distance --% %{ -if iscell(phantom.Sigma) - Gd = phantom.Sigma{1}; -else - Gd = phantom.Sigma; -end Lpr = invert.exp_dist_lpr(Gd,grid_x.elements(:,2),... grid_x.elements(:,1)); [~,spo] = tools.get_posterior(... diff --git a/run_inversions_i.m b/run_inversions_i.m index 09f84e6..42c5fbc 100644 --- a/run_inversions_i.m +++ b/run_inversions_i.m @@ -6,11 +6,7 @@ if iscell(phantom.Sigma) Gd = phantom.Sigma{1}; elseif isempty(phantom.Sigma) - p = phantom.p; ll = 2; - Gd = inv([(1/log10(p(ll).smd))^2,... - -p(ll).Dm/log10(p(ll).smd)^2;... - -p(ll).Dm/log10(p(ll).smd)^2,... - 1/log10(p(ll).sg)^2+p(ll).Dm^2/log10(p(ll).smd)^2]); + [~,Gd] = phantom.p2cov(phantom.p(2),phantom.modes(2)); else Gd = phantom.Sigma; end diff --git a/run_inversions_j.m b/run_inversions_j.m index 712cb48..b1844b2 100644 --- a/run_inversions_j.m +++ b/run_inversions_j.m @@ -29,11 +29,7 @@ if iscell(phantom.Sigma) Gd = phantom.Sigma{1}; elseif isempty(phantom.Sigma) - p = phantom.p; ll = 2; - Gd = inv([(1/log10(p(ll).smd))^2,... - -p(ll).Dm/log10(p(ll).smd)^2;... - -p(ll).Dm/log10(p(ll).smd)^2,... - 1/log10(p(ll).sg)^2+p(ll).Dm^2/log10(p(ll).smd)^2]); + [~,Gd] = phantom.p2cov(phantom.p(2),phantom.modes(2)); else Gd = phantom.Sigma; end diff --git a/run_inversions_k.m b/run_inversions_k.m index 2539955..e2a39e2 100644 --- a/run_inversions_k.m +++ b/run_inversions_k.m @@ -35,11 +35,6 @@ t(tt).tk1 = toc; - if iscell(phantom.Sigma) - Gd = phantom.Sigma{1}; - else - Gd = phantom.Sigma; - end tic; invert.exp_dist(... From e8d944a065c6c60363e7c92a7fa21213295d16c7 Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Sat, 28 Mar 2020 22:36:24 -0700 Subject: [PATCH 49/56] Update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1a23e56..aa83655 100644 --- a/README.md +++ b/README.md @@ -388,7 +388,7 @@ To incorporate `Lb`, use `Lb*A` and `Lb*b` when calling the inversion functions Details on the available approaches to inversion are provided in the associated paper, [Sipkens et al. (2020a)][1_JAS1]. -Development is underway on the use of an exponential distance covariance function to correlate pixel values and reduce reconstruction errors [Sipkens et al. (Under preparation)][4]. +Development is underway on the use of an exponential distance covariance function to correlate pixel values and reduce reconstruction errors [Sipkens et al. (2020c)][4]. ### 4.4 +optimize @@ -397,6 +397,7 @@ iterations for the Twomey and MART schemes or the optimal prior parameter set fo Of particular note are a subset of the methods that implement evaluation of the Bayes factor for a range of methods, namely the `optimize.bayesf*.m` methods. The functions have inputs that mirror the functions in the `invert` package, this means that data uncertainties can be included in the procedure by giving `Lb*A` as an input to the program in the place of `A`. The methods general take `lambda` as a separate parameter, to promote the stability of the algorithm. +More details on this method are found in [Sipkens et al. (2020c)][4]. Appendix C of that work includes a discussion of the special considerations required to compute the determinants of the large covariance matrices in this problem. ### 4.5 +tools @@ -453,7 +454,7 @@ Information on the provided colormaps can be found in an associated README in th [Sipkens, T. A., Olfert, J. S., & Rogak, S. N. (2020b). New approaches to calculate the transfer function of particle mass analyzers. *Aerosol Sci. Technol.* 54, 111-127.][2_AST] -[Sipkens, T. A., Olfert, J. S., & Rogak, S. N. (Under preparation). Inversion methods to determine two-dimensional aerosol mass-mobility distributions: Existing and novel Bayesian methods.][4] +[Sipkens, T. A., Olfert, J. S., & Rogak, S. N. (2020c). Inversion methods to determine two-dimensional aerosol mass-mobility distributions II: Existing and novel Bayesian methods.][4] [Stolzenburg, M. R. (2018). A review of transfer theory and characterization of measured performance for differential mobility analyzers. *Aerosol Sci. Technol.* 52, 1194-1218.][Stolz18] From 7f0244beb0b2a982cd8638b1ef7c55ef497f9cd0 Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Mon, 30 Mar 2020 12:38:45 -0700 Subject: [PATCH 50/56] Adjusted mu and Sigma handling in Phantom The mu and Sigma variables are no longer cells for distributions with more than one mode. Instead, properties are appended in multidimensional arrays, e.g. Sigma is now a 3D array with the covariance in the first two dimensions, which are stacked for different modes in the third dimension. * Note, this makes this implementation much closer to the built-in Gaussian mixture model class in MATLAB. --- @Grid/Grid.m | 2 +- @Phantom/Phantom.m | 78 ++++++++++++++++----------------------------- @Phantom/fit2.m | 6 ++-- @Phantom/fit2_rho.m | 6 ++-- 4 files changed, 35 insertions(+), 57 deletions(-) diff --git a/@Grid/Grid.m b/@Grid/Grid.m index 881e2f0..fadc2e5 100644 --- a/@Grid/Grid.m +++ b/@Grid/Grid.m @@ -684,7 +684,7 @@ % Can form N x 2 vector, where N is the number of points % to be found. % Outputs: - % idx Global index on the grid, incorporating missing pixels + % k Global index on the grid, incorporating missing pixels % idx_2d Pair of indices of pixel location %-----------------------------------------------------------------% function [k,idx_2d] = closest_idx(obj,r0) diff --git a/@Phantom/Phantom.m b/@Phantom/Phantom.m index 3b35f88..a8b89db 100644 --- a/@Phantom/Phantom.m +++ b/@Phantom/Phantom.m @@ -208,13 +208,8 @@ else; vec = grid_vec; % if a set of element centers was provided directly end - if ~iscell(obj.mu); mu0 = {obj.mu}; - else; mu0 = obj.mu; - end - - if ~iscell(obj.Sigma); Sigma0 = {obj.Sigma}; - else; Sigma0 = obj.Sigma; - end + mu0 = obj.mu; + Sigma0 = obj.Sigma; %-------------------------------------------------------------% @@ -225,7 +220,9 @@ x = zeros(size(m_vec)); for ll=1:obj.n_modes % loop through distribution modes x = x + w(ll).*... % add new mode, reweighting accordingly - mvnpdf(log10([m_vec,d_vec]),mu0{ll},Sigma0{ll}); + mvnpdf(log10([m_vec,d_vec]),... + obj.mu(ll,:),... + obj.Sigma(:,:,ll)); end end %=================================================================% @@ -305,14 +302,10 @@ if and(~isempty(obj1.mu),~isempty(obj2.mu)) % bivariate lognormal phantoms - if ~iscell(obj1.mu); obj1.mu = {obj1.mu}; end - if ~iscell(obj1.Sigma); obj1.Sigma = {obj1.Sigma}; end - if ~iscell(obj2.mu); obj2.mu = {obj2.mu}; end - if ~iscell(obj2.Sigma); obj2.Sigma = {obj2.Sigma}; end objn = Phantom('standard',span,... - [obj1.mu,obj2.mu],... - [obj1.Sigma,obj2.Sigma],... + [obj1.mu;obj2.mu],... + cat(3,obj1.Sigma,obj2.Sigma),... w); @@ -334,14 +327,9 @@ A = [1,-3;0,1]; % corresponds to mass-mobility relation - if obj.n_modes==1 % for unimodal phantom - mu_rhod = (A*obj.mu'+[log10(6/pi)+9;0])'; - Sigma_rhod = A*obj.Sigma*A'; - else% for a multimodal phantom - for ii=1:obj.n_modes - mu_rhod{ii} = (A*obj.mu{ii}'+[log10(6/pi)+9;0])'; - Sigma_rhod{ii} = A*obj.Sigma{ii}*A'; - end + for ll=1:obj.n_modes + mu_rhod(ll,:) = (A*obj.mu(ll,:)'+[log10(6/pi)+9;0])'; + Sigma_rhod(:,:,ll) = A*obj.Sigma(:,:,ll)*A'; end phantom = Phantom('standard',grid_rho,mu_rhod,Sigma_rhod); @@ -444,35 +432,33 @@ % Author: Timothy Sipkens, 2019-10-29 function [p,Dm,l1,l2] = cov2p(mu,Sigma,modes) - if ~iscell(mu); mu = {mu}; end - if ~iscell(Sigma); Sigma = {Sigma}; end if ~exist('modes','var'); modes = [];end if isempty(modes); modes = repmat({'logn'},[1,length(mu)]); end p = []; - for ll=length(mu):-1:1 % loop through modes - p(ll).dg = 10.^mu{ll}(2); - p(ll).mg = mu{ll}(1); + for ll=length(modes):-1:1 % loop through modes + p(ll).dg = 10.^mu(ll,2); + p(ll).mg = mu(ll,1); if strcmp(modes{ll},'logn') % if lognormal distribution, convert to geometric mean p(ll).mg = 10.^p(ll).mg; end - p(ll).sg = 10^sqrt(Sigma{ll}(2,2)); - p(ll).sm = 10^sqrt(Sigma{ll}(1,1)); + p(ll).sg = 10^sqrt(Sigma(2,2,ll)); + p(ll).sm = 10^sqrt(Sigma(1,1,ll)); - R12 = Sigma{1}(1,2)/... - sqrt(Sigma{1}(1,1)*Sigma{1}(2,2)); - p(ll).smd = 10^sqrt(Sigma{1}(1,1)*(1-R12^2)); + R12 = Sigma(1,2,ll)/... + sqrt(Sigma(1,1,ll)*Sigma(2,2,ll)); + p(ll).smd = 10^sqrt(Sigma(1,1,ll)*(1-R12^2)); % conditional distribution width - p(ll).Dm = Sigma{ll}(1,2)/Sigma{ll}(2,2); + p(ll).Dm = Sigma(1,2,ll)/Sigma(2,2,ll); % corresponds to slope of "locus of vertical" % (Friendly, Monette, and Fox, 2013) % can be calculated as Dm = corr*sy/sx - % t0 = eigs(rot90(Sigma{ll},2),1); - % p(ll).ma = (t0-Sigma{ll}(2,2))./Sigma{ll}(1,2); + % t0 = eigs(rot90(Sigma(:,:,ll),2),1); + % p(ll).ma = (t0-Sigma(2,2,ll))./Sigma(1,2,ll); % calculate the major axis slope p(ll).rhog = p(ll).mg/(pi*p(ll).dg^3/6)*1e9; @@ -493,30 +479,25 @@ function [mu,Sigma] = p2cov(p,modes) for ll=length(p):-1:1 - mu{ll} = log10([p(ll).mg,p(ll).dg]); + mu(ll,:) = log10([p(ll).mg,p(ll).dg]); % use geometric mean if strcmp(modes{ll},'logn') % R12 = (1+1/(p(ll).Dm^2)*... % (log10(p(ll).smd)/log10(p(ll).sg))... % ^2)^(-1/2); - Sigma{ll} = inv([(1/log10(p(ll).smd))^2,... + Sigma(:,:,ll) = inv([(1/log10(p(ll).smd))^2,... -p(ll).Dm/log10(p(ll).smd)^2;... -p(ll).Dm/log10(p(ll).smd)^2,... 1/log10(p(ll).sg)^2+p(ll).Dm^2/log10(p(ll).smd)^2]); else % for non-bivariate lognormal distributions, approximate - Sigma{ll} = inv([(1/p(ll).smd)^2,... + Sigma(:,:,ll) = inv([(1/p(ll).smd)^2,... -p(ll).Dm/p(ll).smd^2;... -p(ll).Dm/p(ll).smd^2,... 1/log10(p(ll).sg)^2+p(ll).Dm^2/p(ll).smd^2]); end end - - if length(mu)==1 - mu = mu{1}; - Sigma = Sigma{1}; - end end %=================================================================% @@ -526,15 +507,12 @@ % Function to convert covariance matrix to correlation matrix. % Author: Timothy Sipkens, 2019-10-29 function R = sigma2r(Sigma) - if ~iscell(Sigma); Sigma = {Sigma}; end - - R = {}; - for ll=length(Sigma):-1:1 - R12 = Sigma{ll}(1,2)/... - sqrt(Sigma{ll}(1,1)*Sigma{ll}(2,2)); + for ll=length(Sigma(1,1,:)):-1:1 + R12 = Sigma(1,2,ll)/... + sqrt(Sigma(1,1,ll)*Sigma(2,2,ll)); % off-diagonal correlation - R{ll} = diag([1,1])+rot90(diag([R12,R12])); + R(:,:,ll) = diag([1,1])+rot90(diag([R12,R12])); % form correlation matrix end end diff --git a/@Phantom/fit2.m b/@Phantom/fit2.m index f48d43f..a9d6243 100644 --- a/@Phantom/fit2.m +++ b/@Phantom/fit2.m @@ -76,9 +76,9 @@ y_out = y1; for ii=0:(n_modes-1) - mu{ii+1} = [y1(6*ii+2),y1(6*ii+3)]; - sigma{ii+1} = [y1(6*ii+4),y1(6*ii+5)]; - Sigma{ii+1} = corr2cov(sigma{ii+1},[1,sin(y1(6*ii+6));sin(y1(6*ii+6)),1]); + mu(ii+1,:) = [y1(6*ii+2),y1(6*ii+3)]; + sigma(ii+1,:) = [y1(6*ii+4),y1(6*ii+5)]; + Sigma(:,:,ii+1) = corr2cov(sigma(ii+1,:),[1,sin(y1(6*ii+6));sin(y1(6*ii+6)),1]); end phantom = Phantom('standard',grid,mu,Sigma,N); diff --git a/@Phantom/fit2_rho.m b/@Phantom/fit2_rho.m index 15662e3..db54c3f 100644 --- a/@Phantom/fit2_rho.m +++ b/@Phantom/fit2_rho.m @@ -76,9 +76,9 @@ y_out = y1; for ii=0:(n_modes-1) - mu{ii+1} = [y1(6*ii+2),y1(6*ii+3)]; - sigma{ii+1} = [y1(6*ii+4),y1(6*ii+5)]; - Sigma{ii+1} = corr2cov(sigma{ii+1},[1,sin(y1(6*ii+6));sin(y1(6*ii+6)),1]); + mu(ii+1,:) = [y1(6*ii+2),y1(6*ii+3)]; + sigma(ii+1,:) = [y1(6*ii+4),y1(6*ii+5)]; + Sigma(:,:,ii+1) = corr2cov(sigma(ii+1,:),[1,sin(y1(6*ii+6));sin(y1(6*ii+6)),1]); end phantom = Phantom('standard',grid,mu,Sigma,N); From 369b522d853f020128697a7b4d6a41e079ff8136 Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Tue, 31 Mar 2020 11:22:08 -0700 Subject: [PATCH 51/56] Added Phantom.fit_gmm + Minor updates to Phantom class following previous commit. + Working updates. --- +tools/mass2rho.m | 2 +- +tools/overlay_phantom.m | 7 +++-- +tools/overlay_phantom_marg.m | 28 -------------------- +tools/sample_dist_a.m | 26 +++++++++++++++++++ +tools/sample_dist_c.m | 26 +++++++++++++++++++ @Grid/Grid.m | 5 ++-- @Phantom/Phantom.m | 34 ++++++++++++++---------- @Phantom/fit.m | 1 + @Phantom/fit2.m | 1 + @Phantom/fit2_rho.m | 1 + @Phantom/fit_gmm.m | 49 +++++++++++++++++++++++++++++++++++ main_bayes.m | 10 +++---- 12 files changed, 136 insertions(+), 54 deletions(-) create mode 100644 +tools/sample_dist_a.m create mode 100644 +tools/sample_dist_c.m create mode 100644 @Phantom/fit_gmm.m diff --git a/+tools/mass2rho.m b/+tools/mass2rho.m index 0504a7a..50f4638 100644 --- a/+tools/mass2rho.m +++ b/+tools/mass2rho.m @@ -1,5 +1,5 @@ -% MASS2RHO Converts a mass-mobility distribution to a effective density-mobility distribution. +% MASS2RHO Converts a mass-mobility distribution to an effective density-mobility distribution. % Author: Timothy Sipkens, 2019-05-17 %=========================================================================% diff --git a/+tools/overlay_phantom.m b/+tools/overlay_phantom.m index fb90505..bc2034a 100644 --- a/+tools/overlay_phantom.m +++ b/+tools/overlay_phantom.m @@ -29,12 +29,11 @@ mu = pha.mu; % local copies of parameters Sigma = pha.Sigma; p = pha.p; -if ~iscell(mu); mu = {mu}; Sigma = {Sigma}; end % handle unimodal case -for ii=1:pha.n_modes +for ll=1:pha.n_modes for jj=1:length(iso_levels) - tools.overlay_ellipse(mu{ii},Sigma{ii},iso_levels(jj),varargin{:}); + tools.overlay_ellipse(mu(ll,:),Sigma(:,:,ll),iso_levels(jj),varargin{:}); end - tools.overlay_line(grid,fliplr(mu{ii}),p(ii).Dm,varargin{:}); + tools.overlay_line(grid,fliplr(mu(ll,:)),p(ll).Dm,varargin{:}); end diff --git a/+tools/overlay_phantom_marg.m b/+tools/overlay_phantom_marg.m index 567e02f..035dd35 100644 --- a/+tools/overlay_phantom_marg.m +++ b/+tools/overlay_phantom_marg.m @@ -52,37 +52,9 @@ hold off; - subplot(4,4,[5,15]); % refocus center panel if nargout==0; clear pha N; end % prevent unnecessary output -%-------------------------% -%{ -if isa(x_pha,'Phantom') - pha = x_pha; - grid = pha.grid; - N = []; -else - [pha,N] = Phantom.fit(x_pha,grid); -end - -if isempty(varargin); varargin = {'Color',[1,1,0,1]}; end - % specify line properties (default: yellow, no transparency) - -%-- Proceed with plotting ----------------% -tools.overlay_ellipse(pha.mu,pha.Sigma,1,varargin{:}); -tools.overlay_ellipse(pha.mu,pha.Sigma,2,varargin{:}); -tools.overlay_ellipse(pha.mu,pha.Sigma,3,varargin{:}); - -tools.overlay_line(grid,fliplr(pha.mu),... - pha.p.Dm,varargin{:}); - -ylim(ylim_st); % restore original plot bounds -xlim(xlim_st); - -if nargout==0; clear pha N; end % prevent unnecessary output -%} - end diff --git a/+tools/sample_dist_a.m b/+tools/sample_dist_a.m new file mode 100644 index 0000000..23d2d1b --- /dev/null +++ b/+tools/sample_dist_a.m @@ -0,0 +1,26 @@ + +% SAMPLE_DIST_A Sample from a discrete 2D distribution using 'slicesample'. +% Author: Timothy Sipkens, 2020-03-30 +%=========================================================================% + +function [rnd] = sample_dist_a(x,grid,nsamples) + +if~exist('nsamples','var'); nsamples = []; end +if isempty(nsamples); nsamples = 1e4; end + +[~,ind_max] = max(x); +initial = log10(grid.elements(ind_max,:)); + +rnd = slicesample(initial,nsamples,'pdf',@(r) pdf_fun(x,grid,r)); + +end + + +function p = pdf_fun(x,grid,r) + +k = grid.closest_idx(10.^r); + +p = zeros(size(r,1),1); +p(~isnan(k)) = x(k(~isnan(k))); + +end \ No newline at end of file diff --git a/+tools/sample_dist_c.m b/+tools/sample_dist_c.m new file mode 100644 index 0000000..755ac70 --- /dev/null +++ b/+tools/sample_dist_c.m @@ -0,0 +1,26 @@ + +% SAMPLE_DIST_c Sample from a discrete 2D distribution using 'slicesample'. +% Author: Timothy Sipkens, 2020-03-30 +%=========================================================================% + +function [rnd] = sample_dist_c(x,vec,nsamples) + +if~exist('nsamples','var'); nsamples = []; end +if isempty(nsamples); nsamples = 1e4; end + +[~,ind_max] = max(x); +initial = log10(vec(ind_max,:)); +F = scatteredInterpolant(log10(vec(:,1)),log10(vec(:,2)),x,... + 'linear','none'); + +rnd = slicesample(initial,nsamples,'pdf',@(r) pdf_fun(F,r)); + +end + + +function p = pdf_fun(F,r) + +p = F(r(:,1),r(:,2)); +p(isnan(p)) = 0; + +end \ No newline at end of file diff --git a/@Grid/Grid.m b/@Grid/Grid.m index fadc2e5..5c74b05 100644 --- a/@Grid/Grid.m +++ b/@Grid/Grid.m @@ -2,11 +2,12 @@ % GRID Responsible for discretizing space as a grid and related operations. % Author: Timothy Sipkens, 2019-02-03 % -% The grid class is currently used when a simple discretization of +% Notes: +% - The grid class is currently used when a simple discretization of % two-dimensional space is required. It then takes either the span % of spcae to be covered or pre-defined edge vectors to form a grid. % -% See constructor method for list of other variables required +% - See constructor method for list of other variables required % for creation. %=========================================================================% diff --git a/@Phantom/Phantom.m b/@Phantom/Phantom.m index a8b89db..d458341 100644 --- a/@Phantom/Phantom.m +++ b/@Phantom/Phantom.m @@ -62,8 +62,7 @@ %-- OPTION 1: Standard bivariate lognormal distribution --% case {'standard'} - n_modes = length(mu_p); - if ~iscell(mu_p); n_modes = 1; end + n_modes = size(mu_p,1); obj.mu = mu_p; obj.Sigma = Sigma_modes; @@ -199,7 +198,7 @@ if isempty(w); w = ones(obj.n_modes,1)./obj.n_modes; end % weight modes evenly - if ~exist('grid','var'); grid_vec = []; end + if ~exist('grid_vec','var'); grid_vec = []; end if isempty(grid_vec); grid_vec = obj.grid; end % use phantom grid if isempty(grid_vec); error('For an empty Phantom, grid_vec is required.'); end % if an empty phantom (e.g. Phantom.eval_p(p);) @@ -342,32 +341,39 @@ methods (Static) %== PRESET_PHANTOMS (External definition) ========================% - % Returns a set of parameters for preset/sample phantoms. + % Returns a set of parameters for preset/sample phantoms. [p,modes,type] = presets(obj,type); %=================================================================% %== FIT (External definition) ====================================% - % Fits a phantom to a given set of data, x, defined on a given grid, - % or vector of elements. Outputs a fit phantom object. + % Fits a phantom to a given set of data, x, defined on a given grid, + % or vector of elements. Outputs a fit phantom object. [phantom,N,y_out,J] = fit(x,vec_grid,logr0); %=================================================================% %== FIT2 (External definition) ===================================% - % Fits a multimodal phantom object to a given set of data, x, - % defined on a given grid or vector of elements. - % Outputs a fit phantom object. + % Fits a multimodal phantom object to a given set of data, x, + % defined on a given grid or vector of elements. + % Outputs a fit phantom object. [phantom,N,y_out,J] = fit2(x,vec_grid,n_modes,logr0); %=================================================================% %== FIT2_RHO (External definition) ===============================% - % Fits a multimodal phantom object to a given set of data, x, - % defined on a given grid or vector of elements. - % Outputs a fit phantom object. - % Tuned specifically for effective density-mobility distributions - % (e.g. for negative or nearly zero correlation) + % Fits a multimodal phantom object to a given set of data, x, + % defined on a given grid or vector of elements. + % Outputs a fit phantom object. + % Tuned specifically for effective density-mobility distributions + % (e.g. for negative or nearly zero correlation) [phantom,N,y_out,J] = fit2_rho(x,vec_grid,n_modes,logr0); %=================================================================% + %== FIT_GMM (External definition) ================================% + % Fits a phantom to a given set of data, x, defined on a given grid. + % Uses sampling and k-means to fit a Guassian mixture model. + % Outputs a fit phantom object. + [phantom,N,s] = fit_gmm(x,grid,k); + %=================================================================% + %== FILL_P =======================================================% diff --git a/@Phantom/fit.m b/@Phantom/fit.m index 477d6d9..ed889db 100644 --- a/@Phantom/fit.m +++ b/@Phantom/fit.m @@ -18,6 +18,7 @@ function [phantom,N,y_out,J] = fit(x,vec_grid,logr0) +disp(' '); disp('[ Fitting phantom object... -------------]'); %-- Parse inputs ---------------------------------------------% diff --git a/@Phantom/fit2.m b/@Phantom/fit2.m index a9d6243..3acaa65 100644 --- a/@Phantom/fit2.m +++ b/@Phantom/fit2.m @@ -22,6 +22,7 @@ function [phantom,N,y_out,J] = fit2(x,vec_grid,n_modes,logr0) +disp(' '); disp('[ Fitting phantom object... -------------]'); %-- Parse inputs ---------------------------------------------% diff --git a/@Phantom/fit2_rho.m b/@Phantom/fit2_rho.m index db54c3f..ee4b2a4 100644 --- a/@Phantom/fit2_rho.m +++ b/@Phantom/fit2_rho.m @@ -22,6 +22,7 @@ function [phantom,N,y_out,J] = fit2_rho(x,vec_grid,n_modes,logr0) +disp(' '); disp('[ Fitting phantom object... -------------]'); %-- Parse inputs ---------------------------------------------% diff --git a/@Phantom/fit_gmm.m b/@Phantom/fit_gmm.m new file mode 100644 index 0000000..7227f80 --- /dev/null +++ b/@Phantom/fit_gmm.m @@ -0,0 +1,49 @@ + +% FIT_GMM Fits a phantom to a given set of data, x, defined on a given grid. +% Uses sampling and k-means to fit a Guassian mixture model. +% Outputs a fit phantom object. +% +% Inputs: +% x Input data (2D distribution data) +% grid A `Grid` object on which input data is defined +% k Number of modes +% +% Outputs: +% phantom Output phantom object representing a fit bivariate lognormal +% N Scaling parameter that acts to scale the phantom to the data +% y_out Direct output from the fitting procedure +% J Jacobian of fitting procedure +%=============================================================% + +function [phantom,N,s] = fit_gmm(x,grid_vec,k) + +disp(' '); +disp('[ Fitting phantom object... -------------]'); +disp(' '); + +disp('Sampling from distribution...'); +if isa(grid_vec,'Grid') % get samples from distribution + s = tools.sample_dist_a(x,grid_vec); + [~,tot] = grid_vec.marginalize(x); +else + s = tools.sample_dist_c(x,grid_vec); + tot = sum(x); +end +disp('Sampling complete.'); +disp(' '); + +disp('Estimating a Gaussian mixture model...'); +GMModel = fitgmdist(s,k); +N = GMModel.ComponentProportion.*tot; +disp('Complete'); +disp(' '); + +phantom = Phantom('standard',grid_vec,GMModel.mu,... + GMModel.Sigma,GMModel.ComponentProportion); + + +disp('[ Complete ------------------------------]'); +disp(' '); + +end + diff --git a/main_bayes.m b/main_bayes.m index 0523c04..bfe9460 100644 --- a/main_bayes.m +++ b/main_bayes.m @@ -25,7 +25,7 @@ % grid to generate x. span_t = [10^-1.5,10^1.5;20,10^3]; % range of mobility and mass -phantom = Phantom('3',span_t); +phantom = Phantom('1',span_t); x_t = phantom.x; grid_t = phantom.grid; nmax = max(x_t); @@ -105,10 +105,10 @@ [pha_b,Nb] = Phantom.fit2(b,grid_b,2,[0,1.7,0.1,2.3]); %-- pha_b.Sigma properties --------% -s1b = sqrt(pha_b.Sigma{1}(1,1)); -s2b = sqrt(pha_b.Sigma{1}(2,2)); -R12b = pha_b.Sigma{1}(1,2)/(s1b*s2b); -Dmb = pha_b.Sigma{1}(1,2)/pha_b.Sigma{1}(2,2); % also s1*R12/s2 +s1b = sqrt(pha_b.Sigma(1,1,1)); +s2b = sqrt(pha_b.Sigma(2,2,1)); +R12b = pha_b.Sigma(1,2,1)/(s1b*s2b); +Dmb = pha_b.Sigma(1,2,1)/pha_b.Sigma(2,2,1); % also s1*R12/s2 %----------------------------------% From f9739027d9ec587712fe8617f748dacb5b769d69 Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Wed, 1 Apr 2020 09:56:37 -0700 Subject: [PATCH 52/56] Added tools.mrbc2frac Function converts mrBC-mp distributions to fraction mrBC-mp distributions. + Added tools.plot2d_patch + Merged Grid.plot2d_sweep* methods --- +io/import_c.m | 2 +- +tools/mrbc2frac.m | 48 ++++++++++++++++++++++++++++++++++++++ +tools/plot2d_patch.m | 29 +++++++++++++++++++++++ +tools/plot2d_scatter.m | 16 ++++++------- @Grid/Grid.m | 51 +++++++++++++++-------------------------- 5 files changed, 105 insertions(+), 41 deletions(-) create mode 100644 +tools/mrbc2frac.m create mode 100644 +tools/plot2d_patch.m diff --git a/+io/import_c.m b/+io/import_c.m index 7455f56..c56649d 100644 --- a/+io/import_c.m +++ b/+io/import_c.m @@ -3,7 +3,7 @@ % Author: Timothy Sipkens, 2019-12-17 % % Note: -% The prop_pma input woudl override deriving some of the properties from +% The prop_pma input would override deriving some of the properties from % the imported data file. %=========================================================================% diff --git a/+tools/mrbc2frac.m b/+tools/mrbc2frac.m new file mode 100644 index 0000000..f3b7a08 --- /dev/null +++ b/+tools/mrbc2frac.m @@ -0,0 +1,48 @@ + +% MRBC2FRAC Converts a mrBC-mp distribution to a fraction mrBC-mp distribution. +% Author: Timothy Sipkens, 2019-05-17 +%=========================================================================% + +function [y,grid_f] = mrbc2frac(x,grid_x,span_f,n_f) + +%-- Parse inputs -----------------------------------% +if ~exist('n_f','var'); n_f = []; end +if isempty(n_f); n_f = 600; end + +if ~exist('span_f','var'); span_f = []; end +if isempty(span_f); span_f = [1e-3,1]; end +%---------------------------------------------------% + + +f_min = span_f(1); % get span for fraction rBC +f_max = span_f(2); +rho_n = logspace(log10(f_min),log10(f_max),n_f); % discretize rho space +grid_f = Grid([f_min,f_max;grid_x.span(2,:)],... + [n_f,length(grid_x.edges{2})],'logarithmic'); + % generate grid + +x_rs = grid_x.reshape(x); + +n_d = grid_x.ne(2); +y = zeros(grid_x.ne(2),length(rho_n)); +for ii=1:n_d % loop over mobility diameter + c4 = zeros(grid_f.ne(1),grid_x.ne(1)); + f_old = log10(grid_x.nodes{1}./grid_x.edges{2}(ii)); + % convert x nodes to effective density for iith mobility + + for jj=1:grid_x.ne(1) + c4(:,jj) = max(... + min(log10(grid_f.nodes{1}(2:end)),f_old(jj+1))-... % lower bound + max(log10(grid_f.nodes{1}(1:(end-1))),f_old(jj))... % upper bound + ,0)./... + (log10(grid_f.nodes{1}(2:end))-log10(grid_f.nodes{1}(1:(end-1)))); % normalize by rho_old bin size + end + + y(ii,:) = c4*x_rs(:,ii); +end + +y = y'; +y = y(:); + +end + diff --git a/+tools/plot2d_patch.m b/+tools/plot2d_patch.m new file mode 100644 index 0000000..75dca2a --- /dev/null +++ b/+tools/plot2d_patch.m @@ -0,0 +1,29 @@ + +% PLOT2D_PATCH Sweep through data using patch and creating a 3D plot. +% Author: Timothy Sipkens, 2020-04-02 +%=========================================================================% + +function [] = plot2d_patch(grid,x,cm) + +x_rs = grid.reshape(x); +min_x = max(log10(x))-3; + +n1 = ceil(grid.ne(1)./20); +n2 = floor(grid.ne(1)/n1); +n3 = floor(length(cm)/n2); +cm2 = cm(1:n3:end,:); + +patch(log10(grid.edges{1}),... + log10(grid.edges{2}(1)).*ones(1,grid.ne(1)),... + max(log10(x_rs(:,1)'),min_x),cm2(1,:)); +for ii=2:grid.ne(2) + hold on; + patch(log10(grid.edges{1}),... + log10(grid.edges{2}(ii)).*ones(1,grid.ne(1)),... + max(log10(x_rs(:,ii)'),min_x),cm2(ii,:)); + hold off; +end + +view([-20,45,70]); + +end diff --git a/+tools/plot2d_scatter.m b/+tools/plot2d_scatter.m index 0c6b435..e1473c0 100644 --- a/+tools/plot2d_scatter.m +++ b/+tools/plot2d_scatter.m @@ -3,7 +3,7 @@ % Author: Timothy Sipkens, 2019-11-28 %=========================================================================% -function [] = plot2d_scatter(m,d,b,cmap) +function [] = plot2d_scatter(vec1,vec2,b,cm) b = b./max(b); @@ -17,17 +17,17 @@ marker_size = 12.*bscl+0.1; corder = 1-bscl; % color is logscale -if ~exist('cmap','var'); cmap = []; end -if isempty(cmap); cmap = colormap('gray'); end +if ~exist('cmap','var'); cm = []; end +if isempty(cm); cm = colormap('gray'); end -N = size(cmap,1); -color = cmap(round(corder.*(N-1)+1),:); +N = size(cm,1); +color = cm(round(corder.*(N-1)+1),:); color(corder==1,:) = 1; clf; -for ii=1:length(m) +for ii=1:length(vec1) if ii==2; hold on; end - loglog(d(ii),m(ii),'.',... + loglog(vec2(ii),vec1(ii),'.',... 'Color',color(ii,:),... 'MarkerSize',marker_size(ii)); % marker_size is also logscale @@ -40,7 +40,7 @@ t0 = (bmax+ii-5-bmin)/(bmax-bmin); if t0>=0 h(6-ii) = plot(NaN,NaN,'.',... - 'Color',cmap(round((1-t0)*(N-1)+1),:),... + 'Color',cm(round((1-t0)*(N-1)+1),:),... 'MarkerSize',12*t0+0.1); text{6-ii} = num2str(10^(ii-5)); end diff --git a/@Grid/Grid.m b/@Grid/Grid.m index 5c74b05..91ec0f9 100644 --- a/@Grid/Grid.m +++ b/@Grid/Grid.m @@ -668,7 +668,7 @@ %-- Modify rmin and rmax for output -----% rmin = fliplr(rmin); rmax = fliplr(rmax); - + end % end loop over multiple rays end @@ -726,7 +726,7 @@ %-- Issue warning if grid edges are not be uniform -----------% % The imagesc function used here does not conserve proportions. - [dr,dr1,dr2] = obj.dr; % used to give warning below + [~,dr1,dr2] = obj.dr; % used to give warning below dr0 = dr1(:).*dr2(:); if ~all(abs(dr0(2:end)-dr0(1))<1e-10) warning(['The plot2d method does not display ',... @@ -824,16 +824,25 @@ %== PLOT2D_SWEEP =================================================% % Plot data in slices, sweeping through the provided colormap. % Author: Timothy Sipkens, 2019-11-28 - function [h,x] = plot2d_sweep(grid,x,cmap) + function [h,x] = plot2d_sweep(grid,x,cm,dim) - n1 = ceil(grid.ne(1)./20); - n2 = floor(grid.ne(1)/n1); - n3 = floor(length(cmap)/n2); - cmap2 = cmap(1:n3:end,:); - - set(gca,'ColorOrder',cmap2,'NextPlot','replacechildren'); + if ~exist('dim','var'); dim = []; end + if isempty(dim); dim = 1; end + % dimension to sweep through + % e.g. sweep through mass setpoints on standard grid, dim = 1 + + dim2 = setdiff([1,2],dim); % other dimension, dimension to plot + + n1 = ceil(grid.ne(dim)./20); + n2 = floor(grid.ne(dim)/n1); + n3 = floor(length(cm)/n2); + cm2 = cm(1:n3:end,:); % adjust colormap to appropriate size + + set(gca,'ColorOrder',cm2,'NextPlot','replacechildren'); x = reshape(x,grid.ne); - h = semilogx(grid.edges{2},x(1:n1:end,:),... + if dim==2; x = x'; end + + h = semilogx(grid.edges{dim2},x(1:n1:end,:),... 'o-','MarkerSize',2.5,'MarkerFaceColor',[1,1,1]); if nargout==0; clear h; end @@ -843,28 +852,6 @@ - %== PLOT2D_SWEEPT ================================================% - % Plot transposed data in slices, sweeping through the provided colormap. - % Author: Timothy Sipkens, 2019-11-28 - function [h,x] = plot2d_sweept(grid,x,cmap) - - n1 = ceil(grid.ne(2)./20); - n2 = floor(grid.ne(2)/n1); - n3 = floor(length(cmap)/n2); - cmap2 = cmap(1:n3:end,:); - - set(gca,'ColorOrder',cmap2,'NextPlot','replacechildren'); - x = reshape(x,grid.ne)'; - h = semilogx(grid.edges{1},x(1:n1:end,:),... - 'o-','MarkerSize',2.5,'MarkerFaceColor',[1,1,1]); - - if nargout==0; clear h; end - - end - %=================================================================% - - - %== PLOT_MARGINAL ================================================% % Plot marginal distributions % Author: Timothy Sipkens, 2019-07-17 From 89e29c99c870a2b5faa2c89e2dcb817fcf65c53d Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Wed, 1 Apr 2020 11:59:04 -0700 Subject: [PATCH 53/56] Colour bug fix in Grid.plot2d_sweep Also applies the same bug fix to tools.plot2d_patch. --- +tools/plot2d_patch.m | 43 ++++++++++++++++++++++++++----------------- @Grid/Grid.m | 13 ++++++------- 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/+tools/plot2d_patch.m b/+tools/plot2d_patch.m index 75dca2a..4695f82 100644 --- a/+tools/plot2d_patch.m +++ b/+tools/plot2d_patch.m @@ -3,27 +3,36 @@ % Author: Timothy Sipkens, 2020-04-02 %=========================================================================% -function [] = plot2d_patch(grid,x,cm) +function [] = plot2d_patch(grid,x,cm,dim) -x_rs = grid.reshape(x); +if ~exist('dim','var'); dim = []; end +if isempty(dim); dim = 1; end + % dimension to sweep through + % e.g. sweep through mass setpoints on standard grid, dim = 1 + +dim2 = setdiff([1,2],dim); % other dimension, dimension to plot + + +x_rs = grid.reshape(x); % reshape data +if dim==1; x_rs = x_rs'; end min_x = max(log10(x))-3; -n1 = ceil(grid.ne(1)./20); -n2 = floor(grid.ne(1)/n1); -n3 = floor(length(cm)/n2); -cm2 = cm(1:n3:end,:); - -patch(log10(grid.edges{1}),... - log10(grid.edges{2}(1)).*ones(1,grid.ne(1)),... - max(log10(x_rs(:,1)'),min_x),cm2(1,:)); -for ii=2:grid.ne(2) - hold on; - patch(log10(grid.edges{1}),... - log10(grid.edges{2}(ii)).*ones(1,grid.ne(1)),... - max(log10(x_rs(:,ii)'),min_x),cm2(ii,:)); - hold off; +n1 = floor(size(cm,1)/grid.ne(dim)); +n2 = length(cm)-grid.ne(dim)*n1+1; +cm2 = cm(n2:n1:end,:); % adjust colormap to appropriate size + +clf; +patch(log10(grid.edges{dim2}([1,1:end,end])),... % plot data slices as patches + log10(grid.edges{dim}(1)).*ones(1,grid.ne(dim2)+2),... + [min_x,max(log10(x_rs(:,1)'),min_x),min_x],cm2(1,:)); +hold on; +for ii=2:grid.ne(dim) + patch(log10(grid.edges{dim2}([1,1:end,end])),... + log10(grid.edges{dim}(ii)).*ones(1,grid.ne(dim2)+2),... + [min_x,max(log10(x_rs(:,ii)'),min_x),min_x],cm2(ii,:)); end +hold off; -view([-20,45,70]); +view([-20,45,70]); % adjust view so slices are visible end diff --git a/@Grid/Grid.m b/@Grid/Grid.m index 91ec0f9..e255617 100644 --- a/@Grid/Grid.m +++ b/@Grid/Grid.m @@ -833,16 +833,15 @@ dim2 = setdiff([1,2],dim); % other dimension, dimension to plot - n1 = ceil(grid.ne(dim)./20); - n2 = floor(grid.ne(dim)/n1); - n3 = floor(length(cm)/n2); - cm2 = cm(1:n3:end,:); % adjust colormap to appropriate size + n1 = floor(size(cm,1)/grid.ne(dim)); + n2 = length(cm)-grid.ne(dim)*n1+1; + cm2 = cm(n2:n1:end,:); % adjust colormap to appropriate size set(gca,'ColorOrder',cm2,'NextPlot','replacechildren'); - x = reshape(x,grid.ne); - if dim==2; x = x'; end + x_rs = reshape(x,grid.ne); + if dim==2; x_rs = x_rs'; end - h = semilogx(grid.edges{dim2},x(1:n1:end,:),... + h = semilogx(grid.edges{dim2},x_rs,... 'o-','MarkerSize',2.5,'MarkerFaceColor',[1,1,1]); if nargout==0; clear h; end From 743bbfccc17954d3c84978365d1727afe60aa3df Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Wed, 1 Apr 2020 12:25:28 -0700 Subject: [PATCH 54/56] Added alpha opt. to plot2d_patch The option to added alpha is included as commented code. --- +tools/plot2d_patch.m | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/+tools/plot2d_patch.m b/+tools/plot2d_patch.m index 4695f82..3533c1d 100644 --- a/+tools/plot2d_patch.m +++ b/+tools/plot2d_patch.m @@ -22,16 +22,19 @@ cm2 = cm(n2:n1:end,:); % adjust colormap to appropriate size clf; -patch(log10(grid.edges{dim2}([1,1:end,end])),... % plot data slices as patches +p = patch(log10(grid.edges{dim2}([1,1:end,end])),... % plot data slices as patches log10(grid.edges{dim}(1)).*ones(1,grid.ne(dim2)+2),... [min_x,max(log10(x_rs(:,1)'),min_x),min_x],cm2(1,:)); +p.FaceAlpha = 1;%1; hold on; for ii=2:grid.ne(dim) - patch(log10(grid.edges{dim2}([1,1:end,end])),... + p = patch(log10(grid.edges{dim2}([1,1:end,end])),... log10(grid.edges{dim}(ii)).*ones(1,grid.ne(dim2)+2),... [min_x,max(log10(x_rs(:,ii)'),min_x),min_x],cm2(ii,:)); + p.FaceAlpha = 1;%1-ii/(grid.ne(dim)); end hold off; +zlim([min_x,inf]); view([-20,45,70]); % adjust view so slices are visible From 19564cbed206295217dfb0474e60d2643f6a3ddc Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Mon, 6 Apr 2020 12:24:22 -0700 Subject: [PATCH 55/56] Developer (#18) (#19) * Update README.md * Update to overlay* functions This includes moving overlay_line from the Grid class to the tools package, to be consistent with other overlay functions. + Bug fixes in some of the overlay functions. * Updated Phantom.fit This function can now take the vec1 and vec2 vectors (lists of elements) directly. This allows compatibility with ungridded experimental data. * Updated n_B in get_setpoint + Other updateds to get_setpoint. + Added contourf functionality to plot2d functions. + Update to mass-mobility relation parameters in io.import_c. + Added fblue colormap. + Minor README updates. * Minor update to io.import_c.m * Update import_c.m * Bug fixes to Grid + Bug fix to mass2rho + Added forange colormap * Added lower cut for partial grids * Rename prop_pma.Dm and prop_pma.rho0 This is associated with syncing the program with updates to the tfer_pma package. Default mass-mobiltiy relations used in defining the PMA setpoint are now specifies in the prop_pma functions instead of in the mp2zp function. + Minor updates to import_c. + Minor bug fix in Grid class. * Removed Dm and rho0 defn. in import + Update to .gitignore * Update gen_grid_c2.m with prop_pma.Dm name change * Intro. varargin input to overlay*.m These functions now take a varaible number of input which is eventually passed to the plot function to allow for line specifications. + Added custom legend to plot2d_scatter.m. + Commenting updates to gen*.m functions. * Bug fix in overlay_isorho.m The function was missing a pi/6 factor such that lines were plotted incorrectly. * Updates to f* colormaps + Updated .gitignore * Update plot2d_marg_b.m Based on sugggestions by @ArashNaseri. * Updated marginalize functions Added marginalize_op function to generate marginalization operators. + Updated both marginalize functions to include partial elements (where only part of the element lies below the cut line). * Bug fix to plot2d_marg_b * Bug fix for Grid.ray_sum This bug fix only applies for partial grids. + Added 1D kernel generation for DMA. * Bug fixes to Grid.dr function * Update overlay_ellipse.m * Reverted plot2d mod based on element size * Update to io.import_a + Increased the number of contours. + Added fgreen colormap. * Added Phantom.fit2 method This method allows for fitting of multimodal distributions to data/reconstructions. + Separated both fit methods from the single Phantom class definition. The methods are now externally defined to make them more apparent in the file tree. This is also to emphasize these methods as alternate routes to make Phantom objects (i.e. they create an instance of the Phantom class without requiring the user to call the constructor). + Minor updates to the rest of the Phantom class. * Updated Phantom.mass2rho method for multimodal * Added tools for fitting distributions Specifically, modified Phantom.fit2 in a continued effort to improve multimodal fitting. + Added `logr0` arguement to Phantom.fit + Added sin(corr) durign fitting in an attempt to better span the space between -1 and 1 * Added tools.overlay_phantom_marg Overlays phantom ellipses for multimodal phantoms and adds marginal distributions to appropriate panels. This function expects a figure previously produced by `Grid.plot2d_marg`. + Updates to inputs of tools.overlay_phantom to allow for multimodal cases. + Updates to commenting in tools.overlay_phantom. * Bug fixes to Phantom.fill_p + REAMDE update README updates consistute a sweeping overhaul of the Phantom class description, which now includes more detail and code details. * Improved Phantom.fill_p method Now can take rhog or mg and smd or sm. + Further updates to the description of the Phantom class in the README. * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Limited support for uncertainty analysis of Phantom fitting + Added tools.redistribute * Update to colormaps Added CMasher colormaps. * Updates to commenting and headers * Moved fit2 to fit2_rho The Phantom.fit2_rho method is tuned for effective density-mobility distributions + New Phantom.fit method tuned for mass-mobility distributions + Updated Phantom.eval* methods to take more flexible or clear arguments + Working updates * Updated Phantom for mode weighting The Phantom class can now take an additional arguement, `w`, that will rewieght the modes in the Phantom. This allows for appropriate Phantom instances when using Phantom.fit2*. * Added Phantom.plus method Users can now use the `+` operator to add two phantoms together. * Updated chi > err in run_inversions* This better reflects the Euclidean error nature of this quantity. * Added Dm, l1, and l2 output from cov2p * Updated `err` or `chi` -> `eps` This variable name change is consistent with the this quantity uniformly representing the Euclidean error (Euclidean distance to exact solution). * Update to display from Phantom.fit* * Working updates * Update run_inversions_g.m * Working updates * Update README.md * Adjusted mu and Sigma handling in Phantom The mu and Sigma variables are no longer cells for distributions with more than one mode. Instead, properties are appended in multidimensional arrays, e.g. Sigma is now a 3D array with the covariance in the first two dimensions, which are stacked for different modes in the third dimension. * Note, this makes this implementation much closer to the built-in Gaussian mixture model class in MATLAB. * Added Phantom.fit_gmm + Minor updates to Phantom class following previous commit. + Working updates. * Added tools.mrbc2frac Function converts mrBC-mp distributions to fraction mrBC-mp distributions. + Added tools.plot2d_patch + Merged Grid.plot2d_sweep* methods * Colour bug fix in Grid.plot2d_sweep Also applies the same bug fix to tools.plot2d_patch. * Added alpha opt. to plot2d_patch The option to added alpha is included as commented code. From b065efb7b1a678dce35b224e8cefa53a9213fb93 Mon Sep 17 00:00:00 2001 From: "Timothy A. Sipkens" Date: Mon, 6 Apr 2020 14:41:59 -0700 Subject: [PATCH 56/56] Simplified main_bayes for archive + Added plot2d_slices --- +tools/plot2d_patch.m | 2 +- +tools/plot2d_slices.m | 39 ++++++++++++++++++++++++++++ main_bayes.m | 59 ++++++++++++++++++++++++++++-------------- run_inversions_h.m | 46 ++++++++++++++++++++++++++++++++ run_inversions_i.m | 7 ++--- run_inversions_j.m | 7 ++--- 6 files changed, 130 insertions(+), 30 deletions(-) create mode 100644 +tools/plot2d_slices.m create mode 100644 run_inversions_h.m diff --git a/+tools/plot2d_patch.m b/+tools/plot2d_patch.m index 3533c1d..36b3cff 100644 --- a/+tools/plot2d_patch.m +++ b/+tools/plot2d_patch.m @@ -25,7 +25,7 @@ p = patch(log10(grid.edges{dim2}([1,1:end,end])),... % plot data slices as patches log10(grid.edges{dim}(1)).*ones(1,grid.ne(dim2)+2),... [min_x,max(log10(x_rs(:,1)'),min_x),min_x],cm2(1,:)); -p.FaceAlpha = 1;%1; +p.FaceAlpha = 1; hold on; for ii=2:grid.ne(dim) p = patch(log10(grid.edges{dim2}([1,1:end,end])),... diff --git a/+tools/plot2d_slices.m b/+tools/plot2d_slices.m new file mode 100644 index 0000000..6a05db2 --- /dev/null +++ b/+tools/plot2d_slices.m @@ -0,0 +1,39 @@ + +% PLOT2D_SLICES Sweep through data using patch and creating a 3D plot. +% Author: Timothy Sipkens, 2020-04-06 +%=========================================================================% + +function [] = plot2d_slices(grid,x,cm,dim) + +if ~exist('dim','var'); dim = []; end +if isempty(dim); dim = 1; end + % dimension to sweep through + % e.g. sweep through mass setpoints on standard grid, dim = 1 + +dim2 = setdiff([1,2],dim); % other dimension, dimension to plot + + +x_rs = grid.reshape(x); % reshape data +if dim==1; x_rs = x_rs'; end +min_x = max(log10(x))-3; + +n1 = floor(size(cm,1)/grid.ne(dim)); +n2 = length(cm)-grid.ne(dim)*n1+1; +cm2 = cm(n2:n1:end,:); % adjust colormap to appropriate size + +clf; +plot3(log10(grid.edges{dim2}),... % plot data slices as patches + log10(grid.edges{dim}(1)).*ones(1,grid.ne(dim2)),... + max(log10(x_rs(:,1)'),min_x),'Color',cm2(1,:)); +hold on; +for ii=2:grid.ne(dim) + plot3(log10(grid.edges{dim2}),... + log10(grid.edges{dim}(ii)).*ones(1,grid.ne(dim2)),... + max(log10(x_rs(:,ii)'),min_x),'Color',cm2(ii,:)); +end +hold off; +zlim([min_x,inf]); + +view([-20,45,70]); % adjust view so slices are visible + +end diff --git a/main_bayes.m b/main_bayes.m index 2490463..60e8269 100644 --- a/main_bayes.m +++ b/main_bayes.m @@ -8,10 +8,8 @@ clc; close all; - %-- Load colour maps -----------------------------------------------------% addpath('cmap'); -cm_alt = load_cmap('BuPu',255); cm_b = load_cmap('inferno',255); cm_b = cm_b(40:end,:); cm_div = load_cmap('RdBu',200); @@ -25,7 +23,7 @@ % grid to generate x. span_t = [10^-1.5,10^1.5;20,10^3]; % range of mobility and mass -phantom = Phantom('3',span_t); +phantom = Phantom('1',span_t); x_t = phantom.x; grid_t = phantom.grid; nmax = max(x_t); @@ -44,6 +42,9 @@ phantom.plot; colormap(gcf,[cm;1,1,1]); caxis([0,cmax*(1+1/256)]); +subplot(4,4,[1,3]); +title('Phantom'); +subplot(4,4,[5,15]); hold on; % plots mg ridges of phantom plot(log10(grid_t.edges{2}),... @@ -76,6 +77,9 @@ colormap(gcf,[cm;1,1,1]); grid_x.plot2d_marg(x0,grid_t,x_t); caxis([0,cmax*(1+1/256)]); +subplot(4,4,[1,3]); +title('x_{projected}'); +subplot(4,4,[5,15]); @@ -93,13 +97,16 @@ figure(5); tools.plot2d_scatter(... grid_b.elements(:,1),grid_b.elements(:,2),b,cm_b); +title('Data: 2D scatter'); figure(6); -colormap(cm_b); -grid_b.plot2d_marg(b); +% tools.plot2d_patch(grid_b,b,cm_b); +tools.plot2d_slices(grid_b,b,cm_b); +title('Data: 2D slices'); figure(20); grid_b.plot2d_sweep(b,cm_b); +title('Data: Color sweep'); [pha_b,Nb] = Phantom.fit2(b,grid_b,2,[0,1.7,0.1,2.3]); @@ -115,19 +122,24 @@ %% %== STEP 3: Perform inversions ===========================================% -run_inversions_g; -run_inversions_i; -run_inversions_j; +run_inversions_h; % simple, faster, stand-alone Tikhonov + ED + +% run_inversions_g; +% run_inversions_i; +% run_inversions_j; + % run_inversions_k; % time methods %% %== STEP 4: Visualize the results ========================================% -ind = out_tk1.ind_min; -x_plot = out_tk1(ind).x; -out = out_tk1; -lambda = out_tk1(ind).lambda; +x_plot = x_ed; + +% ind = out_tk1.ind_min; +% x_plot = out_tk1(ind).x; +% out = out_tk1; +% lambda = out_tk1(ind).lambda; % [~,ind] = max([out_ed_lam.B]); % x_plot = out_ed_lam(ind).x; @@ -140,14 +152,25 @@ figure(10); colormap(gcf,[cm;1,1,1]); grid_x.plot2d_marg(x_plot,grid_t,x_t); -% colorbar; caxis([0,cmax*(1+1/256)]); +subplot(4,4,[1,3]); +title('x_{ed}'); +subplot(4,4,[5,15]); + +figure(11); +colormap(gcf,[cm;1,1,1]); +grid_x.plot2d_marg(x_tk1,grid_t,x_t); +caxis([0,cmax*(1+1/256)]); +subplot(4,4,[1,3]); +title('x_{tk1}'); +subplot(4,4,[5,15]); %-{ +%-- Requires running run_inversions_(g,i,j) --% %-- Plot posterior uncertainties ------------------------------------% -%-- Tikhonov --% +% ... for Tikhonov -----------------% % [~,spo] = tools.get_posterior(... % A,Lb,out_tk1(ind).lambda.*out_tk1(1).Lpr); % figure(12); @@ -155,8 +178,7 @@ % grid_x.plot2d(spo); % colorbar; - -%-- Exponential distance --% +% ...for exponential distance ------% %{ Lpr = invert.exp_dist_lpr(Gd,grid_x.elements(:,2),... grid_x.elements(:,1)); @@ -170,7 +192,8 @@ %-- Plot regularization parameter selection schemes ----------------------% -%-{ +%-- Requires running run_inversions_(g,i,j) --% +%{ figure(13); loglog([out.lambda],[out.eps]); % plot absolute Euclidean error hold on; @@ -181,7 +204,5 @@ %} - figure(10); - diff --git a/run_inversions_h.m b/run_inversions_h.m new file mode 100644 index 0000000..c7d7a4c --- /dev/null +++ b/run_inversions_h.m @@ -0,0 +1,46 @@ + +% RUN_INVERSIONS_H Run exponential distance and 1st-order Tikhonov regularization. +% Author: Timothy Sipkens, 2020-04-06 +%=========================================================================% + + + + +%-- Tikhonov (1st order) -------------------------------------------------% +disp('Performing Tikhonov (1st) regularization...'); +lambda_tk1 = 1.1053; % found using other run_inversion* scripts +x_tk1 = invert.tikhonov(... + Lb*A,Lb*b,lambda_tk1,1,n_x(1)); +disp('Inversion complete.'); +disp(' '); + +eps.tk1_0 = norm(x0-x_tk1); + + + + +%-- Exponential distance approach ----------------------------------------% +Gd = phantom.Sigma(:,:,1); +if isempty(Gd) % for Phantom 3 + [~,Gd] = phantom.p2cov(phantom.p(2),phantom.modes(2)); +end + +%-- Gd properties -----------------% +l1 = sqrt(Gd(1,1)); +l2 = sqrt(Gd(2,2)); +R12 = Gd(1,2)/(l1*l2); +Dm = Gd(1,2)/Gd(2,2); % s1*R12/s2 +%----------------------------------% + +disp('Performing exponential distance regularization...'); +lambda_ed = 1.0826; % found using other run_inversion* scripts +[x_ed] = ... + invert.exp_dist(... + Lb*A,Lb*b,lambda_ed,Gd,... + grid_x,[]); +disp('Complete.'); +disp(' '); + +eps.ed_0 = norm(x_ed-x0); + + diff --git a/run_inversions_i.m b/run_inversions_i.m index 42c5fbc..382e7f3 100644 --- a/run_inversions_i.m +++ b/run_inversions_i.m @@ -3,12 +3,9 @@ % Author: Timothy Sipkens, 2019-05-28 %=========================================================================% -if iscell(phantom.Sigma) - Gd = phantom.Sigma{1}; -elseif isempty(phantom.Sigma) +Gd = phantom.Sigma(:,:,1); +if isempty(Gd) % for Phantom 3 [~,Gd] = phantom.p2cov(phantom.p(2),phantom.modes(2)); -else - Gd = phantom.Sigma; end %-- Gd properties -----------------% diff --git a/run_inversions_j.m b/run_inversions_j.m index b1844b2..1e5a432 100644 --- a/run_inversions_j.m +++ b/run_inversions_j.m @@ -26,12 +26,9 @@ %-{ -if iscell(phantom.Sigma) - Gd = phantom.Sigma{1}; -elseif isempty(phantom.Sigma) +Gd = phantom.Sigma(:,:,1); +if isempty(Gd) % for Phantom 3 [~,Gd] = phantom.p2cov(phantom.p(2),phantom.modes(2)); -else - Gd = phantom.Sigma; end [x_ed_corr,out_ed_corr] = ... optimize.exp_dist_op1d(Lb*A,Lb*b,lambda_ed_lam,Gd,...