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 @*/
PetscMatlabEngineCreate(MPI_Comm comm,const char host[],PetscMatlabEngine * mengine)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 @*/
PetscMatlabEngineDestroy(PetscMatlabEngine * v)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 @*/
PetscMatlabEngineEvaluate(PetscMatlabEngine mengine,const char string[],...)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 @*/
PetscMatlabEngineGetOutput(PetscMatlabEngine mengine,const char * string[])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 @*/
PetscMatlabEnginePrintOutput(PetscMatlabEngine mengine,FILE * fd)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 @*/
PetscMatlabEnginePut(PetscMatlabEngine mengine,PetscObject obj)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 @*/
PetscMatlabEngineGet(PetscMatlabEngine mengine,PetscObject obj)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 @*/
PETSC_MATLAB_ENGINE_(MPI_Comm comm)317 PetscMatlabEngine PETSC_MATLAB_ENGINE_(MPI_Comm comm)
318 {
319 PetscErrorCode ierr;
320 PetscMPIInt iflg;
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, &iflg);
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 (!iflg) { /* 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 @*/
PetscMatlabEnginePutArray(PetscMatlabEngine mengine,int m,int n,const PetscScalar array[],const char name[])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 @*/
PetscMatlabEngineGetArray(PetscMatlabEngine mengine,int m,int n,PetscScalar array[],const char name[])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