Skip to content

Commit 71d42a5

Browse files
committed
Update for v4.0.0-alpha-4.1.0
1 parent ba390b1 commit 71d42a5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+2206
-241
lines changed

.gitmodules

-9
Original file line numberDiff line numberDiff line change
@@ -1,9 +0,0 @@
1-
[submodule "4. PSMs/Specific Forward Models/MgCa/BAYMAG"]
2-
path = 4. PSMs/Specific Forward Models/MgCa/BAYMAG
3-
url = https://github.com/jesstierney/BAYMAG
4-
[submodule "4. PSMs/Specific Forward Models/UK 37/BAYSPLINE"]
5-
path = 4. PSMs/Specific Forward Models/UK 37/BAYSPLINE
6-
url = [email protected]:jesstierney/BAYSPLINE
7-
[submodule "PSMs/Submodules"]
8-
path = PSMs/Submodules
9-
url = https://github.com/jesstierney/BAYMAG

@PSM/PSM.m

+175
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
classdef (Abstract) PSM
2+
%% Implements a common interface for all PSMs
3+
%
4+
% PSM Methods:
5+
% estimate - Estimate proxy values from a state vector ensemble
6+
% rename - Renames a PSM
7+
8+
properties (SetAccess = private)
9+
name;
10+
rows;
11+
estimatesR;
12+
end
13+
14+
methods
15+
% Constructor
16+
function[obj] = PSM(name, estimatesR)
17+
%% PSM constructor. Records a name and whether the PSM can estimate R
18+
%
19+
% obj = PSM
20+
% Does not specify a name. Does not estimate R.
21+
%
22+
% obj = PSM(name)
23+
% Creates a PSM with a particular name. Does not estimate R.
24+
%
25+
% obj = PSM(name, estimatesR)
26+
% Creates a PSM with a name and specifies whether the PSM can
27+
% estimate R.
28+
%
29+
% ----- Inputs -----
30+
%
31+
% name: A name for the PSM. A string.
32+
%
33+
% estimatesR: A scalar logical indicating whether the PSM can
34+
% estimate R (true), or not (false -- defalt)
35+
%
36+
% ----- Outputs -----
37+
%
38+
% obj: The new PSM object
39+
40+
% Note if PSM can estimate R
41+
if ~exist('estimatesR', 'var') || isempty(estimatesR)
42+
estimatesR = false;
43+
end
44+
obj = obj.canEstimateR(estimatesR);
45+
46+
% Optionally record a name
47+
if ~exist('name','var') || isempty(name)
48+
name = "";
49+
end
50+
obj = obj.rename(name);
51+
end
52+
53+
% Rename
54+
function[obj] = rename(obj, name)
55+
%% Renames a PSM
56+
%
57+
% obj = obj.rename(newName)
58+
%
59+
% ----- Inputs -----
60+
%
61+
% newName: A new name for the PSM. A string
62+
%
63+
% ----- Outputs -----
64+
%
65+
% obj: The renamed PSM
66+
67+
obj.name = dash.assertStrFlag(name, 'name');
68+
end
69+
70+
% Return name for error messages
71+
function[name] = messageName(obj, n)
72+
%% Returns an identifying name for the PSM for use in messages
73+
%
74+
% name = obj.messageName
75+
% Returns a name for the PSM.
76+
%
77+
% name = obj.messageName(n)
78+
% Returns a name for the PSM and identifies its position in a list
79+
%
80+
% ----- Inputs -----
81+
%
82+
% n: A list index. A scalar, positive integer
83+
%
84+
% ----- Outputs -----
85+
%
86+
% name: A string identifying the PSM
87+
88+
% Get some settings
89+
hasNumber = exist('n', 'var');
90+
hasName = ~strcmp(obj.name, "");
91+
92+
% Create the message string
93+
name = "PSM";
94+
if hasNumber
95+
name = strcat(name, sprintf(' %.f', n));
96+
if hasName
97+
name = strcat(name, sprintf(' ("%s")', obj.name));
98+
end
99+
elseif hasName
100+
name = strcat(name, sprintf(' "%s"', obj.name));
101+
end
102+
end
103+
104+
% Set the rows
105+
function[obj] = useRows(obj, rows)
106+
%% Specifies the state vector rows used by a PSM
107+
%
108+
% obj = obj.useRows(rows)
109+
%
110+
% ----- Inputs -----
111+
%
112+
% rows: The rows used by the PSM. A vector of positive integers.
113+
%
114+
% ----- Outputs -----
115+
%
116+
% obj: The updated PSM object
117+
118+
% Error check
119+
assert(isvector(rows), 'rows must be a vector');
120+
assert(islogical(rows) || isnumeric(rows), 'rows must be numeric or logical');
121+
122+
% Error check numeric indices
123+
if isnumeric(rows)
124+
dash.assertPositiveIntegers(rows, 'rows');
125+
126+
% Convert logical to numeric
127+
else
128+
rows = find(rows);
129+
end
130+
131+
% Save
132+
obj.rows = rows(:);
133+
end
134+
135+
% Allow R to be estimated
136+
function[obj] = canEstimateR(obj, tf)
137+
%% Specifies whether a PSM can estimate R
138+
%
139+
% obj = obj.canEstimateR(tf)
140+
%
141+
% ----- Inputs -----
142+
%
143+
% tf: A scalar logical indicating if the PSM can estimate R
144+
% (true) or not (false)
145+
%
146+
% ----- Outputs -----
147+
%
148+
% obj: The updated PSM object
149+
150+
dash.assertScalarType(tf, 'tf', 'logical', 'logical');
151+
obj.estimatesR = tf;
152+
end
153+
end
154+
155+
methods (Static)
156+
% Estimate Y values for a set of PSMs
157+
[Ye, R] = estimate(X, F)
158+
159+
% Download the code for a PSM
160+
download(psmName, path);
161+
162+
% Get the repository and commit information for a PSM
163+
[repo, commit] = githubLocation(psmName);
164+
end
165+
166+
% Run individual PSM objects
167+
methods (Abstract)
168+
[Y, R] = runPSM(obj, X);
169+
end
170+
171+
% Run PSMs directly
172+
methods (Abstract, Static)
173+
[Y, R] = run(varargin);
174+
end
175+
end

