simjsr 0.0.2__tar.gz → 0.0.3__tar.gz
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.
- {simjsr-0.0.2 → simjsr-0.0.3}/PKG-INFO +2 -1
- {simjsr-0.0.2 → simjsr-0.0.3}/pyproject.toml +2 -1
- {simjsr-0.0.2 → simjsr-0.0.3}/src/simjsr/__init__.py +1 -1
- {simjsr-0.0.2 → simjsr-0.0.3}/src/simjsr/config.py +1 -1
- {simjsr-0.0.2 → simjsr-0.0.3}/src/simjsr/run.py +64 -43
- {simjsr-0.0.2 → simjsr-0.0.3}/src/simjsr/cli.py +0 -0
- {simjsr-0.0.2 → simjsr-0.0.3}/src/simjsr/convert.py +0 -0
- {simjsr-0.0.2 → simjsr-0.0.3}/src/simjsr/data/chem.inp +0 -0
- {simjsr-0.0.2 → simjsr-0.0.3}/src/simjsr/data/chem.yaml +0 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: simjsr
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
4
4
|
Author: Andreas V. Copan
|
|
5
5
|
Author-email: Andreas V. Copan <avcopan@uga.edu>
|
|
6
6
|
Requires-Dist: cantera>=3.2.0
|
|
7
|
+
Requires-Dist: func-timeout>=4.3.5
|
|
7
8
|
Requires-Dist: polars>=1.41.2
|
|
8
9
|
Requires-Dist: pydantic>=2.13.4
|
|
9
10
|
Requires-Dist: pyyaml>=6.0.3
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "simjsr"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.3"
|
|
4
4
|
authors = [{name = "Andreas V. Copan", email = "avcopan@uga.edu"}]
|
|
5
5
|
requires-python = ">= 3.13"
|
|
6
6
|
dependencies = [
|
|
7
7
|
"cantera>=3.2.0",
|
|
8
|
+
"func-timeout>=4.3.5",
|
|
8
9
|
"polars>=1.41.2",
|
|
9
10
|
"pydantic>=2.13.4",
|
|
10
11
|
"pyyaml>=6.0.3",
|
|
@@ -33,7 +33,7 @@ class Config(BaseModel):
|
|
|
33
33
|
description="Starting composition (mole fractions)"
|
|
34
34
|
)
|
|
35
35
|
volume: float = Field(description="Reactor volume (cm^3)", default=1.0)
|
|
36
|
-
time_out:
|
|
36
|
+
time_out: float | None = Field(
|
|
37
37
|
description="Time limit for the simulation (s)", default=None
|
|
38
38
|
)
|
|
39
39
|
chemkin_file: Path | None = Field(
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"""Reactors."""
|
|
2
2
|
|
|
3
|
-
import signal
|
|
4
3
|
import textwrap
|
|
5
4
|
from collections.abc import Callable, Mapping, Sequence
|
|
6
5
|
from copy import replace
|
|
@@ -11,6 +10,7 @@ from typing import Any
|
|
|
11
10
|
|
|
12
11
|
import cantera as ct
|
|
13
12
|
import polars as pl
|
|
13
|
+
from func_timeout import FunctionTimedOut, func_timeout
|
|
14
14
|
|
|
15
15
|
from . import convert
|
|
16
16
|
from .config import Config
|
|
@@ -19,7 +19,11 @@ Logger = Callable[[str], Any]
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
def single(
|
|
22
|
-
config: Config,
|
|
22
|
+
config: Config,
|
|
23
|
+
*,
|
|
24
|
+
logger: Logger | None = None,
|
|
25
|
+
output_file: Path | None = None,
|
|
26
|
+
raise_on_timout: bool = True,
|
|
23
27
|
) -> dict[str, float]:
|
|
24
28
|
"""Run a single jet-stirred reactor simulation.
|
|
25
29
|
|
|
@@ -27,6 +31,7 @@ def single(
|
|
|
27
31
|
config: Configuration for the simulation
|
|
28
32
|
logger: Optional logger for messages
|
|
29
33
|
output_file: Optional file to save simulation results
|
|
34
|
+
raise_on_timout: Whether to raise a TimeoutError if the simulation times out
|
|
30
35
|
|
|
31
36
|
Returns:
|
|
32
37
|
Steady state mole fractions
|
|
@@ -34,48 +39,26 @@ def single(
|
|
|
34
39
|
Raises:
|
|
35
40
|
TimeoutError: If the simulation exceeds the configured time limit
|
|
36
41
|
"""
|
|
37
|
-
|
|
38
|
-
if config.time_out is not None:
|
|
39
|
-
signal.signal(signal.SIGALRM, _timeout_handler)
|
|
40
|
-
signal.alarm(config.time_out)
|
|
41
|
-
|
|
42
|
-
if logger is not None:
|
|
43
|
-
start_time = datetime.now(tz=UTC)
|
|
44
|
-
start_counter = perf_counter()
|
|
45
|
-
logger(f"Starting simulation at {start_time:%Y-%m-%d %H:%M:%S}")
|
|
42
|
+
start_counter = clock_start(logger, "Starting simulation")
|
|
46
43
|
|
|
47
44
|
if not config.cantera_file.exists():
|
|
48
|
-
|
|
49
|
-
msg = "chemkin_file must be provided if cantera_file does not exist."
|
|
50
|
-
raise ValueError(msg)
|
|
51
|
-
|
|
52
|
-
if not config.chemkin_file.exists():
|
|
53
|
-
msg = f"{config.chemkin_file = } does not exist."
|
|
54
|
-
raise ValueError(msg)
|
|
45
|
+
generate_cantera_file_from_chemkin(config, logger=logger)
|
|
55
46
|
|
|
56
|
-
|
|
57
|
-
logger(
|
|
58
|
-
f"Converting {config.chemkin_file} to Cantera format "
|
|
59
|
-
f"({config.cantera_file})"
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
convert.from_chemkin(
|
|
63
|
-
chemkin_file=config.chemkin_file,
|
|
64
|
-
chemkin_thermo_file=config.chemkin_thermo_file,
|
|
65
|
-
cantera_file=config.cantera_file,
|
|
66
|
-
)
|
|
47
|
+
phase = ct.Solution(config.cantera_file)
|
|
67
48
|
|
|
68
49
|
try:
|
|
69
|
-
mole_fracs =
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
50
|
+
mole_fracs = (
|
|
51
|
+
_single(config)
|
|
52
|
+
if config.time_out is None
|
|
53
|
+
else func_timeout(config.time_out, _single, (config,))
|
|
54
|
+
)
|
|
55
|
+
except FunctionTimedOut:
|
|
56
|
+
clock_end(logger, "Simulation timed out", start_counter)
|
|
57
|
+
mole_fracs = {s: float("nan") for s in phase.species_names}
|
|
58
|
+
if raise_on_timout:
|
|
59
|
+
raise
|
|
60
|
+
else:
|
|
61
|
+
clock_end(logger, "Finished simulation", start_counter)
|
|
79
62
|
|
|
80
63
|
if output_file is not None:
|
|
81
64
|
pl.from_dicts([mole_fracs]).write_csv(output_file)
|
|
@@ -120,7 +103,7 @@ def multi(
|
|
|
120
103
|
):
|
|
121
104
|
config = replace(init_config, composition=last_mole_fracs)
|
|
122
105
|
|
|
123
|
-
mole_fracs = single(config=config, logger=logger)
|
|
106
|
+
mole_fracs = single(config=config, logger=logger, raise_on_timout=False)
|
|
124
107
|
mole_fracs_lst.append(mole_fracs)
|
|
125
108
|
|
|
126
109
|
last_config = init_config
|
|
@@ -206,6 +189,44 @@ def _single(config: Config) -> dict[str, float]:
|
|
|
206
189
|
return reactor.phase.mole_fraction_dict()
|
|
207
190
|
|
|
208
191
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
192
|
+
# Helpers
|
|
193
|
+
def generate_cantera_file_from_chemkin(
|
|
194
|
+
config: Config, *, logger: Logger | None = None
|
|
195
|
+
) -> None:
|
|
196
|
+
"""Ensure the Cantera file is available."""
|
|
197
|
+
if config.chemkin_file is None:
|
|
198
|
+
msg = "chemkin_file must be provided if cantera_file does not exist."
|
|
199
|
+
raise ValueError(msg)
|
|
200
|
+
|
|
201
|
+
if not config.chemkin_file.exists():
|
|
202
|
+
msg = f"{config.chemkin_file = } does not exist."
|
|
203
|
+
raise ValueError(msg)
|
|
204
|
+
|
|
205
|
+
if logger is not None:
|
|
206
|
+
logger(
|
|
207
|
+
f"Converting {config.chemkin_file} to Cantera format "
|
|
208
|
+
f"({config.cantera_file})"
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
convert.from_chemkin(
|
|
212
|
+
chemkin_file=config.chemkin_file,
|
|
213
|
+
chemkin_thermo_file=config.chemkin_thermo_file,
|
|
214
|
+
cantera_file=config.cantera_file,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def clock_start(logger: Logger | None, event: str) -> float:
|
|
219
|
+
"""Log the start time of an event."""
|
|
220
|
+
if logger is not None:
|
|
221
|
+
start_time = datetime.now(tz=UTC)
|
|
222
|
+
logger(f"{event} at {start_time:%Y-%m-%d %H:%M:%S}")
|
|
223
|
+
return perf_counter()
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def clock_end(logger: Logger | None, event: str, start_counter: float) -> None:
|
|
227
|
+
"""Log the start time of a simulation."""
|
|
228
|
+
if logger is not None:
|
|
229
|
+
end_time = datetime.now(tz=UTC)
|
|
230
|
+
elapsed = timedelta(seconds=perf_counter() - start_counter)
|
|
231
|
+
logger(f"{event} at {end_time:%Y-%m-%d %H:%M:%S}")
|
|
232
|
+
logger(f"Elapsed time: {elapsed}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|