py-pluto 1.1.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. pyPLUTO/__init__.py +22 -0
  2. pyPLUTO/amr.py +745 -0
  3. pyPLUTO/baseloadmixin.py +258 -0
  4. pyPLUTO/baseloadstate.py +45 -0
  5. pyPLUTO/codes/echo_load.py +161 -0
  6. pyPLUTO/configure.py +261 -0
  7. pyPLUTO/gui/config.py +174 -0
  8. pyPLUTO/gui/custom_var.py +435 -0
  9. pyPLUTO/gui/globals.py +108 -0
  10. pyPLUTO/gui/main.py +17 -0
  11. pyPLUTO/gui/main_window.py +177 -0
  12. pyPLUTO/gui/panels.py +66 -0
  13. pyPLUTO/gui/utils.py +273 -0
  14. pyPLUTO/h_pypluto.py +84 -0
  15. pyPLUTO/image.py +302 -0
  16. pyPLUTO/imagefuncs/colorbar.py +240 -0
  17. pyPLUTO/imagefuncs/contour.py +254 -0
  18. pyPLUTO/imagefuncs/create_axes.py +464 -0
  19. pyPLUTO/imagefuncs/display.py +306 -0
  20. pyPLUTO/imagefuncs/figure.py +395 -0
  21. pyPLUTO/imagefuncs/imagetools.py +487 -0
  22. pyPLUTO/imagefuncs/interactive.py +403 -0
  23. pyPLUTO/imagefuncs/legend.py +250 -0
  24. pyPLUTO/imagefuncs/plot.py +311 -0
  25. pyPLUTO/imagefuncs/range.py +242 -0
  26. pyPLUTO/imagefuncs/scatter.py +270 -0
  27. pyPLUTO/imagefuncs/set_axis.py +497 -0
  28. pyPLUTO/imagefuncs/streamplot.py +297 -0
  29. pyPLUTO/imagefuncs/zoom.py +428 -0
  30. pyPLUTO/imagemixin.py +259 -0
  31. pyPLUTO/imagestate.py +45 -0
  32. pyPLUTO/load.py +447 -0
  33. pyPLUTO/loadfuncs/baseloadtools.py +71 -0
  34. pyPLUTO/loadfuncs/codeselection.py +48 -0
  35. pyPLUTO/loadfuncs/defpluto.py +123 -0
  36. pyPLUTO/loadfuncs/descriptor.py +102 -0
  37. pyPLUTO/loadfuncs/findfiles.py +182 -0
  38. pyPLUTO/loadfuncs/findformat.py +245 -0
  39. pyPLUTO/loadfuncs/initload.py +203 -0
  40. pyPLUTO/loadfuncs/loadvars.py +227 -0
  41. pyPLUTO/loadfuncs/offsetdata.py +87 -0
  42. pyPLUTO/loadfuncs/offsetfluid.py +408 -0
  43. pyPLUTO/loadfuncs/read_files.py +213 -0
  44. pyPLUTO/loadfuncs/readdata.py +619 -0
  45. pyPLUTO/loadfuncs/readdata_old.py +567 -0
  46. pyPLUTO/loadfuncs/readdefplini.py +101 -0
  47. pyPLUTO/loadfuncs/readfluid.py +479 -0
  48. pyPLUTO/loadfuncs/readformat.py +277 -0
  49. pyPLUTO/loadfuncs/readgridalone.py +224 -0
  50. pyPLUTO/loadfuncs/readgridfile.py +255 -0
  51. pyPLUTO/loadfuncs/readgridout.py +451 -0
  52. pyPLUTO/loadfuncs/readpart.py +419 -0
  53. pyPLUTO/loadfuncs/readtab.py +105 -0
  54. pyPLUTO/loadfuncs/write_files.py +283 -0
  55. pyPLUTO/loadmixin.py +419 -0
  56. pyPLUTO/loadpart.py +233 -0
  57. pyPLUTO/loadstate.py +68 -0
  58. pyPLUTO/newload.py +81 -0
  59. pyPLUTO/pytools.py +145 -0
  60. pyPLUTO/toolfuncs/findlines.py +551 -0
  61. pyPLUTO/toolfuncs/fourier.py +149 -0
  62. pyPLUTO/toolfuncs/nabla.py +676 -0
  63. pyPLUTO/toolfuncs/parttools.py +152 -0
  64. pyPLUTO/toolfuncs/transform.py +638 -0
  65. pyPLUTO/utils/annotator.py +27 -0
  66. pyPLUTO/utils/inspector.py +145 -0
  67. pyPLUTO/utils/make_docstrings.py +3 -0
  68. py_pluto-1.1.4.dist-info/METADATA +218 -0
  69. py_pluto-1.1.4.dist-info/RECORD +73 -0
  70. py_pluto-1.1.4.dist-info/WHEEL +5 -0
  71. py_pluto-1.1.4.dist-info/entry_points.txt +2 -0
  72. py_pluto-1.1.4.dist-info/licenses/LICENSE +27 -0
  73. py_pluto-1.1.4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,567 @@
