xref: /petsc/src/sys/classes/matlabengine/matlab.c (revision 0226ec35a99031c5bdd82a492055a25793359ffb)
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    Notes:
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    One must `./configure` PETSc with  `--with-matlab [-with-matlab-dir=matlab_root_directory]` to
37    use this capability
38 
39    Level: advanced
40 
41 .seealso: `PetscMatlabEngineDestroy()`, `PetscMatlabEnginePut()`, `PetscMatlabEngineGet()`,
42           `PetscMatlabEngineEvaluate()`, `PetscMatlabEngineGetOutput()`, `PetscMatlabEnginePrintOutput()`,
43           `PETSC_MATLAB_ENGINE_()`, `PetscMatlabEnginePutArray()`, `PetscMatlabEngineGetArray()`, `PetscMatlabEngine`
44 @*/
45 PetscErrorCode PetscMatlabEngineCreate(MPI_Comm comm, const char host[], PetscMatlabEngine *mengine)
46 {
47   PetscMPIInt       rank, size;
48   char              buffer[256];
49   PetscMatlabEngine e;
50   PetscBool         flg = PETSC_FALSE;
51   char              lhost[64];
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 Parameters:
99 .  e  - 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\n%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 -    object - 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 -    object - 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,n - the dimensions 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,n - the dimensions of the array
400 .    array - the array (represented in one dimension)
401 -    name - the name of the array
402 
403    Level: advanced
404 
405 .seealso: `PetscMatlabEngineDestroy()`, `PetscMatlabEnginePut()`, `PetscMatlabEngineCreate()`,
406           `PetscMatlabEngineEvaluate()`, `PetscMatlabEngineGetOutput()`, `PetscMatlabEnginePrintOutput()`,
407           `PETSC_MATLAB_ENGINE_()`, `PetscMatlabEnginePutArray()`, `PetscMatlabEngineGet()`, `PetscMatlabEngine`
408 @*/
409 PetscErrorCode PetscMatlabEngineGetArray(PetscMatlabEngine mengine, int m, int n, PetscScalar *array, const char name[])
410 {
411   mxArray *mat;
412 
413   PetscFunctionBegin;
414   PetscCheck(mengine, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Null argument: probably PETSC_MATLAB_ENGINE_() failed");
415   PetscCall(PetscInfo(0, "Getting MATLAB array %s\n", name));
416   mat = engGetVariable(mengine->ep, name);
417   PetscCheck(mat, PETSC_COMM_SELF, PETSC_ERR_LIB, "Unable to get array %s from matlab", name);
418   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);
419   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);
420   PetscCall(PetscArraycpy(array, mxGetPr(mat), m * n));
421   PetscCall(PetscInfo(0, "Got MATLAB array %s\n", name));
422   PetscFunctionReturn(PETSC_SUCCESS);
423 }
424