openflume 0.3.1__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.
- openflume-0.3.1/.gitignore +106 -0
- openflume-0.3.1/LICENSE +21 -0
- openflume-0.3.1/PKG-INFO +120 -0
- openflume-0.3.1/README.md +85 -0
- openflume-0.3.1/hatch_build.py +30 -0
- openflume-0.3.1/pyproject.toml +94 -0
- openflume-0.3.1/src/openflume/__init__.py +79 -0
- openflume-0.3.1/src/openflume/__main__.py +90 -0
- openflume-0.3.1/src/openflume/_bridge.py +279 -0
- openflume-0.3.1/src/openflume/_js/coolprop.wasm +0 -0
- openflume-0.3.1/src/openflume/_js/openflume-bridge.mjs +35226 -0
- openflume-0.3.1/src/openflume/errors.py +103 -0
- openflume-0.3.1/src/openflume/fluids.py +234 -0
- openflume-0.3.1/src/openflume/network.py +222 -0
- openflume-0.3.1/src/openflume/py.typed +0 -0
- openflume-0.3.1/src/openflume/results.py +226 -0
- openflume-0.3.1/tests/conftest.py +12 -0
- openflume-0.3.1/tests/test_fluids.py +79 -0
- openflume-0.3.1/tests/test_network.py +129 -0
- openflume-0.3.1/tests/test_runtime.py +98 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules/
|
|
3
|
+
.pnpm-store/
|
|
4
|
+
|
|
5
|
+
# Build output
|
|
6
|
+
dist/
|
|
7
|
+
*.tsbuildinfo
|
|
8
|
+
|
|
9
|
+
# Test & coverage
|
|
10
|
+
test-results/
|
|
11
|
+
playwright-report/
|
|
12
|
+
coverage/
|
|
13
|
+
*.lcov
|
|
14
|
+
|
|
15
|
+
# Logs
|
|
16
|
+
*.log
|
|
17
|
+
npm-debug.log*
|
|
18
|
+
|
|
19
|
+
# OS
|
|
20
|
+
.DS_Store
|
|
21
|
+
Thumbs.db
|
|
22
|
+
|
|
23
|
+
# IDE
|
|
24
|
+
.vscode/
|
|
25
|
+
.idea/
|
|
26
|
+
*.swp
|
|
27
|
+
*.swo
|
|
28
|
+
|
|
29
|
+
# Local env
|
|
30
|
+
.env
|
|
31
|
+
|
|
32
|
+
# Python venv for scripts/build-cea-tables.py (offline table generator only —
|
|
33
|
+
# never imported at runtime; see docs/combustion.md)
|
|
34
|
+
.venv-cea/
|
|
35
|
+
__pycache__/
|
|
36
|
+
.env.local
|
|
37
|
+
|
|
38
|
+
# Misc
|
|
39
|
+
*.tgz
|
|
40
|
+
debug_*.ts
|
|
41
|
+
debug-*.ts
|
|
42
|
+
check_*.ts
|
|
43
|
+
check-*.ts
|
|
44
|
+
diag_*.ts
|
|
45
|
+
diag-*.ts
|
|
46
|
+
scratch_*.ts
|
|
47
|
+
scratch-*.ts
|
|
48
|
+
tmp_*.ts
|
|
49
|
+
tmp-*.ts
|
|
50
|
+
# Root-level scratch scripts named with suffixes (e.g. nh3_debug.ts, nh3_probe.mjs)
|
|
51
|
+
/*_debug*.ts
|
|
52
|
+
/*_debug*.mjs
|
|
53
|
+
/*_probe*.ts
|
|
54
|
+
/*_probe*.mjs
|
|
55
|
+
# Preferred home for local scratch work
|
|
56
|
+
/scratch/
|
|
57
|
+
|
|
58
|
+
# Vite cache
|
|
59
|
+
.vite/
|
|
60
|
+
|
|
61
|
+
# Validation source documents — re-fetchable via URLs/DOIs in the CATALOG files
|
|
62
|
+
validation/**/*.pdf
|
|
63
|
+
|
|
64
|
+
# Cloned upstream repo (nested .git) — re-clonable:
|
|
65
|
+
# git clone github.com/fateh-btpfl/FlowBoilingHTC && git checkout f2e6d83
|
|
66
|
+
# (see validation/references/CATALOG_TIERS.md, tier-5 entry #31)
|
|
67
|
+
validation/references/tier5/FlowBoilingHTC/
|
|
68
|
+
|
|
69
|
+
# QC render PNGs (~31 MB, regenerable via validation/qc_review/build_gallery.py) —
|
|
70
|
+
# the gallery scripts/HTML under qc_review/ ARE committed
|
|
71
|
+
validation/qc_review/pdf_pages/
|
|
72
|
+
validation/qc_review/plots/
|
|
73
|
+
|
|
74
|
+
# Python bytecode from validation extraction scripts
|
|
75
|
+
__pycache__/
|
|
76
|
+
|
|
77
|
+
# Local digitization scratch workspaces (untracked, regenerable): the ~97 MB
|
|
78
|
+
# nbs9264_verify replot/screenshot workspace and the clicker analysis overlays.
|
|
79
|
+
# Canonical digitized CSVs, calibrations, and annotations remain tracked.
|
|
80
|
+
validation/data/digitized/chilldown/nbs9264_verify/
|
|
81
|
+
validation/data/digitized/chilldown/nbs9264_clicker/analysis/
|
|
82
|
+
|
|
83
|
+
# Regenerable digitization workspace outputs (retain source points/logs/docs)
|
|
84
|
+
validation/data/digitized/chilldown/*/auto/cache/
|
|
85
|
+
validation/data/digitized/chilldown/*/auto/overlays/
|
|
86
|
+
validation/data/digitized/chilldown/*/analysis/previews/
|
|
87
|
+
validation/data/digitized/chilldown/*/analysis/overlays/
|
|
88
|
+
validation/data/digitized/chilldown/*/analysis/*.png
|
|
89
|
+
validation/data/digitized/chilldown/*/analysis/rejected_*/
|
|
90
|
+
validation/data/digitized/chilldown/*/previews/
|
|
91
|
+
validation/data/digitized/chilldown/*/shots/
|
|
92
|
+
validation/data/digitized/chilldown/*/rejected_*/
|
|
93
|
+
validation/data/digitized/chilldown/*/fast_extract/**/cache.npz
|
|
94
|
+
validation/data/digitized/chilldown/*/fast_extract/**/.preprocess_time.json
|
|
95
|
+
validation/data/digitized/chilldown/*/fast_extract/**/diagnostics.png
|
|
96
|
+
validation/data/digitized/chilldown/*/fast_extract/**/overlay.png
|
|
97
|
+
|
|
98
|
+
# Python package (python/): bundled JS core + license copy are build
|
|
99
|
+
# artifacts of `npm run build:python`; wheels/sdists of `python -m build`.
|
|
100
|
+
python/src/openflume/_js/
|
|
101
|
+
python/LICENSE
|
|
102
|
+
python/dist/
|
|
103
|
+
python/build/
|
|
104
|
+
.venv/
|
|
105
|
+
*.egg-info/
|
|
106
|
+
.pytest_cache/
|
openflume-0.3.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 OpenFLUME contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
openflume-0.3.1/PKG-INFO
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: openflume
|
|
3
|
+
Version: 0.3.1
|
|
4
|
+
Summary: Python API for the OpenFLUME thermal-fluid network solver (steady and transient, real fluids via CoolProp).
|
|
5
|
+
Project-URL: Homepage, https://github.com/OpenFLUME/OpenFLUME
|
|
6
|
+
Project-URL: Documentation, https://github.com/OpenFLUME/OpenFLUME/blob/main/docs/python.md
|
|
7
|
+
Project-URL: Issues, https://github.com/OpenFLUME/OpenFLUME/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/OpenFLUME/OpenFLUME/blob/main/CHANGELOG.md
|
|
9
|
+
Author: OpenFLUME contributors
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: CoolProp,GFSSP,aerospace,cryogenics,fluid network,propulsion,solver,thermal-fluid
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
24
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Requires-Dist: nodejs-wheel-binaries>=22.13
|
|
28
|
+
Provides-Extra: numpy
|
|
29
|
+
Requires-Dist: numpy>=1.24; extra == 'numpy'
|
|
30
|
+
Provides-Extra: pandas
|
|
31
|
+
Requires-Dist: pandas>=2.0; extra == 'pandas'
|
|
32
|
+
Provides-Extra: test
|
|
33
|
+
Requires-Dist: pytest>=8; extra == 'test'
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# openflume (Python)
|
|
37
|
+
|
|
38
|
+
Python API for [OpenFLUME](https://github.com/OpenFLUME/OpenFLUME), an open-source
|
|
39
|
+
thermal-fluid network solver for steady and transient analysis of propulsion feed
|
|
40
|
+
systems, cryogenic lines, and tank pressurization, with real-fluid properties from
|
|
41
|
+
CoolProp.
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install openflume # add [numpy,pandas] for array/DataFrame output
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
No other installation is needed. The solver core is the same TypeScript code as the
|
|
48
|
+
web app, bundled into the package and run on a Node.js runtime that pip installs as
|
|
49
|
+
a dependency (`nodejs-wheel-binaries`). It is the same solver, with the same
|
|
50
|
+
verification record, not a port.
|
|
51
|
+
|
|
52
|
+
## Quick start
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
import openflume as of
|
|
56
|
+
|
|
57
|
+
net = of.load("feed_system.fn") # .fn from the web app, or .json
|
|
58
|
+
print(net.validate()) # [] means runnable
|
|
59
|
+
|
|
60
|
+
result = net.solve() # steady or transient per settings.mode
|
|
61
|
+
print(result.converged, result.node("tank")["pressure"]) # SI units: Pa
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Start from a bundled example and edit the model as a plain dict:
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
net = of.example("Tank blowdown")
|
|
68
|
+
net.settings["endTime"] = 2.0
|
|
69
|
+
net.branch("orifice")["component"]["cd"] = 0.65
|
|
70
|
+
|
|
71
|
+
tr = net.solve(progress=lambda p: print(f"t = {p['time']:.3f} s", end="\r"))
|
|
72
|
+
t, p = tr.times, tr.series("tank", "pressure") # numpy arrays if numpy is installed
|
|
73
|
+
df = tr.frame() # pandas: index time, columns (id, quantity)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Parameter sweeps are ordinary Python:
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
base = of.example("Sanity: orifice hand-calc")
|
|
80
|
+
for cd in (0.55, 0.60, 0.65):
|
|
81
|
+
net = base.copy()
|
|
82
|
+
net.branch("o")["component"]["cd"] = cd
|
|
83
|
+
print(cd, net.solve().branch("o")["mdot"])
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Fluid properties use the same EOS models as the solver:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
lox = of.Fluid.real("Oxygen")
|
|
90
|
+
lox.density(2e6, 95.0) # kg/m³
|
|
91
|
+
lox.saturation_temperature([1e5, 5e5, 1e6])
|
|
92
|
+
of.fluids(curated=True) # CoolProp HEOS catalogue
|
|
93
|
+
of.combustion_gas("lox-rp1", pc=7e6, of=2.6) # NASA CEA equilibrium tables
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Command line:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
openflume validate model.fn
|
|
100
|
+
openflume solve model.fn -o result.json
|
|
101
|
+
openflume info
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Notes
|
|
105
|
+
|
|
106
|
+
- **Units** are SI throughout: Pa, K, kg/s, m, m², J/kg.
|
|
107
|
+
- **Always check `result.converged`.** The solver returns its best state either way.
|
|
108
|
+
- **Model format.** `Network.config` is the `NetworkConfig` dict (schema v2) defined in
|
|
109
|
+
`src/core/schema.ts` and documented in the user manual. `.fn` files round-trip
|
|
110
|
+
losslessly with the web app.
|
|
111
|
+
- **User code.** Models can embed JavaScript user components (`componentLibrary`). That
|
|
112
|
+
code runs in the Node process with your user's privileges, so only solve models you
|
|
113
|
+
trust.
|
|
114
|
+
- **Runtime.** One Node process per `Session` is started on first use and stopped at
|
|
115
|
+
exit. Set `OPENFLUME_NODE` to use a specific Node.js 22+ executable. For parallel
|
|
116
|
+
sweeps, use one process (and therefore one session) per worker, e.g. with
|
|
117
|
+
`concurrent.futures.ProcessPoolExecutor`. Ctrl-C interrupts a running solve.
|
|
118
|
+
|
|
119
|
+
Full documentation: [docs/python.md](https://github.com/OpenFLUME/OpenFLUME/blob/main/docs/python.md).
|
|
120
|
+
License: MIT.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# openflume (Python)
|
|
2
|
+
|
|
3
|
+
Python API for [OpenFLUME](https://github.com/OpenFLUME/OpenFLUME), an open-source
|
|
4
|
+
thermal-fluid network solver for steady and transient analysis of propulsion feed
|
|
5
|
+
systems, cryogenic lines, and tank pressurization, with real-fluid properties from
|
|
6
|
+
CoolProp.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pip install openflume # add [numpy,pandas] for array/DataFrame output
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
No other installation is needed. The solver core is the same TypeScript code as the
|
|
13
|
+
web app, bundled into the package and run on a Node.js runtime that pip installs as
|
|
14
|
+
a dependency (`nodejs-wheel-binaries`). It is the same solver, with the same
|
|
15
|
+
verification record, not a port.
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
import openflume as of
|
|
21
|
+
|
|
22
|
+
net = of.load("feed_system.fn") # .fn from the web app, or .json
|
|
23
|
+
print(net.validate()) # [] means runnable
|
|
24
|
+
|
|
25
|
+
result = net.solve() # steady or transient per settings.mode
|
|
26
|
+
print(result.converged, result.node("tank")["pressure"]) # SI units: Pa
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Start from a bundled example and edit the model as a plain dict:
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
net = of.example("Tank blowdown")
|
|
33
|
+
net.settings["endTime"] = 2.0
|
|
34
|
+
net.branch("orifice")["component"]["cd"] = 0.65
|
|
35
|
+
|
|
36
|
+
tr = net.solve(progress=lambda p: print(f"t = {p['time']:.3f} s", end="\r"))
|
|
37
|
+
t, p = tr.times, tr.series("tank", "pressure") # numpy arrays if numpy is installed
|
|
38
|
+
df = tr.frame() # pandas: index time, columns (id, quantity)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Parameter sweeps are ordinary Python:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
base = of.example("Sanity: orifice hand-calc")
|
|
45
|
+
for cd in (0.55, 0.60, 0.65):
|
|
46
|
+
net = base.copy()
|
|
47
|
+
net.branch("o")["component"]["cd"] = cd
|
|
48
|
+
print(cd, net.solve().branch("o")["mdot"])
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Fluid properties use the same EOS models as the solver:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
lox = of.Fluid.real("Oxygen")
|
|
55
|
+
lox.density(2e6, 95.0) # kg/m³
|
|
56
|
+
lox.saturation_temperature([1e5, 5e5, 1e6])
|
|
57
|
+
of.fluids(curated=True) # CoolProp HEOS catalogue
|
|
58
|
+
of.combustion_gas("lox-rp1", pc=7e6, of=2.6) # NASA CEA equilibrium tables
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Command line:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
openflume validate model.fn
|
|
65
|
+
openflume solve model.fn -o result.json
|
|
66
|
+
openflume info
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Notes
|
|
70
|
+
|
|
71
|
+
- **Units** are SI throughout: Pa, K, kg/s, m, m², J/kg.
|
|
72
|
+
- **Always check `result.converged`.** The solver returns its best state either way.
|
|
73
|
+
- **Model format.** `Network.config` is the `NetworkConfig` dict (schema v2) defined in
|
|
74
|
+
`src/core/schema.ts` and documented in the user manual. `.fn` files round-trip
|
|
75
|
+
losslessly with the web app.
|
|
76
|
+
- **User code.** Models can embed JavaScript user components (`componentLibrary`). That
|
|
77
|
+
code runs in the Node process with your user's privileges, so only solve models you
|
|
78
|
+
trust.
|
|
79
|
+
- **Runtime.** One Node process per `Session` is started on first use and stopped at
|
|
80
|
+
exit. Set `OPENFLUME_NODE` to use a specific Node.js 22+ executable. For parallel
|
|
81
|
+
sweeps, use one process (and therefore one session) per worker, e.g. with
|
|
82
|
+
`concurrent.futures.ProcessPoolExecutor`. Ctrl-C interrupts a running solve.
|
|
83
|
+
|
|
84
|
+
Full documentation: [docs/python.md](https://github.com/OpenFLUME/OpenFLUME/blob/main/docs/python.md).
|
|
85
|
+
License: MIT.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Build hook: refuse to produce a wheel/sdist without the bundled JS core.
|
|
2
|
+
|
|
3
|
+
The Node bridge and coolprop.wasm in src/openflume/_js/ are generated by
|
|
4
|
+
`npm run build:python` at the repository root. Building from an sdist works
|
|
5
|
+
without Node because the sdist already carries them.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
|
|
13
|
+
|
|
14
|
+
REQUIRED = ("openflume-bridge.mjs", "coolprop.wasm")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CustomBuildHook(BuildHookInterface):
|
|
18
|
+
def initialize(self, version: str, build_data: dict) -> None:
|
|
19
|
+
root = Path(self.root)
|
|
20
|
+
js_dir = root / "src" / "openflume" / "_js"
|
|
21
|
+
missing = [name for name in REQUIRED if not (js_dir / name).is_file()]
|
|
22
|
+
if not (root / "LICENSE").is_file():
|
|
23
|
+
missing.append("../LICENSE copy")
|
|
24
|
+
if missing:
|
|
25
|
+
raise RuntimeError(
|
|
26
|
+
"openflume: bundled JS core not found ("
|
|
27
|
+
+ ", ".join(missing)
|
|
28
|
+
+ "). Run `npm ci && npm run build:python` at the repository "
|
|
29
|
+
"root before building the Python package."
|
|
30
|
+
)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.27"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "openflume"
|
|
7
|
+
# Kept equal to ../package.json "version" — tests/test_packaging.py enforces it.
|
|
8
|
+
version = "0.3.1"
|
|
9
|
+
description = "Python API for the OpenFLUME thermal-fluid network solver (steady and transient, real fluids via CoolProp)."
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
requires-python = ">=3.10"
|
|
14
|
+
authors = [{ name = "OpenFLUME contributors" }]
|
|
15
|
+
keywords = [
|
|
16
|
+
"fluid network",
|
|
17
|
+
"thermal-fluid",
|
|
18
|
+
"solver",
|
|
19
|
+
"aerospace",
|
|
20
|
+
"propulsion",
|
|
21
|
+
"cryogenics",
|
|
22
|
+
"CoolProp",
|
|
23
|
+
"GFSSP",
|
|
24
|
+
]
|
|
25
|
+
classifiers = [
|
|
26
|
+
"Development Status :: 3 - Alpha",
|
|
27
|
+
"Intended Audience :: Science/Research",
|
|
28
|
+
"Intended Audience :: Developers",
|
|
29
|
+
"Operating System :: OS Independent",
|
|
30
|
+
"Programming Language :: Python :: 3",
|
|
31
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
32
|
+
"Programming Language :: Python :: 3.10",
|
|
33
|
+
"Programming Language :: Python :: 3.11",
|
|
34
|
+
"Programming Language :: Python :: 3.12",
|
|
35
|
+
"Programming Language :: Python :: 3.13",
|
|
36
|
+
"Programming Language :: Python :: 3.14",
|
|
37
|
+
"Topic :: Scientific/Engineering :: Physics",
|
|
38
|
+
"Typing :: Typed",
|
|
39
|
+
]
|
|
40
|
+
dependencies = [
|
|
41
|
+
# Supplies a Node.js runtime as a regular wheel, so `pip install openflume`
|
|
42
|
+
# needs nothing else on the machine. OPENFLUME_NODE overrides it.
|
|
43
|
+
"nodejs-wheel-binaries>=22.13",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
[project.optional-dependencies]
|
|
47
|
+
numpy = ["numpy>=1.24"]
|
|
48
|
+
pandas = ["pandas>=2.0"]
|
|
49
|
+
test = ["pytest>=8"]
|
|
50
|
+
|
|
51
|
+
[project.scripts]
|
|
52
|
+
openflume = "openflume.__main__:main"
|
|
53
|
+
|
|
54
|
+
[project.urls]
|
|
55
|
+
Homepage = "https://github.com/OpenFLUME/OpenFLUME"
|
|
56
|
+
Documentation = "https://github.com/OpenFLUME/OpenFLUME/blob/main/docs/python.md"
|
|
57
|
+
Issues = "https://github.com/OpenFLUME/OpenFLUME/issues"
|
|
58
|
+
Changelog = "https://github.com/OpenFLUME/OpenFLUME/blob/main/CHANGELOG.md"
|
|
59
|
+
|
|
60
|
+
# The bundled JS core (src/openflume/_js/) is a gitignored build artifact
|
|
61
|
+
# produced by `npm run build:python` at the repository root; `artifacts`
|
|
62
|
+
# re-includes it despite .gitignore, and hatch_build.py refuses to build a
|
|
63
|
+
# package without it.
|
|
64
|
+
[tool.hatch.build.hooks.custom]
|
|
65
|
+
path = "hatch_build.py"
|
|
66
|
+
|
|
67
|
+
[tool.hatch.build.targets.wheel]
|
|
68
|
+
packages = ["src/openflume"]
|
|
69
|
+
artifacts = ["src/openflume/_js/*"]
|
|
70
|
+
|
|
71
|
+
[tool.hatch.build.targets.sdist]
|
|
72
|
+
include = [
|
|
73
|
+
"src/openflume",
|
|
74
|
+
"tests",
|
|
75
|
+
"hatch_build.py",
|
|
76
|
+
"README.md",
|
|
77
|
+
"LICENSE",
|
|
78
|
+
]
|
|
79
|
+
artifacts = ["src/openflume/_js/*", "LICENSE"]
|
|
80
|
+
|
|
81
|
+
[tool.pytest.ini_options]
|
|
82
|
+
testpaths = ["tests"]
|
|
83
|
+
addopts = "-ra"
|
|
84
|
+
|
|
85
|
+
[tool.ruff]
|
|
86
|
+
line-length = 120
|
|
87
|
+
target-version = "py310"
|
|
88
|
+
|
|
89
|
+
[tool.ruff.lint]
|
|
90
|
+
select = ["E", "F", "W", "I", "UP", "B", "SIM", "RUF"]
|
|
91
|
+
ignore = [
|
|
92
|
+
"RUF001", "RUF002", "RUF003", # ρ, μ, ² etc. in docstrings are intentional
|
|
93
|
+
"RUF022", # __all__ is grouped by topic, not sorted
|
|
94
|
+
]
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""OpenFLUME — thermal-fluid network solver, from Python.
|
|
2
|
+
|
|
3
|
+
Quick start::
|
|
4
|
+
|
|
5
|
+
import openflume as of
|
|
6
|
+
|
|
7
|
+
net = of.load("feed_system.fn") # or of.example("Tank blowdown")
|
|
8
|
+
errors = net.validate() # [] means runnable
|
|
9
|
+
result = net.solve() # steady or transient per settings.mode
|
|
10
|
+
print(result.converged, result.node("tank")["pressure"])
|
|
11
|
+
|
|
12
|
+
n2 = of.Fluid.real("Nitrogen")
|
|
13
|
+
n2.density(101325, 300.0) # kg/m³
|
|
14
|
+
|
|
15
|
+
Everything is SI (Pa, K, kg/s, m, J/kg). The solver core is the same
|
|
16
|
+
TypeScript code that runs the web app, executed in a bundled Node.js runtime
|
|
17
|
+
(see :class:`Session`). Models that embed JavaScript user components run that
|
|
18
|
+
code with the privileges of your Python process — only solve models you trust.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from importlib.metadata import PackageNotFoundError
|
|
24
|
+
from importlib.metadata import version as _dist_version
|
|
25
|
+
|
|
26
|
+
from ._bridge import Session, default_session, find_node, shutdown
|
|
27
|
+
from .errors import (
|
|
28
|
+
BridgeError,
|
|
29
|
+
ConfigDecodeError,
|
|
30
|
+
CoreError,
|
|
31
|
+
InvalidRequestError,
|
|
32
|
+
OpenFLUMEError,
|
|
33
|
+
ParseError,
|
|
34
|
+
UnknownExampleError,
|
|
35
|
+
UnsupportedPropertyError,
|
|
36
|
+
ValidationError,
|
|
37
|
+
)
|
|
38
|
+
from .fluids import Fluid, combustion_gas, combustion_propellants, fluids
|
|
39
|
+
from .network import Network, example, examples, load, solve, validate
|
|
40
|
+
from .results import SteadyResult, TransientResult
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
__version__ = _dist_version("openflume")
|
|
44
|
+
except PackageNotFoundError: # running from a source tree without install
|
|
45
|
+
__version__ = "0+unknown"
|
|
46
|
+
|
|
47
|
+
__all__ = [
|
|
48
|
+
"__version__",
|
|
49
|
+
# models
|
|
50
|
+
"Network",
|
|
51
|
+
"load",
|
|
52
|
+
"example",
|
|
53
|
+
"examples",
|
|
54
|
+
"solve",
|
|
55
|
+
"validate",
|
|
56
|
+
# results
|
|
57
|
+
"SteadyResult",
|
|
58
|
+
"TransientResult",
|
|
59
|
+
# fluids
|
|
60
|
+
"Fluid",
|
|
61
|
+
"fluids",
|
|
62
|
+
"combustion_gas",
|
|
63
|
+
"combustion_propellants",
|
|
64
|
+
# runtime
|
|
65
|
+
"Session",
|
|
66
|
+
"default_session",
|
|
67
|
+
"shutdown",
|
|
68
|
+
"find_node",
|
|
69
|
+
# errors
|
|
70
|
+
"OpenFLUMEError",
|
|
71
|
+
"BridgeError",
|
|
72
|
+
"ConfigDecodeError",
|
|
73
|
+
"ValidationError",
|
|
74
|
+
"ParseError",
|
|
75
|
+
"CoreError",
|
|
76
|
+
"UnknownExampleError",
|
|
77
|
+
"InvalidRequestError",
|
|
78
|
+
"UnsupportedPropertyError",
|
|
79
|
+
]
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Command line: ``openflume info | validate MODEL | solve MODEL [-o OUT]``.
|
|
2
|
+
|
|
3
|
+
Also runnable as ``python -m openflume``. Results are written as JSON (the
|
|
4
|
+
core's raw result object); exit status is 0 on success, 1 on validation
|
|
5
|
+
failure or a non-converged solve, 2 on usage/IO errors.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import json
|
|
12
|
+
import sys
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from . import __version__
|
|
16
|
+
from ._bridge import _encode_nonfinite, default_session, find_node
|
|
17
|
+
from .errors import OpenFLUMEError
|
|
18
|
+
from .network import Network
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _cmd_info(_: argparse.Namespace) -> int:
|
|
22
|
+
info = default_session().start().info
|
|
23
|
+
print(f"openflume {__version__}")
|
|
24
|
+
print(f"core {info.get('version')} (config schema v{info.get('configVersion')})")
|
|
25
|
+
print(f"node {info.get('node')} ({find_node()})")
|
|
26
|
+
return 0
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _cmd_validate(args: argparse.Namespace) -> int:
|
|
30
|
+
errors = Network.load(args.model).validate()
|
|
31
|
+
for e in errors:
|
|
32
|
+
print(e)
|
|
33
|
+
if not errors:
|
|
34
|
+
print("valid")
|
|
35
|
+
return 1 if errors else 0
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _cmd_solve(args: argparse.Namespace) -> int:
|
|
39
|
+
net = Network.load(args.model)
|
|
40
|
+
|
|
41
|
+
def progress(p: dict) -> None:
|
|
42
|
+
if p.get("kind") == "transient":
|
|
43
|
+
print(f"\r t = {p['time']:.6g} / {p['endTime']:.6g} s", end="", file=sys.stderr)
|
|
44
|
+
else:
|
|
45
|
+
print(f"\r iter {p['iteration']} residual {p['residual']:.3e}", end="", file=sys.stderr)
|
|
46
|
+
|
|
47
|
+
result = net.solve(args.mode, progress=None if args.quiet else progress)
|
|
48
|
+
if not args.quiet:
|
|
49
|
+
print(file=sys.stderr)
|
|
50
|
+
print(repr(result), file=sys.stderr)
|
|
51
|
+
text = json.dumps(_encode_nonfinite(result.raw), indent=None if args.compact else 2)
|
|
52
|
+
if args.output:
|
|
53
|
+
Path(args.output).write_text(text + "\n", encoding="utf-8")
|
|
54
|
+
else:
|
|
55
|
+
print(text)
|
|
56
|
+
return 0 if result.converged else 1
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def main(argv: list[str] | None = None) -> int:
|
|
60
|
+
parser = argparse.ArgumentParser(prog="openflume", description="OpenFLUME thermal-fluid network solver")
|
|
61
|
+
parser.add_argument("--version", action="version", version=f"openflume {__version__}")
|
|
62
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
63
|
+
|
|
64
|
+
sub.add_parser("info", help="show package, core and Node.js versions").set_defaults(fn=_cmd_info)
|
|
65
|
+
|
|
66
|
+
p_val = sub.add_parser("validate", help="validate a .fn/.json model")
|
|
67
|
+
p_val.add_argument("model")
|
|
68
|
+
p_val.set_defaults(fn=_cmd_validate)
|
|
69
|
+
|
|
70
|
+
p_solve = sub.add_parser("solve", help="solve a .fn/.json model and print the JSON result")
|
|
71
|
+
p_solve.add_argument("model")
|
|
72
|
+
p_solve.add_argument("-o", "--output", help="write the JSON result here instead of stdout")
|
|
73
|
+
p_solve.add_argument("--mode", choices=["steady", "transient"], help="override settings.mode")
|
|
74
|
+
p_solve.add_argument("-q", "--quiet", action="store_true", help="no progress on stderr")
|
|
75
|
+
p_solve.add_argument("--compact", action="store_true", help="single-line JSON")
|
|
76
|
+
p_solve.set_defaults(fn=_cmd_solve)
|
|
77
|
+
|
|
78
|
+
args = parser.parse_args(argv)
|
|
79
|
+
try:
|
|
80
|
+
return int(args.fn(args))
|
|
81
|
+
except OpenFLUMEError as exc:
|
|
82
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
83
|
+
return 1
|
|
84
|
+
except OSError as exc:
|
|
85
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
86
|
+
return 2
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
if __name__ == "__main__":
|
|
90
|
+
sys.exit(main())
|