1
+ import warnings
2
+ from pathlib import Path
3
+
4
+ import numpy as np
5
+
6
+ from ..h_pypluto import makelist
7
+
8
+
9
+ def _load_variables(
10
+ self,
11
+ vars: str | list[str] | bool | None,
12
+ i: int,
13
+ exout: int,
14
+ endian: str | None,
15
+ ) -> None:
16
+ """Loads the variables in the class. The function checks if the
17
+ variables to be loaded are valid and then loads them. If the
18
+ variables are not valid, an error is raised. If the variables are
19
+ valid, the function loads them in the class through memory mapping.
20
+ The offset and shape of each variable is computed depenging on the
21
+ format and typefile characteristics. In case the files are
22
+ standalone, the relevand time and grid information is loaded.
23
+
24
+ Returns
25
+ -------
26
+ - None
27
+
28
+ Parameters
29
+ ----------
30
+ - endian (not optional): bool
31
+ The endianess of the files. If True the endianess is big, otherwise it
32
+ is little.
33
+ - exout (not optional): int
34
+ The index of the output to be loaded.
35
+ - i (not optional): int
36
+ The index of the file to be loaded.
37
+ - vars (not optional): str | list[str] | bool | None, default True
38
+ If True all the variables are loaded, otherwise just a selection is
39
+ loaded.
40
+
41
+ ----
42
+
43
+ Examples
44
+ --------
45
+ - Example #1: Load all the variables
46
+
47
+ >>> _load_variables(True, 0, 0, True)
48
+
49
+ - Example #2: Load only the selected variables
50
+
51
+ >>> _load_variables(["rho", "vx1"], 0, 0, True)
52
+
53
+ - Example #3: Load all the variables (little endian)
54
+
55
+ >>> _load_variables(True, 0, 0, False)
56
+
57
+ - Example #4: Load all the variables from a specific output file
58
+
59
+ >>> _load_variables(True, 0, 1, True)
60
+
61
+ """
62
+ # Find the class name and find the single_file filepath
63
+ class_name: str = self.__class__.__name__
64
+ if class_name == "Load":
65
+ # If the class name is Load (single file), the filepath is data
66
+ self._filepath = self.pathdir / ("data" + self._d_info["endpath"][i])
67
+ elif class_name == "LoadPart":
68
+ # If the class name is LoadPart, the filepath is particles
69
+ self._filepath = self.pathdir / (
70
+ "particles" + self._d_info["endpath"][i]
71
+ )
72
+ else:
73
+ # If the class name is not recognized, raise an error
74
+ raise NameError("Invalid class name.")
75
+
76
+ # If files in single_file format, inspect the file
77
+ # or compute the offset and shape
78
+ if self._d_info["typefile"][i] == "single_file":
79
+ self._compute_offset(i, endian, exout, None)
80
+ if self.format == "hdf5":
81
+ return None
82
+
83
+ # Check if only specific variables should be loaded
84
+ if vars is True:
85
+ # If all the variables are to be loaded, the load_vars
86
+ # is set to the variables list
87
+ self._load_vars = self._d_info["varslist"][i]
88
+ elif vars is not None:
89
+ # If only specific variables are to be loaded, the load_vars
90
+ # becomes the list of the selected variables
91
+ self._load_vars = makelist(vars)
92
+ else:
93
+ # If no variables are to be loaded, return None (WIP)
94
+ return None
95
+
96
+ # If the format is tab, the data have been already loaded, so
97
+ # the function returns None
98
+ if self.format == "tab":
99
+ return None
100
+
101
+ # ERROR: TOO MANY OPEN FILES!!!
102
+ # This chunk of code is here simply to show how the variables were loaded
103
+ # in a preliminary version of the memory mapping procedure.
104
+ """
105
+ # Loop over the variables to be loaded
106
+ for j in self._load_vars:
107
+
108
+ # Change filepath, offset and shape in case of multiple_files
109
+ if self._d_info['typefile'][i] == 'multiple_files':
110
+ # If the files are multiple, the filepath is changed for each
111
+ # variable and the offset and shape are computed
112
+ self._filepath = self.pathdir / (j + self._d_info['endpath'][i])
113
+ self._compute_offset(i, endian, exout, j)
114
+
115
+ # Initialize the variables dictionary
116
+ self._init_vardict(j) if self._lennout != 1 else None
117
+
118
+ # Load the variable through memory mapping and store them in the class
119
+ scrh = np.memmap(self._filepath,self._d_info['binformat'][i],mode="r+",
120
+ offset=self._offset[j], shape = self._shape[j]).T
121
+ self._assign_var(exout, j, scrh)
122
+
123
+ """
124
+
125
+ # Compute the byte range for all variables in the current loop
126
+ if self._d_info["typefile"][i] == "single_file" and class_name == "Load":
127
+ start_byte = min(self._offset[j] for j in self._load_vars)
128
+ end_byte = max(
129
+ self._offset[j]
130
+ + np.prod(self._shape[j])
131
+ * np.dtype(self._d_info["binformat"][i]).itemsize
132
+ for j in self._load_vars
133
+ )
134
+
135
+ # Create a single memmap spanning the required byte range
136
+ # This is the original code that was used to load the variables
137
+
138
+ # file_memmap = np.memmap(
139
+ # self._filepath,
140
+ # dtype=self._d_info["binformat"][i],
141
+ # mode="r+",
142
+ # offset=start_byte,
143
+ # shape=(end_byte - start_byte),
144
+ # )
145
+
146
+ file_memmap = np.memmap(
147
+ self._filepath,
148
+ dtype="uint8", # Raw byte access for all data types
149
+ mode="r+",
150
+ offset=start_byte,
151
+ shape=(end_byte - start_byte),
152
+ )
153
+
154
+ # Loop over the variables to extract slices
155
+ for j in self._load_vars:
156
+ if self._d_info["typefile"][i] == "multiple_files":
157
+ self._filepath = self.pathdir / (j + self._d_info["endpath"][i])
158
+ self._compute_offset(i, endian, exout, j)
159
+ start_byte = self._offset[j]
160
+ # Reload memmap for the new file
161
+ file_memmap = np.memmap(
162
+ self._filepath,
163
+ self._d_info["binformat"][i],
164
+ mode="r+",
165
+ offset=self._offset[j],
166
+ shape=self._shape[j],
167
+ )
168
+
169
+ # Initialize the variables dictionary
170
+ self._init_vardict(j) if self._lennout != 1 else None
171
+
172
+ # Extract the relevant slice and reshape
173
+ if class_name == "Load":
174
+ # Calculate the relative start and end for this variable
175
+ rel_start = (
176
+ self._offset[j] - start_byte
177
+ ) # Offset relative to the memory-mapped file
178
+ rel_end = (
179
+ rel_start
180
+ + np.prod(self._shape[j])
181
+ * np.dtype(self._d_info["binformat"][i]).itemsize
182
+ )
183
+
184
+ # Step 3: Extract the raw data slice from the memory map
185
+ raw_data = file_memmap[rel_start:rel_end]
186
+
187
+ # View the slice with the desired dtype and reshape
188
+ scrh = np.ndarray(
189
+ shape=self._shape[j],
190
+ dtype=self._d_info["binformat"][i],
191
+ buffer=raw_data,
192
+ ).T
193
+
194
+ # Calculate the relative offset within the mapped range
195
+ # rel_start = (self._offset[j] - start_byte) // file_memmap.itemsize
196
+ # rel_end = rel_start + np.prod(self._shape[j])
197
+ # scrh = file_memmap[rel_start:rel_end].reshape(self._shape[j]).T
198
+
199
+ elif class_name == "LoadPart":
200
+ scrh = np.memmap(
201
+ self._filepath,
202
+ self._d_info["binformat"][i],
203
+ mode="r+",
204
+ offset=self._offset[j],
205
+ shape=self._shape[j],
206
+ ).T
207
+
208
+ # Assign the variable
209
+ self._assign_var(exout, j, scrh)
210
+
211
+ # End of function
212
+ return None
213
+
214
+
215
+ def _check_nout(self, nout: int | str | list[int | str]) -> None:
216
+ """Finds the number of datafile to be loaded. If nout is a list, the
217
+ function checks if the list contains the keyword 'last' or -1. If
218
+ so, the keyword is replaced with the last file number. If nout is a
219
+ string, the function checks if the string contains the keyword
220
+ 'last' or -1. If so, the keyword is replaced with the last file
221
+ number. If nout is an integer, the function returns a list
222
+ containing the integer. If nout is 'all', the function returns a
223
+ list containing all the file numbers.
224
+
225
+ Returns
226
+ -------
227
+ - None
228
+
229
+ Parameters
230
+ ----------
231
+ - nout (not optional): int | str | list[int|str]
232
+ The output file to be loaded.
233
+
234
+ ----
235
+
236
+ Examples
237
+ --------
238
+ - Example #1: Load the last file
239
+
240
+ >>> _check_nout("last")
241
+
242
+ - Example #2: Load the first file
243
+
244
+ >>> _check_nout(0)
245
+
246
+ - Example #3: Load all the files
247
+
248
+ >>> _check_nout("all")
249
+
250
+ - Example #4: Load multiple specific files
251
+
252
+ >>> _check_nout([0, 1, 2, 3])
253
+
254
+ """
255
+ # Assign the last possible output file
256
+ last: int = self.outlist.tolist()[-1]
257
+
258
+ # Check if nout is a list and change the keywords
259
+ if not isinstance(nout, list):
260
+ # If nout is a string, get the keywords
261
+ Dnout = {nout: nout, "last": last, -1: last, "all": self.outlist}[nout]
262
+ else:
263
+ # If nout is a list, replace the keywords
264
+ Dnout = [last if i in {"last", -1} else i for i in nout]
265
+
266
+ # Sort the list, compute the corresponding time and store its length
267
+ self.nout = np.sort(np.unique(np.atleast_1d(Dnout)))
268
+
269
+ # Check if the output files are in the list
270
+ if np.any(~np.isin(self.nout, self.outlist)):
271
+ raise ValueError(
272
+ f"Error: Wrong output file(s) {self.nout} \
273
+ in path {self.pathdir}."
274
+ )
275
+
276
+ # End of the function
277
+
278
+
279
+ def _findfiles(self, nout: int | str | list[int | str]) -> None:
280
+ """Finds the files to be loaded. If nout is a list, the function
281
+ loops over the list and finds the corresponding files. If nout is an
282
+ integer, the function finds the corresponding file. If nout is
283
+ 'last', the function finds the last file. If nout is 'all', the
284
+ function finds all the files. Then, the function stores the relevant
285
+ information in a dictionary _d_info.
286
+
287
+ Returns
288
+ -------
289
+ - None
290
+
291
+ Parameters
292
+ ----------
293
+ - nout (not optional): int | str | list[int|str]
294
+ The output file to be loaded
295
+
296
+ ----
297
+
298
+ Examples
299
+ --------
300
+ - Example #1: Load the last file
301
+
302
+ >>> _findfiles("last")
303
+
304
+ - Example #2: Load the first file
305
+
306
+ >>> _findfiles(0)
307
+
308
+ - Example #3: Load all the files
309
+
310
+ >>> _findfiles("all")
311
+
312
+ - Example #4: Load multiple specific files
313
+
314
+ >>> _findfiles([0, 1, 2, 3])
315
+
316
+ """
317
+ # Initialization or declaration of variables
318
+ class_name = self.__class__.__name__ # The class name
319
+ self.set_vars = set()
320
+ self.set_outs = set()
321
+
322
+ # Loop over the matching files and call the functions
323
+ for elem in self._matching_files:
324
+ # varsouts[condition](self, elem)
325
+ _varsouts(self, elem, class_name)
326
+
327
+ # Check if the files are present
328
+ if len(self.set_vars) == 0:
329
+ raise FileNotFoundError(f"No files found in {self.pathdir}!")
330
+
331
+ # Sort the outputs in an array and check the number of outputs
332
+ self.outlist = np.array(sorted(self.set_outs))
333
+ self._check_nout(nout)
334
+
335
+ # Store the number of outputs and the time
336
+ self._lennout = len(self.nout)
337
+ self.ntime = np.empty(self._lennout)
338
+
339
+ # Initialize the info dictionary and initialize some relevant variables
340
+ self._d_info = {}
341
+ self._d_info["typefile"] = np.empty(self._lennout, dtype="U20")
342
+ self._d_info["endianess"] = np.empty(self._lennout, dtype="U20")
343
+ self._d_info["binformat"] = np.empty(self._lennout, dtype="U20")
344
+
345
+ if class_name == "LoadPart":
346
+ # Check if the particles file is present
347
+ if "particles" not in self.set_vars:
348
+ raise FileNotFoundError(
349
+ f"file particles.*.{self.format} \
350
+ not found!"
351
+ )
352
+
353
+ # Particles are always 'single_file', initialize additional variables
354
+ self._d_info["typefile"][:] = "single_file"
355
+ self._d_info["varslist"] = [[] for _ in range(self._lennout)]
356
+ self._d_info["varskeys"] = [[] for _ in range(self._lennout)]
357
+
358
+ # Check if we are loading a single file (to be fixed)
359
+ if self._lennout != 1:
360
+ # Particles can be read only at a single fixed time
361
+ raise NotImplementedError("multiple loading not implemented yet")
362
+
363
+ elif class_name == "Load":
364
+ # Check if the fluid files are present as multiple files
365
+ if "data" not in self.set_vars or self._multiple is True:
366
+ # If the files are multiple, the typefile is set to 'multiple_files'
367
+ self._d_info["typefile"][:] = "multiple_files"
368
+ self._d_info["varslist"] = np.empty(
369
+ (self._lennout, len(self.set_vars)), dtype="U20"
370
+ )
371
+ self._d_info["varslist"][:] = list(self.set_vars)
372
+ else:
373
+ # If the files are single, the typefile is set to 'single_file'
374
+ self._d_info["typefile"][:] = "single_file"
375
+ self._d_info["varslist"] = [[] for _ in range(self._lennout)]
376
+
377
+ else:
378
+ # If the class name is not recognized, raise an error
379
+ raise NameError("Invalid class name.")
380
+
381
+ # Compute the endpath
382
+ if self.nfile_lp is None:
383
+ # If the number of LP files is not given, the format is standard
384
+ format_string = f".%04d.{self.format}"
385
+ else:
386
+ # If the number of LP files is given, the format is different
387
+ format_string = f".%04d_ch{self.nfile_lp:02d}.{self.format}"
388
+ self._d_info["endpath"] = np.char.mod(format_string, self.nout)
389
+
390
+ # End of the function
391
+
392
+
393
+ def _init_vardict(self, var: str) -> None:
394
+ """If not initialized, a new dictionary is created to store the
395
+ variables. The dictionary is stored in the class. The shape of the
396
+ dictionary is computed depending on the number of outputs and the
397
+ shape of the variable.
398
+
399
+ Returns
400
+ -------
401
+ - None
402
+
403
+ Parameters
404
+ ----------
405
+ - var (not optional): str
406
+ The variable to be loaded.
407
+
408
+ ----
409
+
410
+ Examples
411
+ --------
412
+ - Example #1: Initialize the dictionary of a non-initialized variable
413
+
414
+ >>> _init_vardict("rho")
415
+
416
+ """
417
+ # If the variable is not initialized, create a new dictionary
418
+ if var not in self._d_vars.keys():
419
+ self._d_vars[var] = {}
420
+ """
421
+ if isinstance(self._shape[var], tuple):
422
+ # If the shape is a tuple, the shape is reversed
423
+ sh_type = self._shape[var][::-1]
424
+ else:
425
+ # If the shape is not a tuple, the shape is converted to a tuple
426
+ sh_type = (self._shape[var],)
427
+
428
+ if self.__class__.__name__ == 'LoadPart':
429
+ varsh = self._dictdim[var]
430
+ # If the particles have multidimensional vars, the shape is changed
431
+ shape = (varsh,) + sh_type if varsh != 1 else sh_type
432
+ else:
433
+ # If multiple fluid files are loaded, the shape is changed
434
+ shape = (self._lennout,) + sh_type if self._lennout != 1 \
435
+ else sh_type
436
+
437
+ # Create a temporary file to store the data
438
+ with tempfile.NamedTemporaryFile() as temp_file:
439
+ # Create the dictionary key and fill the values with nan
440
+ self._d_vars[var] = np.memmap(temp_file, \
441
+ mode='w+', \
442
+ dtype=np.float32, \
443
+ shape = shape)
444
+ self._d_vars[var][:] = np.nan
445
+ """
446
+
447
+ # End of the function
448
+
449
+
450
+ def _assign_var(self, time: int, var: str, scrh: np.memmap) -> None:
451
+ """Assigns the memmap object to the dictionary. If the number of
452
+ outputs is 1, the variable is stored directly in the dictionary,
453
+ otherwise the variable is stored in the dictionary at the
454
+ corresponding output.
455
+
456
+ Returns
457
+ -------
458
+ - None
459
+
460
+ Parameters
461
+ ----------
462
+ - scrh (not optional): np.memmap
463
+ The memmap object containing the data to be stored.
464
+ - time (not optional): int
465
+ The output file to be loaded
466
+ - var (not optional): str
467
+ The variable to be loaded.
468
+
469
+ ----
470
+
471
+ Examples
472
+ --------
473
+ - Example #1: Assign the variable to the dictionary (single output time)
474
+
475
+ >>> _assign_var(3, "rho", scrh)
476
+
477
+ - Example #2: Assign the variable to the dictionary (multiple output times)
478
+
479
+ >>> _assign_var(1, "rho", scrh)
480
+
481
+ """
482
+ # Assign the memmap object to the dictionary
483
+ if self._lennout != 1:
484
+ # If the number of outputs is not 1, the variable is stored at the
485
+ # corresponding output
486
+ self._d_vars[var][time] = scrh
487
+ else:
488
+ # If the number of outputs is 1, the variable is stored directly
489
+ self._d_vars[var] = scrh
490
+
491
+ # End of the function
492
+
493
+
494
+ def _varsouts(self, elem: str, class_name: str) -> None:
495
+ """From the matching files finds the variables and the outputs for
496
+ the fluid and particles files (variables are to be intended here as
497
+ the first part of the output filename, they are the effective
498
+ variables only in case of multiple fluid files).
499
+
500
+ Returns
501
+ -------
502
+ - None
503
+
504
+ Parameters
505
+ ----------
506
+ - class_name (not optional): str
507
+ The name of the class. Supported classes are 'Load' or 'LoadPart'.
508
+ - elem (not optional): str
509
+ The matching file.
510
+
511
+ ----
512
+
513
+ Examples
514
+ --------
515
+ - Example #1: Find the outputs (particles, non LP)
516
+
517
+ >>> _varsouts_p("particles.0000.dbl")
518
+
519
+ - Example #2: Find the outputs (fluid)
520
+
521
+ >>> _varsouts_f("rho.0000.dbl")
522
+
523
+ - Example #3: Find the outputs (LP)
524
+
525
+ >>> _varsouts_lp("particles.0000_ch_00.dbl")
526
+
527
+ """
528
+ # Splits the matching filename (variable/data and output number)
529
+ raw_str = Path(elem).name.split(".")
530
+ vars = raw_str[0]
531
+ outs = raw_str[1]
532
+
533
+ # Set the conditions if the file is fluid or particles
534
+ isfluid = vars != "particles" and class_name == "Load"
535
+ ispart = vars == "particles" and class_name == "LoadPart"
536
+
537
+ if isfluid or (ispart and "_" not in outs):
538
+ # File is fluid or particles, but not LP
539
+ if self.nfile_lp is not None:
540
+ # If the file is not LP, but nfile_lp is not None, raise a warning
541
+ warn = f"nfile_lp is not None, but the file {elem} is not LP."
542
+ warnings.warn(warn, UserWarning)
543
+ # Control variable set to True
544
+ outc = True
545
+
546
+ elif ispart and "_" in outs:
547
+ # File is LP
548
+ if self.nfile_lp is None:
549
+ # If the file is LP, but nfile_lp is None, raise a warning
550
+ self.nfile_lp = 0
551
+ warn = f"nfile_lp is None, but the file {elem} is LP, set to 0."
552
+ warnings.warn(warn, UserWarning)
553
+
554
+ # Control variable set to the number of LP files
555
+ scrh = outs.split("_")
556
+ outs = int(scrh[0])
557
+ outc = int(scrh[1][2:])
558
+ else:
559
+ # Control variable set to False
560
+ outc = False
561
+
562
+ # Add the variables and the outputs
563
+ if outc is True or outc == self.nfile_lp:
564
+ self.set_vars.add(vars)
565
+ self.set_outs.add(int(outs))
566
+
567
+ # End of the function
@@ -0,0 +1,101 @@
1
+ import re
2
+ from pathlib import Path
3
+ from typing import Any
4
+
5
+ import inifix
6
+
7
+ from pyPLUTO.loadmixin import LoadMixin
8
+ from pyPLUTO.loadstate import LoadState
9
+
10
+
11
+ class FiledefpliniManager(LoadMixin):
12
+ def __init__(self, state: LoadState, **kwargs: Any) -> None:
13
+ self.state = state
14
+ defh = kwargs.get("defh")
15
+ if defh is not False:
16
+ pathdefh = self.pathdir / Path("definitions.h")
17
+ defhfile = "definitions.hpp"
18
+ if not pathdefh.exists():
19
+ pathdefh = self.pathdir / Path("definitions.hpp")
20
+ defhfile = "definitions.h"
21
+ try:
22
+ self.defh = self.read_defh(pathdefh)
23
+ except FileNotFoundError:
24
+ print(f"No {defhfile} is read!") if defh is True else ...
25
+
26
+ # Try to read the file pluto.ini
27
+ plini = kwargs.get("plini")
28
+ if isinstance(plini, str):
29
+ pathplini = self.pathdir / Path(plini)
30
+ elif plini is not False:
31
+ pathplini = self.pathdir / Path("pluto.ini")
32
+ try:
33
+ self.plini = inifix.load(pathplini, sections="require")
34
+ except FileNotFoundError:
35
+ print("No pluto.ini is read!") if plini is True else ...
36
+
37
+ def read_defh(self, filepath: Path) -> dict:
38
+ """Read a header file and extract definitions.
39
+
40
+ This function reads a header file, extracts lines that start with
41
+ '#define', and converts the values to their appropriate types (boolean,
42
+ integer, float, or string).
43
+
44
+ Returns
45
+ -------
46
+ dict
47
+ A dictionary where keys are the defined names and values are the
48
+ converted values.
49
+
50
+ Parameters
51
+ ----------
52
+ filepath : str
53
+ The path to the header file to read.
54
+
55
+ """
56
+ # Read the file, check if a line starts with '#define',
57
+ # and split the line into key and value.
58
+ # Convert the value using _convert_value function.
59
+ with open(filepath) as file:
60
+ # Return a dictionary comprehension that processes each line
61
+ return {
62
+ key: self.convert_value(value)
63
+ for line in file
64
+ if line.strip().startswith("#define")
65
+ for _, key, value in [
66
+ re.split(r"\s+", line.strip(), maxsplit=2)
67
+ ]
68
+ }
69
+
70
+ def convert_value(self, value: str) -> bool | int | float | str:
71
+ """Convert a string value to its appropriate type.
72
+
73
+ This function attempts to convert a string value into a boolean,
74
+ integer, float, or leave it as a string if it cannot be converted.
75
+
76
+ Returns
77
+ -------
78
+ bool | int | float | str
79
+ The converted value, which can be a boolean, integer, float, or the
80
+ original string if conversion is not possible.
81
+
82
+ Parameters
83
+ ----------
84
+ value : str
85
+ The string value to convert.
86
+
87
+ """
88
+ # Convert the value to uppercase to handle case-insensitive comparisons
89
+ value_upper = value.upper()
90
+
91
+ # Check for boolean values first
92
+ if value_upper in {"YES", "TRUE"}:
93
+ return True
94
+ if value_upper in {"NO", "FALSE"}:
95
+ return False
96
+
97
+ # Try to convert to an integer or float, if possible
98
+ try:
99
+ return float(value) if "." in value or "e" in value else int(value)
100
+ except ValueError:
101
+ return value