foamlib 0.8.8__py3-none-any.whl → 0.8.9__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.
foamlib/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """A Python interface for interacting with OpenFOAM."""
2
2
 
3
- __version__ = "0.8.8"
3
+ __version__ = "0.8.9"
4
4
 
5
5
  from ._cases import (
6
6
  AsyncFoamCase,
foamlib/_cases/_async.py CHANGED
@@ -45,8 +45,20 @@ class AsyncFoamCase(FoamCaseRunBase):
45
45
  Provides methods for running and cleaning cases, as well as accessing files.
46
46
 
47
47
  Access the time directories of the case as a sequence, e.g. `case[0]` or `case[-1]`.
48
+ These will return `AsyncFoamCase.TimeDirectory` objects.
48
49
 
49
- :param path: The path to the case directory.
50
+ :param path: The path to the case directory. Defaults to the current working
51
+ directory.
52
+
53
+ Example usage: ::
54
+ from foamlib import AsyncFoamCase
55
+
56
+ case = AsyncFoamCase("path/to/case") # Load an OpenFOAM case
57
+ case[0]["U"].internal_field = [0, 0, 0] # Set the initial velocity field to zero
58
+ await case.run() # Run the case
59
+ for time in case: # Iterate over the time directories
60
+ print(time.time) # Print the time
61
+ print(time["U"].internal_field) # Print the velocity field
50
62
  """
51
63
 
52
64
  class TimeDirectory(FoamCaseRunBase.TimeDirectory):
@@ -55,7 +67,12 @@ class AsyncFoamCase(FoamCaseRunBase):
55
67
  return AsyncFoamCase(self.path.parent)
56
68
 
57
69
  async def cell_centers(self) -> FoamFieldFile:
58
- """Write and return the cell centers."""
70
+ """
71
+ Write and return the cell centers.
72
+
73
+ Currently only works for reconstructed cases (decomposed cases will need to
74
+ be reconstructed first).
75
+ """
59
76
  calls = ValuedGenerator(self._cell_centers_calls())
60
77
 
61
78
  for coro in calls:
@@ -126,7 +143,20 @@ class AsyncFoamCase(FoamCaseRunBase):
126
143
  """
127
144
  Clean this case.
128
145
 
129
- :param check: If True, raise a CalledProcessError if the clean script returns a non-zero exit code.
146
+ If a `clean` or `Allclean` script is present in the case directory, it will be invoked.
147
+ Otherwise, the case directory will be cleaned using these rules:
148
+
149
+ - All time directories except `0` will be deleted.
150
+ - The `0` time directory will be deleted if `0.orig` exists.
151
+ - `processor*` directories will be deleted if a `system/decomposeParDict` file is present.
152
+ - `constant/polyMesh` will be deleted if a `system/blockMeshDict` file is present.
153
+ - All `log.*` files will be deleted.
154
+
155
+ If this behavior is not appropriate for a case, it is recommended to write a custom
156
+ `clean` script.
157
+
158
+ :param check: If True, raise a `CalledProcessError` if the clean script returns a
159
+ non-zero exit code.
130
160
  """
131
161
  for coro in self._clean_calls(check=check):
132
162
  await coro
@@ -161,11 +191,35 @@ class AsyncFoamCase(FoamCaseRunBase):
161
191
  """
162
192
  Run this case, or a specified command in the context of this case.
163
193
 
164
- :param cmd: The command to run. If None, run the case. If a sequence, the first element is the command and the rest are arguments. If a string, `cmd` is executed in a shell.
165
- :param parallel: If True, run in parallel using MPI. If None, autodetect whether to run in parallel.
166
- :param cpus: The number of CPUs to use. If None, autodetect according to the case.
167
- :param check: If True, raise a CalledProcessError if any command returns a non-zero exit code.
168
- :param log: If True, log the command output to a file.
194
+ If `cmd` is given, this method will run the given command in the context of the case.
195
+
196
+ If `cmd` is `None`, a series of heuristic rules will be used to run the case. This works as
197
+ follows:
198
+
199
+ - If a `run`, `Allrun` or `Allrun-parallel` script is present in the case directory,
200
+ it will be invoked. If both `run` and `Allrun` are present, `Allrun` will be used. If
201
+ both `Allrun` and `Allrun-parallel` are present and `parallel` is `None`, an error will
202
+ be raised.
203
+ - If no run script is present but an `Allrun.pre` script exists, it will be invoked.
204
+ - Otherwise, if a `system/blockMeshDict` file is present, the method will call
205
+ `self.block_mesh()`.
206
+ - Then, if a `0.orig` directory is present, it will call `self.restore_0_dir()`.
207
+ - Then, if the case is to be run in parallel (see the `parallel` option) and no
208
+ `processor*` directories exist but a`system/decomposeParDict` file is present, it will
209
+ call `self.decompose_par()`.
210
+ - Then, it will run the case using the application specified in the `controlDict` file.
211
+
212
+ If this behavior is not appropriate for a case, it is recommended to write a custom
213
+ `run`, `Allrun`, `Allrun-parallel` or `Allrun.pre` script.
214
+
215
+ :param cmd: The command to run. If `None`, run the case. If a sequence, the first element
216
+ is the command and the rest are arguments. If a string, `cmd` is executed in a shell.
217
+ :param parallel: If `True`, run in parallel using MPI. If None, autodetect whether to run
218
+ in parallel.
219
+ :param cpus: The number of CPUs to use. If `None`, autodetect from to the case.
220
+ :param check: If `True`, raise a `CalledProcessError` if any command returns a non-zero
221
+ exit code.
222
+ :param log: If `True`, log the command output to `log.*` files in the case directory.
169
223
  """
170
224
  for coro in self._run_calls(
171
225
  cmd=cmd, parallel=parallel, cpus=cpus, check=check, log=log
@@ -200,9 +254,21 @@ class AsyncFoamCase(FoamCaseRunBase):
200
254
  """
201
255
  Make a copy of this case.
202
256
 
203
- Use as an async context manager to automatically delete the copy when done.
257
+ If used as an asynchronous context manager (i.e., within an `async with` block) the copy
258
+ will be deleted automatically when exiting the block.
259
+
260
+ :param dst: The destination path. If `None`, clone to `$FOAM_RUN/foamlib`.
261
+
262
+ :return: The copy of the case.
204
263
 
205
- :param dst: The destination path. If None, clone to `$FOAM_RUN/foamlib`.
264
+ Example usage: ::
265
+ import os
266
+ from pathlib import Path
267
+ from foamlib import AsyncFoamCase
268
+
269
+ pitz_tutorial = AsyncFoamCase(Path(os.environ["FOAM_TUTORIALS"]) / "incompressible/simpleFoam/pitzDaily")
270
+
271
+ my_pitz = await pitz_tutorial.copy("myPitz")
206
272
  """
207
273
  calls = ValuedGenerator(self._copy_calls(dst))
208
274
 
@@ -221,9 +287,24 @@ class AsyncFoamCase(FoamCaseRunBase):
221
287
  """
222
288
  Clone this case (make a clean copy).
223
289
 
224
- Use as an async context manager to automatically delete the clone when done.
290
+ This is equivalent to running `(await self.copy()).clean()`, but it can be more efficient
291
+ in cases that do not contain custom clean scripts.
292
+
293
+ If used as an asynchronous context manager (i.e., within an `async with` block) the cloned
294
+ copy will be deleted automatically when exiting the block.
295
+
296
+ :param dst: The destination path. If `None`, clone to `$FOAM_RUN/foamlib`.
297
+
298
+ :return: The clone of the case.
299
+
300
+ Example usage: ::
301
+ import os
302
+ from pathlib import Path
303
+ from foamlib import AsyncFoamCase
304
+
305
+ pitz_tutorial = AsyncFoamCase(Path(os.environ["FOAM_TUTORIALS"]) / "incompressible/simpleFoam/pitzDaily")
225
306
 
226
- :param dst: The destination path. If None, clone to `$FOAM_RUN/foamlib`.
307
+ my_pitz = await pitz_tutorial.clone("myPitz")
227
308
  """
228
309
  calls = ValuedGenerator(self._clone_calls(dst))
229
310
 
foamlib/_cases/_base.py CHANGED
@@ -18,16 +18,32 @@ if TYPE_CHECKING:
18
18
 
19
19
 
20
20
  class FoamCaseBase(Sequence["FoamCaseBase.TimeDirectory"]):
21
+ """
22
+ Base class for OpenFOAM cases.
23
+
24
+ Provides methods for accessing files and time directories in the case, but does not
25
+ provide methods for running the case or any commands. Users are encouraged to use
26
+ `FoamCase` or `AsyncFoamCase` instead of this class.
27
+
28
+ Access the time directories of the case as a sequence, e.g. `case[0]` or `case[-1]`.
29
+ These will return `FoamCaseBase.TimeDirectory` objects.
30
+
31
+ :param path: The path to the case directory. Defaults to the current working
32
+ directory.
33
+ """
34
+
21
35
  def __init__(self, path: os.PathLike[str] | str = Path()) -> None:
22
36
  self.path = Path(path).absolute()
23
37
 
24
38
  class TimeDirectory(AbstractSet[FoamFieldFile]):
25
39
  """
26
- An OpenFOAM time directory in a case.
40
+ A time directory in an OpenFOAM case.
27
41
 
28
- Use to access field files in the directory, e.g. `time["U"]`.
42
+ Use to access field files in the directory (e.g. `time["U"]`). These will be
43
+ returned as `FoamFieldFile` objects.
29
44
 
30
- :param path: The path to the time directory.
45
+ It also behaves as a set of `FoamFieldFile` objects (e.g. it can be
46
+ iterated over with `for field in time: ...`).
31
47
  """
32
48
 
33
49
  def __init__(self, path: os.PathLike[str] | str) -> None:
@@ -39,12 +55,12 @@ class FoamCaseBase(Sequence["FoamCaseBase.TimeDirectory"]):
39
55
 
40
56
  @property
41
57
  def time(self) -> float:
42
- """The time that corresponds to this directory."""
58
+ """The time that corresponds to this directory, as a float."""
43
59
  return float(self.path.name)
44
60
 
45
61
  @property
46
62
  def name(self) -> str:
47
- """The name of this time directory."""
63
+ """The name of this time directory (the time as a string)."""
48
64
  return self.path.name
49
65
 
50
66
  def __getitem__(self, key: str) -> FoamFieldFile:
foamlib/_cases/_run.py CHANGED
@@ -46,6 +46,12 @@ if TYPE_CHECKING:
46
46
 
47
47
 
48
48
  class FoamCaseRunBase(FoamCaseBase):
49
+ """
50
+ Abstract base class of `FoamCase` and `AsyncFoamCase`.
51
+
52
+ Do not use this class directly: use `FoamCase` or `AsyncFoamCase` instead.
53
+ """
54
+
49
55
  class TimeDirectory(FoamCaseBase.TimeDirectory):
50
56
  @abstractmethod
51
57
  def cell_centers(
foamlib/_cases/_slurm.py CHANGED
@@ -17,7 +17,16 @@ if TYPE_CHECKING:
17
17
 
18
18
 
19
19
  class AsyncSlurmFoamCase(AsyncFoamCase):
20
- """An asynchronous OpenFOAM case that launches jobs on a Slurm cluster."""
20
+ """
21
+ An asynchronous OpenFOAM case that launches jobs on a Slurm cluster.
22
+
23
+ `AsyncSlurmFoamCase` is a subclass of `AsyncFoamCase`. It provides the same interface,
24
+ as the latter, except that it will launch jobs on a Slurm cluster (using `salloc` and
25
+ `srun`) on the user's behalf when running a case or command.
26
+
27
+ :param path: The path to the case directory. Defaults to the current working
28
+ directory.
29
+ """
21
30
 
22
31
  @staticmethod
23
32
  async def _run(
foamlib/_cases/_sync.py CHANGED
@@ -33,8 +33,21 @@ class FoamCase(FoamCaseRunBase):
33
33
  Provides methods for running and cleaning cases, as well as accessing files.
34
34
 
35
35
  Access the time directories of the case as a sequence, e.g. `case[0]` or `case[-1]`.
36
+ These will return `FoamCase.TimeDirectory` objects.
36
37
 
37
- :param path: The path to the case directory.
38
+ :param path: The path to the case directory. Defaults to the current working
39
+ directory.
40
+
41
+ Example usage: ::
42
+
43
+ from foamlib import FoamCase
44
+
45
+ case = FoamCase("path/to/case") # Load an OpenFOAM case
46
+ case[0]["U"].internal_field = [0, 0, 0] # Set the initial velocity field to zero
47
+ case.run() # Run the case
48
+ for time in case: # Iterate over the time directories
49
+ print(time.time) # Print the time
50
+ print(time["U"].internal_field) # Print the velocity field
38
51
  """
39
52
 
40
53
  class TimeDirectory(FoamCaseRunBase.TimeDirectory):
@@ -43,7 +56,12 @@ class FoamCase(FoamCaseRunBase):
43
56
  return FoamCase(self.path.parent)
44
57
 
45
58
  def cell_centers(self) -> FoamFieldFile:
46
- """Write and return the cell centers."""
59
+ """
60
+ Write and return the cell centers.
61
+
62
+ Currently only works for reconstructed cases (decomposed cases will need to
63
+ be reconstructed first).
64
+ """
47
65
  calls = ValuedGenerator(self._cell_centers_calls())
48
66
 
49
67
  for _ in calls:
@@ -107,7 +125,20 @@ class FoamCase(FoamCaseRunBase):
107
125
  """
108
126
  Clean this case.
109
127
 
110
- :param check: If True, raise a CalledProcessError if the clean script returns a non-zero exit code.
128
+ If a `clean` or `Allclean` script is present in the case directory, it will be invoked.
129
+ Otherwise, the case directory will be cleaned using these rules:
130
+
131
+ - All time directories except `0` will be deleted.
132
+ - The `0` time directory will be deleted if `0.orig` exists.
133
+ - `processor*` directories will be deleted if a `system/decomposeParDict` file is present.
134
+ - `constant/polyMesh` will be deleted if a `system/blockMeshDict` file is present.
135
+ - All `log.*` files will be deleted.
136
+
137
+ If this behavior is not appropriate for a case, it is recommended to write a custom
138
+ `clean` script.
139
+
140
+ :param check: If True, raise a `CalledProcessError` if the clean script returns a
141
+ non-zero exit code.
111
142
  """
112
143
  for _ in self._clean_calls(check=check):
113
144
  pass
@@ -128,11 +159,35 @@ class FoamCase(FoamCaseRunBase):
128
159
  """
129
160
  Run this case, or a specified command in the context of this case.
130
161
 
131
- :param cmd: The command to run. If None, run the case. If a sequence, the first element is the command and the rest are arguments. If a string, `cmd` is executed in a shell.
132
- :param parallel: If True, run in parallel using MPI. If None, autodetect whether to run in parallel.
133
- :param cpus: The number of CPUs to use. If None, autodetect according to the case.
134
- :param check: If True, raise a CalledProcessError if any command returns a non-zero exit code.
135
- :param log: If True, log the command output to a file.
162
+ If `cmd` is given, this method will run the given command in the context of the case.
163
+
164
+ If `cmd` is `None`, a series of heuristic rules will be used to run the case. This works as
165
+ follows:
166
+
167
+ - If a `run`, `Allrun` or `Allrun-parallel` script is present in the case directory,
168
+ it will be invoked. If both `run` and `Allrun` are present, `Allrun` will be used. If
169
+ both `Allrun` and `Allrun-parallel` are present and `parallel` is `None`, an error will
170
+ be raised.
171
+ - If no run script is present but an `Allrun.pre` script exists, it will be invoked.
172
+ - Otherwise, if a `system/blockMeshDict` file is present, the method will call
173
+ `self.block_mesh()`.
174
+ - Then, if a `0.orig` directory is present, it will call `self.restore_0_dir()`.
175
+ - Then, if the case is to be run in parallel (see the `parallel` option) and no
176
+ `processor*` directories exist but a`system/decomposeParDict` file is present, it will
177
+ call `self.decompose_par()`.
178
+ - Then, it will run the case using the application specified in the `controlDict` file.
179
+
180
+ If this behavior is not appropriate for a case, it is recommended to write a custom
181
+ `run`, `Allrun`, `Allrun-parallel` or `Allrun.pre` script.
182
+
183
+ :param cmd: The command to run. If `None`, run the case. If a sequence, the first element
184
+ is the command and the rest are arguments. If a string, `cmd` is executed in a shell.
185
+ :param parallel: If `True`, run in parallel using MPI. If None, autodetect whether to run
186
+ in parallel.
187
+ :param cpus: The number of CPUs to use. If `None`, autodetect from to the case.
188
+ :param check: If `True`, raise a `CalledProcessError` if any command returns a non-zero
189
+ exit code.
190
+ :param log: If `True`, log the command output to `log.*` files in the case directory.
136
191
  """
137
192
  for _ in self._run_calls(
138
193
  cmd=cmd, parallel=parallel, cpus=cpus, check=check, log=log
@@ -163,9 +218,21 @@ class FoamCase(FoamCaseRunBase):
163
218
  """
164
219
  Make a copy of this case.
165
220
 
166
- Use as a context manager to automatically delete the copy when done.
221
+ If used as a context manager (i.e., within a `with` block) the copy will be deleted
222
+ automatically when exiting the block.
223
+
224
+ :param dst: The destination path. If `None`, clone to `$FOAM_RUN/foamlib`.
167
225
 
168
- :param dst: The destination path. If None, clone to `$FOAM_RUN/foamlib`.
226
+ :return: The copy of the case.
227
+
228
+ Example usage: ::
229
+ import os
230
+ from pathlib import Path
231
+ from foamlib import FoamCase
232
+
233
+ pitz_tutorial = FoamCase(Path(os.environ["FOAM_TUTORIALS"]) / "incompressible/simpleFoam/pitzDaily")
234
+
235
+ my_pitz = pitz_tutorial.copy("myPitz")
169
236
  """
170
237
  calls = ValuedGenerator(self._copy_calls(dst))
171
238
 
@@ -178,9 +245,24 @@ class FoamCase(FoamCaseRunBase):
178
245
  """
179
246
  Clone this case (make a clean copy).
180
247
 
181
- Use as a context manager to automatically delete the clone when done.
248
+ This is equivalent to running `self.copy().clean()`, but it can be more efficient in cases
249
+ that do not contain custom clean scripts.
250
+
251
+ If used as a context manager (i.e., within a `with` block) the cloned copy will be deleted
252
+ automatically when exiting the block.
253
+
254
+ :param dst: The destination path. If `None`, clone to `$FOAM_RUN/foamlib`.
255
+
256
+ :return: The clone of the case.
257
+
258
+ Example usage: ::
259
+ import os
260
+ from pathlib import Path
261
+ from foamlib import FoamCase
262
+
263
+ pitz_tutorial = FoamCase(Path(os.environ["FOAM_TUTORIALS"]) / "incompressible/simpleFoam/pitzDaily")
182
264
 
183
- :param dst: The destination path. If None, clone to `$FOAM_RUN/foamlib`.
265
+ my_pitz = pitz_tutorial.clone("myPitz")
184
266
  """
185
267
  calls = ValuedGenerator(self._clone_calls(dst))
186
268
 
foamlib/_files/_files.py CHANGED
@@ -23,8 +23,9 @@ from ._types import (
23
23
  Dict_,
24
24
  Dimensioned,
25
25
  DimensionSet,
26
- Entry,
26
+ EntryLike,
27
27
  Field,
28
+ FieldLike,
28
29
  File,
29
30
  MutableEntry,
30
31
  )
@@ -40,9 +41,46 @@ class FoamFile(
40
41
  """
41
42
  An OpenFOAM data file.
42
43
 
43
- Use as a mutable mapping (i.e., like a dict) to access and modify entries.
44
+ `FoamFile` supports most OpenFOAM data and configuration files (i.e., files with a
45
+ "FoamFile" header), including those with regular expressions and #-based directives.
46
+ Notable exceptions are FoamFiles with #codeStreams and those multiple #-directives
47
+ with the same name, which are currently not supported. Non-FoamFile output files are
48
+ also not suppored by this class. Regular expressions and #-based directives can be
49
+ accessed and modified, but they are not evaluated or expanded by this library.
44
50
 
45
- Use as a context manager to make multiple changes to the file while saving all changes only once at the end.
51
+ Use `FoamFile` as a mutable mapping (i.e., like a `dict`) to access and modify
52
+ entries. When accessing a sub-dictionary, the returned value will be a
53
+ `FoamFile.SubDict` object, that allows for further access and modification of nested
54
+ dictionaries within the `FoamFile` in a single operation.
55
+
56
+ If the `FoamFile` does not store a dictionary, the main stored value can be accessed
57
+ and modified by passing `None` as the key (e.g., `file[None]`).
58
+
59
+ You can also use the `FoamFile` as a context manager (i.e., within a `with` block)
60
+ to make multiple changes to the file while saving any and all changes only once at
61
+ the end.
62
+
63
+ :param path: The path to the file. If the file does not exist, it will be created
64
+ when the first change is made. However, if an attempt is made to access entries
65
+ in a non-existent file, a `FileNotFoundError` will be raised.
66
+
67
+ Example usage: ::
68
+ from foamlib import FoamFile
69
+
70
+ file = FoamFile("path/to/case/system/controlDict") # Load a controlDict file
71
+ print(file["endTime"]) # Print the end time
72
+ file["writeInterval"] = 100 # Set the write interval to 100
73
+ file["writeFormat"] = "binary" # Set the write format to binary
74
+
75
+ or (better): ::
76
+ from foamlib import FoamCase
77
+
78
+ case = FoamCase("path/to/case")
79
+
80
+ with case.control_dict as file: # Load the controlDict file
81
+ print(file["endTime"]) # Print the end time
82
+ file["writeInterval"] = 100
83
+ file["writeFormat"] = "binary"
46
84
  """
47
85
 
48
86
  Dimensioned = Dimensioned
@@ -51,7 +89,32 @@ class FoamFile(
51
89
  class SubDict(
52
90
  MutableMapping[str, MutableEntry],
53
91
  ):
54
- """An OpenFOAM dictionary within a file as a mutable mapping."""
92
+ """
93
+ An OpenFOAM sub-dictionary within a file.
94
+
95
+ `FoamFile.SubDict` is a mutable mapping that allows for accessing and modifying
96
+ nested dictionaries within a `FoamFile` in a single operation. It behaves like a
97
+ `dict` and can be used to access and modify entries in the sub-dictionary.
98
+
99
+ To obtain a `FoamFile.SubDict` object, access a sub-dictionary in a `FoamFile`
100
+ object (e.g., `file["subDict"]`).
101
+
102
+ Example usage: ::
103
+ from foamlib import FoamFile
104
+
105
+ file = FoamFile("path/to/case/system/fvSchemes") # Load an fvSchemes file
106
+ print(file["ddtSchemes"]["default"]) # Print the default ddt scheme
107
+ file["ddtSchemes"]["default"] = "Euler" # Set the default ddt scheme
108
+
109
+ or (better): ::
110
+ from foamlib import FoamCase
111
+
112
+ case = FoamCase("path/to/case")
113
+
114
+ with case.fv_schemes as file: # Load the fvSchemes file
115
+ print(file["ddtSchemes"]["default"])
116
+ file["ddtSchemes"]["default"] = "Euler"
117
+ """
55
118
 
56
119
  def __init__(self, _file: FoamFile, _keywords: tuple[str, ...]) -> None:
57
120
  self._file = _file
@@ -63,7 +126,7 @@ class FoamFile(
63
126
  def __setitem__(
64
127
  self,
65
128
  keyword: str,
66
- data: Entry,
129
+ data: EntryLike,
67
130
  ) -> None:
68
131
  self._file[(*self._keywords, keyword)] = data
69
132
 
@@ -93,7 +156,7 @@ class FoamFile(
93
156
  return f"{type(self).__qualname__}('{self._file}', {self._keywords})"
94
157
 
95
158
  def as_dict(self) -> Dict_:
96
- """Return a nested dict representation of the dictionary."""
159
+ """Return a nested dict representation of the sub-dictionary."""
97
160
  ret = self._file.as_dict(include_header=True)
98
161
 
99
162
  for k in self._keywords:
@@ -190,13 +253,15 @@ class FoamFile(
190
253
  return FoamFile.SubDict(self, keywords)
191
254
  return deepcopy(value)
192
255
 
193
- def __setitem__(self, keywords: str | tuple[str, ...] | None, data: Entry) -> None:
256
+ def __setitem__(
257
+ self, keywords: str | tuple[str, ...] | None, data: EntryLike
258
+ ) -> None:
194
259
  if not keywords:
195
260
  keywords = ()
196
261
  elif not isinstance(keywords, tuple):
197
262
  keywords = (keywords,)
198
263
 
199
- if keywords and not isinstance(normalize(keywords[-1]), str):
264
+ if keywords and not isinstance(normalize(keywords[-1], kind=Kind.KEYWORD), str):
200
265
  msg = f"Invalid keyword: {keywords[-1]}"
201
266
  raise ValueError(msg)
202
267
 
@@ -379,15 +444,44 @@ class FoamFile(
379
444
 
380
445
 
381
446
  class FoamFieldFile(FoamFile):
382
- """An OpenFOAM dictionary file representing a field as a mutable mapping."""
447
+ """
448
+ Subclass of `FoamFile` for representing OpenFOAM field files specifically.
449
+
450
+ The difference between `FoamFieldFile` and `FoamFile` is that `FoamFieldFile` has
451
+ the additional properties `dimensions`, `internal_field`, and `boundary_field` that
452
+ are commonly found in OpenFOAM field files. Note that these are only a shorthand for
453
+ accessing the corresponding entries in the file.
454
+
455
+ See `FoamFile` for more information on how to read and edit OpenFOAM files.
456
+
457
+ :param path: The path to the file. If the file does not exist, it will be created
458
+ when the first change is made. However, if an attempt is made to access entries
459
+ in a non-existent file, a `FileNotFoundError` will be raised.
460
+
461
+ Example usage: ::
462
+ from foamlib import FoamFieldFile
463
+
464
+ field = FoamFieldFile("path/to/case/0/U") # Load a field
465
+ print(field.dimensions) # Print the dimensions
466
+ print(field.boundary_field) # Print the boundary field
467
+ field.internal_field = [0, 0, 0] # Set the internal field
468
+
469
+ or (better): ::
470
+ from foamlib import FoamCase
471
+
472
+ case = FoamCase("path/to/case")
473
+
474
+ with case[0]["U"] as field: # Load a field
475
+ print(field.dimensions)
476
+ print(field.boundary_field)
477
+ field.internal_field = [0, 0, 0]
478
+ """
383
479
 
384
480
  class BoundariesSubDict(FoamFile.SubDict):
385
- def __getitem__(self, keyword: str) -> FoamFieldFile.BoundarySubDict:
481
+ def __getitem__(self, keyword: str) -> FoamFieldFile.BoundarySubDict | Data:
386
482
  value = super().__getitem__(keyword)
387
- if not isinstance(value, FoamFieldFile.BoundarySubDict):
388
- assert not isinstance(value, FoamFile.SubDict)
389
- msg = f"boundary {keyword} is not a dictionary"
390
- raise TypeError(msg)
483
+ if isinstance(value, FoamFieldFile.SubDict):
484
+ assert isinstance(value, FoamFieldFile.BoundarySubDict)
391
485
  return value
392
486
 
393
487
  class BoundarySubDict(FoamFile.SubDict):
@@ -419,7 +513,7 @@ class FoamFieldFile(FoamFile):
419
513
  @value.setter
420
514
  def value(
421
515
  self,
422
- value: Field,
516
+ value: FieldLike,
423
517
  ) -> None:
424
518
  self["value"] = value
425
519
 
@@ -466,7 +560,7 @@ class FoamFieldFile(FoamFile):
466
560
  @internal_field.setter
467
561
  def internal_field(
468
562
  self,
469
- value: Field,
563
+ value: FieldLike,
470
564
  ) -> None:
471
565
  self["internalField"] = value
472
566
 
@@ -12,7 +12,15 @@ else:
12
12
  import numpy as np
13
13
 
14
14
  from ._parsing import parse_data
15
- from ._types import Data, Dimensioned, DimensionSet, Entry, is_sequence
15
+ from ._types import (
16
+ Data,
17
+ DataLike,
18
+ Dimensioned,
19
+ DimensionSet,
20
+ Entry,
21
+ EntryLike,
22
+ is_sequence,
23
+ )
16
24
 
17
25
 
18
26
  class Kind(Enum):
@@ -23,17 +31,18 @@ class Kind(Enum):
23
31
  BINARY_FIELD = auto()
24
32
  SCALAR_BINARY_FIELD = auto()
25
33
  DIMENSIONS = auto()
34
+ KEYWORD = auto()
26
35
 
27
36
 
28
37
  @overload
29
- def normalize(data: Data, *, kind: Kind = Kind.DEFAULT) -> Data: ...
38
+ def normalize(data: DataLike, *, kind: Kind = Kind.DEFAULT) -> Data: ...
30
39
 
31
40
 
32
41
  @overload
33
- def normalize(data: Entry, *, kind: Kind = Kind.DEFAULT) -> Entry: ...
42
+ def normalize(data: EntryLike, *, kind: Kind = Kind.DEFAULT) -> Entry: ...
34
43
 
35
44
 
36
- def normalize(data: Entry, *, kind: Kind = Kind.DEFAULT) -> Entry:
45
+ def normalize(data: EntryLike, *, kind: Kind = Kind.DEFAULT) -> Entry:
37
46
  if kind in (
38
47
  Kind.ASCII_FIELD,
39
48
  Kind.SCALAR_ASCII_FIELD,
@@ -52,12 +61,12 @@ def normalize(data: Entry, *, kind: Kind = Kind.DEFAULT) -> Entry:
52
61
  if arr.ndim == 1 or (arr.ndim == 2 and arr.shape[1] in (3, 6, 9)):
53
62
  return arr # type: ignore [return-value]
54
63
 
55
- return data
64
+ return [normalize(d, kind=Kind.SINGLE_ENTRY) for d in data]
56
65
 
57
66
  if isinstance(data, int):
58
67
  return float(data)
59
68
 
60
- return data
69
+ return normalize(data)
61
70
 
62
71
  if isinstance(data, np.ndarray):
63
72
  ret = data.tolist()
@@ -83,7 +92,10 @@ def normalize(data: Entry, *, kind: Kind = Kind.DEFAULT) -> Entry:
83
92
  return [normalize(d, kind=Kind.SINGLE_ENTRY) for d in data]
84
93
 
85
94
  if isinstance(data, str):
86
- return parse_data(data)
95
+ parsed_data = parse_data(data)
96
+ if kind == Kind.KEYWORD and isinstance(parsed_data, bool):
97
+ return data
98
+ return parsed_data
87
99
 
88
100
  if isinstance(
89
101
  data,
@@ -96,7 +108,7 @@ def normalize(data: Entry, *, kind: Kind = Kind.DEFAULT) -> Entry:
96
108
 
97
109
 
98
110
  def dumps(
99
- data: Entry,
111
+ data: EntryLike,
100
112
  *,
101
113
  kind: Kind = Kind.DEFAULT,
102
114
  ) -> bytes:
foamlib/_files/_types.py CHANGED
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import sys
4
- from dataclasses import dataclass
5
4
  from enum import Enum
6
5
  from typing import Dict, NamedTuple, Optional, Union
7
6
 
@@ -33,8 +32,13 @@ class DimensionSet(NamedTuple):
33
32
 
34
33
  Tensor = Union[
35
34
  float,
35
+ "np.ndarray[tuple[int], np.dtype[np.float64]]",
36
+ ]
37
+
38
+ TensorLike = Union[
36
39
  Sequence[float],
37
- "np.ndarray[tuple[()] | tuple[int], np.dtype[np.float64]]",
40
+ "np.ndarray[tuple[()], np.dtype[np.float64]]",
41
+ Tensor,
38
42
  ]
39
43
 
40
44
 
@@ -71,21 +75,25 @@ class TensorKind(Enum):
71
75
  raise ValueError(msg)
72
76
 
73
77
 
74
- @dataclass
75
78
  class Dimensioned:
76
- value: Tensor = 0
77
- dimensions: DimensionSet | Sequence[float] = ()
78
- name: str | None = None
79
+ def __init__(
80
+ self,
81
+ value: TensorLike,
82
+ dimensions: DimensionSet | Sequence[float],
83
+ name: str | None = None,
84
+ ) -> None:
85
+ if is_sequence(value):
86
+ self.value: Tensor = np.array(value, dtype=float) # type: ignore [assignment]
87
+ else:
88
+ assert isinstance(value, (int, float, np.ndarray))
89
+ self.value = float(value)
79
90
 
80
- def __post_init__(self) -> None:
81
- if is_sequence(self.value):
82
- self.value = np.asarray(self.value, dtype=float) # type: ignore [assignment]
91
+ if not isinstance(dimensions, DimensionSet):
92
+ self.dimensions = DimensionSet(*dimensions)
83
93
  else:
84
- assert isinstance(self.value, (int, float, np.ndarray))
85
- self.value = float(self.value)
94
+ self.dimensions = dimensions
86
95
 
87
- if not isinstance(self.dimensions, DimensionSet):
88
- self.dimensions = DimensionSet(*self.dimensions)
96
+ self.name = name
89
97
 
90
98
  def __eq__(self, other: object) -> bool:
91
99
  if not isinstance(other, Dimensioned):
@@ -99,11 +107,18 @@ class Dimensioned:
99
107
 
100
108
 
101
109
  Field = Union[
102
- Tensor,
103
- Sequence[Tensor],
110
+ float,
104
111
  "np.ndarray[tuple[int] | tuple[int, int], np.dtype[np.float64 | np.float32]]",
105
112
  ]
106
113
 
114
+ FieldLike = Union[
115
+ TensorLike,
116
+ Sequence[TensorLike],
117
+ Sequence[Sequence[TensorLike]],
118
+ Field,
119
+ ]
120
+
121
+
107
122
  Data = Union[
108
123
  str,
109
124
  int,
@@ -123,11 +138,22 @@ Entry = Union[
123
138
  A value that can be stored in an OpenFOAM file.
124
139
  """
125
140
 
141
+ DataLike = Union[
142
+ FieldLike,
143
+ Sequence["EntryLike"],
144
+ Data,
145
+ ]
146
+
147
+ EntryLike = Union[
148
+ DataLike,
149
+ Mapping[str, "EntryLike"],
150
+ ]
151
+
126
152
 
127
153
  def is_sequence(
128
- value: Entry,
154
+ value: EntryLike,
129
155
  ) -> TypeGuard[
130
- Sequence[Entry]
156
+ Sequence[EntryLike]
131
157
  | np.ndarray[tuple[int] | tuple[int, int], np.dtype[np.float64 | np.float32]]
132
158
  ]:
133
159
  return (isinstance(value, Sequence) and not isinstance(value, str)) or (
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: foamlib
3
- Version: 0.8.8
3
+ Version: 0.8.9
4
4
  Summary: A Python interface for interacting with OpenFOAM
5
5
  Project-URL: Homepage, https://github.com/gerlero/foamlib
6
6
  Project-URL: Repository, https://github.com/gerlero/foamlib
@@ -27,7 +27,6 @@ Classifier: Typing :: Typed
27
27
  Requires-Python: >=3.7
28
28
  Requires-Dist: aioshutil<2,>=1
29
29
  Requires-Dist: numpy<3,>=1
30
- Requires-Dist: numpy<3,>=1.25.0; python_version >= '3.10'
31
30
  Requires-Dist: pyparsing<4,>=3.1.2
32
31
  Requires-Dist: rich<15,>=13
33
32
  Requires-Dist: typing-extensions<5,>=4; python_version < '3.11'
@@ -37,22 +36,29 @@ Requires-Dist: pytest-asyncio<0.27,>=0.21; extra == 'dev'
37
36
  Requires-Dist: pytest-cov; extra == 'dev'
38
37
  Requires-Dist: pytest<9,>=7; extra == 'dev'
39
38
  Requires-Dist: ruff; extra == 'dev'
39
+ Requires-Dist: scipy-stubs; (python_version >= '3.10') and extra == 'dev'
40
+ Requires-Dist: scipy<2,>=1; extra == 'dev'
40
41
  Requires-Dist: sphinx-rtd-theme; extra == 'dev'
41
42
  Requires-Dist: sphinx<9,>=5; extra == 'dev'
42
43
  Provides-Extra: docs
44
+ Requires-Dist: ruff; extra == 'docs'
43
45
  Requires-Dist: sphinx-rtd-theme; extra == 'docs'
44
46
  Requires-Dist: sphinx<9,>=5; extra == 'docs'
45
47
  Provides-Extra: lint
46
48
  Requires-Dist: ruff; extra == 'lint'
47
49
  Provides-Extra: test
50
+ Requires-Dist: mypy<2,>=1; extra == 'test'
48
51
  Requires-Dist: pytest-asyncio<0.27,>=0.21; extra == 'test'
49
52
  Requires-Dist: pytest-cov; extra == 'test'
50
53
  Requires-Dist: pytest<9,>=7; extra == 'test'
54
+ Requires-Dist: scipy<2,>=1; extra == 'test'
51
55
  Provides-Extra: typing
52
56
  Requires-Dist: mypy<2,>=1; extra == 'typing'
53
57
  Requires-Dist: pytest-asyncio<0.27,>=0.21; extra == 'typing'
54
58
  Requires-Dist: pytest-cov; extra == 'typing'
55
59
  Requires-Dist: pytest<9,>=7; extra == 'typing'
60
+ Requires-Dist: scipy-stubs; (python_version >= '3.10') and extra == 'typing'
61
+ Requires-Dist: scipy<2,>=1; extra == 'typing'
56
62
  Description-Content-Type: text/markdown
57
63
 
58
64
  [<img alt="foamlib" src="https://github.com/gerlero/foamlib/raw/main/logo.png" height="65">](https://github.com/gerlero/foamlib)
@@ -73,11 +79,18 @@ Description-Content-Type: text/markdown
73
79
 
74
80
  **foamlib** provides a simple, modern, ergonomic and fast Python interface for interacting with [OpenFOAM](https://www.openfoam.com).
75
81
 
76
- <p align="center">
77
- <img alt="benchmark" src="https://github.com/gerlero/foamlib/raw/main/benchmark.png" height="250">
78
- <br>
79
- <i>Parsing a </i>volVectorField<i> with 200k cells.</i>
80
- </p>
82
+ <div align="center">
83
+ <img alt="benchmark" src="https://github.com/gerlero/foamlib/raw/main/benchmark/benchmark.png" height="250">
84
+
85
+ Parsing a volVectorField with 200k cells.<sup>[1](#benchmark)</sup>
86
+ </div>
87
+
88
+
89
+ ## 🚀 Introduction
90
+
91
+ **foamlib** is a Python package designed to simplify the manipulation of OpenFOAM cases and files. Its standalone parser makes it easy to work with OpenFOAM’s input/output files from Python, while its case-handling capabilities facilitate various execution workflows—reducing boilerplate code and enabling efficient Python-based pre- and post-processing, as well as simulation management.
92
+
93
+ Compared to [PyFoam](https://openfoamwiki.net/index.php/Contrib/PyFoam) and other similar tools like [fluidfoam](https://github.com/fluiddyn/fluidfoam), [fluidsimfoam](https://foss.heptapod.net/fluiddyn/fluidsimfoam), and [Ofpp](https://github.com/xu-xianghua/ofpp), **foamlib** offers advantages such as modern Python compatibility, support for binary-formatted fields, a fully type-hinted API, and asynchronous operations; making OpenFOAM workflows more accessible and streamlined.
81
94
 
82
95
  ## 👋 Basics
83
96
 
@@ -208,6 +221,159 @@ case = FoamCase(Path(__file__).parent)
208
221
  case.run()
209
222
  ```
210
223
 
211
- ## 📘 Documentation
224
+ ## ▶️ A complete example
225
+
226
+ The following is a fully self-contained example that demonstrates how to create an OpenFOAM case from scratch, run it, and analyze the results.
227
+
228
+ <details>
229
+
230
+ <summary>Example</summary>
231
+
232
+ ```python
233
+ #!/usr/bin/env python3
234
+ """Check the diffusion of a scalar field in a scalarTransportFoam case."""
235
+
236
+ import shutil
237
+ from pathlib import Path
238
+
239
+ import numpy as np
240
+ from scipy.special import erfc
241
+ from foamlib import FoamCase
242
+
243
+ path = Path(__file__).parent / "diffusionCheck"
244
+ shutil.rmtree(path, ignore_errors=True)
245
+ path.mkdir(parents=True)
246
+ (path / "system").mkdir()
247
+ (path / "constant").mkdir()
248
+ (path / "0").mkdir()
249
+
250
+ case = FoamCase(path)
251
+
252
+ with case.control_dict as f:
253
+ f["application"] = "scalarTransportFoam"
254
+ f["startFrom"] = "latestTime"
255
+ f["stopAt"] = "endTime"
256
+ f["endTime"] = 5
257
+ f["deltaT"] = 1e-3
258
+ f["writeControl"] = "adjustableRunTime"
259
+ f["writeInterval"] = 1
260
+ f["purgeWrite"] = 0
261
+ f["writeFormat"] = "ascii"
262
+ f["writePrecision"] = 6
263
+ f["writeCompression"] = False
264
+ f["timeFormat"] = "general"
265
+ f["timePrecision"] = 6
266
+ f["adjustTimeStep"] = False
267
+ f["runTimeModifiable"] = False
268
+
269
+ with case.fv_schemes as f:
270
+ f["ddtSchemes"] = {"default": "Euler"}
271
+ f["gradSchemes"] = {"default": "Gauss linear"}
272
+ f["divSchemes"] = {"default": "none", "div(phi,U)": "Gauss linear", "div(phi,T)": "Gauss linear"}
273
+ f["laplacianSchemes"] = {"default": "Gauss linear corrected"}
274
+
275
+ with case.fv_solution as f:
276
+ f["solvers"] = {"T": {"solver": "PBiCG", "preconditioner": "DILU", "tolerance": 1e-6, "relTol": 0}}
277
+
278
+ with case.block_mesh_dict as f:
279
+ f["scale"] = 1
280
+ f["vertices"] = [
281
+ [0, 0, 0],
282
+ [1, 0, 0],
283
+ [1, 0.5, 0],
284
+ [1, 1, 0],
285
+ [0, 1, 0],
286
+ [0, 0.5, 0],
287
+ [0, 0, 0.1],
288
+ [1, 0, 0.1],
289
+ [1, 0.5, 0.1],
290
+ [1, 1, 0.1],
291
+ [0, 1, 0.1],
292
+ [0, 0.5, 0.1],
293
+ ]
294
+ f["blocks"] = [
295
+ "hex", [0, 1, 2, 5, 6, 7, 8, 11], [400, 20, 1], "simpleGrading", [1, 1, 1],
296
+ "hex", [5, 2, 3, 4, 11, 8, 9, 10], [400, 20, 1], "simpleGrading", [1, 1, 1],
297
+ ]
298
+ f["edges"] = []
299
+ f["boundary"] = [
300
+ ("inletUp", {"type": "patch", "faces": [[5, 4, 10, 11]]}),
301
+ ("inletDown", {"type": "patch", "faces": [[0, 5, 11, 6]]}),
302
+ ("outletUp", {"type": "patch", "faces": [[2, 3, 9, 8]]}),
303
+ ("outletDown", {"type": "patch", "faces": [[1, 2, 8, 7]]}),
304
+ ("walls", {"type": "wall", "faces": [[4, 3, 9, 10], [0, 1, 7, 6]]}),
305
+ ("frontAndBack", {"type": "empty", "faces": [[0, 1, 2, 5], [5, 2, 3, 4], [6, 7, 8, 11], [11, 8, 9, 10]]}),
306
+ ]
307
+ f["mergePatchPairs"] = []
308
+
309
+ with case.transport_properties as f:
310
+ f["DT"] = f.Dimensioned(1e-3, f.DimensionSet(length=2, time=-1), "DT")
311
+
312
+ with case[0]["U"] as f:
313
+ f.dimensions = f.DimensionSet(length=1, time=-1)
314
+ f.internal_field = [1, 0, 0]
315
+ f.boundary_field = {
316
+ "inletUp": {"type": "fixedValue", "value": [1, 0, 0]},
317
+ "inletDown": {"type": "fixedValue", "value": [1, 0, 0]},
318
+ "outletUp": {"type": "zeroGradient"},
319
+ "outletDown": {"type": "zeroGradient"},
320
+ "walls": {"type": "zeroGradient"},
321
+ "frontAndBack": {"type": "empty"},
322
+ }
323
+
324
+ with case[0]["T"] as f:
325
+ f.dimensions = f.DimensionSet(temperature=1)
326
+ f.internal_field = 0
327
+ f.boundary_field = {
328
+ "inletUp": {"type": "fixedValue", "value": 0},
329
+ "inletDown": {"type": "fixedValue", "value": 1},
330
+ "outletUp": {"type": "zeroGradient"},
331
+ "outletDown": {"type": "zeroGradient"},
332
+ "walls": {"type": "zeroGradient"},
333
+ "frontAndBack": {"type": "empty"},
334
+ }
335
+
336
+ case.run()
337
+
338
+ x, y, z = case[0].cell_centers().internal_field.T
339
+
340
+ end = x == x.max()
341
+ x = x[end]
342
+ y = y[end]
343
+ z = z[end]
344
+
345
+ DT = case.transport_properties["DT"].value
346
+ U = case[0]["U"].internal_field[0]
347
+
348
+ for time in case[1:]:
349
+ if U*time.time < 2*x.max():
350
+ continue
351
+
352
+ T = time["T"].internal_field[end]
353
+ analytical = 0.5 * erfc((y - 0.5) / np.sqrt(4 * DT * x/U))
354
+ if np.allclose(T, analytical, atol=0.1):
355
+ print(f"Time {time.time}: OK")
356
+ else:
357
+ raise RuntimeError(f"Time {time.time}: {T} != {analytical}")
358
+ ```
359
+
360
+ </details>
361
+
362
+
363
+ ## 📘 API documentation
364
+
365
+ For more information on how to use **foamlibs**'s classes and methods, check out the [documentation](https://foamlib.readthedocs.io/).
366
+
367
+ ## 🙋 Support
368
+
369
+ If you have any questions or need help, feel free to open a [discussion](https://github.com/gerlero/foamlib/discussions).
370
+
371
+ If you believe you have found a bug in **foamlib**, please open an [issue](https://github.com/gerlero/foamlib/issues).
372
+
373
+ ## 🧑‍💻 Contributing
374
+
375
+ You're welcome to contribute to **foamlib**! Check out the [contributing guidelines](CONTRIBUTING.md) for more information.
376
+
377
+ ## Footnotes
212
378
 
213
- For more information, check out the [documentation](https://foamlib.readthedocs.io/).
379
+ <a id="benchmark">[1]</a> foamlib 0.8.1 vs PyFoam 2023.7 on a MacBook Air (2020, M1) with 8 GB of RAM. [Benchmark script](benchmark/benchmark.py).
@@ -0,0 +1,20 @@
1
+ foamlib/__init__.py,sha256=UPZX4CKrZGbzDVviSntbJjI7Bcv7ufYZWlGZbVVlHyQ,452
2
+ foamlib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ foamlib/_cases/__init__.py,sha256=_A1TTHuQfS9FH2_33lSEyLtOJZGFHZBco1tWJCVOHks,358
4
+ foamlib/_cases/_async.py,sha256=e4lGTcQBbFGwfG6SmJks5aa5LWd_0dy01kgKZWAgTGQ,11655
5
+ foamlib/_cases/_base.py,sha256=CNfutRnqniTNS1xQZ2EUeK0n2VXTgRoFHadepWBndKs,7434
6
+ foamlib/_cases/_run.py,sha256=aD9JNv7YAs5GJGWXlYQAhr_-QT-46CUxecCPyrCmFA0,15631
7
+ foamlib/_cases/_slurm.py,sha256=2nimUWxHSkZdtmRROzcvnLW5urgmkNqxoTkCUmxALVE,2689
8
+ foamlib/_cases/_subprocess.py,sha256=VHV2SuOLqa711an6kCuvN6UlIkeh4qqFfdrpNoKzQps,5630
9
+ foamlib/_cases/_sync.py,sha256=yhrkwStKri7u41YImYCGBH4REcKn8Ar-32VW_WPa40c,9641
10
+ foamlib/_cases/_util.py,sha256=QCizfbuJdOCeF9ogU2R-y-iWX5kfaOA4U2W68t6QlOM,2544
11
+ foamlib/_files/__init__.py,sha256=q1vkjXnjnSZvo45jPAICpWeF2LZv5V6xfzAR6S8fS5A,96
12
+ foamlib/_files/_files.py,sha256=gSJQjvB1f7N2yJtCTx9kpivKqSSNjDj37qNMpned5CM,19505
13
+ foamlib/_files/_io.py,sha256=BGbbm6HKxL2ka0YMCmHqZQZ1R4PPQlkvWWb4FHMAS8k,2217
14
+ foamlib/_files/_parsing.py,sha256=IB6c9mc0AM_z_Gh7K82Z1ANseGaNwmzUo8AYv3pxEBw,14111
15
+ foamlib/_files/_serialization.py,sha256=QJ-F6BKizVe0gpjnpIfPxNGTqWwalY4PQtCKdDY9D70,5502
16
+ foamlib/_files/_types.py,sha256=PDhFW5hUzcoQsLx7M0Va1oaYV6km02jFgrvKJof0JKQ,3750
17
+ foamlib-0.8.9.dist-info/METADATA,sha256=zkGORMaEmCITWARJJOI-HxsAZGeXknnzKOMqA6ixa-c,14014
18
+ foamlib-0.8.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
19
+ foamlib-0.8.9.dist-info/licenses/LICENSE.txt,sha256=5Dte9TUnLZzPRs4NQzl-Jc2-Ljd-t_v0ZR5Ng5r0UsY,35131
20
+ foamlib-0.8.9.dist-info/RECORD,,
@@ -1,20 +0,0 @@
1
- foamlib/__init__.py,sha256=ef8IIQjKj7fPW-7t7Oeo-bUD9Wy3bJY5Q63lobifP7o,452
2
- foamlib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- foamlib/_cases/__init__.py,sha256=_A1TTHuQfS9FH2_33lSEyLtOJZGFHZBco1tWJCVOHks,358
4
- foamlib/_cases/_async.py,sha256=vXpq5T2gDomuawARsLaUEMjUBhMsZK58q7iVwQpXsYE,7903
5
- foamlib/_cases/_base.py,sha256=37oBbM3NM-hpG7dKewZvyJNtqSAogMurcbmX-wLIgMU,6727
6
- foamlib/_cases/_run.py,sha256=L2q9wab_pIbDpqC_N59ZpatgvjuDzQAWllQMyOHb1Hk,15475
7
- foamlib/_cases/_slurm.py,sha256=Hzpf5Ugahwq7sUsCfhZ6JgXDOdkoYGGsHGiO7NV8uFs,2331
8
- foamlib/_cases/_subprocess.py,sha256=VHV2SuOLqa711an6kCuvN6UlIkeh4qqFfdrpNoKzQps,5630
9
- foamlib/_cases/_sync.py,sha256=e06aGGZ9n6WTWK9eWZhrezsT4yebGhKPE0sn0nwQH8A,5977
10
- foamlib/_cases/_util.py,sha256=QCizfbuJdOCeF9ogU2R-y-iWX5kfaOA4U2W68t6QlOM,2544
11
- foamlib/_files/__init__.py,sha256=q1vkjXnjnSZvo45jPAICpWeF2LZv5V6xfzAR6S8fS5A,96
12
- foamlib/_files/_files.py,sha256=YAKw3RHEw8eCja4JAxobp6BC-2Kyy7AvG1O7yOqyMic,15414
13
- foamlib/_files/_io.py,sha256=BGbbm6HKxL2ka0YMCmHqZQZ1R4PPQlkvWWb4FHMAS8k,2217
14
- foamlib/_files/_parsing.py,sha256=IB6c9mc0AM_z_Gh7K82Z1ANseGaNwmzUo8AYv3pxEBw,14111
15
- foamlib/_files/_serialization.py,sha256=PvMzNyTja6OKT_GUfExTulx9nMLBjfu0-9yThCKbvxs,5227
16
- foamlib/_files/_types.py,sha256=m-fFjJnS4sFSavDsijlXpAfEhnbh10RBumSHAT0GOgQ,3408
17
- foamlib-0.8.8.dist-info/METADATA,sha256=DdEh3U5bZ9VZmxA-3-0GlMgl_XNlPhwRDdE_K41bvT4,7996
18
- foamlib-0.8.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
19
- foamlib-0.8.8.dist-info/licenses/LICENSE.txt,sha256=5Dte9TUnLZzPRs4NQzl-Jc2-Ljd-t_v0ZR5Ng5r0UsY,35131
20
- foamlib-0.8.8.dist-info/RECORD,,