xref: /petsc/src/sys/classes/matlabengine/matlab.c (revision fc47f7259de0629fb6058a3a6076517fd44721f2)
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 /*@
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   PetscAssertPointer(mengine, 3);
54   if (MATLABENGINE_CLASSID == -1) PetscCall(PetscClassIdRegister("MATLAB Engine", &MATLABENGINE_CLASSID));
55 
56   PetscCall(PetscHeaderCreate(e, MATLABENGINE_CLASSID, "MatlabEngine", "MATLAB Engine", "Sys", comm, PetscMatlabEngineDestroy, NULL));
57   if (!host) {
58     PetscCall(PetscOptionsGetString(NULL, NULL, "-matlab_engine_host", lhost, sizeof(lhost), &flg));
59     if (flg) host = lhost;
60   }
61   flg = PETSC_FALSE;
62   PetscCall(PetscOptionsGetBool(NULL, NULL, "-matlab_engine_graphics", &flg, NULL));
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   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 %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, const 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 .vb
309   PetscMatlabEngineYYY(XXX object, PETSC_MATLAB_ENGINE_(comm));
310 .ve
311 
312 .seealso: `PetscMatlabEngineDestroy()`, `PetscMatlabEnginePut()`, `PetscMatlabEngineGet()`,
313           `PetscMatlabEngineEvaluate()`, `PetscMatlabEngineGetOutput()`, `PetscMatlabEnginePrintOutput()`,
314           `PetscMatlabEngineCreate()`, `PetscMatlabEnginePutArray()`, `PetscMatlabEngineGetArray()`, `PetscMatlabEngine`,
315           `PETSC_MATLAB_ENGINE_WORLD`, `PETSC_MATLAB_ENGINE_SELF`
316 @*/
317 PetscMatlabEngine PETSC_MATLAB_ENGINE_(MPI_Comm comm)
318 {
319   PetscErrorCode    ierr;
320   PetscBool         flg;
321   PetscMatlabEngine mengine;
322 
323   PetscFunctionBegin;
324   if (Petsc_Matlab_Engine_keyval == MPI_KEYVAL_INVALID) {
325     ierr = MPI_Comm_create_keyval(MPI_COMM_NULL_COPY_FN, MPI_COMM_NULL_DELETE_FN, &Petsc_Matlab_Engine_keyval, 0);
326     if (ierr) {
327       PetscError(PETSC_COMM_SELF, __LINE__, "PETSC_MATLAB_ENGINE_", __FILE__, PETSC_ERR_PLIB, PETSC_ERROR_INITIAL, " ");
328       PetscFunctionReturn(NULL);
329     }
330   }
331   ierr = MPI_Comm_get_attr(comm, Petsc_Matlab_Engine_keyval, (void **)&mengine, (int *)&flg);
332   if (ierr) {
333     PetscError(PETSC_COMM_SELF, __LINE__, "PETSC_MATLAB_ENGINE_", __FILE__, PETSC_ERR_PLIB, PETSC_ERROR_INITIAL, " ");
334     PetscFunctionReturn(NULL);
335   }
336   if (!flg) { /* viewer not yet created */
337     ierr = PetscMatlabEngineCreate(comm, NULL, &mengine);
338     if (ierr) {
339       PetscError(PETSC_COMM_SELF, __LINE__, "PETSC_MATLAB_ENGINE_", __FILE__, PETSC_ERR_PLIB, PETSC_ERROR_REPEAT, " ");
340       PetscFunctionReturn(NULL);
341     }
342     ierr = PetscObjectRegisterDestroy((PetscObject)mengine);
343     if (ierr) {
344       PetscError(PETSC_COMM_SELF, __LINE__, "PETSC_MATLAB_ENGINE_", __FILE__, PETSC_ERR_PLIB, PETSC_ERROR_REPEAT, " ");
345       PetscFunctionReturn(NULL);
346     }
347     ierr = MPI_Comm_set_attr(comm, Petsc_Matlab_Engine_keyval, mengine);
348     if (ierr) {
349       PetscError(PETSC_COMM_SELF, __LINE__, "PETSC_MATLAB_ENGINE_", __FILE__, PETSC_ERR_PLIB, PETSC_ERROR_INITIAL, " ");
350       PetscFunctionReturn(NULL);
351     }
352   }
353   PetscFunctionReturn(mengine);
354 }
355 
356 /*@
357   PetscMatlabEnginePutArray - Puts an array into the MATLAB space, treating it as a Fortran style (column major ordering) array. For parallel objects,
358   each processors part is put in a separate  MATLAB process.
359 
360   Collective
361 
362   Input Parameters:
363 + mengine - the MATLAB engine
364 . m       - the x dimension of the array
365 . n       - the y dimension of the array
366 . array   - the array (represented in one dimension)
367 - name    - the name of the array
368 
369   Level: advanced
370 
371 .seealso: `PetscMatlabEngineDestroy()`, `PetscMatlabEngineCreate()`, `PetscMatlabEngineGet()`,
372           `PetscMatlabEngineEvaluate()`, `PetscMatlabEngineGetOutput()`, `PetscMatlabEnginePrintOutput()`,
373           `PETSC_MATLAB_ENGINE_()`, `PetscMatlabEnginePut()`, `PetscMatlabEngineGetArray()`, `PetscMatlabEngine`
374 @*/
375 PetscErrorCode PetscMatlabEnginePutArray(PetscMatlabEngine mengine, int m, int n, const PetscScalar array[], const char name[])
376 {
377   mxArray *mat;
378 
379   PetscFunctionBegin;
380   PetscCheck(mengine, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Null argument: probably PETSC_MATLAB_ENGINE_() failed");
381   PetscCall(PetscInfo(0, "Putting MATLAB array %s\n", name));
382 #if !defined(PETSC_USE_COMPLEX)
383   mat = mxCreateDoubleMatrix(m, n, mxREAL);
384 #else
385   mat = mxCreateDoubleMatrix(m, n, mxCOMPLEX);
386 #endif
387   PetscCall(PetscArraycpy(mxGetPr(mat), array, m * n));
388   engPutVariable(mengine->ep, name, mat);
389 
390   PetscCall(PetscInfo(0, "Put MATLAB array %s\n", name));
391   PetscFunctionReturn(PETSC_SUCCESS);
392 }
393 
394 /*@
395   PetscMatlabEngineGetArray - Gets a variable from MATLAB into an array
396 
397   Not Collective
398 
399   Input Parameters:
400 + mengine - the MATLAB engine
401 . m       - the x dimension of the array
402 . n       - the y dimension of the array
403 . array   - the array (represented in one dimension), much be large enough to hold all the data
404 - name    - the name of the array
405 
406   Level: advanced
407 
408 .seealso: `PetscMatlabEngineDestroy()`, `PetscMatlabEnginePut()`, `PetscMatlabEngineCreate()`,
409           `PetscMatlabEngineEvaluate()`, `PetscMatlabEngineGetOutput()`, `PetscMatlabEnginePrintOutput()`,
410           `PETSC_MATLAB_ENGINE_()`, `PetscMatlabEnginePutArray()`, `PetscMatlabEngineGet()`, `PetscMatlabEngine`
411 @*/
412 PetscErrorCode PetscMatlabEngineGetArray(PetscMatlabEngine mengine, int m, int n, PetscScalar array[], const char name[])
413 {
414   mxArray *mat;
415 
416   PetscFunctionBegin;
417   PetscCheck(mengine, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Null argument: probably PETSC_MATLAB_ENGINE_() failed");
418   PetscCall(PetscInfo(0, "Getting MATLAB array %s\n", name));
419   mat = engGetVariable(mengine->ep, name);
420   PetscCheck(mat, PETSC_COMM_SELF, PETSC_ERR_LIB, "Unable to get array %s from matlab", name);
421   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);
422   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);
423   PetscCall(PetscArraycpy(array, mxGetPr(mat), m * n));
424   PetscCall(PetscInfo(0, "Got MATLAB array %s\n", name));
425   PetscFunctionReturn(PETSC_SUCCESS);
426 }
427