xref: /petsc/src/sys/classes/matlabengine/matlab.c (revision 66af8762ec03dbef0e079729eb2a1734a35ed7ff)
1 #include <engine.h> /* MATLAB include file */
2 #include <petscsys.h>
3 #include <petscmatlab.h> /*I   "petscmatlab.h"  I*/
4 #include <petsc/private/petscimpl.h>
5 
6 struct _p_PetscMatlabEngine {
7   PETSCHEADER(int);
8   Engine *ep;
9   char    buffer[1024];
10 };
11 
12 PetscClassId MATLABENGINE_CLASSID = -1;
13 
14 /*@C
15   PetscMatlabEngineCreate - Creates a MATLAB engine object
16 
17   Not Collective
18 
19   Input Parameters:
20 + comm - a separate MATLAB engine is started for each process in the communicator
21 - host - name of machine where MATLAB engine is to be run (usually NULL)
22 
23   Output Parameter:
24 . mengine - the resulting object
25 
26   Options Database Keys:
27 + -matlab_engine_graphics - allow the MATLAB engine to display graphics
28 . -matlab_engine_host     - hostname, machine to run the MATLAB engine on
29 - -info                   - print out all requests to MATLAB and all if its responses (for debugging)
30 
31   Level: advanced
32 
33   Notes:
34   If a host string is passed in, any MATLAB scripts that need to run in the
35   engine must be available via MATLABPATH on that machine.
36 
37   One must `./configure` PETSc with  `--with-matlab [-with-matlab-dir=matlab_root_directory]` to
38   use this capability
39 
40 .seealso: `PetscMatlabEngineDestroy()`, `PetscMatlabEnginePut()`, `PetscMatlabEngineGet()`,
41           `PetscMatlabEngineEvaluate()`, `PetscMatlabEngineGetOutput()`, `PetscMatlabEnginePrintOutput()`,
42           `PETSC_MATLAB_ENGINE_()`, `PetscMatlabEnginePutArray()`, `PetscMatlabEngineGetArray()`, `PetscMatlabEngine`
43 @*/
44 PetscErrorCode PetscMatlabEngineCreate(MPI_Comm comm, const char host[], PetscMatlabEngine *mengine)
45 {
46   PetscMPIInt       rank, size;
47   char              buffer[256];
48   PetscMatlabEngine e;
49   PetscBool         flg = PETSC_FALSE;
50   char              lhost[64];
51   PetscFunctionBegin;
52   if (MATLABENGINE_CLASSID == -1) PetscCall(PetscClassIdRegister("MATLAB Engine", &MATLABENGINE_CLASSID));
53   PetscCall(PetscHeaderCreate(e, MATLABENGINE_CLASSID, "MatlabEngine", "MATLAB Engine", "Sys", comm, PetscMatlabEngineDestroy, NULL));
54 
55   if (!host) {
56     PetscCall(PetscOptionsGetString(NULL, NULL, "-matlab_engine_host", lhost, sizeof(lhost), &flg));
57     if (flg) host = lhost;
58   }
59   flg = PETSC_FALSE;
60   PetscCall(PetscOptionsGetBool(NULL, NULL, "-matlab_engine_graphics", &flg, NULL));
61 
62   if (host) {
63     PetscCall(PetscInfo(0, "Starting MATLAB engine on %s\n", host));
64     PetscCall(PetscStrncpy(buffer, "ssh ", sizeof(buffer)));
65     PetscCall(PetscStrlcat(buffer, host, sizeof(buffer)));
66     PetscCall(PetscStrlcat(buffer, " \"", sizeof(buffer)));
67     PetscCall(PetscStrlcat(buffer, PETSC_MATLAB_COMMAND, sizeof(buffer)));
68     if (!flg) PetscCall(PetscStrlcat(buffer, " -nodisplay ", sizeof(buffer)));
69     PetscCall(PetscStrlcat(buffer, " -nosplash ", sizeof(buffer)));
70     PetscCall(PetscStrlcat(buffer, "\"", sizeof(buffer)));
71   } else {
72     PetscCall(PetscStrncpy(buffer, PETSC_MATLAB_COMMAND, sizeof(buffer)));
73     if (!flg) PetscCall(PetscStrlcat(buffer, " -nodisplay ", sizeof(buffer)));
74     PetscCall(PetscStrlcat(buffer, " -nosplash ", sizeof(buffer)));
75   }
76   PetscCall(PetscInfo(0, "Starting MATLAB engine with command %s\n", buffer));
77   e->ep = engOpen(buffer);
78   PetscCheck(e->ep, PETSC_COMM_SELF, PETSC_ERR_LIB, "Unable to start MATLAB engine with %s", buffer);
79   engOutputBuffer(e->ep, e->buffer, sizeof(e->buffer));
80   if (host) PetscCall(PetscInfo(0, "Started MATLAB engine on %s\n", host));
81   else PetscCall(PetscInfo(0, "Started MATLAB engine\n"));
82 
83   PetscCallMPI(MPI_Comm_rank(comm, &rank));
84   PetscCallMPI(MPI_Comm_size(comm, &size));
85   PetscCall(PetscMatlabEngineEvaluate(e, "MPI_Comm_rank = %d; MPI_Comm_size = %d;\n", rank, size));
86   /* work around bug in MATLAB R2021b https://www.mathworks.com/matlabcentral/answers/1566246-got-error-using-exit-in-nodesktop-mode */
87   PetscCall(PetscMatlabEngineEvaluate(e, "settings"));
88   *mengine = e;
89   PetscFunctionReturn(PETSC_SUCCESS);
90 }
91 
92 /*@
93   PetscMatlabEngineDestroy - Shuts down a MATLAB engine.
94 
95   Collective
96 
97   Input Parameter:
98 . v - the engine
99 
100   Level: advanced
101 
102 .seealso: `PetscMatlabEngineCreate()`, `PetscMatlabEnginePut()`, `PetscMatlabEngineGet()`,
103           `PetscMatlabEngineEvaluate()`, `PetscMatlabEngineGetOutput()`, `PetscMatlabEnginePrintOutput()`,
104           `PETSC_MATLAB_ENGINE_()`, `PetscMatlabEnginePutArray()`, `PetscMatlabEngineGetArray()`, `PetscMatlabEngine`
105 @*/
106 PetscErrorCode PetscMatlabEngineDestroy(PetscMatlabEngine *v)
107 {
108   int err;
109 
110   PetscFunctionBegin;
111   if (!*v) PetscFunctionReturn(PETSC_SUCCESS);
112   PetscValidHeaderSpecific(*v, MATLABENGINE_CLASSID, 1);
113   if (--((PetscObject)(*v))->refct > 0) PetscFunctionReturn(PETSC_SUCCESS);
114   PetscCall(PetscInfo(0, "Stopping MATLAB engine\n"));
115   err = engClose((*v)->ep);
116   PetscCheck(!err, PETSC_COMM_SELF, PETSC_ERR_LIB, "Error closing Matlab engine");
117   PetscCall(PetscInfo(0, "MATLAB engine stopped\n"));
118   PetscCall(PetscHeaderDestroy(v));
119   PetscFunctionReturn(PETSC_SUCCESS);
120 }
121 
122 /*@C
123   PetscMatlabEngineEvaluate - Evaluates a string in MATLAB
124 
125   Not Collective
126 
127   Input Parameters:
128 + mengine - the MATLAB engine
129 - string  - format as in a printf()
130 
131   Notes:
132   Run the PETSc program with -info to always have printed back MATLAB's response to the string evaluation
133 
134   If the string utilizes a MATLAB script that needs to run in the engine, the script must be available via MATLABPATH on that machine.
135 
136   Level: advanced
137 
138 .seealso: `PetscMatlabEngineDestroy()`, `PetscMatlabEnginePut()`, `PetscMatlabEngineGet()`,
139           `PetscMatlabEngineCreate()`, `PetscMatlabEngineGetOutput()`, `PetscMatlabEnginePrintOutput()`,
140           `PETSC_MATLAB_ENGINE_()`, `PetscMatlabEnginePutArray()`, `PetscMatlabEngineGetArray()`, `PetscMatlabEngine`
141 @*/
142 PetscErrorCode PetscMatlabEngineEvaluate(PetscMatlabEngine mengine, const char string[], ...)
143 {
144   va_list Argp;
145   char    buffer[1024];
146   size_t  fullLength;
147 
148   PetscFunctionBegin;
149   va_start(Argp, string);
150   PetscCall(PetscVSNPrintf(buffer, sizeof(buffer) - 9 - 5, string, &fullLength, Argp));
151   va_end(Argp);
152 
153   PetscCall(PetscInfo(0, "Evaluating MATLAB string: %s\n", buffer));
154   engEvalString(mengine->ep, buffer);
155   PetscCall(PetscInfo(0, "Done evaluating MATLAB string: %s\n", buffer));
156   PetscCall(PetscInfo(0, "  MATLAB output message: %s\n", mengine->buffer));
157 
158   /*
159      Check for error in MATLAB: indicated by ? as first character in engine->buffer
160   */
161   PetscCheck(mengine->buffer[4] != '?', PETSC_COMM_SELF, PETSC_ERR_LIB, "Error in evaluating MATLAB command:%s\n%s", string, mengine->buffer);
162   PetscFunctionReturn(PETSC_SUCCESS);
163 }
164 
165 /*@C
166   PetscMatlabEngineGetOutput - Gets a string buffer where the MATLAB output is
167   printed
168 
169   Not Collective
170 
171   Input Parameter:
172 . mengine - the MATLAB engine
173 
174   Output Parameter:
175 . string - buffer where MATLAB output is printed
176 
177   Level: advanced
178 
179 .seealso: `PetscMatlabEngineDestroy()`, `PetscMatlabEnginePut()`, `PetscMatlabEngineGet()`,
180           `PetscMatlabEngineEvaluate()`, `PetscMatlabEngineCreate()`, `PetscMatlabEnginePrintOutput()`,
181           `PETSC_MATLAB_ENGINE_()`, `PetscMatlabEnginePutArray()`, `PetscMatlabEngineGetArray()`, `PetscMatlabEngine`
182 @*/
183 PetscErrorCode PetscMatlabEngineGetOutput(PetscMatlabEngine mengine, char **string)
184 {
185   PetscFunctionBegin;
186   PetscCheck(mengine, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Null argument: probably PETSC_MATLAB_ENGINE_() failed");
187   *string = mengine->buffer;
188   PetscFunctionReturn(PETSC_SUCCESS);
189 }
190 
191 /*@C
192   PetscMatlabEnginePrintOutput - prints the output from MATLAB to an ASCII file
193 
194   Collective
195 
196   Input Parameters:
197 + mengine - the MATLAB engine
198 - fd      - the file
199 
200   Level: advanced
201 
202 .seealso: `PetscMatlabEngineDestroy()`, `PetscMatlabEnginePut()`, `PetscMatlabEngineGet()`,
203           `PetscMatlabEngineEvaluate()`, `PetscMatlabEngineGetOutput()`, `PetscMatlabEngineCreate()`,
204           `PETSC_MATLAB_ENGINE_()`, `PetscMatlabEnginePutArray()`, `PetscMatlabEngineGetArray()`, `PetscMatlabEngine`
205 @*/
206 PetscErrorCode PetscMatlabEnginePrintOutput(PetscMatlabEngine mengine, FILE *fd)
207 {
208   PetscMPIInt rank;
209 
210   PetscFunctionBegin;
211   PetscCheck(mengine, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Null argument: probably PETSC_MATLAB_ENGINE_() failed");
212   PetscCallMPI(MPI_Comm_rank(PetscObjectComm((PetscObject)mengine), &rank));
213   PetscCall(PetscSynchronizedFPrintf(PetscObjectComm((PetscObject)mengine), fd, "[%d]%s", rank, mengine->buffer));
214   PetscCall(PetscSynchronizedFlush(PetscObjectComm((PetscObject)mengine), fd));
215   PetscFunctionReturn(PETSC_SUCCESS);
216 }
217 
218 /*@
219   PetscMatlabEnginePut - Puts a Petsc object, such as a `Mat` or `Vec` into the MATLAB space. For parallel objects,
220   each processor's part is put in a separate  MATLAB process.
221 
222   Collective
223 
224   Input Parameters:
225 + mengine - the MATLAB engine
226 - obj     - the PETSc object, for example Vec
227 
228   Level: advanced
229 
230   Note:
231   `Mat`s transferred between PETSc and MATLAB and vis versa are transposed in the other space
232   (this is because MATLAB uses compressed column format and PETSc uses compressed row format)
233 
234 .seealso: `PetscMatlabEngineDestroy()`, `PetscMatlabEngineCreate()`, `PetscMatlabEngineGet()`,
235           `PetscMatlabEngineEvaluate()`, `PetscMatlabEngineGetOutput()`, `PetscMatlabEnginePrintOutput()`,
236           `PETSC_MATLAB_ENGINE_()`, `PetscMatlabEnginePutArray()`, `PetscMatlabEngineGetArray()`, `PetscMatlabEngine`
237 @*/
238 PetscErrorCode PetscMatlabEnginePut(PetscMatlabEngine mengine, PetscObject obj)
239 {
240   PetscErrorCode (*put)(PetscObject, void *);
241 
242   PetscFunctionBegin;
243   PetscCheck(mengine, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Null argument: probably PETSC_MATLAB_ENGINE_() failed");
244   PetscCall(PetscObjectQueryFunction(obj, "PetscMatlabEnginePut_C", &put));
245   PetscCheck(put, PETSC_COMM_SELF, PETSC_ERR_SUP, "Object %s cannot be put into MATLAB engine", obj->class_name);
246   PetscCall(PetscInfo(0, "Putting MATLAB object\n"));
247   PetscCall((*put)(obj, mengine->ep));
248   PetscCall(PetscInfo(0, "Put MATLAB object: %s\n", obj->name));
249   PetscFunctionReturn(PETSC_SUCCESS);
250 }
251 
252 /*@
253   PetscMatlabEngineGet - Gets a variable from MATLAB into a PETSc object.
254 
255   Collective
256 
257   Input Parameters:
258 + mengine - the MATLAB engine
259 - obj     - the PETSc object, for example a `Vec`
260 
261   Level: advanced
262 
263   Note:
264   `Mat`s transferred between PETSc and MATLAB and vis versa are transposed in the other space
265   (this is because MATLAB uses compressed column format and PETSc uses compressed row format)
266 
267 .seealso: `PetscMatlabEngineDestroy()`, `PetscMatlabEnginePut()`, `PetscMatlabEngineCreate()`,
268           `PetscMatlabEngineEvaluate()`, `PetscMatlabEngineGetOutput()`, `PetscMatlabEnginePrintOutput()`,
269           `PETSC_MATLAB_ENGINE_()`, `PetscMatlabEnginePutArray()`, `PetscMatlabEngineGetArray()`, `PetscMatlabEngine`
270 @*/
271 PetscErrorCode PetscMatlabEngineGet(PetscMatlabEngine mengine, PetscObject obj)
272 {
273   PetscErrorCode (*get)(PetscObject, void *);
274 
275   PetscFunctionBegin;
276   PetscCheck(mengine, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Null argument: probably PETSC_MATLAB_ENGINE_() failed");
277   PetscCheck(obj->name, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE, "Cannot get object that has no name");
278   PetscCall(PetscObjectQueryFunction(obj, "PetscMatlabEngineGet_C", &get));
279   PetscCheck(get, PETSC_COMM_SELF, PETSC_ERR_SUP, "Object %s cannot be gotten from MATLAB engine", obj->class_name);
280   PetscCall(PetscInfo(0, "Getting MATLAB object\n"));
281   PetscCall((*get)(obj, mengine->ep));
282   PetscCall(PetscInfo(0, "Got MATLAB object: %s\n", obj->name));
283   PetscFunctionReturn(PETSC_SUCCESS);
284 }
285 
286 /*
287     The variable Petsc_Matlab_Engine_keyval is used to indicate an MPI attribute that
288   is attached to a communicator, in this case the attribute is a PetscMatlabEngine
289 */
290 static PetscMPIInt Petsc_Matlab_Engine_keyval = MPI_KEYVAL_INVALID;
291 
292 /*@C
293    PETSC_MATLAB_ENGINE_ - Creates a MATLAB engine on each process in a communicator.
294 
295    Not Collective
296 
297    Input Parameter:
298 .  comm - the MPI communicator to share the engine
299 
300    Options Database Key:
301 .  -matlab_engine_host - hostname on which to run MATLAB, one must be able to ssh to this host
302 
303    Level: developer
304 
305    Note:
306    Unlike almost all other PETSc routines, this does not return
307    an error code. Usually used in the form
308 $      PetscMatlabEngineYYY(XXX object, PETSC_MATLAB_ENGINE_(comm));
309 
310 .seealso: `PetscMatlabEngineDestroy()`, `PetscMatlabEnginePut()`, `PetscMatlabEngineGet()`,
311           `PetscMatlabEngineEvaluate()`, `PetscMatlabEngineGetOutput()`, `PetscMatlabEnginePrintOutput()`,
312           `PetscMatlabEngineCreate()`, `PetscMatlabEnginePutArray()`, `PetscMatlabEngineGetArray()`, `PetscMatlabEngine`,
313           `PETSC_MATLAB_ENGINE_WORLD`, `PETSC_MATLAB_ENGINE_SELF`
314 @*/
315 PetscMatlabEngine PETSC_MATLAB_ENGINE_(MPI_Comm comm)
316 {
317   PetscErrorCode    ierr;
318   PetscBool         flg;
319   PetscMatlabEngine mengine;
320 
321   PetscFunctionBegin;
322   if (Petsc_Matlab_Engine_keyval == MPI_KEYVAL_INVALID) {
323     ierr = MPI_Comm_create_keyval(MPI_COMM_NULL_COPY_FN, MPI_COMM_NULL_DELETE_FN, &Petsc_Matlab_Engine_keyval, 0);
324     if (ierr) {
325       PetscError(PETSC_COMM_SELF, __LINE__, "PETSC_MATLAB_ENGINE_", __FILE__, PETSC_ERR_PLIB, PETSC_ERROR_INITIAL, " ");
326       PetscFunctionReturn(NULL);
327     }
328   }
329   ierr = MPI_Comm_get_attr(comm, Petsc_Matlab_Engine_keyval, (void **)&mengine, (int *)&flg);
330   if (ierr) {
331     PetscError(PETSC_COMM_SELF, __LINE__, "PETSC_MATLAB_ENGINE_", __FILE__, PETSC_ERR_PLIB, PETSC_ERROR_INITIAL, " ");
332     PetscFunctionReturn(NULL);
333   }
334   if (!flg) { /* viewer not yet created */
335     ierr = PetscMatlabEngineCreate(comm, NULL, &mengine);
336     if (ierr) {
337       PetscError(PETSC_COMM_SELF, __LINE__, "PETSC_MATLAB_ENGINE_", __FILE__, PETSC_ERR_PLIB, PETSC_ERROR_REPEAT, " ");
338       PetscFunctionReturn(NULL);
339     }
340     ierr = PetscObjectRegisterDestroy((PetscObject)mengine);
341     if (ierr) {
342       PetscError(PETSC_COMM_SELF, __LINE__, "PETSC_MATLAB_ENGINE_", __FILE__, PETSC_ERR_PLIB, PETSC_ERROR_REPEAT, " ");
343       PetscFunctionReturn(NULL);
344     }
345     ierr = MPI_Comm_set_attr(comm, Petsc_Matlab_Engine_keyval, mengine);
346     if (ierr) {
347       PetscError(PETSC_COMM_SELF, __LINE__, "PETSC_MATLAB_ENGINE_", __FILE__, PETSC_ERR_PLIB, PETSC_ERROR_INITIAL, " ");
348       PetscFunctionReturn(NULL);
349     }
350   }
351   PetscFunctionReturn(mengine);
352 }
353 
354 /*@C
355   PetscMatlabEnginePutArray - Puts an array into the MATLAB space, treating it as a Fortran style (column major ordering) array. For parallel objects,
356   each processors part is put in a separate  MATLAB process.
357 
358   Collective
359 
360   Input Parameters:
361 + mengine - the MATLAB engine
362 . m       - the x dimension of the array
363 . n       - the y dimension of the array
364 . array   - the array (represented in one dimension)
365 - name    - the name of the array
366 
367   Level: advanced
368 
369 .seealso: `PetscMatlabEngineDestroy()`, `PetscMatlabEngineCreate()`, `PetscMatlabEngineGet()`,
370           `PetscMatlabEngineEvaluate()`, `PetscMatlabEngineGetOutput()`, `PetscMatlabEnginePrintOutput()`,
371           `PETSC_MATLAB_ENGINE_()`, `PetscMatlabEnginePut()`, `PetscMatlabEngineGetArray()`, `PetscMatlabEngine`
372 @*/
373 PetscErrorCode PetscMatlabEnginePutArray(PetscMatlabEngine mengine, int m, int n, const PetscScalar *array, const char name[])
374 {
375   mxArray *mat;
376 
377   PetscFunctionBegin;
378   PetscCheck(mengine, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Null argument: probably PETSC_MATLAB_ENGINE_() failed");
379   PetscCall(PetscInfo(0, "Putting MATLAB array %s\n", name));
380 #if !defined(PETSC_USE_COMPLEX)
381   mat = mxCreateDoubleMatrix(m, n, mxREAL);
382 #else
383   mat = mxCreateDoubleMatrix(m, n, mxCOMPLEX);
384 #endif
385   PetscCall(PetscArraycpy(mxGetPr(mat), array, m * n));
386   engPutVariable(mengine->ep, name, mat);
387 
388   PetscCall(PetscInfo(0, "Put MATLAB array %s\n", name));
389   PetscFunctionReturn(PETSC_SUCCESS);
390 }
391 
392 /*@C
393   PetscMatlabEngineGetArray - Gets a variable from MATLAB into an array
394 
395   Not Collective
396 
397   Input Parameters:
398 + mengine - the MATLAB engine
399 . m       - the x dimension of the array
400 . n       - the y dimension of the array
401 . array   - the array (represented in one dimension)
402 - name    - the name of the array
403 
404   Level: advanced
405 
406 .seealso: `PetscMatlabEngineDestroy()`, `PetscMatlabEnginePut()`, `PetscMatlabEngineCreate()`,
407           `PetscMatlabEngineEvaluate()`, `PetscMatlabEngineGetOutput()`, `PetscMatlabEnginePrintOutput()`,
408           `PETSC_MATLAB_ENGINE_()`, `PetscMatlabEnginePutArray()`, `PetscMatlabEngineGet()`, `PetscMatlabEngine`
409 @*/
410 PetscErrorCode PetscMatlabEngineGetArray(PetscMatlabEngine mengine, int m, int n, PetscScalar *array, const char name[])
411 {
412   mxArray *mat;
413 
414   PetscFunctionBegin;
415   PetscCheck(mengine, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Null argument: probably PETSC_MATLAB_ENGINE_() failed");
416   PetscCall(PetscInfo(0, "Getting MATLAB array %s\n", name));
417   mat = engGetVariable(mengine->ep, name);
418   PetscCheck(mat, PETSC_COMM_SELF, PETSC_ERR_LIB, "Unable to get array %s from matlab", name);
419   PetscCheck(mxGetM(mat) == (size_t)m, PETSC_COMM_SELF, PETSC_ERR_LIB, "Array %s in MATLAB first dimension %d does not match requested size %d", name, (int)mxGetM(mat), m);
420   PetscCheck(mxGetN(mat) == (size_t)n, PETSC_COMM_SELF, PETSC_ERR_LIB, "Array %s in MATLAB second dimension %d does not match requested size %d", name, (int)mxGetN(mat), m);
421   PetscCall(PetscArraycpy(array, mxGetPr(mat), m * n));
422   PetscCall(PetscInfo(0, "Got MATLAB array %s\n", name));
423   PetscFunctionReturn(PETSC_SUCCESS);
424 }
425