diff --git a/API/projectClass/projectClass.m b/API/projectClass/projectClass.m index 12164d34b..0b6090819 100644 --- a/API/projectClass/projectClass.m +++ b/API/projectClass/projectClass.m @@ -917,6 +917,16 @@ function delete(obj) end + function problem = clone(obj) + % Makes a clone of the project by making a script and using it + % to recreate the project + % + % project.clone(); + script = obj.toScript(objName='problem'); + eval(script); + problem = eval('problem'); + end + function writeScript(obj, options) % Writes a MATLAB script that can be run to reproduce this % projectClass object. @@ -942,14 +952,157 @@ function writeScript(obj, options) throw(exceptions.invalidValue(sprintf('The filename chosen for the script has the "%s" extension, rather than a MATLAB ".m" extension', extension))); end + script = obj.toScript(objName=options.objName); fileID = fopen(options.script, 'w'); - fprintf(fileID, "%s\n\n", '% THIS FILE IS GENERATED FROM RAT VIA THE "WRITESCRIPT" ROUTINE. IT IS NOT PART OF THE RAT CODE.'); + fprintf(fileID, '%s\n', script); + fclose(fileID); + end + + end % end public methods + + % ------------------------------------------------------------------ + + methods (Access = protected) + % Display methods + function group = getPropertyGroup1(obj) + % Initial Parameters at the start of the class + masterPropList = struct('modelType',{obj.modelType},... + 'experimentName',{obj.experimentName},... + 'geometry', obj.geometry); + + if isscalar(obj) + group = matlab.mixin.util.PropertyGroup(masterPropList); + else + group = getPropertyGroup1@matlab.mixin.CustomDisplay(obj); + end + end + + function displayScalarObject(obj) + % Display the whole class. Call the display methods for + % the sub-classes where appropriate + + % There are two versions, depending on whether the model + % is standard layers or custom, the difference being + % the display of the layers table and domain contrasts. + + % Display initial properties + startProps = getPropertyGroup1(obj); + matlab.mixin.CustomDisplay.displayPropertyGroups(obj,startProps); + + % Display the parameters table + fprintf('\n Parameters: ---------------------------------------------------------------------------------------------- \n\n'); + obj.parameters.displayTable; + + % Display the Bulk In table + fprintf('\n Bulk In: -------------------------------------------------------------------------------------------------- \n\n'); + obj.bulkIn.displayTable; + + % Display the Bulk Out table + fprintf('\n Bulk Out: ------------------------------------------------------------------------------------------------- \n\n'); + obj.bulkOut.displayTable; + + % Display the Scalefactors table + fprintf('\n Scalefactors: ------------------------------------------------------------------------------------------------- \n\n'); + obj.scalefactors.displayTable; + + % Display the domain ratio if defined + if isprop(obj, 'domainRatio') && isa(obj.domainRatio, 'parametersClass') + fprintf('\n Domain Ratios: ----------------------------------------------------------------------------------------------- \n\n'); + obj.domainRatio.displayTable; + end + + % Display the backgrounds object + fprintf('\n Backgrounds: ----------------------------------------------------------------------------------------------- \n\n'); + obj.background.displayBackgroundsObject; + + % Display the resolutions object + fprintf('\n Resolutions: --------------------------------------------------------------------------------------------- \n\n'); + obj.resolution.displayResolutionsObject; + + % Display the layers table if not a custom model + if isa(obj.layers, 'layersClass') + fprintf('\n Layers: -------------------------------------------------------------------------------------------------- \n\n'); + obj.layers.displayTable; + end + + % Display custom files object + fprintf('\n Custom Files: ------------------------------------------------------------------------------------------------------ \n\n'); + obj.customFile.displayTable; + + % Display the data object + fprintf('\n Data: ------------------------------------------------------------------------------------------------------ \n\n'); + obj.data.displayTable; + + % Display the domainContrasts object if defined + if isprop(obj, 'domainContrasts') && isa(obj.domainContrasts, 'domainContrastsClass') + fprintf('\n Domains Contrasts: ----------------------------------------------------------------------------------------------- \n\n'); + obj.domainContrasts.displayContrastsObject; + end + + % Display the contrasts object + fprintf('\n Contrasts: ----------------------------------------------------------------------------------------------- \n\n'); + obj.contrasts.displayContrastsObject; + + end + + end + + methods (Access = protected, Hidden) + + function modifyLayersTable(obj,~,~) + % Add or remove a column from the layers table whenever the + % "absorption" property is modified. + if isa(obj.layers, 'layersClass') + if obj.absorption + newCol = repmat("", height(obj.layers.varTable), 1); + obj.layers.varTable = addvars(obj.layers.varTable, newCol, 'After', 'SLD', 'NewVariableNames', 'SLD Imaginary'); + obj.layers.varTable = renamevars(obj.layers.varTable, 'SLD', 'SLD Real'); + else + obj.layers.varTable = removevars(obj.layers.varTable, 'SLD Imaginary'); + obj.layers.varTable = renamevars(obj.layers.varTable, 'SLD Real', 'SLD'); + end + end + end + + end + + methods (Hidden) + + function domainsObj = domainsClass(obj) + % Converter routine from projectClass to domainsClass. + % This routine takes the currently defined project and + % converts it to a domains calculation, preserving all + % currently defined properties. + % + % domainsProject = project.domainsClass(); + domainsObj = domainsClass(obj.experimentName, calculationTypes.Domains, obj.modelType, obj.geometry, obj.absorption); + domainsObj = copyProperties(obj, domainsObj); + + % Need to treat contrasts separately due to changes in the + % class for domains calculations + domainsObj.contrasts = copyProperties(obj.contrasts, contrastsClass(domains=true, oilWater=obj.contrasts.oilWaterCalc)); + for i=1:domainsObj.contrasts.numberOfContrasts + domainsObj.contrasts.contrasts{i}.domainRatio = ''; + end + end + + function script = toScript(obj, options) + % Convert to a MATLAB script that can be run to reproduce this + % projectClass object. + % + % script = project.toScript(); + arguments + obj + options.objName {mustBeTextScalar} = 'project' + end + script = ""; + script = script + sprintf("%s\n\n", '% THIS FILE IS GENERATED FROM RAT VIA THE "WRITESCRIPT" ROUTINE. IT IS NOT PART OF THE RAT CODE.'); % Start by getting input arguments projectSpec = "%s = createProject(name='%s', calcType='%s', model='%s', geometry='%s', absorption=%s);\n\n"; - fprintf(fileID, projectSpec, options.objName, obj.experimentName, obj.calculationType, obj.modelType, obj.geometry, string(obj.absorption)); + script = script + sprintf(projectSpec, options.objName, obj.experimentName, obj.calculationType, obj.modelType, obj.geometry, string(obj.absorption)); if obj.usePriors - fprintf(fileID, "%s.setUsePriors(true);\n\n", options.objName); + script = script + sprintf("%s.setUsePriors(true);\n\n", options.objName); end % Add all parameters, with different actions for protected @@ -959,10 +1112,10 @@ function writeScript(obj, options) for i=1:height(obj.parameters.varTable) % Set protected parameters if any(strcmpi(obj.parameters.varTable{i, 1}, obj.protectedParameters)) - fprintf(fileID, options.objName + ".setParameterValue(%d, %.15g);\n", i, obj.parameters.varTable{i, 3}); - fprintf(fileID, options.objName + ".setParameterLimits(%d, %.15g, %.15g);\n", i, obj.parameters.varTable{i, 2}, obj.parameters.varTable{i, 4}); - fprintf(fileID, options.objName + ".setParameterFit(%d, %s);\n", i, string(obj.parameters.varTable{i, 5})); - fprintf(fileID, options.objName + ".setParameterPrior(%d, '%s', %.15g, %.15g);\n", i, obj.parameters.varTable{i, 6}, obj.parameters.varTable{i, 7}, obj.parameters.varTable{i, 8}); + script = script + sprintf(options.objName + ".setParameterValue(%d, %.15g);\n", i, obj.parameters.varTable{i, 3}); + script = script + sprintf(options.objName + ".setParameterLimits(%d, %.15g, %.15g);\n", i, obj.parameters.varTable{i, 2}, obj.parameters.varTable{i, 4}); + script = script + sprintf(options.objName + ".setParameterFit(%d, %s);\n", i, string(obj.parameters.varTable{i, 5})); + script = script + sprintf(options.objName + ".setParameterPrior(%d, '%s', %.15g, %.15g);\n", i, obj.parameters.varTable{i, 6}, obj.parameters.varTable{i, 7}, obj.parameters.varTable{i, 8}); % Add non-protected parameters to a parameter group else paramRow = table2cell(obj.parameters.varTable(i, :))'; @@ -972,20 +1125,20 @@ function writeScript(obj, options) end end - fprintf(fileID, "\n"); + script = script + newline; % Write the parameter group to the script if size(paramGroup, 1) > 0 - fprintf(fileID, "paramGroup = {\n"); + script = script + sprintf("paramGroup = {\n"); for i = 1:size(paramGroup, 1) paramSpec = blanks(14) + "{'%s', %.15g, %.15g, %.15g, %s, '%s', %.15g, %.15g};\n"; - fprintf(fileID, paramSpec, paramGroup{i}{:}); + script = script + sprintf(paramSpec, paramGroup{i}{:}); end - fprintf(fileID, blanks(14) + "%s\n\n", "};"); - fprintf(fileID, options.objName + ".addParameterGroup(paramGroup);\n"); + script = script + sprintf(blanks(14) + "%s\n\n", "};"); + script = script + sprintf(options.objName + ".addParameterGroup(paramGroup);\n"); end - fprintf(fileID, "\n"); + script = script + newline; % Add all parameters based on a parametersClass paramClasses = ["bulkIn", "bulkOut", "scalefactors", "background", "resolution"]; @@ -1015,15 +1168,15 @@ function writeScript(obj, options) end % Remove default parameter - fprintf(fileID, options.objName + "." + removeRoutine + "(1);\n"); + script = script + sprintf(options.objName + "." + removeRoutine + "(1);\n"); % Convert logical parameter for j=1:numParams paramTable{5, j} = string(paramTable{5, j}); end % Add the parameters that have been defined paramSpec = options.objName + "." + addRoutine + "('%s', %.15g, %.15g, %.15g, %s, '%s', %.15g, %.15g);\n"; - fprintf(fileID, paramSpec, paramTable{:}); - fprintf(fileID, "\n"); + script = script + sprintf(paramSpec, paramTable{:}); + script = script + newline; end @@ -1038,8 +1191,8 @@ function writeScript(obj, options) % Add parameters if any have been defined if ~isempty(stringTable) stringSpec = options.objName + "." + addRoutine + "(" + join(repmat("'%s'", 1, numCols), ", ") + ");\n"; - fprintf(fileID, stringSpec, stringTable); - fprintf(fileID, "\n"); + script = script + sprintf(stringSpec, stringTable); + script = script + newline; end % Now deal with background and resolutions, which have @@ -1052,9 +1205,9 @@ function writeScript(obj, options) stringSubclasses = ["", stringSubclasses]; end - fprintf(fileID, options.objName + ".removeBackground(1);\n"); - fprintf(fileID, options.objName + ".removeResolution(1);\n"); - fprintf(fileID, "\n"); + script = script + sprintf(options.objName + ".removeBackground(1);\n"); + script = script + sprintf(options.objName + ".removeResolution(1);\n"); + script = script + newline; for i=1:length(stringClasses) stringTable = table(); @@ -1076,37 +1229,37 @@ function writeScript(obj, options) % Add parameters if any have been defined if ~isempty(stringTable) stringSpec = options.objName + "." + addRoutine + "(" + join(repmat("'%s'", 1, numCols), ", ") + ");\n"; - fprintf(fileID, stringSpec, stringTable); - fprintf(fileID, "\n"); + script = script + sprintf(stringSpec, stringTable); + script = script + newline; end end % Data class requires writing and reading the data - fprintf(fileID, options.objName + ".removeData(1);\n"); + script = script + sprintf(options.objName + ".removeData(1);\n"); for i=1:obj.data.rowCount % Write and read data if it exists, else add an empty, % named row if isempty(obj.data.varTable{i, 2}{:}) - fprintf(fileID, options.objName + ".addData('%s');\n", obj.data.varTable{i, 1}); + script = script + sprintf(options.objName + ".addData('%s');\n", obj.data.varTable{i, 1}); else writematrix(obj.data.varTable{i, 2}{:}, "data_" + string(i) + ".dat"); - fprintf(fileID, "data_%d = readmatrix('%s');\n", i, "data_" + string(i) + ".dat"); - fprintf(fileID, options.objName + ".addData('%s', data_%d);\n", obj.data.varTable{i, 1}, i); + script = script + sprintf("data_%d = readmatrix('%s');\n", i, "data_" + string(i) + ".dat"); + script = script + sprintf(options.objName + ".addData('%s', data_%d);\n", obj.data.varTable{i, 1}, i); end % Also need to set dataRange and simRange explicitly as they % are optional if ~isempty(obj.data.varTable{i, 3}{:}) - fprintf(fileID, options.objName + ".setData(%d, 'dataRange', [%.15g %.15g]);\n", i, obj.data.varTable{i, 3}{:}); + script = script + sprintf(options.objName + ".setData(%d, 'dataRange', [%.15g %.15g]);\n", i, obj.data.varTable{i, 3}{:}); end if ~isempty(obj.data.varTable{i, 4}{:}) - fprintf(fileID, options.objName + ".setData(%d, 'simRange', [%.15g %.15g]);\n", i, obj.data.varTable{i, 4}{:}); + script = script + sprintf(options.objName + ".setData(%d, 'simRange', [%.15g %.15g]);\n", i, obj.data.varTable{i, 4}{:}); end - fprintf(fileID, "\n"); + script = script + newline; end @@ -1117,12 +1270,12 @@ function writeScript(obj, options) reducedStruct = rmfield(obj.contrasts.contrasts{i}, {'resample', 'model'}); contrastParams = string(namedargs2cell(reducedStruct)); contrastSpec = options.objName + ".addContrast(" + join(repmat("'%s'", 1, length(contrastParams)), ", ") + ");\n"; - fprintf(fileID, contrastSpec, contrastParams); - fprintf(fileID, options.objName + ".setContrast(%d, 'resample', %s);\n", i, string(obj.contrasts.contrasts{i}.resample)); + script = script + sprintf(contrastSpec, contrastParams); + script = script + sprintf(options.objName + ".setContrast(%d, 'resample', %s);\n", i, string(obj.contrasts.contrasts{i}.resample)); if ~isempty(obj.contrasts.contrasts{i}.model) - fprintf(fileID, options.objName + ".setContrastModel(%d, {" + join(repmat("'%s'", 1, length(obj.contrasts.contrasts{i}.model))) +"});\n", i, obj.contrasts.contrasts{i}.model{:}); + script = script + sprintf(options.objName + ".setContrastModel(%d, {" + join(repmat("'%s'", 1, length(obj.contrasts.contrasts{i}.model))) +"});\n", i, obj.contrasts.contrasts{i}.model{:}); end - fprintf(fileID, "\n"); + script = script + newline; end @@ -1132,147 +1285,15 @@ function writeScript(obj, options) reducedStruct = rmfield(obj.domainContrasts.contrasts{i}, {'model'}); contrastParams = string(namedargs2cell(reducedStruct)); contrastSpec = options.objName + ".addDomainContrast(" + join(repmat("'%s'", 1, length(contrastParams)), ", ") + ");\n"; - fprintf(fileID, contrastSpec, contrastParams); + script = script + sprintf(contrastSpec, contrastParams); if ~isempty(obj.domainContrasts.contrasts{i}.model) - fprintf(fileID, options.objName + ".setDomainContrastModel(%d, {" + join(repmat("'%s'", 1, length(obj.domainContrasts.contrasts{i}.model))) +"});\n", i, obj.domainContrasts.contrasts{i}.model{:}); + script = script + sprintf(options.objName + ".setDomainContrastModel(%d, {" + join(repmat("'%s'", 1, length(obj.domainContrasts.contrasts{i}.model))) +"});\n", i, obj.domainContrasts.contrasts{i}.model{:}); end - fprintf(fileID, "\n"); + script = script + newline; end end - - fclose(fileID); - - end - - end % end public methods - - % ------------------------------------------------------------------ - - methods (Access = protected) - % Display methods - function group = getPropertyGroup1(obj) - % Initial Parameters at the start of the class - masterPropList = struct('modelType',{obj.modelType},... - 'experimentName',{obj.experimentName},... - 'geometry', obj.geometry); - - if isscalar(obj) - group = matlab.mixin.util.PropertyGroup(masterPropList); - else - group = getPropertyGroup1@matlab.mixin.CustomDisplay(obj); - end - end - - function displayScalarObject(obj) - % Display the whole class. Call the display methods for - % the sub-classes where appropriate - - % There are two versions, depending on whether the model - % is standard layers or custom, the difference being - % the display of the layers table and domain contrasts. - - % Display initial properties - startProps = getPropertyGroup1(obj); - matlab.mixin.CustomDisplay.displayPropertyGroups(obj,startProps); - - % Display the parameters table - fprintf('\n Parameters: ---------------------------------------------------------------------------------------------- \n\n'); - obj.parameters.displayTable; - - % Display the Bulk In table - fprintf('\n Bulk In: -------------------------------------------------------------------------------------------------- \n\n'); - obj.bulkIn.displayTable; - - % Display the Bulk Out table - fprintf('\n Bulk Out: ------------------------------------------------------------------------------------------------- \n\n'); - obj.bulkOut.displayTable; - - % Display the Scalefactors table - fprintf('\n Scalefactors: ------------------------------------------------------------------------------------------------- \n\n'); - obj.scalefactors.displayTable; - - % Display the domain ratio if defined - if isprop(obj, 'domainRatio') && isa(obj.domainRatio, 'parametersClass') - fprintf('\n Domain Ratios: ----------------------------------------------------------------------------------------------- \n\n'); - obj.domainRatio.displayTable; - end - - % Display the backgrounds object - fprintf('\n Backgrounds: ----------------------------------------------------------------------------------------------- \n\n'); - obj.background.displayBackgroundsObject; - - % Display the resolutions object - fprintf('\n Resolutions: --------------------------------------------------------------------------------------------- \n\n'); - obj.resolution.displayResolutionsObject; - - % Display the layers table if not a custom model - if isa(obj.layers, 'layersClass') - fprintf('\n Layers: -------------------------------------------------------------------------------------------------- \n\n'); - obj.layers.displayTable; - end - - % Display custom files object - fprintf('\n Custom Files: ------------------------------------------------------------------------------------------------------ \n\n'); - obj.customFile.displayTable; - - % Display the data object - fprintf('\n Data: ------------------------------------------------------------------------------------------------------ \n\n'); - obj.data.displayTable; - - % Display the domainContrasts object if defined - if isprop(obj, 'domainContrasts') && isa(obj.domainContrasts, 'domainContrastsClass') - fprintf('\n Domains Contrasts: ----------------------------------------------------------------------------------------------- \n\n'); - obj.domainContrasts.displayContrastsObject; - end - - % Display the contrasts object - fprintf('\n Contrasts: ----------------------------------------------------------------------------------------------- \n\n'); - obj.contrasts.displayContrastsObject; - - end - - end - - methods (Access = protected, Hidden) - - function modifyLayersTable(obj,~,~) - % Add or remove a column from the layers table whenever the - % "absorption" property is modified. - if isa(obj.layers, 'layersClass') - if obj.absorption - newCol = repmat("", height(obj.layers.varTable), 1); - obj.layers.varTable = addvars(obj.layers.varTable, newCol, 'After', 'SLD', 'NewVariableNames', 'SLD Imaginary'); - obj.layers.varTable = renamevars(obj.layers.varTable, 'SLD', 'SLD Real'); - else - obj.layers.varTable = removevars(obj.layers.varTable, 'SLD Imaginary'); - obj.layers.varTable = renamevars(obj.layers.varTable, 'SLD Real', 'SLD'); - end - end - end - - end - - methods (Hidden) - - function domainsObj = domainsClass(obj) - % Converter routine from projectClass to domainsClass. - % This routine takes the currently defined project and - % converts it to a domains calculation, preserving all - % currently defined properties. - % - % domainsProject = project.domainsClass(); - domainsObj = domainsClass(obj.experimentName, calculationTypes.Domains, obj.modelType, obj.geometry, obj.absorption); - domainsObj = copyProperties(obj, domainsObj); - - % Need to treat contrasts separately due to changes in the - % class for domains calculations - domainsObj.contrasts = copyProperties(obj.contrasts, contrastsClass(domains=true, oilWater=obj.contrasts.oilWaterCalc)); - for i=1:domainsObj.contrasts.numberOfContrasts - domainsObj.contrasts.contrasts{i}.domainRatio = ''; - end end - end end diff --git a/examples/miscellaneous/alternativeLanguages/customBilayer.py b/examples/miscellaneous/alternativeLanguages/customBilayer.py index 2b0315aaa..5cf896b58 100644 --- a/examples/miscellaneous/alternativeLanguages/customBilayer.py +++ b/examples/miscellaneous/alternativeLanguages/customBilayer.py @@ -2,9 +2,9 @@ import numpy as np def customBilayer(params, bulk_in, bulk_out, contrast): - params = np.array(params); - bulk_in = np.array(bulk_in); - bulk_out = np.array(bulk_out); + params = params + bulk_in = bulk_in + bulk_out = bulk_out sub_rough = params[0] oxide_thick = params[1] diff --git a/tests/testExamples.m b/tests/testExamples.m index 4b6b6ab64..03c9cad99 100644 --- a/tests/testExamples.m +++ b/tests/testExamples.m @@ -138,8 +138,10 @@ function testWriteScript(testCase, exampleScriptFile) % closes all the figures generated by the examples close all; - % Write the script - problem.writeScript(objName="scriptProblem", script="projectScript"); + % Write the script using a deep copy of the project + clonedProblem = problem.clone(); + testCase.verifyNotEqual(clonedProblem, problem); + clonedProblem.writeScript(objName="scriptProblem", script="projectScript"); run("projectScript.m"); % Test general properties diff --git a/tests/testProjectClass.m b/tests/testProjectClass.m index 352ed2ac7..9c0114109 100644 --- a/tests/testProjectClass.m +++ b/tests/testProjectClass.m @@ -90,6 +90,20 @@ function testCreation(testCase) testCase.verifyEqual(newProject.calculationType, calculationTypes.NonPolarised.value, 'Calculation Type not set correctly'); testCase.verifyError(@() projectClass(1), 'MATLAB:validators:mustBeTextScalar') end + + function testClone(testCase) + % Tests clone project is different from original + newProject = projectClass(); + clonedProject = newProject.clone(); + testCase.verifyEqual(newProject.experimentName, clonedProject.experimentName, 'clone not working correctly'); + clonedProject.experimentName = 'project'; + testCase.verifyNotEqual(newProject.experimentName, 'project', 'clone not working correctly'); + + varTable = newProject.parameters.varTable; + newProject.parameters.varTable = [varTable; vertcat(testCase.parameters{:})]; + testCase.verifySize( newProject.parameters.varTable, [10, 8], 'Parameters has wrong dimension'); + testCase.verifySize( clonedProject.parameters.varTable, [1, 8], 'Parameters has wrong dimension') + end function testConversion(testCase) % Tests project class can be converted to domains class and