Mantid
Loading...
Searching...
No Matches
GeneratePythonFitScript.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2021 ISIS Rutherford Appleton Laboratory UKRI,
4// NScD Oak Ridge National Laboratory, European Spallation Source,
5// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6// SPDX - License - Identifier: GPL - 3.0 +
8
20
21#include <boost/algorithm/string.hpp>
22#include <boost/algorithm/string/detail/classification.hpp>
23#include <boost/algorithm/string/split.hpp>
24
25#include <fstream>
26#include <streambuf>
27#include <string>
28
29using namespace Mantid::API;
30using namespace Mantid::Kernel;
31
32namespace {
33
34template <typename T> std::string joinVector(std::vector<T> const &vec, std::string const &delimiter = ", ") {
35 std::stringstream ss;
36 std::copy(vec.cbegin(), vec.cend(), std::ostream_iterator<T>(ss, delimiter.c_str()));
37 auto const str = ss.str();
38 return str.substr(0, str.size() - delimiter.size());
39}
40
41std::string constructInputDictionaryEntry(std::string const &workspaceName, std::size_t const &workspaceIndex,
42 double const startX, double const endX) {
43 return "\"" + workspaceName + "\": (" + std::to_string(workspaceIndex) + ", " + std::to_string(startX) + ", " +
44 std::to_string(endX) + ")";
45}
46
47std::string constructInputDictionary(std::vector<std::string> const &inputWorkspaces,
48 std::vector<std::size_t> const &workspaceIndices,
49 std::vector<double> const &startXs, std::vector<double> const &endXs) {
50 std::vector<std::string> entries;
51 entries.reserve(inputWorkspaces.size());
52 for (auto i = 0u; i < inputWorkspaces.size(); ++i)
53 entries.emplace_back(constructInputDictionaryEntry(inputWorkspaces[i], workspaceIndices[i], startXs[i], endXs[i]));
54
55 return "{\n " + joinVector(entries, ",\n ") + "\n}";
56}
57
58std::vector<std::string> splitStringBy(std::string const &str, std::string const &delimiter) {
59 std::vector<std::string> subStrings;
60 boost::split(subStrings, str, boost::is_any_of(delimiter));
61 subStrings.erase(std::remove_if(subStrings.begin(), subStrings.end(),
62 [](std::string const &subString) { return subString.empty(); }),
63 subStrings.end());
64 return subStrings;
65}
66
67void replaceAll(std::string &str, std::string const &remove, std::string const &insert) {
68 std::string::size_type pos = 0;
69 while ((pos = str.find(remove, pos)) != std::string::npos) {
70 str.replace(pos, remove.size(), insert);
71 pos++;
72 }
73}
74
75std::string getFileContents(std::string const &filename) {
76 auto const directory = ConfigService::Instance().getString("python.templates.directory");
77
78 std::ifstream filestream(directory + "/" + filename);
79 if (!filestream) {
80 filestream.close();
81 throw std::runtime_error("Error occured when attempting to load file: " + filename);
82 }
83
84 std::string fileText((std::istreambuf_iterator<char>(filestream)), std::istreambuf_iterator<char>());
85 filestream.close();
86 return fileText;
87}
88
89} // namespace
90
91namespace Mantid::Algorithms {
92
93DECLARE_ALGORITHM(GeneratePythonFitScript)
94
95std::string const GeneratePythonFitScript::name() const { return "GeneratePythonFitScript"; }
96
97int GeneratePythonFitScript::version() const { return 1; }
98
99std::string const GeneratePythonFitScript::category() const { return "Utility\\Python"; }
100
101std::string const GeneratePythonFitScript::summary() const {
102 return "An algorithm to generate a Python script file for performing a sequential or simultaneous fit.";
103}
104
105std::vector<std::string> const GeneratePythonFitScript::seeAlso() const { return {"Fit", "GeneratePythonScript"}; }
106
108 auto mustBePositive = std::make_shared<BoundedValidator<int>>();
109 mustBePositive->setLower(0);
110
111 declareProperty(std::make_unique<ArrayProperty<std::string>>("InputWorkspaces", std::make_shared<ADSValidator>(),
113 "A list of workspace names to be fitted. The workspace name at index i in the list corresponds with "
114 "the 'WorkspaceIndices', 'StartXs' and 'EndXs' properties.");
115 declareProperty(std::make_unique<ArrayProperty<std::size_t>>("WorkspaceIndices", Direction::Input),
116 "A list of workspace indices to be fitted. The workspace index at index i in the list will "
117 "correspond to the input workspace at index i.");
118
119 declareProperty(std::make_unique<ArrayProperty<double>>("StartXs", Direction::Input),
120 "A list of start X's to be used for the fitting. The Start X at index i will correspond to the input "
121 "workspace at index i.");
122
123 declareProperty(std::make_unique<ArrayProperty<double>>("EndXs", Direction::Input),
124 "A list of end X's to be used for the fitting. The End X at index i will correspond to the input "
125 "workspace at index i.");
126
127 auto const fittingTypes = std::vector<std::string>{"Sequential", "Simultaneous"};
128 auto const fittingTypesValidator = std::make_shared<ListValidator<std::string>>(fittingTypes);
129 declareProperty("FittingType", "Sequential", fittingTypesValidator,
130 "The type of fitting to generate a python script for (Sequential or Simultaneous).",
132
134 std::make_unique<FunctionProperty>("Function", Direction::Input),
135 "The function to use for the fitting. This should be a single domain function if the Python script will be for "
136 "sequential fitting, or a MultiDomainFunction if the Python script is for simultaneous fitting.");
137
138 declareProperty("MaxIterations", 500, mustBePositive->clone(),
139 "The MaxIterations to be passed to the Fit algorithm in the Python script.", Direction::Input);
140
141 auto const minimizerOptions = FuncMinimizerFactory::Instance().getKeys();
142 auto const minimizerValidator = std::make_shared<ListValidator<std::string>>(minimizerOptions);
143 declareProperty("Minimizer", "Levenberg-Marquardt", minimizerValidator,
144 "The Minimizer to be passed to the Fit algorithm in the Python script.", Direction::Input);
145
146 auto const costFunctionOptions = CostFunctionFactory::Instance().getKeys();
147 auto const costFunctionValidator = std::make_shared<ListValidator<std::string>>(costFunctionOptions);
148 declareProperty("CostFunction", "Least squares", costFunctionValidator,
149 "The CostFunction to be passed to the Fit algorithm in the Python script.", Direction::Input);
150
151 std::array<std::string, 2> evaluationTypes = {{"CentrePoint", "Histogram"}};
152 declareProperty("EvaluationType", "CentrePoint", IValidator_sptr(new ListValidator<std::string>(evaluationTypes)),
153 "The EvaluationType to be passed to the Fit algorithm in the Python script.", Direction::Input);
154
155 declareProperty("OutputBaseName", "Output_Fit",
156 "The OutputBaseName is the base output name to use for the resulting Fit workspaces.");
157
159 "PlotOutput", true,
160 "If true, code used for plotting the results of a fit will be generated and added to the python script.");
161
162 std::vector<std::string> extensions{".py"};
163 declareProperty(std::make_unique<FileProperty>("Filepath", "", FileProperty::OptionalSave, extensions),
164 "The name of the Python fit script which will be generated and saved in the selected location.");
165
166 declareProperty("ScriptText", "", Direction::Output);
167}
168
169std::map<std::string, std::string> GeneratePythonFitScript::validateInputs() {
170 std::vector<std::string> const inputWorkspaces = getProperty("InputWorkspaces");
171 std::vector<std::size_t> const workspaceIndices = getProperty("WorkspaceIndices");
172 std::vector<double> const startXs = getProperty("StartXs");
173 std::vector<double> const endXs = getProperty("EndXs");
174 auto const fittingType = getPropertyValue("FittingType");
175 IFunction_sptr function = getProperty("Function");
176 std::string const outputBaseName = getProperty("OutputBaseName");
177
178 std::map<std::string, std::string> errors;
179 if (workspaceIndices.size() != inputWorkspaces.size())
180 errors["WorkspaceIndices"] = "The number of workspace indices must be equal to the number of input workspaces.";
181 if (startXs.size() != inputWorkspaces.size())
182 errors["StartXs"] = "The number of Start Xs must be equal to the number of input workspaces.";
183 if (endXs.size() != inputWorkspaces.size())
184 errors["EndXs"] = "The number of End Xs must be equal to the number of input workspaces.";
185 if (fittingType == "Sequential") {
186 if (std::dynamic_pointer_cast<MultiDomainFunction>(function))
187 errors["Function"] = "The Function cannot be a MultiDomainFunction when in Sequential fit mode.";
188 }
189 if (fittingType == "Simultaneous") {
190 if (getNumberOfDomainsInFunction(function) != inputWorkspaces.size())
191 errors["Function"] = "The Function provided does not have the same number of domains as there are input "
192 "workspaces. This is a requirement for Simultaneous fitting.";
193 }
194 if (outputBaseName.empty())
195 errors["OutputBaseName"] = "The OutputBaseName is empty, please provide a base name for the output fit.";
196
197 return errors;
198}
199
201 auto const fittingType = getPropertyValue("FittingType");
202 auto const generatedScript = generateFitScript(fittingType);
203
204 auto const filepath = getPropertyValue("Filepath");
205 if (!filepath.empty())
206 savePythonScript(filepath, generatedScript);
207
208 setProperty("ScriptText", generatedScript);
209}
210
212 if (!function)
213 return 0u;
214 if (auto const multiDomainFunction = std::dynamic_pointer_cast<MultiDomainFunction>(function))
215 return multiDomainFunction->getNumberDomains();
216 return 1u;
217}
218
219std::string GeneratePythonFitScript::generateFitScript(std::string const &fittingType) const {
220 std::string generatedScript;
221 generatedScript += generateVariableSetupCode();
222 generatedScript += "\n";
223 if (fittingType == "Sequential")
224 generatedScript += getFileContents("GeneratePythonFitScript_SequentialFit.py.in");
225 else if (fittingType == "Simultaneous")
226 generatedScript += generateSimultaneousFitCode();
227
228 bool plotOutput = getProperty("PlotOutput");
229 if (plotOutput) {
230 generatedScript += "\n";
231 std::vector<double> const startXs = getProperty("StartXs");
232 if (startXs.size() == 1u) {
233 generatedScript += getFileContents("GeneratePythonFitScript_PlottingSingleOutput.py.in");
234 } else {
235 generatedScript += getFileContents("GeneratePythonFitScript_PlottingMultiOutput.py.in");
236 }
237 }
238
239 return generatedScript;
240}
241
243 std::string code = getFileContents("GeneratePythonFitScript_VariableSetup.py.in");
244
245 std::vector<std::string> const inputWorkspaces = getProperty("InputWorkspaces");
246 std::vector<std::size_t> const workspaceIndices = getProperty("WorkspaceIndices");
247 std::vector<double> const startXs = getProperty("StartXs");
248 std::vector<double> const endXs = getProperty("EndXs");
249
250 int const maxIterations = getProperty("MaxIterations");
251 std::string const minimizer = getProperty("Minimizer");
252 std::string const costFunction = getProperty("CostFunction");
253 std::string const evaluationType = getProperty("EvaluationType");
254 std::string const outputBaseName = getProperty("OutputBaseName");
255
256 replaceAll(code, "{{input_dictionary}}", constructInputDictionary(inputWorkspaces, workspaceIndices, startXs, endXs));
257 replaceAll(code, "{{function_string}}", generateFunctionString());
258 replaceAll(code, "{{max_iterations}}", std::to_string(maxIterations));
259 replaceAll(code, "{{minimizer}}", minimizer);
260 replaceAll(code, "{{cost_function}}", costFunction);
261 replaceAll(code, "{{evaluation_type}}", evaluationType);
262 replaceAll(code, "{{output_base_name}}", outputBaseName);
263 return code;
264}
265
267 std::string code = getFileContents("GeneratePythonFitScript_SimultaneousFit.py.in");
268 std::string const line = getFileContents("GeneratePythonFitScript_SimultaneousFitDomainLine.py.in");
269
270 std::vector<std::string> const inputWorkspaces = getProperty("InputWorkspaces");
271 std::string domainLines;
272 for (auto i = 1u; i < inputWorkspaces.size(); ++i) {
273 std::string snippet = line;
274 replaceAll(snippet, "{{i}}", std::to_string(i));
275 domainLines += snippet;
276 }
277
278 replaceAll(code, "{{other_domains}}", domainLines);
279 return code;
280}
281
283 IFunction_const_sptr function = getProperty("Function");
284 auto const functionSplit = splitStringBy(function->asString(), ";");
285
286 std::string code = "\\\n \"";
287 code += joinVector(functionSplit, ";\" \\\n \"");
288 code += "\"";
289 return code;
290}
291
292void GeneratePythonFitScript::savePythonScript(std::string const &filepath, std::string const &contents) const {
293 std::ofstream file(filepath.c_str(), std::ofstream::trunc);
294 file << contents;
295 file.flush();
296 file.close();
297}
298
299} // namespace Mantid::Algorithms
#define DECLARE_ALGORITHM(classname)
Definition: Algorithm.h:576
void declareProperty(std::unique_ptr< Kernel::Property > p, const std::string &doc="") override
Add a property to the list of managed properties.
Definition: Algorithm.cpp:1913
std::string getPropertyValue(const std::string &name) const override
Get the value of a property as a string.
Definition: Algorithm.cpp:2026
TypedValue getProperty(const std::string &name) const override
Get the value of a property.
Definition: Algorithm.cpp:2076
@ OptionalSave
to specify a file to write to but an empty string is
Definition: FileProperty.h:50
std::vector< std::string > const seeAlso() const override
Function to return all of the seeAlso algorithms related to this algorithm.
std::string generateFitScript(std::string const &fittingType) const
void init() override
Virtual method - must be overridden by concrete algorithm.
std::string const category() const override
function to return a category of the algorithm.
std::map< std::string, std::string > validateInputs() override
Method checking errors on ALL the inputs, before execution.
std::string const summary() const override
function returns a summary message that will be displayed in the default GUI, and in the help.
void savePythonScript(std::string const &filepath, std::string const &contents) const
std::size_t getNumberOfDomainsInFunction(Mantid::API::IFunction_sptr const &function) const
int version() const override
function to return a version of the algorithm, must be overridden in all algorithms
void exec() override
Virtual method - must be overridden by concrete algorithm.
Support for a property that holds an array of values.
Definition: ArrayProperty.h:28
IPropertyManager * setProperty(const std::string &name, const T &value)
Templated method to set the value of a PropertyWithValue.
ListValidator is a validator that requires the value of a property to be one of a defined list of pos...
Definition: ListValidator.h:29
static T & Instance()
Return a reference to the Singleton instance, creating it if it does not already exist Creation is do...
EXPORT_OPT_MANTIDQT_COMMON std::vector< std::string > splitStringBy(std::string const &str, std::string const &delimiter)
Splits the string by the given delimiters.
std::shared_ptr< IFunction > IFunction_sptr
shared pointer to the function base class
Definition: IFunction.h:732
std::shared_ptr< const IFunction > IFunction_const_sptr
shared pointer to the function base class (const version)
Definition: IFunction.h:734
MANTID_KERNEL_DLL std::string replaceAll(const std::string &input, const std::string &charStr, const std::string &substitute)
Return a string with all occurrences of the characters in the input replaced by the replace string.
Definition: Strings.cpp:94
std::shared_ptr< IValidator > IValidator_sptr
A shared_ptr to an IValidator.
Definition: IValidator.h:26
STL namespace.
std::string to_string(const wide_integer< Bits, Signed > &n)
@ Input
An input workspace.
Definition: Property.h:53
@ Output
An output workspace.
Definition: Property.h:54