Skip to content

Commit fa628de

Browse files
committed
Separate temperature and pressure from model parameters (#636)
1 parent 0f1ffde commit fa628de

18 files changed

Lines changed: 238 additions & 125 deletions
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
"""add conditions to simulation
2+
3+
Revision ID: 9b4e2c1a7d50
4+
Revises: 01aaa143d690
5+
Create Date: 2026-05-06 00:00:00.000000
6+
7+
"""
8+
9+
import json
10+
from typing import Any, Sequence, Union
11+
12+
import sqlalchemy as sa
13+
from alembic import op
14+
15+
16+
revision: str = "9b4e2c1a7d50"
17+
down_revision: Union[str, Sequence[str], None] = "01aaa143d690"
18+
branch_labels: Union[str, Sequence[str], None] = None
19+
depends_on: Union[str, Sequence[str], None] = None
20+
21+
22+
_CONDITION_KEYS = ("temperature", "pressure")
23+
24+
25+
def _as_dict(value: Any) -> dict:
26+
if value is None:
27+
return {}
28+
if isinstance(value, str):
29+
return json.loads(value or "{}")
30+
return dict(value)
31+
32+
33+
def upgrade() -> None:
34+
op.add_column(
35+
"simulations",
36+
sa.Column(
37+
"conditions",
38+
sa.JSON(),
39+
nullable=False,
40+
server_default=sa.text("'{}'"),
41+
),
42+
)
43+
44+
# Backfill: lift temperature/pressure from each model_input's parameters
45+
# up to the parent simulation's new conditions column.
46+
bind = op.get_bind()
47+
simulations = sa.table(
48+
"simulations",
49+
sa.column("id", sa.Uuid()),
50+
sa.column("conditions", sa.JSON()),
51+
)
52+
model_inputs = sa.table(
53+
"model_inputs",
54+
sa.column("id", sa.Uuid()),
55+
sa.column("simulation_id", sa.Uuid()),
56+
sa.column("parameters", sa.JSON()),
57+
)
58+
59+
rows = bind.execute(
60+
sa.select(
61+
model_inputs.c.id, model_inputs.c.simulation_id, model_inputs.c.parameters
62+
)
63+
).all()
64+
65+
sim_conditions: dict = {}
66+
for mi_id, sim_id, params in rows:
67+
params = _as_dict(params)
68+
extracted = {k: params.pop(k) for k in _CONDITION_KEYS if k in params}
69+
if extracted:
70+
# Last writer wins if model_inputs disagree; they should match in practice.
71+
sim_conditions.setdefault(sim_id, {}).update(extracted)
72+
bind.execute(
73+
sa.update(model_inputs)
74+
.where(model_inputs.c.id == mi_id)
75+
.values(parameters=params)
76+
)
77+
78+
for sim_id, conditions in sim_conditions.items():
79+
bind.execute(
80+
sa.update(simulations)
81+
.where(simulations.c.id == sim_id)
82+
.values(conditions=conditions)
83+
)
84+
85+
86+
def downgrade() -> None:
87+
# Push conditions back into each model_input's parameters before dropping the column.
88+
bind = op.get_bind()
89+
simulations = sa.table(
90+
"simulations",
91+
sa.column("id", sa.Uuid()),
92+
sa.column("conditions", sa.JSON()),
93+
)
94+
model_inputs = sa.table(
95+
"model_inputs",
96+
sa.column("id", sa.Uuid()),
97+
sa.column("simulation_id", sa.Uuid()),
98+
sa.column("parameters", sa.JSON()),
99+
)
100+
101+
sim_rows = bind.execute(sa.select(simulations.c.id, simulations.c.conditions)).all()
102+
for sim_id, conditions in sim_rows:
103+
conditions = _as_dict(conditions)
104+
if not conditions:
105+
continue
106+
mi_rows = bind.execute(
107+
sa.select(model_inputs.c.id, model_inputs.c.parameters).where(
108+
model_inputs.c.simulation_id == sim_id
109+
)
110+
).all()
111+
for mi_id, params in mi_rows:
112+
merged = {**conditions, **_as_dict(params)}
113+
bind.execute(
114+
sa.update(model_inputs)
115+
.where(model_inputs.c.id == mi_id)
116+
.values(parameters=merged)
117+
)
118+
119+
op.drop_column("simulations", "conditions")

backend/src/acidwatch_api/database.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
create_engine,
1717
StaticPool,
1818
make_url,
19+
text,
1920
)
2021
from sqlalchemy.orm import (
2122
DeclarativeBase,
@@ -45,6 +46,9 @@ class Simulation(Base):
4546

4647
owner_id: Mapped[UUID | None] = mapped_column(Uuid)
4748
concentrations: Mapped[dict[str, float]] = mapped_column(JSON)
49+
conditions: Mapped[dict[str, float]] = mapped_column(
50+
JSON, default=dict, server_default=text("'{}'")
51+
)
4852

4953
model_inputs: Mapped[list[ModelInput]] = relationship(back_populates="simulation")
5054

backend/src/acidwatch_api/models/arcs.py

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@
22

33
from acidwatch_api.models.base import (
44
BaseAdapter,
5-
BaseParameters,
6-
Parameter,
75
RunResult,
8-
Unit,
96
)
107
from acidwatch_api.settings import SETTINGS
118

@@ -17,26 +14,6 @@
1714
"""
1815

1916

20-
class ArcsParameters(BaseParameters):
21-
temperature: int = Parameter(
22-
300,
23-
label="Temperature",
24-
unit=Unit.TEMPERATURE_KELVIN,
25-
min=200,
26-
max=400,
27-
description="Temperature in Celsius",
28-
)
29-
30-
pressure: int = Parameter(
31-
10,
32-
label="Pressure",
33-
unit="bara",
34-
min=1,
35-
max=300,
36-
description="Pressure in bara",
37-
)
38-
39-
4017
class ArcsAdapter(BaseAdapter):
4118
model_id = "arcs"
4219
display_name = "ARCS"
@@ -69,7 +46,6 @@ class ArcsAdapter(BaseAdapter):
6946
"NOHSO4",
7047
]
7148

72-
parameters: ArcsParameters
7349
base_url = SETTINGS.arcs_api_base_uri
7450

7551
async def run(self) -> RunResult:
@@ -79,8 +55,8 @@ async def run(self) -> RunResult:
7955
"concs": {
8056
key: value / 1e6 for key, value in self.concentrations.items()
8157
},
82-
"temperature": self.parameters.temperature,
83-
"pressure": self.parameters.pressure,
58+
"temperature": self.conditions.temperature,
59+
"pressure": self.conditions.pressure,
8460
"samples": 2000, # Default to 2000 samples
8561
},
8662
timeout=300.0,

backend/src/acidwatch_api/models/arcs_exp.py

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
from acidwatch_api.models.base import (
22
BaseAdapter,
3-
BaseParameters,
4-
Parameter,
53
RunResult,
6-
Unit,
74
)
85
from acidwatch_api.settings import SETTINGS
96

@@ -16,26 +13,6 @@
1613
"""
1714

1815

19-
class ArcsParameters(BaseParameters):
20-
temperature: int = Parameter(
21-
300,
22-
label="Temperature",
23-
unit=Unit.TEMPERATURE_KELVIN,
24-
min=200,
25-
max=400,
26-
description="Temperature in Celsius",
27-
)
28-
29-
pressure: int = Parameter(
30-
10,
31-
label="Pressure",
32-
unit="bara",
33-
min=1,
34-
max=300,
35-
description="Pressure in bara",
36-
)
37-
38-
3916
class ArcsExpAdapter(BaseAdapter):
4017
model_id = "arcs_exp"
4118
display_name = "ARCS experimental"
@@ -68,7 +45,6 @@ class ArcsExpAdapter(BaseAdapter):
6845
"NOHSO4",
6946
]
7047

