Skip to content

Commit 1a227ef

Browse files
[WIP] Working on Package
1 parent 4f26341 commit 1a227ef

File tree

11 files changed

+1280
-821
lines changed

11 files changed

+1280
-821
lines changed

cadetgui/cadetprocessadapter.py

Lines changed: 199 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@
88
ComponentSystem,
99
GeneralRateModel,
1010
LumpedRateModelWithPores,
11+
LumpedRateModelWithoutPores,
1112
Cstr
1213
)
1314

15+
from CADETProcess.modelBuilder import LWE, BatchElution, CLR, FlipFlop, MRSSR
16+
1417
FieldKind = str
1518
Validator = Callable[[Any], None]
1619
Transform = Callable[[Any], Any]
@@ -47,15 +50,22 @@ def parse_float_list(v: Any) -> list[float]:
4750
"c_feed": FieldSpec("c_feed", "float_list", "Feed concentration", [1.0], transform=parse_float_list),
4851
"c_load": FieldSpec("c_load", "float_list", "Load concentration", [1.0], transform=parse_float_list),
4952
"c_salt_low": FieldSpec("c_salt_low", "float_list", "Low-salt buffer", [0.0], transform=parse_float_list),
50-
"c_salt_high": FieldSpec("c_salt_high", "float_list", "High-salt buffer", [1.0], transform=parse_float_list),
51-
"flow_rate": FieldSpec("flow_rate", "float", "Flow rate", 1.0, validate=require_positive),
53+
"c_salt_high": FieldSpec("c_salt_high", "float_list", "High-salt buffer", [0.0], transform=parse_float_list),
54+
"flow_rate": FieldSpec("flow_rate", "float", "Flow rate", 1.0e-6, validate=require_positive),
5255
"feed_duration": FieldSpec("feed_duration", "float", "Feed duration", 10.0, validate=require_positive),
5356
"load_duration": FieldSpec("load_duration", "float", "Load duration", 10.0, validate=require_positive),
5457
"cycle_time": FieldSpec("cycle_time", "float", "Cycle time", 100.0, validate=require_positive),
5558
"wash_duration": FieldSpec("wash_duration", "float", "Wash duration", 10.0, validate=require_positive),
5659
"gradient_duration": FieldSpec("gradient_duration", "float", "Gradient duration", 30.0, validate=require_positive),
5760
"final_wash_duration": FieldSpec("final_wash_duration", "float", "Final wash duration", 0.0),
5861
"c_eluent": FieldSpec("c_eluent", "float", "Eluent (scalar)", 0.0),
62+
"recycle_off": FieldSpec("recycle_off", "float", "Recycle off (t)", 30.0, validate=require_positive),
63+
"pump_volume": FieldSpec("pump_volume", "float", "Pump volume", 1e-9, validate=require_positive),
64+
"delay_flip": FieldSpec("delay_flip", "float", "Delay flip (t)", 5.0, validate=require_positive),
65+
"delay_injection": FieldSpec("delay_injection", "float", "Delay injection (t)", 5.0, validate=require_positive),
66+
"c_tank_init": FieldSpec("c_tank_init", "float_list", "Initial tank c",transform=parse_float_list)
67+
68+
5969
}
6070

6171

@@ -72,8 +82,6 @@ def _pick(keys: Sequence[str]) -> list[FieldSpec]:
7282