@PSM/download.m

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
function[] = download(psmName, path)
2+
%% Loads the code for a PSM and adds it to the active path.
3+
%
4+
% PSM.download(psmName)
5+
% Downloads the code for a PSM to the current directory and adds it to the
6+
% active path.
7+
%
8+
% PSM.download(psmName, path)
9+
% Downloads the code for a PSM to the specified directory and adds it to the
10+
% active path. The specified directory must be empty
11+
%
12+
% ----- Inputs -----
13+
%
14+
% psmName: The name of a PSM to download. A string. Options are
15+
% 'bayspar': The BAYSPAR TEX86 PSM
16+
% 'bayspline': The BAYSPLINE UK37 PSM
17+
% 'baymag': The BAYMAG Mg/Ca PSM
18+
%
19+
% path: Indicates the folder where the PSM code should be downloaded. A
20+
% string.
21+
22+
% Default and error check the path
23+
if ~exist('path','var') || isempty(path)
24+
path = pwd;
25+
userPath = false;
26+
else
27+
userPath = true;
28+
path = dash.assertStrFlag(path);
29+
end
30+
31+
% Get the Github repository for the PSM
32+
[repo, commit] = PSM.githubLocation(psmName);
33+
34+
% Get the final download path. Ensure the folder is empty if it exists
35+
if ~userPath
36+
[~, defaultFolder] = fileparts(repo);
37+
path = fullfile(path, defaultFolder);
38+
end
39+
if isfolder(path)
40+
contents = dir(path);
41+
if ~strcmp([contents.name], "...")
42+
error('The folder "%s" is not empty. You can only download PSM code to a new or empty folder.', path);
43+
end
44+
end
45+
46+
% Clone the repository
47+
clone = sprintf("git clone %s %s", repo, path);
48+
status = system(clone);
49+
if status~=0
50+
gitFailureError(repo, commit);
51+
end
52+
53+
% Checkout the commit
54+
home = pwd;
55+
try
56+
cd(path);
57+
checkout = sprintf( "git checkout %s -q", commit );
58+
status = system(checkout);
59+
assert(status==0);
60+
61+
% Delete the clone and return to the working directory if checkout fails
62+
catch
63+
cd(home);
64+
rmdir(path, 's');
65+
gitFailureError(repo, commit);
66+
end
67+
68+
% Add PSM code to path and return to working directory
69+
cd(home);
70+
addpath(genpath(path));
71+
72+
end
73+
74+
% Long error message
75+
function[] = gitFailureError(repo, commit)
76+
github = strcat(repo, '/tree/', commit);
77+
error(['Could not download the repository. Please check that you have ',...
78+
'git installed and on your system path. If problems persist, you ',...
79+
'can download the PSM code manually at "%s".'], github);
80+
end

