ParaMonte MATLAB 3.0.0
Parallel Monte Carlo and Machine Learning Library
See the latest version documentation.
run.m
Go to the documentation of this file.
1%> \brief
2%> Perform the basic runtime checks for the sampler and return nothing.
3%>
4%> \param[inout] self : The input/output parent object of class [pm.sampling.Sampler](@ref Sampler)
5%> which is **implicitly** passed to this dynamic method (not by the user).<br>
6%> \param[in] getLogFunc : The input MATLAB function handle or anonymous (lambda) function
7%> containing the implementation of the objective function to be sampled.<br>
8%> This user-specified function must have the following interface,<br>
9%> \code{.m}
10%> function logFunc = getLogFunc(state)
11%> ...
12%> end<br>
13%> \endcode
14%> where,<br>
15%> <ol>
16%> <li> the input argument ``state`` is a vector of type MATLAB ``double``
17%> of size ``ndim`` representing a single point from within the ``ndim``
18%> dimensional domain of the mathematical object function to be explored.<br>
19%> <li> the output argument `logFunc` is a scalar of the same type as the
20%> input ``state`` containing the natural logarithm of the objective
21%> function at the specified input ``state`` within its domain.<br>
22%> </ol>
23%> \param[in] ndim : The input scalar positive-valued whole-number representing the number of dimensions
24%> of the domain of the user-specified objective function in the input ``getLogFunc()``.<br>
25%>
26%> \interface{run}
27%> \code{.m}
28%>
29%> sampler = pm.sampling.Sampler();
30%> sampleList = sampler.run(getLogFunc, ndim);
31%>
32%> \endcode
33%>
34%> \final{run}
35%>
36%> \author
37%> \JoshuaOsborne, May 21 2024, 12:38 AM, University of Texas at Arlington<br>
38%> \FatemehBagheri, May 20 2024, 1:25 PM, NASA Goddard Space Flight Center (GSFC), Washington, D.C.<br>
39%> \AmirShahmoradi, May 16 2016, 9:03 AM, Oden Institute for Computational Engineering and Sciences (ICES), UT Austin<br>
40function run(self, getLogFunc, ndim)
41
42 %%%%
43 %%%% Sanitize ``sampler.silent``.
44 %%%%
45
46 if ~pm.introspection.istype(self.silent, "logical", 1)
47 help("pm.sampling.Sampler.silent");
48 disp("self.silent =");
49 disp(self.silent);
50 error ( newline ...
51 + "The sampler attribute ``silent`` must be a scalar of type ``logical``." + newline ...
52 + "For more information, see the documentation displayed above." + newline ...
53 + newline ...
54 );
55 end
56
57 %%%%
58 %%%% Sanitize parallelism method to set reporting permission.
59 %%%%
60
61 % global mpiname;
62 % ismember('mpiname', who('global'));
63 % if pm.array.len(self.mpiname) == 0 && ~isempty(mpiname) && pm.introspection.istype(mpiname, "string", 1) % MPI enabled by a global definition of ``mpiname``.
64 % self.mpiname = mpiname;
65 % end
66
67 if ~pm.introspection.istype(self.mpiname, "string", 1)
68
69 %%%%
70 %%%% Sanitize ``mpiname``.
71 %%%%
72
73 help("pm.sampling.Sampler.mpiname");
74 disp("mpiname =");
75 disp(self.mpiname);
76 error ( newline ...
77 + "The sampler attribute ``mpiname`` must be a scalar of type ``char`` or ``string``." + newline ...
78 + "For more information, see the documentation displayed above." + newline ...
79 + newline ...
80 );
81
82 elseif 0 == pm.array.len(self.mpiname)
83
84 %%%%
85 %%%% Detect potential MPI launcher.
86 %%%%
87
88 [mpiname, nproc, ~] = pm.lib.mpi.runtime.detect();
89 if pm.array.len(mpiname) > 0 && nproc > 1
90 self.mpiname = mpiname;
91 end
92
93 end
94
95 %%%%
96 %%%% First detect potential use of MPI, then check for thread parallelism.
97 %%%%
98
99 if 0 < pm.array.len(self.mpiname) % MPI enabled.
100
101 self.silent = true; % Otherwise, we keep the default value of self.silent.
102 self.partype = string(pm.lib.mpi.name(self.mpiname));
103
104 elseif ~isempty(self.spec.parallelismNumThread)
105
106 %%%%
107 %%%% Sanitize ``self.spec.parallelismNumThread``.
108 %%%%
109
110 % The following separate conditions are crucial to remain separate.
111 failed = ~pm.introspection.istype(self.spec.parallelismNumThread, "integer", 1);
112 if ~failed
113 failed = self.spec.parallelismNumThread < 0;
114 end
115 if ~failed
116 failed = ~pm.matlab.has.parallel;
117 end
118 if ~failed
119 self.partype = "openmp";
120 if self.spec.parallelismNumThread == 0
121 %%%% Negative threads count indicates to the ParaMonte sampler that the user set `parallelismNumThread` to zero.
122 self.spec.parallelismNumThread = -maxNumCompThreads();
123 end
124 else
125 help("pm.sampling.Sampler.run");
126 error ( newline ...
127 + "The simulation specification ``parallelismNumThread`` must be" + newline ...
128 + "a scalar non-negative integer or whole-number representing the number" + newline ...
129 + "of processor threads to use for a thread-parallel simulation." + newline ...
130 + "Specifying ``0`` as the value of ``parallelismNumThread`` will" + newline ...
131 + "lead to using all available CPU processes for thread-parallelism." + newline ...
132 + "Ideally, a number less than the maximum available number" + newline ...
133 + "of threads should be specified for ``parallelismNumThread`` as using" + newline ...
134 + "all available threads exclusively for a simulation will" + newline ...
135 + "slow down all open system applications including MATLAB." + newline ...
136 + "Specifying this option requires the MATLAB parallel toolbox." + newline ...
137 + "If missing or specified as empty `[]`, the simulation will run in serial mode." + newline ...
138 + "You have specified:" + newline ...
139 + newline ...
140 + pm.io.tab + "self.spec.parallelismNumThread = " + string(self.parallelismNumThread) + newline ...
141 + newline ...
142 + "Does your matlab have Parallel Computing Toolbox?" + newline ...
143 + newline ...
144 + pm.io.tab + "pm.matlab.has.parallel() = " + string(pm.matlab.has.parallel()) + newline ...
145 + newline ...
146 + "For more information, see the documentation displayed above." + newline ...
147 + newline ...
148 );
149 end
150
151 end
152
153 %%%%
154 %%%% Sanitize ``getLogFunc``.
155 %%%%
156
157 if ~isa(getLogFunc, "function_handle")
158 help("pm.sampling.Sampler.run");
159 error ( newline ...
160 + "The input argument getLogFunc must be a callable function." + newline ...
161 + "It represents the user's objective function to be sampled," + newline ...
162 + "which must take a single input argument of type numpy" + newline ...
163 + "float64 array of length ndim and must return the" + newline ...
164 + "natural logarithm of the objective function." + newline ...
165 + newline ...
166 + "For more information, see the documentation displayed above." + newline ...
167 + newline ...
168 );
169 end
170
171 %%%%
172 %%%% Sanitize ``ndim``.
173 %%%%
174
175 failed = ~pm.introspection.istype(ndim, "integer", 1);
176 if ~failed
177 failed = ndim < 1;
178 end
179 if ~failed
180 self.ndim = ndim;
181 else
182 help("pm.sampling.Sampler.run");
183 disp("ndim = ");
184 disp(ndim);
185 error ( newline ...
186 + "The input argument ``ndim`` must be a scalar positive integer (or whole-number)." + newline ...
187 + "The input argument ``ndim`` represents the number of dimensions of the domain" + newline ...
188 + "of the user-specified objective function ``getLogFunc()``." + newline ...
189 + "For more information, see the documentation displayed above." + newline ...
190 + newline ...
191 );
192 end
193
194 %%%%
195 %%%% Sanitize ``input`` specifications/file string.
196 %%%%
197
198 if ~pm.introspection.istype(self.input, "string", 1)
199 help("pm.sampling.Sampler.input");
200 disp("self.input = ");
201 disp(self.input);
202 error ( newline ...
203 + "The specified sampler attribute ``input`` must be scalar MATLAB string." + newline ...
204 + "For more information, see the documentation displayed above." + newline ...
205 + newline ...
206 );
207 else
208 self.input = string(self.input);
209 if self.input ~= ""
210 if isfile(self.input)
211 self.nml = pm.sys.path.abs(self.input, "lean");
212 else
213 self.nml = self.input; % Could still be a valid namelist.
214 end
215 if ~self.silent
216 warning ( newline ...
217 + "User-specified input namelist file detected: " + newline ...
218 + newline ...
219 + pm.io.tab + """" + self.input + """" + newline ...
220 + newline ...
221 + "All simulation specifications will be read from the input file." + newline ...
222 + "All simulation specifications in the ``spec`` component of the sampler object will be ignored." + newline ...
223 + newline ...
224 );
225 end
226 else
227 %self.nml = [convertStringsToChars(self.getInputFile())];
228 self.nml = "&" + self.method + " " + self.spec.getEntriesNML(ndim) + " " + "/" + newline;
229 end
230 end
231
232 %%%%
233 %%%% Sanitize ``checked``.
234 %%%%
235
236 if ~isempty(self.checked)
237 if ~pm.introspection.istype(self.checked, "logical", 1)
238 help("pm.sampling.Sampler");
239 disp("self.checked =");
240 disp(self.checked);
241 error ( newline ...
242 + "The specified sampler attribute ``checked`` must be a scalar MATLAB logical." + newline ...
243 + "For more information, see the documentation displayed above." + newline ...
244 + newline ...
245 );
246 elseif ~self.checked
247 chktypes = "nocheck";
248 else
249 chktypes = "checked";
250 end
251 else
252 chktypes = ["nocheck", "checked"];
253 end
254
255 %%%%
256 %%%% Setup the ParaMonter sampler library name.
257 %%%%
258
259 libspecs = [pm.os.namel(), pm.sys.arch(), self.libtype, self.memtype, self.partype];
260 mexdirs = pm.lib.path.mexdir(self.mexname, libspecs);
261
262 % We will choose the checking mode, compiler suite,
263 % and build mode based on the availability below.
264
265 if self.bldtype == ""
266 bldtypes = pm.lib.bldtypes();
267 else
268 bldtypes = lower(self.bldtype());
269 end
270 if self.clstype == ""
271 clstypes = pm.lib.clstypes();
272 else
273 clstypes = lower(self.clstype());
274 end
275 failed = isempty(mexdirs);
276 if ~failed
277 failed = true;
278 for ichk = 1 : length(chktypes)
279 chktype = filesep + chktypes(ichk);
280 for icls = 1 : length(clstypes)
281 clstype = filesep + clstypes(icls);
282 for ibld = 1 : length(bldtypes)
283 bldtype = filesep + bldtypes(ibld) + filesep;
284 for imex = 1 : length(mexdirs)
285 if contains(mexdirs(imex), clstype) ...
286 && contains(mexdirs(imex), bldtype) ...
287 && contains(mexdirs(imex), chktype)
288 mexdir = mexdirs(imex);
289 if self.bldtype == ""
290 self.bldtype = bldtypes(ibld);
291 end
292 if self.clstype == ""
293 self.clstype = clstypes(icls);
294 end
295 failed = false;
296 break;
297 end
298 end
299 if ~failed
300 break;
301 end
302 end
303 if ~failed
304 break;
305 end
306 end
307 end
308 end
309
310 if failed
311 help("pm.sampling.Sampler");
312 disp("libspecs");
313 disp( libspecs );
314 disp("bldtypes");
315 disp( bldtypes );
316 disp("clstypes");
317 disp( clstypes );
318 error ( newline ...
319 + "There are no MEX libraries associated with the configurations displayed above:" + newline ...
320 + "Either the user has compromised internal structure of the ParaMonte library" + newline ...
321 + "or the user has tempered with hidden attributes of the ParaMonte sampler." + newline ...
322 + "If you believe neither is the case, please report this error at:" + newline ...
323 + newline ...
324 + pm.io.tab + pm.web.href(self.weblinks.github.issues.url) + newline ...
325 + newline ...
326 + "for a quick resolution." + newline ...
327 + newline ...
328 );
329 end
330
331 %%%%
332 %%%% Add the identified MEX path to the MATLAB pat list, only temporarily.
333 %%%%
334
335 pm.lib.path.clean();
336 self.matpath = path;
337 path(self.matpath, mexdir);
338 munlock(self.mexname);
339 clear(self.mexname);
340
341 if ~self.silent && ~(pm.matlab.iscmd || pm.os.is.win)
342 disp( newline ...
343 + "Also check the shell terminal (from which you opened MATLAB)" + newline ...
344 + "for potential realtime simulation progress and report." + newline ...
345 + newline ...
346 );
347 end
348
349 if self.clstype == "gnu"
350 setenv('GFORTRAN_STDIN_UNIT' , '5');
351 setenv('GFORTRAN_STDOUT_UNIT', '6');
352 setenv('GFORTRAN_STDERR_UNIT', '0');
353 end
354
355 %%%%
356 %%%% Set up the MEX file to call.
357 %%%%
358
359 if self.partype == "openmp"
360 mexcall = string(self.mexname + "(convertStringsToChars(self.method), @getLogFuncConcurrent, ndim, convertStringsToChars(self.nml))");
361 if ~self.silent
362 delete(gcp("nocreate"));
363 % The following works only in MATLAB 2022b and beyond.
364 if pm.matlab.release() < "2022b"
365 pool = parpool("threads");
366 maxNumCompThreads(abs(self.spec.parallelismNumThread));
367 else
368 pool = parpool("threads", abs(self.spec.parallelismNumThread));
369 end
370 else
371 evalc('delete(gcp("nocreate"))');
372 if pm.matlab.release() < "2022b"
373 evalc('pool = parpool("threads")');
374 evalc('maxNumCompThreads(abs(self.spec.parallelismNumThread))');
375 else
376 evalc('pool = parpool("threads", abs(self.spec.parallelismNumThread))');
377 end
378 end
379 else
380 mexcall = string(self.mexname + "(convertStringsToChars(self.method), @getLogFuncWrapped, ndim, convertStringsToChars(self.nml))");
381 %getLogFuncSpec = functions(getLogFunc);
382 end
383
384 %%%%
385 %%%% Define the ``getLogFunc`` wrapper function for serial/MPI sampling.
386 %%%%
387
388 function logFunc = getLogFuncWrapped(state)
389 logFunc = getLogFunc(state);
390 end
391
392 %%%%
393 %%%% Define the ``getLogFunc`` wrapper function for OpenMP sampling.
394 %%%%
395
396 %getLogFuncConst = parallel.pool.Constant(@(state) getLogFunc(state));
397 function [logFunc, avgTimePerFunCall, avgCommPerFunCall] = getLogFuncConcurrent(state)
398 %state = parallel.pool.Constant(state(2 : end, :));
399 avgCommPerFunCall = tic();
400 avgTimePerFunCall = 0;
401 njob = size(state, 2);
402
403 % %getState = arrayfun(@(ijob) state(:, ijob), 1 : njob, 'UniformOutput', 0);
404 % fout(1 : njob) = parallel.FevalFuture;
405 % start = zeros(njob, 'uint64');
406 % delta = zeros(njob);
407 % for ijob = 1 : njob
408 % %slice = getState(ijob);
409 % start(ijob) = tic();
410 % %fout(ijob) = parfeval(pool, getLogFunc, 1, slice{1});
411 % %fout(ijob) = parfeval(getLogFunc, 1, state(:, ijob));
412 % fout(ijob) = parfeval(pool, getLogFunc, 1, state(:, ijob));
413 % delta(ijob) = toc(start(ijob));
414 % end
415 % avgTimePerFunCall = sum(delta);
416 % logFunc = fetchOutputs(fout);
417
418 %spmd
419 % indx = spmdIndex;
420 % avgTimePerFunCall = tic();
421 % logFunc = getLogFunc(state(:, indx));
422 % avgTimePerFunCall = toc(avgTimePerFunCall);
423 %end
424 %logFunc = [logFunc{:}];
425 %avgTimePerFunCall = mean([avgTimePerFunCall{:}]);
426
427 %%pause
428
429 % \devnote
430 % `parfor` is slightly slower than `parfeval` (abour 20 ms for density function costing .1 ms).
431 % However, the slight penalty diminishes for costly problems and furthermore, `parfor` allows
432 % a reliable method of timing the density function, unlike `parfeval`.
433
434 logFunc = zeros(njob, 1);
435 %getSlice = arrayfun(@(ijob) state(:, ijob), 1 : njob, 'UniformOutput', 0);
436 parfor ijob = 1 : njob
437 start = tic();
438 %logFunc(ijob) = getLogFunc(getSlice{ijob});
439 %logFunc(ijob) = getLogFunc(state(:, ijob));
440 logFunc(ijob) = feval(getLogFunc, state(:, ijob));
441 avgTimePerFunCall = avgTimePerFunCall + toc(start);
442 end
443
444 avgTimePerFunCall = avgTimePerFunCall / njob;
445 avgCommPerFunCall = toc(avgCommPerFunCall) - avgTimePerFunCall;
446 end
447
448 %%%%
449 %%%% Call the MEX sampler.
450 %%%%
451
452 try
453 eval(mexcall);
454 self.finalize();
455 if ~self.silent
456 disp( newline ...
457 + self.getppm() + newline ...
458 + "For more information and examples on the usage, visit:" + newline ...
459 + newline ...
460 + pm.io.tab + pm.web.href(self.weblinks.docs.url) + newline ...
461 + newline ...
462 );
463 end
464 catch me
465 self.finalize();
466 msg = string(me.identifier) + " : " + string(me.message) + newline;
467 if ismac && strcmpi(me.identifier, 'MATLAB:mex:ErrInvalidMEXFile')
468 msg = msg ...
469 + "This error is most likely due to the ""System Integrity Protection""" + newline ...
470 + "(SIP) of your macOS interfering with the ParaMonte MATLAB MEX files." + newline ...
471 + "You can follow the guidelines in the documentation to resolve this error:" + newline ...
472 + newline ...
473 + pm.io.tab + pm.web.href(self.weblinks.docs.url + "/notes/troubleshooting/macos-developer-cannot-be-verified/") + newline ...
474 + newline ...
475 + "If the problem persists even after following the guidelines" + newline ...
476 + "in the above page, please report this issue to the developers at:" + newline ...
477 + newline ...
478 + pm.io.tab + pm.web.href(self.weblinks.github.issues.url) ...
479 + newline ...
480 ;
481 else
482 msg = msg ...
483 + "The " + self.method + " simulation failed. See the error message above." + newline ...
484 + "Also check the contents of the generated output '*_report.txt' files" + newline ...
485 + "if any such files were generated before the simulation crash:" + newline ...
486 + newline ...
487 + pm.io.tab + self.spec.outputFileName + "*_report.txt" + newline ...
488 + newline ...
489 ;
490 end
491 error(msg);
492 end
493
494end
function name(in vendor)
Return the MPI library name as used in naming the ParaMonte MATLAB shared library directories.
function list()
Return a list of MATLAB strings containing the names of OS platforms supported by the ParaMonte MATLA...
function abs(in path, in style)
Return the Get absolute canonical path of a file or folder.
function arch()
Return the processor architecture name of the current system on which MATLAB is running,...
function bldtypes()
Return a list of MATLAB strings containing the names of all currently possible builds of the ParaMont...
This is the base class for the ParaMonte sampler routines.
Definition: Sampler.m:21
function clean()
Remove all paths that contain the ParaMonte lib directory from the MATLAB path variable.
function clstypes()
Return a list of MATLAB strings containing the names of all supported compiler suites (vendor names) ...
function compiler()
Return a scalar MATLAB logical that is true if and only if the current installation of MATLAB contain...
function detect(in vendor)
Return the MPI image count and current image ID (e.g., MPI rank + 1) and the MPI library name as it a...
function getLogFunc(in point)
function href(in url)
Return an HTML-style decoration of the input URL if the ParaMonte MATLAB library is used in GUI,...
function iscmd()
Return a scalar MATLAB logical true if and only if the the MATLAB binary is being called from the she...
function istype(in varval, in vartype, in varsize)
Return true if and only if the input varval conforms with the specified input type vartype and the sp...
function lib()
Return a scalar MATLAB string containing the path to the lib directory of the ParaMonte library packa...
function mexdir(in mexname, in config)
Return the vector of MATLAB strings containing the directory path(s) containing the specified ParaMon...
function namel()
Return a MATLAB string containing the lower-case name of the current OS.
function nproc(in vendor)
Return the runtime number of MPI processes with which the mpiexec launcher may have been invoked.
function parallel()
Return a scalar MATLAB logical that is true if and only if the current installation of MATLAB contain...
function release(in type)
Return a scalar MATLAB string containing the MATLAB release version, year, or season as requested.
function tab()
Return a scalar MATLAB string containing 4 blank characters equivalent to a tab character.
function which(in vendor)
Return the a MATLAB string containing the path to the first mpiexec executable binary found in system...
function win()
Return true if the current OS is Windows.