7383
def batch_elution_spec(column: ChromatographicColumnBase) -> ModelSpec:
7484
def _build(v: Mapping[str, Any]) -> Any:
75-
# Import here to keep module import cheap
76-
from CADETProcess.modelBuilder import BatchElution
7785
return BatchElution(
7886
column=column,
7987
c_feed=v["c_feed"],
@@ -92,7 +100,6 @@ def _build(v: Mapping[str, Any]) -> Any:
92100

93101
def lwe_spec(column: ChromatographicColumnBase) -> ModelSpec:
94102
def _build(v: Mapping[str, Any]) -> Any:
95-
from CADETProcess.modelBuilder import LWE
96103
return LWE(
97104
column=column,
98105
c_load=v["c_load"],
@@ -114,31 +121,122 @@ def _build(v: Mapping[str, Any]) -> Any:
114121
]),
115122
build=_build,
116123
)
124+
125+
def clr_spec(column: ChromatographicColumnBase) -> ModelSpec:
126+
"""Closed Loop Recycling (CLR) spec."""
127+
def _build(v: Mapping[str, Any]) -> Any:
128+
from CADETProcess.modelBuilder import CLR
129+
return CLR(
130+
column=column,
131+
c_feed=v["c_feed"],
132+
flow_rate=float(v["flow_rate"]),
133+
feed_duration=float(v["feed_duration"]),
134+
recycle_off=float(v["recycle_off"]),
135+
cycle_time=float(v["cycle_time"]),
136+
c_eluent=float(v["c_eluent"]),
137+
pump_volume=float(v["pump_volume"]),
138+
)
139+
140+
return ModelSpec(
141+
title="Closed Loop Recycling (CLR)",
142+
fields=_pick([
143+
"c_feed",
144+
"flow_rate",
145+
"feed_duration",
146+
"recycle_off",
147+
"cycle_time",
148+
"c_eluent",
149+
"pump_volume",
150+
]),
151+
build=_build,
152+
)
153+
154+
def flipflop_spec(column: ChromatographicColumnBase) -> ModelSpec:
155+
"""Flip-Flop spec."""
156+
def _build(v: Mapping[str, Any]) -> Any:
157+
from CADETProcess.modelBuilder import FlipFlop
158+
return FlipFlop(
159+
column=column,
160+
c_feed=v["c_feed"],
161+
flow_rate=float(v["flow_rate"]),
162+
feed_duration=float(v["feed_duration"]),
163+
delay_flip=float(v["delay_flip"]),
164+
delay_injection=float(v["delay_injection"]),
165+
c_eluent=float(v["c_eluent"]), # keep scalar eluent for now
166+
)
167+
168+
return ModelSpec(
169+
title="Flip-Flop",
170+
fields=_pick([
171+
"c_feed",
172+
"flow_rate",
173+
"feed_duration",
174+
"delay_flip",
175+
"delay_injection",
176+
"c_eluent",
177+
]),
178+
build=_build,
179+
)
180+
181+
def mrssr_spec(column: ChromatographicColumnBase) -> ModelSpec:
182+
"""Mixed-Recycle Steady-State Recycling (MRSSR) spec."""
183+
def _build(v: Mapping[str, Any]) -> Any:
184+
from CADETProcess.modelBuilder import MRSSR
185+
# c_tank_init may be None (means: use feed concentration)
186+
return MRSSR(
187+
column=column,
188+
c_feed=v["c_feed"],
189+
flow_rate=float(v["flow_rate"]),
190+
feed_duration=float(v["feed_duration"]),
191+
recycle_on=float(v["recycle_on"]),
192+
recycle_off=float(v["recycle_off"]),
193+
cycle_time=float(v["cycle_time"]),
194+
V_tank=float(v["V_tank"]),
195+
c_eluent=float(v["c_eluent"]),
196+
c_tank_init=v["c_tank_init"],
197+
)
198+
199+
return ModelSpec(
200+
title="MRSSR",
201+
fields=_pick([
202+
"c_feed",
203+
"flow_rate",
204+
"feed_duration",
205+
"recycle_on",
206+
"recycle_off",
207+
"cycle_time",
208+
"V_tank",
209+
"c_eluent",
210+
"c_tank_init",
211+
]),
212+
build=_build,
213+
)
214+
215+
117216

118217

119-
# A simple registry the UI can use to populate the model picker.
120-
# Keys are user-facing names; values are callables: (column) -> ModelSpec
121218
MODEL_REGISTRY: dict[str, Callable[[ChromatographicColumnBase], ModelSpec]] = {
122219
"Batch Elution": batch_elution_spec,
123220
"Load–Wash–Elute (LWE)": lwe_spec,
124-
# Add more later, e.g. "SMB": smb_spec, ...
221+
"CLR": clr_spec,
222+
"Flip-Flop": flipflop_spec,
223+
"MRSSR": mrssr_spec,
125224
}
126225

127-
128-
# ---- Column factories (receive a ComponentSystem and return a column) ----
129226
ColumnFactory = Callable[[ComponentSystem], ChromatographicColumnBase]
130227

131228

132229
def make_grm(cs: ComponentSystem) -> ChromatographicColumnBase:
133230
col = GeneralRateModel(cs, name="GRM")
134-
# Set minimal required parameters as needed, e.g.:
135-
# col.length = 0.10
136231
return col
137232

138233

139234
def make_lrmp(cs: ComponentSystem) -> ChromatographicColumnBase:
140235
col = LumpedRateModelWithPores(cs, name="LRMP")
141-
# col.length = 0.05
236+
return col
237+
238+
def make_lrm(cs: ComponentSystem) -> ChromatographicColumnBase:
239+
col = LumpedRateModelWithoutPores(cs, name="LRMP")
142240
return col
143241

144242

@@ -151,59 +249,115 @@ def make_cstr(cs: ComponentSystem) -> ChromatographicColumnBase:
151249
DEFAULT_COLUMN_FACTORIES: Dict[str, ColumnFactory] = {
152250
"GRM": make_grm,
153251
"LRMP": make_lrmp,
252+
"LRM": make_lrm,
154253
"CSTR": make_cstr,
155-
# "TubularReactor": make_tubular(...),
156-
# "MCT": make_mct(...),
254+
157255
}
158256

159-
# --- Dynamic column configuration from required_parameters ---
257+
PARAM_TYPE_OVERRIDES: Dict[str, FieldKind] = {}
160258

