aquakin 0.1.0__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.
- aquakin/__init__.py +332 -0
- aquakin/core/__init__.py +7 -0
- aquakin/core/conditions.py +162 -0
- aquakin/core/context.py +31 -0
- aquakin/core/hints.py +32 -0
- aquakin/core/introspect.py +174 -0
- aquakin/core/model.py +1492 -0
- aquakin/core/nodes.py +543 -0
- aquakin/core/parser.py +340 -0
- aquakin/core/ph_solver.py +853 -0
- aquakin/core/precipitation.py +244 -0
- aquakin/core/precipitation_equilibrium.py +361 -0
- aquakin/core/speciation.py +221 -0
- aquakin/core/stoich_resolve.py +173 -0
- aquakin/core/temperature.py +82 -0
- aquakin/core/units.py +69 -0
- aquakin/core/vector_kernel.py +368 -0
- aquakin/integrate/__init__.py +104 -0
- aquakin/integrate/_common.py +1523 -0
- aquakin/integrate/_qmc.py +107 -0
- aquakin/integrate/_simultaneous_corrector.py +139 -0
- aquakin/integrate/_transforms.py +72 -0
- aquakin/integrate/batch.py +455 -0
- aquakin/integrate/biofilm.py +997 -0
- aquakin/integrate/calibrate.py +1673 -0
- aquakin/integrate/cfd.py +270 -0
- aquakin/integrate/colored_jacobian.py +493 -0
- aquakin/integrate/design.py +258 -0
- aquakin/integrate/discrete_adjoint.py +713 -0
- aquakin/integrate/events.py +398 -0
- aquakin/integrate/fit.py +231 -0
- aquakin/integrate/forward_sensitivity.py +515 -0
- aquakin/integrate/forward_solve.py +319 -0
- aquakin/integrate/global_sensitivity.py +404 -0
- aquakin/integrate/monte_carlo.py +233 -0
- aquakin/integrate/particle.py +272 -0
- aquakin/integrate/pfr.py +347 -0
- aquakin/integrate/profile.py +390 -0
- aquakin/integrate/scenarios.py +250 -0
- aquakin/integrate/sensitivity.py +164 -0
- aquakin/models/__init__.py +1 -0
- aquakin/models/_make_khalil_balanced.py +149 -0
- aquakin/models/_make_khalil_balanced_biofilm.py +110 -0
- aquakin/models/_make_khalil_balanced_biofilm_biomass.py +177 -0
- aquakin/models/_make_khalil_balanced_biofilm_multispecies.py +308 -0
- aquakin/models/_make_khalil_paper.py +201 -0
- aquakin/models/_make_khalil_thesis.py +135 -0
- aquakin/models/_make_khalil_variants.py +214 -0
- aquakin/models/adm1.yaml +488 -0
- aquakin/models/asm1.yaml +191 -0
- aquakin/models/asm1_ammonia_limitation.yaml +37 -0
- aquakin/models/asm2d.yaml +402 -0
- aquakin/models/asm2d_chemp.yaml +92 -0
- aquakin/models/asm2d_tud.yaml +409 -0
- aquakin/models/asm3.yaml +219 -0
- aquakin/models/asm3_2step.yaml +368 -0
- aquakin/models/asm3_2step_anammox.yaml +95 -0
- aquakin/models/asm3_2step_comammox.yaml +92 -0
- aquakin/models/asm3_2step_n2o.yaml +132 -0
- aquakin/models/asm3_biop.yaml +396 -0
- aquakin/models/ozone_bromate.yaml +166 -0
- aquakin/models/precipitation_metal_phosphate.yaml +98 -0
- aquakin/models/precipitation_metal_phosphate_bounded.yaml +109 -0
- aquakin/models/precipitation_metal_phosphate_equilibrium.yaml +119 -0
- aquakin/models/precipitation_struvite_calcite.yaml +74 -0
- aquakin/models/uv_h2o2.yaml +99 -0
- aquakin/models/wats_sewer.yaml +387 -0
- aquakin/models/wats_sewer_extended.yaml +531 -0
- aquakin/models/wats_sewer_extended_combined.yaml +550 -0
- aquakin/models/wats_sewer_extended_directsulfate.yaml +544 -0
- aquakin/models/wats_sewer_extended_halforder.yaml +545 -0
- aquakin/models/wats_sewer_extended_srbsubstrate.yaml +544 -0
- aquakin/models/wats_sewer_extended_v0.yaml +492 -0
- aquakin/models/wats_sewer_khalil_paper.yaml +321 -0
- aquakin/models/wats_sewer_khalil_paper_balanced.yaml +602 -0
- aquakin/models/wats_sewer_khalil_paper_balanced_biofilm.yaml +621 -0
- aquakin/models/wats_sewer_khalil_paper_balanced_biofilm_biomass.yaml +536 -0
- aquakin/models/wats_sewer_khalil_paper_balanced_biofilm_multispecies.yaml +665 -0
- aquakin/models/wats_sewer_khalil_paper_balanced_combined.yaml +602 -0
- aquakin/models/wats_sewer_khalil_paper_balanced_directsulfate.yaml +599 -0
- aquakin/models/wats_sewer_khalil_paper_balanced_halforder.yaml +16 -0
- aquakin/models/wats_sewer_khalil_paper_balanced_stopatS0.yaml +593 -0
- aquakin/models/wats_sewer_khalil_paper_combined.yaml +545 -0
- aquakin/models/wats_sewer_khalil_paper_directsulfate.yaml +543 -0
- aquakin/models/wats_sewer_khalil_paper_halforder.yaml +16 -0
- aquakin/models/wats_sewer_khalil_paper_srbsubstrate.yaml +542 -0
- aquakin/models/wats_sewer_khalil_paper_stopatS0.yaml +537 -0
- aquakin/models/wats_sewer_khalil_thesis.yaml +846 -0
- aquakin/plant/__init__.py +255 -0
- aquakin/plant/_builder_support.py +96 -0
- aquakin/plant/_constants.py +122 -0
- aquakin/plant/_flow_split.py +62 -0
- aquakin/plant/a2o.py +415 -0
- aquakin/plant/aeration_system.py +253 -0
- aquakin/plant/balance.py +495 -0
- aquakin/plant/bsm/__init__.py +59 -0
- aquakin/plant/bsm/bsm1.py +254 -0
- aquakin/plant/bsm/bsm2.py +880 -0
- aquakin/plant/bsm/data/BSM1_dry.csv +1352 -0
- aquakin/plant/bsm/data/BSM1_rain.csv +1352 -0
- aquakin/plant/bsm/data/BSM1_storm.csv +1352 -0
- aquakin/plant/bsm/data/BSM2_dry.csv +1352 -0
- aquakin/plant/bsm/data/BSM2_rain.csv +1352 -0
- aquakin/plant/bsm/data/BSM2_storm.csv +1352 -0
- aquakin/plant/bsm/data/__init__.py +1 -0
- aquakin/plant/bsm/evaluation.py +928 -0
- aquakin/plant/bsm/warmstart.py +155 -0
- aquakin/plant/calibrate.py +783 -0
- aquakin/plant/characterize.py +310 -0
- aquakin/plant/clarifier.py +171 -0
- aquakin/plant/colored.py +466 -0
- aquakin/plant/control.py +178 -0
- aquakin/plant/cost.py +192 -0
- aquakin/plant/coupling.py +115 -0
- aquakin/plant/cstr.py +580 -0
- aquakin/plant/delay.py +144 -0
- aquakin/plant/design.py +507 -0
- aquakin/plant/digester.py +248 -0
- aquakin/plant/disinfection.py +363 -0
- aquakin/plant/dosing.py +278 -0
- aquakin/plant/errors.py +37 -0
- aquakin/plant/flow_setpoint.py +91 -0
- aquakin/plant/ghg.py +278 -0
- aquakin/plant/ifas.py +382 -0
- aquakin/plant/influent.py +500 -0
- aquakin/plant/interfaces.py +463 -0
- aquakin/plant/mbr.py +278 -0
- aquakin/plant/metrics.py +565 -0
- aquakin/plant/mixer.py +339 -0
- aquakin/plant/plant.py +4167 -0
- aquakin/plant/primary_clarifier.py +173 -0
- aquakin/plant/recycle.py +850 -0
- aquakin/plant/results.py +384 -0
- aquakin/plant/sbr.py +376 -0
- aquakin/plant/schedule.py +77 -0
- aquakin/plant/sensitivity.py +1296 -0
- aquakin/plant/separators.py +181 -0
- aquakin/plant/settling.py +227 -0
- aquakin/plant/solve_cache.py +72 -0
- aquakin/plant/steady.py +811 -0
- aquakin/plant/storage.py +203 -0
- aquakin/plant/streams.py +323 -0
- aquakin/plant/takacs.py +831 -0
- aquakin/plant/temperature.py +164 -0
- aquakin/plant/translators.py +113 -0
- aquakin/plant/units.py +256 -0
- aquakin/schema/__init__.py +22 -0
- aquakin/schema/inheritance.py +137 -0
- aquakin/schema/loader.py +208 -0
- aquakin/schema/model_spec.py +747 -0
- aquakin/transport/__init__.py +1 -0
- aquakin/transport/openfoam/__init__.py +15 -0
- aquakin/transport/openfoam/bridge.py +58 -0
- aquakin/transport/openfoam/tracks.py +160 -0
- aquakin/utils/__init__.py +49 -0
- aquakin/utils/balance.py +153 -0
- aquakin/utils/composition.py +361 -0
- aquakin/utils/latex.py +145 -0
- aquakin/utils/rtd.py +172 -0
- aquakin/utils/units.py +633 -0
- aquakin-0.1.0.dist-info/METADATA +665 -0
- aquakin-0.1.0.dist-info/RECORD +165 -0
- aquakin-0.1.0.dist-info/WHEEL +5 -0
- aquakin-0.1.0.dist-info/licenses/LICENSE +21 -0
- aquakin-0.1.0.dist-info/top_level.txt +1 -0
aquakin/__init__.py
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
"""aquakin: reactive scalar transport kinetics for aqueous environmental systems."""
|
|
2
|
+
|
|
3
|
+
import os as _os
|
|
4
|
+
import sys as _sys
|
|
5
|
+
import warnings as _warnings
|
|
6
|
+
|
|
7
|
+
# aquakin enables JAX 64-bit (x64) mode at import: the stiff implicit ODE solves
|
|
8
|
+
# need double precision (repeatedly forming small net differences in a stiff
|
|
9
|
+
# Newton step loses the accurate digits at float32). This mutates GLOBAL,
|
|
10
|
+
# process-wide JAX state -- any other JAX code in the same process will use
|
|
11
|
+
# float64 afterward. The behaviour is deliberate and documented, but to keep it
|
|
12
|
+
# from being a *silent* surprise we warn when we are overriding what looks like
|
|
13
|
+
# an explicit float32 preference: JAX was already imported (so other code may be
|
|
14
|
+
# relying on the default float32), or JAX_ENABLE_X64 is set to a false value.
|
|
15
|
+
_jax_already_imported = "jax" in _sys.modules
|
|
16
|
+
|
|
17
|
+
import jax
|
|
18
|
+
|
|
19
|
+
if not jax.config.jax_enable_x64:
|
|
20
|
+
_env_x64 = _os.environ.get("JAX_ENABLE_X64", "").strip().lower()
|
|
21
|
+
_overrides_explicit_choice = _jax_already_imported or _env_x64 in ("0", "false", "off", "no")
|
|
22
|
+
if _overrides_explicit_choice:
|
|
23
|
+
_warnings.warn(
|
|
24
|
+
"aquakin is enabling JAX 64-bit (x64) mode, which it requires for "
|
|
25
|
+
"stiff ODE integration. This is GLOBAL, process-wide JAX state: "
|
|
26
|
+
"other JAX code in this process will now use float64. It overrides "
|
|
27
|
+
"an apparent float32 preference (JAX was already imported, or "
|
|
28
|
+
"JAX_ENABLE_X64 is set off). Enable x64 yourself before importing "
|
|
29
|
+
"aquakin, or run aquakin in a separate process, to silence this.",
|
|
30
|
+
stacklevel=2,
|
|
31
|
+
)
|
|
32
|
+
jax.config.update("jax_enable_x64", True)
|
|
33
|
+
|
|
34
|
+
import logging as _logging
|
|
35
|
+
|
|
36
|
+
# Library logging hygiene: attach a NullHandler to the package's root logger so
|
|
37
|
+
# aquakin never configures logging on the application's behalf (it emits nothing
|
|
38
|
+
# unless the caller opts in) and there is no "No handlers could be found" noise.
|
|
39
|
+
# Progress output -- e.g. the ``progress=`` sampling loops in
|
|
40
|
+
# ``plant.steady_state_dgsm`` / ``dynamic_dgsm`` -- is logged at INFO on child
|
|
41
|
+
# loggers; a caller sees it by enabling INFO, e.g.
|
|
42
|
+
# ``logging.basicConfig(level=logging.INFO)`` or
|
|
43
|
+
# ``logging.getLogger("aquakin").setLevel(logging.INFO)`` with a handler.
|
|
44
|
+
_logging.getLogger("aquakin").addHandler(_logging.NullHandler())
|
|
45
|
+
|
|
46
|
+
from aquakin.core.conditions import OperatingConditions, SpatialConditions
|
|
47
|
+
from aquakin.core.model import CompiledModel, compile_model
|
|
48
|
+
from aquakin.core.parser import parse_rate_expression
|
|
49
|
+
from aquakin.integrate.batch import BatchReactor, BatchSolution
|
|
50
|
+
from aquakin.integrate.biofilm import BiofilmReactor, BiofilmSolution
|
|
51
|
+
from aquakin.integrate.calibrate import (
|
|
52
|
+
CalibrationResult,
|
|
53
|
+
FreeICConfig,
|
|
54
|
+
LaplaceConfig,
|
|
55
|
+
OptimizerConfig,
|
|
56
|
+
PredictiveBand,
|
|
57
|
+
calibrate,
|
|
58
|
+
)
|
|
59
|
+
from aquakin.integrate.profile import ProfileResult, profile_likelihood
|
|
60
|
+
from aquakin.integrate.cfd import CFDReactor
|
|
61
|
+
from aquakin.integrate.particle import (
|
|
62
|
+
ParticleTrackReactor,
|
|
63
|
+
Track,
|
|
64
|
+
TrackSolution,
|
|
65
|
+
integrate_ensemble,
|
|
66
|
+
)
|
|
67
|
+
from aquakin.integrate.discrete_adjoint import (
|
|
68
|
+
esdirk_adjoint_solve,
|
|
69
|
+
implicit_euler_adjoint_solve,
|
|
70
|
+
)
|
|
71
|
+
from aquakin.integrate._common import (
|
|
72
|
+
DifferentiationConfig,
|
|
73
|
+
IntegratorConfig,
|
|
74
|
+
check_finite_gradient,
|
|
75
|
+
forward_adjoint,
|
|
76
|
+
)
|
|
77
|
+
from aquakin.integrate.forward_sensitivity import (
|
|
78
|
+
ForwardSensitivityResult,
|
|
79
|
+
forward_sensitivity,
|
|
80
|
+
)
|
|
81
|
+
from aquakin.integrate.pfr import PFRSolution, PlugFlowReactor
|
|
82
|
+
from aquakin.integrate.sensitivity import SensitivityResult, sensitivity
|
|
83
|
+
from aquakin.integrate.fit import FitResult, fit
|
|
84
|
+
from aquakin.integrate.global_sensitivity import DGSMResult, dgsm
|
|
85
|
+
from aquakin.integrate.events import Event, EventedResult, solve_with_events
|
|
86
|
+
from aquakin.integrate.design import Constraint, OptimizeResult, optimize_design
|
|
87
|
+
from aquakin.integrate.monte_carlo import MonteCarloResult, monte_carlo
|
|
88
|
+
from aquakin.integrate.scenarios import (
|
|
89
|
+
KPIComparison,
|
|
90
|
+
ScenarioComparison,
|
|
91
|
+
compare_scenarios,
|
|
92
|
+
kpi_comparison,
|
|
93
|
+
)
|
|
94
|
+
from aquakin.plant import (
|
|
95
|
+
ActivatedSludgeSizing,
|
|
96
|
+
Aeration,
|
|
97
|
+
AerationDesignPoint,
|
|
98
|
+
AerationSystem,
|
|
99
|
+
AlgebraicTemperature,
|
|
100
|
+
BSM1Evaluation,
|
|
101
|
+
BSM2Evaluation,
|
|
102
|
+
CSTRUnit,
|
|
103
|
+
CarbonFootprint,
|
|
104
|
+
ChlorineContactUnit,
|
|
105
|
+
ComponentBalance,
|
|
106
|
+
CostFactors,
|
|
107
|
+
IdentityTranslator,
|
|
108
|
+
InfluentFractions,
|
|
109
|
+
InfluentSeries,
|
|
110
|
+
MassBalance,
|
|
111
|
+
NoDigesterError,
|
|
112
|
+
OperatingCost,
|
|
113
|
+
UnknownPortError,
|
|
114
|
+
UnknownUnitError,
|
|
115
|
+
WiringError,
|
|
116
|
+
characterize_influent,
|
|
117
|
+
fractionate,
|
|
118
|
+
mass_balance,
|
|
119
|
+
read_influent_csv,
|
|
120
|
+
MixerUnit,
|
|
121
|
+
Plant,
|
|
122
|
+
PlantCheck,
|
|
123
|
+
PlantSolution,
|
|
124
|
+
HeatBalanceTemperature,
|
|
125
|
+
RatioSplitter,
|
|
126
|
+
SetpointSplitter,
|
|
127
|
+
SludgeMetrics,
|
|
128
|
+
StateTranslator,
|
|
129
|
+
Stream,
|
|
130
|
+
TemperatureModel,
|
|
131
|
+
ThresholdSplitter,
|
|
132
|
+
UVUnit,
|
|
133
|
+
Unit,
|
|
134
|
+
aeration_energy,
|
|
135
|
+
blower_airflow_total,
|
|
136
|
+
blower_energy,
|
|
137
|
+
blower_power_kw,
|
|
138
|
+
carbon_footprint,
|
|
139
|
+
carbon_mass,
|
|
140
|
+
co2e_from_energy,
|
|
141
|
+
ct_log_removal,
|
|
142
|
+
ct_value,
|
|
143
|
+
design_summary,
|
|
144
|
+
derived_BOD,
|
|
145
|
+
derived_COD,
|
|
146
|
+
derived_TKN,
|
|
147
|
+
derived_TSS,
|
|
148
|
+
direct_n2o_emission,
|
|
149
|
+
effluent_averages,
|
|
150
|
+
effluent_quality_index,
|
|
151
|
+
evaluate_bsm1,
|
|
152
|
+
evaluate_bsm2,
|
|
153
|
+
heating_energy,
|
|
154
|
+
methane_to_co2e,
|
|
155
|
+
mixing_energy,
|
|
156
|
+
n2o_n_to_co2e,
|
|
157
|
+
operating_cost,
|
|
158
|
+
operational_cost_index,
|
|
159
|
+
operational_cost_index_bsm2,
|
|
160
|
+
pumping_energy,
|
|
161
|
+
pumping_energy_bsm2,
|
|
162
|
+
required_airflow,
|
|
163
|
+
size_activated_sludge,
|
|
164
|
+
sludge_metrics,
|
|
165
|
+
stripped_n2o,
|
|
166
|
+
t10_from_baffling,
|
|
167
|
+
t10_from_rtd,
|
|
168
|
+
time_average,
|
|
169
|
+
uv_dose,
|
|
170
|
+
uv_log_inactivation,
|
|
171
|
+
)
|
|
172
|
+
from aquakin.schema.loader import (
|
|
173
|
+
clear_model_cache,
|
|
174
|
+
load_model,
|
|
175
|
+
load_model_from_file,
|
|
176
|
+
)
|
|
177
|
+
from aquakin.schema.model_spec import SpeciationUnitsWarning
|
|
178
|
+
|
|
179
|
+
# The utility helpers are aggregated by aquakin.utils (the single utility
|
|
180
|
+
# surface); the top-level namespace re-exports the curated model-analysis subset
|
|
181
|
+
# from there rather than reaching into utils.balance / .composition / .units.
|
|
182
|
+
from aquakin.utils import (
|
|
183
|
+
UnitWarning,
|
|
184
|
+
canonical_content,
|
|
185
|
+
check_conservation,
|
|
186
|
+
check_model_units,
|
|
187
|
+
check_nitrogen,
|
|
188
|
+
composition_table,
|
|
189
|
+
parse_units,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
__all__ = [
|
|
193
|
+
"ActivatedSludgeSizing",
|
|
194
|
+
"Aeration",
|
|
195
|
+
"AerationDesignPoint",
|
|
196
|
+
"AerationSystem",
|
|
197
|
+
"AlgebraicTemperature",
|
|
198
|
+
"BSM1Evaluation",
|
|
199
|
+
"BSM2Evaluation",
|
|
200
|
+
"BatchReactor",
|
|
201
|
+
"BatchSolution",
|
|
202
|
+
"BiofilmReactor",
|
|
203
|
+
"BiofilmSolution",
|
|
204
|
+
"CFDReactor",
|
|
205
|
+
"CSTRUnit",
|
|
206
|
+
"CalibrationResult",
|
|
207
|
+
"CarbonFootprint",
|
|
208
|
+
"ChlorineContactUnit",
|
|
209
|
+
"CompiledModel",
|
|
210
|
+
"ComponentBalance",
|
|
211
|
+
"Constraint",
|
|
212
|
+
"CostFactors",
|
|
213
|
+
"DGSMResult",
|
|
214
|
+
"DifferentiationConfig",
|
|
215
|
+
"Event",
|
|
216
|
+
"EventedResult",
|
|
217
|
+
"FitResult",
|
|
218
|
+
"ForwardSensitivityResult",
|
|
219
|
+
"FreeICConfig",
|
|
220
|
+
"HeatBalanceTemperature",
|
|
221
|
+
"IdentityTranslator",
|
|
222
|
+
"InfluentFractions",
|
|
223
|
+
"InfluentSeries",
|
|
224
|
+
"IntegratorConfig",
|
|
225
|
+
"KPIComparison",
|
|
226
|
+
"LaplaceConfig",
|
|
227
|
+
"MassBalance",
|
|
228
|
+
"MixerUnit",
|
|
229
|
+
"MonteCarloResult",
|
|
230
|
+
"NoDigesterError",
|
|
231
|
+
"OperatingConditions",
|
|
232
|
+
"OperatingCost",
|
|
233
|
+
"OptimizeResult",
|
|
234
|
+
"OptimizerConfig",
|
|
235
|
+
"PFRSolution",
|
|
236
|
+
"ParticleTrackReactor",
|
|
237
|
+
"Plant",
|
|
238
|
+
"PlantCheck",
|
|
239
|
+
"PlantSolution",
|
|
240
|
+
"PlugFlowReactor",
|
|
241
|
+
"PredictiveBand",
|
|
242
|
+
"ProfileResult",
|
|
243
|
+
"RatioSplitter",
|
|
244
|
+
"ScenarioComparison",
|
|
245
|
+
"SensitivityResult",
|
|
246
|
+
"SetpointSplitter",
|
|
247
|
+
"SludgeMetrics",
|
|
248
|
+
"SpatialConditions",
|
|
249
|
+
"SpeciationUnitsWarning",
|
|
250
|
+
"StateTranslator",
|
|
251
|
+
"Stream",
|
|
252
|
+
"TemperatureModel",
|
|
253
|
+
"ThresholdSplitter",
|
|
254
|
+
"Track",
|
|
255
|
+
"TrackSolution",
|
|
256
|
+
"UVUnit",
|
|
257
|
+
"Unit",
|
|
258
|
+
"UnitWarning",
|
|
259
|
+
"UnknownPortError",
|
|
260
|
+
"UnknownUnitError",
|
|
261
|
+
"WiringError",
|
|
262
|
+
"aeration_energy",
|
|
263
|
+
"blower_airflow_total",
|
|
264
|
+
"blower_energy",
|
|
265
|
+
"blower_power_kw",
|
|
266
|
+
"calibrate",
|
|
267
|
+
"canonical_content",
|
|
268
|
+
"carbon_footprint",
|
|
269
|
+
"carbon_mass",
|
|
270
|
+
"characterize_influent",
|
|
271
|
+
"check_conservation",
|
|
272
|
+
"check_finite_gradient",
|
|
273
|
+
"check_model_units",
|
|
274
|
+
"check_nitrogen",
|
|
275
|
+
"clear_model_cache",
|
|
276
|
+
"co2e_from_energy",
|
|
277
|
+
"compare_scenarios",
|
|
278
|
+
"compile_model",
|
|
279
|
+
"composition_table",
|
|
280
|
+
"ct_log_removal",
|
|
281
|
+
"ct_value",
|
|
282
|
+
"derived_BOD",
|
|
283
|
+
"derived_COD",
|
|
284
|
+
"derived_TKN",
|
|
285
|
+
"derived_TSS",
|
|
286
|
+
"design_summary",
|
|
287
|
+
"dgsm",
|
|
288
|
+
"direct_n2o_emission",
|
|
289
|
+
"effluent_averages",
|
|
290
|
+
"effluent_quality_index",
|
|
291
|
+
"esdirk_adjoint_solve",
|
|
292
|
+
"evaluate_bsm1",
|
|
293
|
+
"evaluate_bsm2",
|
|
294
|
+
"fit",
|
|
295
|
+
"forward_adjoint",
|
|
296
|
+
"forward_sensitivity",
|
|
297
|
+
"fractionate",
|
|
298
|
+
"heating_energy",
|
|
299
|
+
"implicit_euler_adjoint_solve",
|
|
300
|
+
"integrate_ensemble",
|
|
301
|
+
"kpi_comparison",
|
|
302
|
+
"load_model",
|
|
303
|
+
"load_model_from_file",
|
|
304
|
+
"mass_balance",
|
|
305
|
+
"methane_to_co2e",
|
|
306
|
+
"mixing_energy",
|
|
307
|
+
"monte_carlo",
|
|
308
|
+
"n2o_n_to_co2e",
|
|
309
|
+
"operating_cost",
|
|
310
|
+
"operational_cost_index",
|
|
311
|
+
"operational_cost_index_bsm2",
|
|
312
|
+
"optimize_design",
|
|
313
|
+
"parse_rate_expression",
|
|
314
|
+
"parse_units",
|
|
315
|
+
"profile_likelihood",
|
|
316
|
+
"pumping_energy",
|
|
317
|
+
"pumping_energy_bsm2",
|
|
318
|
+
"read_influent_csv",
|
|
319
|
+
"required_airflow",
|
|
320
|
+
"sensitivity",
|
|
321
|
+
"size_activated_sludge",
|
|
322
|
+
"sludge_metrics",
|
|
323
|
+
"solve_with_events",
|
|
324
|
+
"stripped_n2o",
|
|
325
|
+
"t10_from_baffling",
|
|
326
|
+
"t10_from_rtd",
|
|
327
|
+
"time_average",
|
|
328
|
+
"uv_dose",
|
|
329
|
+
"uv_log_inactivation",
|
|
330
|
+
]
|
|
331
|
+
|
|
332
|
+
__version__ = "0.1.0"
|
aquakin/core/__init__.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Core data structures and AST machinery (no Pydantic dependency)."""
|
|
2
|
+
|
|
3
|
+
from aquakin.core.conditions import SpatialConditions
|
|
4
|
+
from aquakin.core.context import CompileContext
|
|
5
|
+
from aquakin.core.model import CompiledModel, compile_model
|
|
6
|
+
|
|
7
|
+
__all__ = ["CompileContext", "CompiledModel", "SpatialConditions", "compile_model"]
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""Spatially varying condition fields (pH, temperature, etc.)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Iterable
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
|
|
8
|
+
import jax.numpy as jnp
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class SpatialConditions:
|
|
13
|
+
"""
|
|
14
|
+
Container for spatially varying condition fields.
|
|
15
|
+
|
|
16
|
+
Each entry in ``fields`` is a JAX array of shape ``(n_locations,)`` giving
|
|
17
|
+
the value of that field at each spatial location. Reactors index into
|
|
18
|
+
these arrays with ``loc_idx`` at runtime.
|
|
19
|
+
|
|
20
|
+
Attributes
|
|
21
|
+
----------
|
|
22
|
+
fields : dict[str, jnp.ndarray]
|
|
23
|
+
Mapping from field name (e.g. ``"pH"``) to a 1-D JAX array.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
fields: dict[str, jnp.ndarray] = field(default_factory=dict)
|
|
27
|
+
|
|
28
|
+
def __post_init__(self) -> None:
|
|
29
|
+
normalised: dict[str, jnp.ndarray] = {}
|
|
30
|
+
n_locations: int | None = None
|
|
31
|
+
for name, value in self.fields.items():
|
|
32
|
+
arr = jnp.asarray(value)
|
|
33
|
+
if arr.ndim == 0:
|
|
34
|
+
arr = arr[None]
|
|
35
|
+
elif arr.ndim != 1:
|
|
36
|
+
raise ValueError(f"Condition field '{name}' must be 1-D, got shape {arr.shape}")
|
|
37
|
+
if n_locations is None:
|
|
38
|
+
n_locations = int(arr.shape[0])
|
|
39
|
+
elif int(arr.shape[0]) != n_locations:
|
|
40
|
+
raise ValueError(
|
|
41
|
+
f"Condition field '{name}' has length {arr.shape[0]}, expected {n_locations}"
|
|
42
|
+
)
|
|
43
|
+
normalised[name] = arr
|
|
44
|
+
self.fields = normalised
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def n_locations(self) -> int:
|
|
48
|
+
"""Number of spatial locations represented."""
|
|
49
|
+
if not self.fields:
|
|
50
|
+
return 0
|
|
51
|
+
return int(next(iter(self.fields.values())).shape[0])
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def uniform(cls, n_locations: int = 1, **kwargs: float) -> SpatialConditions:
|
|
55
|
+
"""
|
|
56
|
+
Build a spatially homogeneous ``SpatialConditions`` object.
|
|
57
|
+
|
|
58
|
+
Parameters
|
|
59
|
+
----------
|
|
60
|
+
n_locations : int, optional
|
|
61
|
+
Number of spatial locations (default 1). Must be at least 1; the
|
|
62
|
+
default suits a 0-D batch reactor, so a batch user writes
|
|
63
|
+
``SpatialConditions.uniform(pH=7.5, T=293.15)``. For the 0-D case the
|
|
64
|
+
:class:`OperatingConditions` alias reads more naturally still.
|
|
65
|
+
**kwargs : float
|
|
66
|
+
Field name -> scalar value. The scalar is broadcast to
|
|
67
|
+
``(n_locations,)``.
|
|
68
|
+
|
|
69
|
+
Returns
|
|
70
|
+
-------
|
|
71
|
+
SpatialConditions
|
|
72
|
+
"""
|
|
73
|
+
if n_locations < 1:
|
|
74
|
+
raise ValueError(f"n_locations must be >= 1, got {n_locations}")
|
|
75
|
+
# Broadcast (don't ``float()``-coerce) so a *traced* condition value -- a
|
|
76
|
+
# user differentiating a solve w.r.t. pH/T -- flows through instead of
|
|
77
|
+
# raising a ConcretizationTypeError. Identity for a concrete value.
|
|
78
|
+
fields = {
|
|
79
|
+
name: jnp.broadcast_to(jnp.asarray(value), (n_locations,))
|
|
80
|
+
for name, value in kwargs.items()
|
|
81
|
+
}
|
|
82
|
+
return cls(fields=fields)
|
|
83
|
+
|
|
84
|
+
def with_(self, **kwargs: float) -> SpatialConditions:
|
|
85
|
+
"""Return a copy with some fields overridden (or added).
|
|
86
|
+
|
|
87
|
+
The common edit-from-defaults pattern: start from
|
|
88
|
+
``model.default_conditions()`` and change only what differs, e.g.::
|
|
89
|
+
|
|
90
|
+
conditions = model.default_conditions().with_(T=283.15) # cold
|
|
91
|
+
|
|
92
|
+
Scalar overrides are broadcast to this object's location count (so the
|
|
93
|
+
result keeps the same ``n_locations``); a length-``n_locations`` array
|
|
94
|
+
override is used as-is. Fields not named are carried over unchanged. The
|
|
95
|
+
original is not modified.
|
|
96
|
+
|
|
97
|
+
Parameters
|
|
98
|
+
----------
|
|
99
|
+
**kwargs : float or array-like
|
|
100
|
+
Field name -> new value (scalar, broadcast to ``n_locations``, or a
|
|
101
|
+
length-``n_locations`` array).
|
|
102
|
+
|
|
103
|
+
Returns
|
|
104
|
+
-------
|
|
105
|
+
SpatialConditions
|
|
106
|
+
A new ``SpatialConditions`` (always the base type, so it stays valid
|
|
107
|
+
for every reactor) with the merged fields.
|
|
108
|
+
"""
|
|
109
|
+
n = self.n_locations or 1
|
|
110
|
+
merged = dict(self.fields)
|
|
111
|
+
for name, value in kwargs.items():
|
|
112
|
+
arr = jnp.asarray(value)
|
|
113
|
+
# Broadcast a scalar rather than ``float()``-coercing it, so a traced
|
|
114
|
+
# override (a gradient w.r.t. a condition) is not concretized.
|
|
115
|
+
merged[name] = jnp.broadcast_to(arr, (n,)) if arr.ndim == 0 else arr
|
|
116
|
+
return SpatialConditions(fields=merged)
|
|
117
|
+
|
|
118
|
+
def validate_required(self, required: Iterable[str]) -> None:
|
|
119
|
+
"""
|
|
120
|
+
Raise ``ValueError`` if any required field is missing.
|
|
121
|
+
|
|
122
|
+
Parameters
|
|
123
|
+
----------
|
|
124
|
+
required : iterable of str
|
|
125
|
+
Field names that must be present.
|
|
126
|
+
"""
|
|
127
|
+
missing = sorted(set(required) - set(self.fields.keys()))
|
|
128
|
+
if missing:
|
|
129
|
+
raise ValueError(
|
|
130
|
+
f"SpatialConditions is missing required fields: {missing}. "
|
|
131
|
+
f"Provided: {sorted(self.fields.keys())}"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class OperatingConditions(SpatialConditions):
|
|
136
|
+
"""Operating conditions for the 0-D (single stirred tank) case.
|
|
137
|
+
|
|
138
|
+
A single stirred tank has no spatial extent, so the spatially-varying
|
|
139
|
+
:class:`SpatialConditions` (with its ``n_locations`` array model) reads as
|
|
140
|
+
over-machinery for the most basic setup. ``OperatingConditions`` is the same
|
|
141
|
+
object specialised to one location, constructed directly from scalar field
|
|
142
|
+
values::
|
|
143
|
+
|
|
144
|
+
conditions = aquakin.OperatingConditions(pH=7.5, T=293.15)
|
|
145
|
+
|
|
146
|
+
It **is** a :class:`SpatialConditions` (one location), so it works unchanged
|
|
147
|
+
in every reactor; for a spatially varying PFR/CFD case use
|
|
148
|
+
:class:`SpatialConditions` (or :meth:`SpatialConditions.uniform`) directly.
|
|
149
|
+
To start from a model's declared defaults instead, use
|
|
150
|
+
``model.default_conditions()`` and :meth:`SpatialConditions.with_` to edit.
|
|
151
|
+
|
|
152
|
+
Parameters
|
|
153
|
+
----------
|
|
154
|
+
**kwargs : float
|
|
155
|
+
Condition field name (e.g. ``pH``, ``T``) -> scalar value.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
def __init__(self, **kwargs: float) -> None:
|
|
159
|
+
# ``jnp.asarray(value)`` (not ``float(value)``) so a traced condition --
|
|
160
|
+
# a gradient w.r.t. an operating condition -- flows through rather than
|
|
161
|
+
# raising a ConcretizationTypeError. Identity for a concrete value.
|
|
162
|
+
super().__init__(fields={name: jnp.asarray(value) for name, value in kwargs.items()})
|
aquakin/core/context.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Compile-time context used by ASTNode.compile()."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True)
|
|
9
|
+
class CompileContext:
|
|
10
|
+
"""
|
|
11
|
+
Index maps used during AST compilation.
|
|
12
|
+
|
|
13
|
+
Attributes
|
|
14
|
+
----------
|
|
15
|
+
species_index : dict[str, int]
|
|
16
|
+
Maps species name to its position in the concentration vector ``C``.
|
|
17
|
+
param_index : dict[str, int]
|
|
18
|
+
Maps fully-namespaced parameter name (e.g. ``"O3_Br_direct.k1"``) to
|
|
19
|
+
its position in the flat ``params`` vector.
|
|
20
|
+
condition_fields : frozenset[str]
|
|
21
|
+
Set of valid condition field names. Used to validate
|
|
22
|
+
``ConditionNode`` references at parse/compile time.
|
|
23
|
+
reaction_name : str
|
|
24
|
+
The reaction that owns the expression currently being compiled. Used
|
|
25
|
+
for namespacing local parameter names.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
species_index: dict[str, int]
|
|
29
|
+
param_index: dict[str, int]
|
|
30
|
+
condition_fields: frozenset[str]
|
|
31
|
+
reaction_name: str = ""
|
aquakin/core/hints.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Close-match suggestion for unknown-name errors.
|
|
2
|
+
|
|
3
|
+
A single home for the "Did you mean: ...?" hint appended to ``KeyError`` /
|
|
4
|
+
``ValueError`` messages when a species / parameter / unit / stream name is not
|
|
5
|
+
found. Callers keep their own message prefix and exception type; this only
|
|
6
|
+
formats the suffix, so the hint wording (and its similarity threshold) is tuned
|
|
7
|
+
in one place.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import difflib
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def did_you_mean(name, choices, n: int = 3) -> str:
|
|
14
|
+
"""Return a ``" Did you mean: a, b?"`` suffix, or ``""`` if nothing is close.
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
name : str
|
|
19
|
+
The unknown name the user supplied.
|
|
20
|
+
choices : iterable of str
|
|
21
|
+
The valid names to match against.
|
|
22
|
+
n : int, optional
|
|
23
|
+
Maximum number of suggestions (default 3).
|
|
24
|
+
|
|
25
|
+
Returns
|
|
26
|
+
-------
|
|
27
|
+
str
|
|
28
|
+
A leading-space suffix ready to append to an error message, empty when
|
|
29
|
+
no close match is found.
|
|
30
|
+
"""
|
|
31
|
+
matches = difflib.get_close_matches(name, list(choices), n=n)
|
|
32
|
+
return f" Did you mean: {', '.join(matches)}?" if matches else ""
|