71-
parameters: ArcsParameters
7248
base_url = SETTINGS.arcs_exp_api_base_uri
7349

7450
async def run(self) -> RunResult:
@@ -78,8 +54,8 @@ async def run(self) -> RunResult:
7854
"concs": {
7955
key: value / 1e6 for key, value in self.concentrations.items()
8056
},
81-
"temperature": self.parameters.temperature,
82-
"pressure": self.parameters.pressure,
57+
"temperature": self.conditions.temperature,
58+
"pressure": self.conditions.pressure,
8359
"samples": 500,
8460
},
8561
timeout=300.0,

backend/src/acidwatch_api/models/base.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
from typing_extensions import Doc
3232

3333
from acidwatch_api.authentication import acquire_token_for_downstream_api
34-
from acidwatch_api.models.datamodel import AnyPanel
34+
from acidwatch_api.models.datamodel import AnyPanel, Conditions
3535

3636

3737
class InputError(ValueError):
@@ -190,17 +190,22 @@ def get_parameters_schema(cls: type[BaseAdapter]) -> Any:
190190

191191

192192
class BaseAdapter:
193+
conditions: Conditions
194+
193195
def __init__(
194196
self,
195197
*,
196198
concentrations: dict[str, int | float] | None = None,
197199
parameters: dict[str, str | bool | int | float] | None,
200+
conditions: Conditions | None = None,
198201
jwt_token: str | None,
199202
) -> None:
200203
if concentrations is not None:
201204
self.validate_concentrations(concentrations)
202205
self.set_concentrations(concentrations)
203206

