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