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