207+
self.conditions = conditions if conditions is not None else Conditions()
208+
204209
parameters_type = _get_parameters_type(type(self))
205210
if parameters and parameters_type is None:
206211
raise InputError(

backend/src/acidwatch_api/models/datamodel.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,14 @@ class ModelInput(_BaseModel):
1919
parameters: dict[str, bool | float | int | str]
2020

2121

22+
class Conditions(_BaseModel):
23+
temperature: float = 300
24+
pressure: float = 10
25+
26+
2227
class Simulation(_BaseModel):
2328
concentrations: dict[str, int | float]
29+
conditions: Conditions = Field(default_factory=Conditions)
2430
models: list[ModelInput] = Field(min_length=1)
2531

2632

backend/src/acidwatch_api/models/gibbs_minimization_model.py

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
BaseParameters,
66
Parameter,
77
RunResult,
8-
Unit,
98
)
109

1110
# Model constants
@@ -90,20 +89,6 @@ class _EquationOfState(StrEnum):
9089

9190

9291
class GibbsMinimizationModelParameters(BaseParameters):
93-
temperature: int = Parameter(
94-
298,
95-
label="Temperature",
96-
unit=Unit.TEMPERATURE_KELVIN,
97-
min=200,
98-
max=450,
99-
)
100-
pressure: int = Parameter(
101-
100,
102-
label="Pressure",
103-
unit="bara",
104-
min=1,
105-
max=300,
106-
)
10792
equation_of_state: _EquationOfState = Parameter(
10893
_EquationOfState.SRK,
10994
label="Equation of State",
@@ -159,8 +144,8 @@ class GibbsMinimizationModelAdapter(BaseAdapter):
159144

160145
async def run(self) -> RunResult:
161146
eos = self.parameters.equation_of_state
162-
temp = self.parameters.temperature
163-
pres = self.parameters.pressure
147+
temp = self.conditions.temperature
148+
pres = self.conditions.pressure
164149

165150
if eos == _EquationOfState.SRK:
166151
system = jneqsim.thermo.system.SystemSrkEos(temp, pres)
@@ -191,8 +176,8 @@ async def run(self) -> RunResult:
191176

192177
# # Create an inlet stream
193178
inlet_stream = jneqsim.process.equipment.stream.Stream("Inlet Stream", system)
194-
inlet_stream.setPressure(self.parameters.pressure, "bara")
195-
inlet_stream.setTemperature(self.parameters.temperature, "K")
179+
inlet_stream.setPressure(self.conditions.pressure, "bara")
180+
inlet_stream.setTemperature(self.conditions.temperature, "K")
196181
inlet_stream.run()
197182

198183
# Create a Gibbs reactor

0 commit comments

Comments
 (0)