161-
# Optional overrides: parameter name -> FieldKind (e.g., "float", "float_list", "bool", "text")
162-
# For now we default everything to "float", but you can override per name here.
163-
PARAM_TYPE_OVERRIDES: Dict[str, FieldKind] = {
164-
# "length": "float",
165-
# "diameter": "float",
166-
# "porosity_bed": "float",
167-
# "porosity_pore": "float",
168-
# "volume": "float",
169-
}
259+
def _unique_preserve_order(names: Sequence[str]) -> list[str]:
260+
"""Return names in original order, dropping duplicates (first occurrence wins)."""
261+
seen: set[str] = set()
262+
out: list[str] = []
263+
for n in names:
264+
if n not in seen:
265+
seen.add(n)
266+
out.append(n)
267+
return out
170268

171-
def column_spec_from_required(column: ChromatographicColumnBase) -> ModelSpec:
269+
def build_column_config_spec(column: ChromatographicColumnBase) -> ModelSpec:
172270
"""
173-
Build a ModelSpec for a column by reflecting over column.required_parameters.
174-
Defaults each field to kind='float' unless overridden in PARAM_TYPE_OVERRIDES.
175-
Uses the current attribute value as the default if present.
271+
Build a ModelSpec for a column by reflecting over `column.required_parameters`.
272+
273+
- De-duplicates parameter names while preserving order.
274+
- Infers kind from the current attribute value:
275+
* list/tuple -> "float_list" (uses parse_float_list)
276+
* bool -> "bool"
277+
* otherwise -> "float" (default)
278+
(You can still override per name via PARAM_TYPE_OVERRIDES.)
279+
- Uses the column's current attribute value as the default (when present).
176280
"""
281+
282+
def _unique_preserve_order(names: Sequence[str]) -> list[str]:
283+
seen: set[str] = set()
284+
out: list[str] = []
285+
for n in names:
286+
if n not in seen:
287+
seen.add(n)
288+
out.append(n)
289+
return out
290+
177291
req = getattr(column, "required_parameters", None)
178292
if not req:
179-
# Fallback: nothing to configure
180293
return ModelSpec(
181294
title=f"Configure {column.__class__.__name__}",
182295
fields=[],
183296
build=lambda _v: column,
184297
)
185298

299+
names = _unique_preserve_order(list(req))
186300
fields: list[FieldSpec] = []
187-
for name in req:
188-
kind = PARAM_TYPE_OVERRIDES.get(name, "float") # default everything to float for now
301+
302+
for name in names:
189303
default = getattr(column, name, None)
304+
305+
# Default kind from overrides, else infer from current value
306+
kind = PARAM_TYPE_OVERRIDES.get(name)
307+
if kind is None:
308+
if isinstance(default, (list, tuple)):
309+
kind = "float_list"
310+
elif isinstance(default, bool):
311+
kind = "bool"
312+
else:
313+
# treat numbers/strings/None as float for now
314+
kind = "float"
315+
190316
label = name.replace("_", " ").capitalize()
191-
# You can extend with validation/transform per kind if you like.
192-
fields.append(FieldSpec(name=name, kind=kind, label=label, default=default))
317+
318+
# Attach transform for lists so the renderer feeds us a parsed list
319+
transform = parse_float_list if kind == "float_list" else None
320+
321+
fields.append(
322+
FieldSpec(
323+
name=name,
324+
kind=kind,
325+
label=label,
326+
default=default,
327+
transform=transform,
328+
)
329+
)
193330

194331
def _apply(v: Mapping[str, Any]) -> Any:
195-
# Assign back to the column
196-
for name in req:
197-
if name in v:
198-
try:
199-
if PARAM_TYPE_OVERRIDES.get(name, "float") == "float":
200-
setattr(column, name, float(v[name]))
332+
for name in names:
333+
if name not in v:
334+
continue
335+
try:
336+
kind = PARAM_TYPE_OVERRIDES.get(name)
337+
if kind is None:
338+
# infer again in case overrides are not set
339+
cur = getattr(column, name, None)
340+
if isinstance(cur, (list, tuple)):
341+
kind = "float_list"
342+
elif isinstance(cur, bool):
343+
kind = "bool"
201344
else:
202-
# room for future kinds
203-
setattr(column, name, v[name])
204-
except Exception:
205-
# keep going even if a parameter fails to set
206-
pass
345+
kind = "float"
346+
347+
val = v[name]
348+
if kind == "float_list":
349+
# val may already be a list (thanks to transform), but normalize anyway
350+
val = parse_float_list(val)
351+
elif kind == "bool":
352+
val = bool(val)
353+
else:
354+
# default "float"
355+
val = float(val)
356+
357+
setattr(column, name, val)
358+
except Exception:
359+
# keep going even if one assignment fails
360+
pass
207361
return column
208362

209363
return ModelSpec(

0 commit comments

Comments
 (0)