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