@PSM/estimate.m

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
function[Ye, R] = estimate(X, F)
2+
%% Estimates proxies values from a state vector ensemble
3+
%
4+
% Ye = PSM.estimate(X, psms)
5+
% Estimates proxies values for a state vector ensemble given a
6+
% set of PSMs.
7+
%
8+
% [Ye, R] = PSM.estimates(X, psms)
9+
% Also estimates proxy uncertainties (R).
10+
%
11+
% ----- Inputs -----
12+
%
13+
% X: A state vector ensemble. A numeric array with the
14+
% following dimensions (State vector x ensemble members x priors)
15+
%
16+
% psms: A set of PSMs. Either a scalar PSM object or a cell
17+
% vector whose elements are PSM objects.
18+
%
19+
% ----- Outputs -----
20+
%
21+
% Ye: Proxy estimates. A numeric array with the dimensions
22+
% (Proxy sites x ensemble members x priors)
23+
%
24+
% R: Proxy uncertainty estimates. A numeric array with
25+
% dimensions (proxy sites x ensemble members x priors)
26+
27+
% Parse and error check the ensemble. Get size
28+
if isa(X, 'ensemble')
29+
isens = true;
30+
assert(isscalar(X), 'ens must be a scalar ensemble object');
31+
[nState, nEns] = X.metadata.sizes;
32+
nPriors = 1;
33+
m = matfile(X.file);
34+
else
35+
assert(isnumeric(X), 'X must be numeric');
36+
assert(ndims(X)<=3, 'X cannot have more than 3 dimensions');
37+
isens = false;
38+
[nState, nEns, nPriors] = size(X);
39+
end
40+
41+
% Error check the ensemble, get sizes
42+
dash.assertRealDefined(X, 'X');
43+
44+
% Parse the PSM vector
45+
nSite = numel(F);
46+
[F, wasCell] = dash.parseInputCell(F, nSite, 'F');
47+
name = "F";
48+
49+
% Error check the individual PSMs
50+
for s = 1:nSite
51+
if wasCell
52+
name = sprintf('Element %.f of F', s);
53+
end
54+
dash.assertScalarType(F{s}, name, 'PSM', 'PSM');
55+
56+
% Check the rows of the PSM do not exceed the number of rows
57+
if max(F{s}.rows) > nState
58+
error('The ensemble has %.f rows, but %s uses rows that are larger (%.f)', ...
59+
nState, F{s}.messageName(s), max(F{s}.rows));
60+
end
61+
end
62+
63+
% Preallocate
64+
Ye = NaN(nSite, nEns, nPriors);
65+
if nargout>1
66+
R = NaN(nSite, nEns, nPriors);
67+
end
68+
69+
% Get the values needed to run each PSM
70+
for s = 1:nSite
71+
if ~isens
72+
Xpsm = X(F{s}.rows,:,:);
73+
74+
% If using an ensemble object, first attempt to read all rows at once
75+
else
76+
try
77+
rows = dash.equallySpacedIndices(F{s}.rows);
78+
Xpsm = m.X(rows,:);
79+
[~, keep] = ismember(F{s}.rows, rows);
80+
Xpsm = Xpsm(keep,:);
81+
82+
% If unsuccessful, load values iteratively
83+
catch
84+
nRows = numel(F{s}.rows);
85+
Xpsm = NaN(nRows, nEns);
86+
for r = 1:nRows
87+
Xpsm(r,:) = m.X(F{s}.rows(r),:);
88+
end
89+
end
90+
end
91+
92+
% Get the values for each prior and run the PSM
93+
for p = 1:nPriors
94+
Xrun = Xpsm(:,:,p);
95+
if nargout>1
96+
[Yrun, Rrun] = F{s}.runPSM(Xrun);
97+
else
98+
Yrun = F{s}.runPSM(Xrun);
99+
end
100+
101+
% Error check the R output
102+
if nargout>1
103+
name = sprintf('R values for %s for prior %.f', F{s}.messageName(s), p);
104+
dash.assertVectorTypeN(Rrun, 'numeric', nEns, name);
105+
if ~isrow(Rrun)
106+
error('%s must be a row vector', name);
107+
end
108+
end
109+
110+
% Error check the Y output
111+
name = sprintf('Y values for %s for prior %.f', F{s}.messageName(s), p);
112+
dash.assertVectorTypeN(Yrun, 'numeric', nEns, name);
113+
if ~isrow(Yrun)
114+
error('%s must be a row vector', name);
115+
end
116+
117+
% Save
118+
Ye(s,:,p) = Yrun;
119+
if nargout>1
120+
R(s,:,p) = Rrun;
121+
end
122+
end
123+
end
124+
end

0 commit comments

Comments
 (0)