bindmc 0.1.6__tar.gz → 0.1.7__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.
- {bindmc-0.1.6 → bindmc-0.1.7}/PKG-INFO +12 -2
- {bindmc-0.1.6 → bindmc-0.1.7}/README.md +10 -0
- {bindmc-0.1.6 → bindmc-0.1.7}/pyproject.toml +2 -2
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/classes/ExptData.py +1 -1
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/classes/FitResult.py +0 -4
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/components/data_model.py +102 -94
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/components/fitting.py +11 -86
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/export/notebook_exporter.py +26 -18
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/state/statemanager.py +4 -5
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/__init__.py +0 -0
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/__main__.py +0 -0
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/main.py +0 -0
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/Class model.md +0 -0
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/TODO.md +0 -0
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/TODO_old.md +0 -0
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/__init__.py +0 -0
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/app.py +0 -0
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/classes/BindingConstant.py +0 -0
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/classes/ChemicalShiftParam.py +0 -0
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/classes/Component.py +0 -0
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/classes/ExptDataType.py +0 -0
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/classes/MCMCSim.py +0 -0
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/classes/Model.py +0 -0
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/classes/RawData.py +0 -0
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/classes/Simulation.py +0 -0
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/classes/UIBindings.py +0 -0
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/classes/__init__.py +0 -0
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/components/__init__.py +0 -0
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/components/base.py +0 -0
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/components/bayes.py +0 -0
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/components/bayes_priors.py +0 -0
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/components/binding_model.py +0 -0
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/components/body.py +0 -0
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/components/data_gen.py +0 -0
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/components/data_import.py +0 -0
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/components/graph.py +0 -0
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/components/header.py +0 -0
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/components/simulation.py +0 -0
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/default_models.json +0 -0
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/export/__init__.py +0 -0
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/state/__init__.py +0 -0
- {bindmc-0.1.6 → bindmc-0.1.7}/src/bindmc/webgui/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: bindmc
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
4
4
|
Keywords: chemistry,analytical chemistry,binding constants,supramolecular
|
|
5
5
|
Author: Martin Peeks
|
|
6
6
|
Author-email: Martin Peeks <martinp23@googlemail.com>, m.peeks@unsw.edu.au
|
|
@@ -12,7 +12,7 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
12
12
|
Classifier: Topic :: Scientific/Engineering :: Chemistry
|
|
13
13
|
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
14
14
|
Requires-Dist: arviz==0.21.0
|
|
15
|
-
Requires-Dist: bindtools>=0.2.
|
|
15
|
+
Requires-Dist: bindtools>=0.2.2
|
|
16
16
|
Requires-Dist: corner==2.2.3
|
|
17
17
|
Requires-Dist: emcee==3.1.6
|
|
18
18
|
Requires-Dist: h5py>=3.14.0
|
|
@@ -53,6 +53,16 @@ Download the latest executable for your platform from the [Releases](https://git
|
|
|
53
53
|
pip install bindmc
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
+
## Upgrades
|
|
57
|
+
|
|
58
|
+
### Pre-built Binary
|
|
59
|
+
Re-download from the link above.
|
|
60
|
+
|
|
61
|
+
### Pip
|
|
62
|
+
```bash
|
|
63
|
+
pip install --upgrade bindmc
|
|
64
|
+
```
|
|
65
|
+
|
|
56
66
|
## Usage
|
|
57
67
|
|
|
58
68
|
If using the pre-built binary, run the downloaded executable.
|
|
@@ -15,6 +15,16 @@ Download the latest executable for your platform from the [Releases](https://git
|
|
|
15
15
|
pip install bindmc
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
+
## Upgrades
|
|
19
|
+
|
|
20
|
+
### Pre-built Binary
|
|
21
|
+
Re-download from the link above.
|
|
22
|
+
|
|
23
|
+
### Pip
|
|
24
|
+
```bash
|
|
25
|
+
pip install --upgrade bindmc
|
|
26
|
+
```
|
|
27
|
+
|
|
18
28
|
## Usage
|
|
19
29
|
|
|
20
30
|
If using the pre-built binary, run the downloaded executable.
|
|
@@ -4,7 +4,7 @@ build-backend = "uv_build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "bindmc"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.7"
|
|
8
8
|
readme = "README.md"
|
|
9
9
|
keywords = ["chemistry", "analytical chemistry", "binding constants", "supramolecular"]
|
|
10
10
|
classifiers = [
|
|
@@ -28,7 +28,7 @@ classifiers = [
|
|
|
28
28
|
requires-python = ">=3.12"
|
|
29
29
|
dependencies = [
|
|
30
30
|
"arviz==0.21.0",
|
|
31
|
-
"bindtools>=0.2.
|
|
31
|
+
"bindtools>=0.2.2",
|
|
32
32
|
"corner==2.2.3",
|
|
33
33
|
"emcee==3.1.6",
|
|
34
34
|
"h5py>=3.14.0",
|
|
@@ -443,7 +443,7 @@ class ExptData:
|
|
|
443
443
|
species_key = species_names[j]
|
|
444
444
|
parameter_matrix[i, j] = _get_parameter_for_species(species_key, delta_col)
|
|
445
445
|
else:
|
|
446
|
-
parameter_matrix[i, j] =
|
|
446
|
+
parameter_matrix[i, j] = None
|
|
447
447
|
|
|
448
448
|
self.delta_to_spec = parameter_matrix
|
|
449
449
|
return parameter_matrix
|
|
@@ -30,8 +30,6 @@ class FitResult:
|
|
|
30
30
|
bd_model: Optional[bd.bindingModel] = None
|
|
31
31
|
analytical_fast_exchange: bool = False
|
|
32
32
|
analytical_topology: Optional[str] = None
|
|
33
|
-
analytical_obs_columns: list[str] = field(default_factory=list)
|
|
34
|
-
analytical_obs_components: list[int] = field(default_factory=list)
|
|
35
33
|
analytical_complex_indices: list[int] = field(default_factory=list)
|
|
36
34
|
|
|
37
35
|
init_model: InitVar[Optional[Model]] = None # The model used for the fit, if any
|
|
@@ -150,8 +148,6 @@ class FitResult:
|
|
|
150
148
|
"success": self.success,
|
|
151
149
|
"analytical_fast_exchange": self.analytical_fast_exchange,
|
|
152
150
|
"analytical_topology": self.analytical_topology,
|
|
153
|
-
"analytical_obs_columns": list(self.analytical_obs_columns),
|
|
154
|
-
"analytical_obs_components": list(self.analytical_obs_components),
|
|
155
151
|
"analytical_complex_indices": list(self.analytical_complex_indices),
|
|
156
152
|
"fit_speciation": (
|
|
157
153
|
self.fit_speciation.to_dict(orient="list") if isinstance(self.fit_speciation, pd.DataFrame) else {}
|
|
@@ -140,14 +140,15 @@ class DataModelPanel(BaseComponent):
|
|
|
140
140
|
|
|
141
141
|
with self.dataModel_specInteg_block:
|
|
142
142
|
self.spec_integ_inps: dict[str, ui.input] = {}
|
|
143
|
+
self.spec_integ_cbs: dict[str, ui.checkbox] = {}
|
|
143
144
|
for i, spec in enumerate(self.sm.species):
|
|
144
145
|
with ui.row().classes("items-center"):
|
|
145
146
|
ui.label(f"Species conc. [{spec}]_free:")
|
|
146
147
|
self.spec_integ_inps[spec] = ui.input().classes("flex-1").props("clearable")
|
|
147
148
|
|
|
148
149
|
self.spec_integ_inps[spec].on("blur", lambda c=self.spec_integ_inps[spec]: self.set_focus(c))
|
|
149
|
-
|
|
150
|
-
self.spec_integ_inps[spec].bind_enabled_from(
|
|
150
|
+
self.spec_integ_cbs[spec] = ui.checkbox("Enabled", value=True).props(f"testid=spec-enabled-{spec}")
|
|
151
|
+
self.spec_integ_inps[spec].bind_enabled_from(self.spec_integ_cbs[spec], "value")
|
|
151
152
|
if (
|
|
152
153
|
hasattr(active_expt, "integ_to_spec")
|
|
153
154
|
and active_expt.integ_to_spec is not None
|
|
@@ -157,9 +158,9 @@ class DataModelPanel(BaseComponent):
|
|
|
157
158
|
active_expt.integ_to_spec[i], active_expt.columns
|
|
158
159
|
)
|
|
159
160
|
if self.spec_integ_inps[spec].value == "":
|
|
160
|
-
|
|
161
|
+
self.spec_integ_cbs[spec].value = False
|
|
161
162
|
else:
|
|
162
|
-
|
|
163
|
+
self.spec_integ_cbs[spec].value = False
|
|
163
164
|
|
|
164
165
|
def _gen_spec_fast_exchange_block(self):
|
|
165
166
|
"""Generate the species fast exchange block.
|
|
@@ -227,95 +228,94 @@ class DataModelPanel(BaseComponent):
|
|
|
227
228
|
self.fast_ex_chem_shift_params = []
|
|
228
229
|
# map per spec-delta index -> { species_name: {card, shift_num, fixed_cb, min_num, max_num} }
|
|
229
230
|
self.fast_ex_chem_shift_map = []
|
|
231
|
+
self.specDeltaCbs = []
|
|
230
232
|
|
|
231
233
|
# keep the fast-exchange column names for later processing and bindings
|
|
232
234
|
self.fast_ex_list_names = list(fast_ex_list)
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
#
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
self.
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
text,
|
|
263
|
-
color="teal",
|
|
264
|
-
on_click=lambda h=text: self._insert_species_into_fast_inp(h),
|
|
265
|
-
)
|
|
266
|
-
# placeholder checkbox to enable the input
|
|
267
|
-
en_cb = ui.checkbox("Enabled", value=True)
|
|
268
|
-
inp.bind_enabled_from(en_cb, "value")
|
|
269
|
-
# placeholder param block element (initially empty/hidden)
|
|
270
|
-
pb = ui.element()
|
|
271
|
-
self.fast_ex_chem_shift_blocks.append(pb)
|
|
272
|
-
self.fast_ex_chem_shift_map.append({})
|
|
273
|
-
# store param dict for later
|
|
274
|
-
# self.fast_ex_chem_shift_params.append({'shift': None, 'fixed': False, 'min': None, 'max': None})
|
|
275
|
-
|
|
276
|
-
# bind blur handler to create/update parameter block
|
|
277
|
-
# use default args to capture current inp and index
|
|
278
|
-
# allow chips/clicks to insert into this input by remembering last focus
|
|
279
|
-
inp.on("focus", lambda e, widget=inp: self.set_focus(widget))
|
|
280
|
-
inp.on("click", lambda e, widget=inp: self.set_focus(widget))
|
|
281
|
-
# handle blur to create/update parameter sub-blocks
|
|
282
|
-
inp.on("blur", lambda e, idx=i, widget=inp: self._handle_spec_delta_blur(idx, widget))
|
|
283
|
-
# immediate change handler: reparse on every change
|
|
284
|
-
inp.on_value_change(lambda e, idx=i, widget=inp: self._handle_spec_delta_blur(idx, widget))
|
|
285
|
-
|
|
286
|
-
# if there is saved delta_to_spec data, populate the input
|
|
287
|
-
if (
|
|
288
|
-
hasattr(active_expt, "delta_to_spec")
|
|
289
|
-
and active_expt.delta_to_spec is not None
|
|
290
|
-
and len(active_expt.delta_to_spec) > 0
|
|
291
|
-
):
|
|
292
|
-
delta_for_eqn = active_expt.delta_to_spec[i].copy()
|
|
293
|
-
|
|
294
|
-
for ij, el in enumerate(delta_for_eqn):
|
|
295
|
-
if np.isclose(el, 0):
|
|
296
|
-
delta_for_eqn[ij] = 0
|
|
297
|
-
else:
|
|
298
|
-
if (f"{self.sm.species[ij]}_free", shift) in active_expt.limiting_shifts:
|
|
299
|
-
s = active_expt.limiting_shifts[f"{self.sm.species[ij]}_free", shift]
|
|
300
|
-
if s.value:
|
|
301
|
-
delta_for_eqn[ij] = delta_for_eqn[ij] / s.value
|
|
302
|
-
else:
|
|
303
|
-
delta_for_eqn[ij] = 1
|
|
304
|
-
|
|
305
|
-
# self.sm.active_expt_data.limiting_shifts[f'{self.sm.species[i]}_free',shift].value
|
|
306
|
-
|
|
307
|
-
# delta_for_eqn[delta_for_eqn != 0] = 1
|
|
308
|
-
|
|
309
|
-
value = self.vec_to_conc_expression(
|
|
310
|
-
delta_for_eqn, [f"{x}_free" for x in self.sm.species]
|
|
235
|
+
with self.dataModel_specFastExchange_block:
|
|
236
|
+
# species_list = [f'{x}_free' for x in self.sm.species]
|
|
237
|
+
|
|
238
|
+
default_shift_species = None
|
|
239
|
+
if simple_model is not None and len(simple_model[1]) > 0:
|
|
240
|
+
default_shift_species = f"{self.sm.species[simple_model[1][0]]}_free"
|
|
241
|
+
|
|
242
|
+
if default_shift_species is not None:
|
|
243
|
+
ui.label(
|
|
244
|
+
f"Analytical model detected: defaulting fast-exchange expression to [{default_shift_species}] "
|
|
245
|
+
"for this observable. You can edit it if needed."
|
|
246
|
+
).classes("text-xs text-gray-600")
|
|
247
|
+
|
|
248
|
+
for i, shift in enumerate(self.fast_ex_list_names):
|
|
249
|
+
# card per chemical shift column
|
|
250
|
+
card = ui.card().classes("mb-2")
|
|
251
|
+
self.specDeltaCards.append(card)
|
|
252
|
+
with card:
|
|
253
|
+
ui.label(f"Fast exchange shift {i + 1} ({shift})").classes("text-sm font-semibold")
|
|
254
|
+
with ui.row().classes("items-center"):
|
|
255
|
+
inp = ui.input().classes("flex-1").props("clearable")
|
|
256
|
+
self.specDeltaInps.append(inp)
|
|
257
|
+
# species chips row will be added below the input to allow quick insertion
|
|
258
|
+
with ui.row().classes("gap-1 mt-2"):
|
|
259
|
+
for text in [f"{x}_free" for x in self.sm.species]:
|
|
260
|
+
ui.chip(
|
|
261
|
+
text,
|
|
262
|
+
color="teal",
|
|
263
|
+
on_click=lambda h=text: self._insert_species_into_fast_inp(h),
|
|
311
264
|
)
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
265
|
+
# placeholder checkbox to enable the input
|
|
266
|
+
en_cb = ui.checkbox("Enabled", value=True)
|
|
267
|
+
self.specDeltaCbs.append(en_cb)
|
|
268
|
+
inp.bind_enabled_from(en_cb, "value")
|
|
269
|
+
# placeholder param block element (initially empty/hidden)
|
|
270
|
+
pb = ui.element()
|
|
271
|
+
self.fast_ex_chem_shift_blocks.append(pb)
|
|
272
|
+
self.fast_ex_chem_shift_map.append({})
|
|
273
|
+
# store param dict for later
|
|
274
|
+
# self.fast_ex_chem_shift_params.append({'shift': None, 'fixed': False, 'min': None, 'max': None})
|
|
275
|
+
|
|
276
|
+
# bind blur handler to create/update parameter block
|
|
277
|
+
# use default args to capture current inp and index
|
|
278
|
+
# allow chips/clicks to insert into this input by remembering last focus
|
|
279
|
+
inp.on("focus", lambda e, widget=inp: self.set_focus(widget))
|
|
280
|
+
inp.on("click", lambda e, widget=inp: self.set_focus(widget))
|
|
281
|
+
# handle blur to create/update parameter sub-blocks
|
|
282
|
+
inp.on("blur", lambda e, idx=i, widget=inp: self._handle_spec_delta_blur(idx, widget))
|
|
283
|
+
# immediate change handler: reparse on every change
|
|
284
|
+
inp.on_value_change(lambda e, idx=i, widget=inp: self._handle_spec_delta_blur(idx, widget))
|
|
285
|
+
|
|
286
|
+
# if there is saved delta_to_spec data, populate the input
|
|
287
|
+
if (
|
|
288
|
+
hasattr(active_expt, "delta_to_spec")
|
|
289
|
+
and active_expt.delta_to_spec is not None
|
|
290
|
+
and len(active_expt.delta_to_spec) > 0
|
|
291
|
+
):
|
|
292
|
+
delta_for_eqn = active_expt.delta_to_spec[i].copy()
|
|
293
|
+
|
|
294
|
+
for ij, el in enumerate(delta_for_eqn):
|
|
295
|
+
if np.isclose(el, 0):
|
|
296
|
+
delta_for_eqn[ij] = 0
|
|
297
|
+
else:
|
|
298
|
+
if (f"{self.sm.species[ij]}_free", shift) in active_expt.limiting_shifts:
|
|
299
|
+
s = active_expt.limiting_shifts[f"{self.sm.species[ij]}_free", shift]
|
|
300
|
+
if s.value:
|
|
301
|
+
delta_for_eqn[ij] = delta_for_eqn[ij] / s.value
|
|
302
|
+
else:
|
|
303
|
+
delta_for_eqn[ij] = 1
|
|
304
|
+
|
|
305
|
+
# self.sm.active_expt_data.limiting_shifts[f'{self.sm.species[i]}_free',shift].value
|
|
306
|
+
|
|
307
|
+
# delta_for_eqn[delta_for_eqn != 0] = 1
|
|
308
|
+
|
|
309
|
+
value = self.vec_to_conc_expression(
|
|
310
|
+
delta_for_eqn, [f"{x}_free" for x in self.sm.species]
|
|
311
|
+
)
|
|
312
|
+
inp.value = value
|
|
313
|
+
if value == "":
|
|
314
|
+
en_cb.value = False
|
|
315
|
+
elif default_shift_species is not None:
|
|
316
|
+
inp.value = f"[{default_shift_species}]"
|
|
317
|
+
# Build corresponding ChemicalShiftParam widgets immediately.
|
|
318
|
+
self._handle_spec_delta_blur(i, inp)
|
|
319
319
|
|
|
320
320
|
# finished generating fast-exchange block
|
|
321
321
|
|
|
@@ -371,6 +371,14 @@ class DataModelPanel(BaseComponent):
|
|
|
371
371
|
if not isinstance(cs_obj, ChemicalShiftParam):
|
|
372
372
|
# create with safe defaults
|
|
373
373
|
cs_obj = ChemicalShiftParam(species=spec_name, col=col_name, fixed=False)
|
|
374
|
+
try:
|
|
375
|
+
if hasattr(active_expt, "data") and active_expt.data is not None and col_name in active_expt.data.columns:
|
|
376
|
+
first_val = float(active_expt.data[col_name].dropna().iloc[0])
|
|
377
|
+
cs_obj.value = round(first_val, 4)
|
|
378
|
+
cs_obj._min = round(first_val - 1.0, 4)
|
|
379
|
+
cs_obj._max = round(first_val + 1.0, 4)
|
|
380
|
+
except Exception:
|
|
381
|
+
pass
|
|
374
382
|
active_expt.limiting_shifts[k] = cs_obj
|
|
375
383
|
|
|
376
384
|
# reuse if exists
|
|
@@ -463,10 +471,10 @@ class DataModelPanel(BaseComponent):
|
|
|
463
471
|
target.integ_to_spec = None
|
|
464
472
|
else:
|
|
465
473
|
integ_to_spec = [
|
|
466
|
-
self.conc_expression_to_vec(
|
|
467
|
-
if
|
|
474
|
+
self.conc_expression_to_vec(self.spec_integ_inps[spec].value, target.columns)
|
|
475
|
+
if self.spec_integ_cbs[spec].value
|
|
468
476
|
else np.zeros(len(target.columns))
|
|
469
|
-
for
|
|
477
|
+
for spec in self.sm.species
|
|
470
478
|
]
|
|
471
479
|
integ_to_spec = np.array(integ_to_spec)
|
|
472
480
|
if integ_to_spec.size == 0 or np.all(np.isclose(integ_to_spec, 0)):
|
|
@@ -483,9 +491,9 @@ class DataModelPanel(BaseComponent):
|
|
|
483
491
|
if hasattr(self, "specDeltaInps") and isinstance(self.specDeltaInps, list) and len(self.specDeltaInps) > 0:
|
|
484
492
|
species_label_list = [f"{s}_free" for s in self.sm.species]
|
|
485
493
|
for idx, input_widget in enumerate(self.specDeltaInps):
|
|
494
|
+
cb = self.specDeltaCbs[idx]
|
|
486
495
|
if (
|
|
487
|
-
|
|
488
|
-
and input_widget.enabled
|
|
496
|
+
cb.value
|
|
489
497
|
and isinstance(input_widget.value, str)
|
|
490
498
|
and input_widget.value.strip()
|
|
491
499
|
):
|
|
@@ -132,8 +132,6 @@ def _infer_analytical_fast_exchange_config(model, expt_data, expt_dtypes: dict)
|
|
|
132
132
|
return {
|
|
133
133
|
"topology": topo_name,
|
|
134
134
|
"complex_indices": complex_indices,
|
|
135
|
-
"obs_columns": [], # no NMR shift columns
|
|
136
|
-
"obs_components": [],
|
|
137
135
|
}
|
|
138
136
|
|
|
139
137
|
# Pure NMR shift path (existing behaviour).
|
|
@@ -146,85 +144,9 @@ def _infer_analytical_fast_exchange_config(model, expt_data, expt_dtypes: dict)
|
|
|
146
144
|
if finite.size > 0 and np.any(~np.isclose(finite, 0.0)):
|
|
147
145
|
return None
|
|
148
146
|
|
|
149
|
-
component_free_labels = [f"{name}_free" for name in model.component_names]
|
|
150
|
-
if len(component_free_labels) != 2:
|
|
151
|
-
return None
|
|
152
|
-
|
|
153
|
-
# Build optional hints from existing user mappings when available.
|
|
154
|
-
shift_species_by_col: dict[str, set[str]] = {}
|
|
155
|
-
if isinstance(expt_data.limiting_shifts, dict):
|
|
156
|
-
for (species, col_name), _ in expt_data.limiting_shifts.items():
|
|
157
|
-
if col_name is None:
|
|
158
|
-
continue
|
|
159
|
-
shift_species_by_col.setdefault(str(col_name), set()).add(str(species))
|
|
160
|
-
|
|
161
|
-
delta_species_hints: dict[str, set[str]] = {}
|
|
162
|
-
delta_to_spec = expt_data.delta_to_spec
|
|
163
|
-
if isinstance(delta_to_spec, np.ndarray) and delta_to_spec.ndim == 2 and delta_to_spec.size > 0:
|
|
164
|
-
# In this UI flow, delta_to_spec rows are created from fast-exchange observable columns.
|
|
165
|
-
# For analytical mode all dependent observables are shift observables, so the row order
|
|
166
|
-
# corresponds to obs_list.
|
|
167
|
-
n_rows = min(delta_to_spec.shape[0], len(obs_list))
|
|
168
|
-
n_species = min(delta_to_spec.shape[1], len(component_free_labels))
|
|
169
|
-
for ridx in range(n_rows):
|
|
170
|
-
col_name = obs_list[ridx]
|
|
171
|
-
for sidx in range(n_species):
|
|
172
|
-
try:
|
|
173
|
-
is_nonzero = not np.isclose(float(delta_to_spec[ridx, sidx]), 0.0)
|
|
174
|
-
except Exception:
|
|
175
|
-
is_nonzero = bool(delta_to_spec[ridx, sidx])
|
|
176
|
-
if is_nonzero:
|
|
177
|
-
delta_species_hints.setdefault(col_name, set()).add(component_free_labels[sidx])
|
|
178
|
-
|
|
179
|
-
def _component_from_text_hints(col_name: str, dtype_key: str | None) -> int | None:
|
|
180
|
-
# Tokenize to avoid over-matching short component names in arbitrary strings.
|
|
181
|
-
tokens = []
|
|
182
|
-
for src in (col_name, dtype_key or ""):
|
|
183
|
-
parts = [t for t in re.split(r"[^A-Za-z0-9]+", str(src).lower()) if t]
|
|
184
|
-
tokens.extend(parts)
|
|
185
|
-
matches = [idx for idx, comp in enumerate(model.component_names) if str(comp).lower() in tokens]
|
|
186
|
-
return matches[0] if len(matches) == 1 else None
|
|
187
|
-
|
|
188
|
-
obs_components: list[int] = []
|
|
189
|
-
unresolved: list[tuple[int, str]] = []
|
|
190
|
-
for obs_idx, col in enumerate(obs_list):
|
|
191
|
-
col_meta = expt_data.col_details.get(col, {})
|
|
192
|
-
dtype_key = col_meta.get("dtype")
|
|
193
|
-
|
|
194
|
-
included_species = set()
|
|
195
|
-
included_species |= shift_species_by_col.get(col, set())
|
|
196
|
-
included_species |= delta_species_hints.get(col, set())
|
|
197
|
-
|
|
198
|
-
has_comp0 = component_free_labels[0] in included_species
|
|
199
|
-
has_comp1 = component_free_labels[1] in included_species
|
|
200
|
-
if has_comp0 != has_comp1:
|
|
201
|
-
obs_components.append(0 if has_comp0 else 1)
|
|
202
|
-
continue
|
|
203
|
-
|
|
204
|
-
inferred = _component_from_text_hints(col, dtype_key)
|
|
205
|
-
if inferred is not None:
|
|
206
|
-
obs_components.append(inferred)
|
|
207
|
-
continue
|
|
208
|
-
|
|
209
|
-
unresolved.append((obs_idx, col))
|
|
210
|
-
obs_components.append(-1)
|
|
211
|
-
|
|
212
|
-
# Final fallback keeps analytical mode enabled even without manual shift metadata.
|
|
213
|
-
if unresolved:
|
|
214
|
-
for obs_idx, col in unresolved:
|
|
215
|
-
fallback = obs_idx % len(component_free_labels)
|
|
216
|
-
obs_components[obs_idx] = fallback
|
|
217
|
-
logger.info(
|
|
218
|
-
"Analytical fast-exchange: inferred observable '%s' as component %d by default fallback.",
|
|
219
|
-
col,
|
|
220
|
-
fallback,
|
|
221
|
-
)
|
|
222
|
-
|
|
223
147
|
return {
|
|
224
148
|
"topology": topo_name,
|
|
225
149
|
"complex_indices": complex_indices,
|
|
226
|
-
"obs_columns": list(obs_list),
|
|
227
|
-
"obs_components": obs_components,
|
|
228
150
|
}
|
|
229
151
|
|
|
230
152
|
|
|
@@ -237,9 +159,9 @@ class FittingPanel(BaseComponent):
|
|
|
237
159
|
ui.label("Fitting panel").classes("text-lg font-bold mb-4")
|
|
238
160
|
with ui.row().classes("w-full gap-4 items-start flex-col lg:flex-row"):
|
|
239
161
|
with ui.card().classes("w-full lg:w-80 shrink-0"):
|
|
240
|
-
ui.label("Fitting options to go here.")
|
|
162
|
+
# ui.label("Fitting options to go here.")
|
|
241
163
|
self.fit_alg_select = ui.select(
|
|
242
|
-
["least_squares", "l-bfgs", "ampgo"],
|
|
164
|
+
["least_squares", "l-bfgs", "l-bfgs-b", "nelder-mead", "ampgo"],
|
|
243
165
|
label="Algorithm",
|
|
244
166
|
on_change=lambda e: print(f"Selected: {e.value}"),
|
|
245
167
|
value="least_squares",
|
|
@@ -494,8 +416,6 @@ class FittingPanel(BaseComponent):
|
|
|
494
416
|
bd_model=self.m1,
|
|
495
417
|
analytical_fast_exchange=self.m1.analytical_fast_exchange,
|
|
496
418
|
analytical_topology=self.m1.analytical_topology,
|
|
497
|
-
analytical_obs_columns=[str(x) for x in self.m1.analytical_obs_columns],
|
|
498
|
-
analytical_obs_components=[int(x) for x in self.m1.analytical_obs_components],
|
|
499
419
|
analytical_complex_indices=self.m1.analytical_complex_indices,
|
|
500
420
|
)
|
|
501
421
|
self.sm.add_fit(new_fit)
|
|
@@ -510,6 +430,11 @@ class FittingPanel(BaseComponent):
|
|
|
510
430
|
"initial_value": v.init_value,
|
|
511
431
|
}
|
|
512
432
|
|
|
433
|
+
print("DEBUG: specToInteg =", self.sm.active_expt_data.integ_to_spec)
|
|
434
|
+
print("DEBUG: specToDd =", self.sm.active_expt_data.delta_to_spec)
|
|
435
|
+
print("DEBUG: obsList =", self.m1.obsList)
|
|
436
|
+
print("DEBUG: calc_obs shape =", np.shape(calc_obs))
|
|
437
|
+
|
|
513
438
|
self.sm.active_fit.calc_obs = pd.DataFrame(calc_obs, columns=self.m1.obsList)
|
|
514
439
|
|
|
515
440
|
self.sm.active_fit.fit_speciation = pd.DataFrame(speciation, columns=[x + "_free" for x in self.sm.species])
|
|
@@ -800,7 +725,7 @@ class FitResultsCard(BaseComponent):
|
|
|
800
725
|
for fit in self.sm.fits.values():
|
|
801
726
|
row = {
|
|
802
727
|
"name": fit.name,
|
|
803
|
-
"chisqr": self.rounded_value(fit.chisqr),
|
|
728
|
+
"chisqr": self.rounded_value(fit.chisqr,scientific=True),
|
|
804
729
|
"aic": self.rounded_value(fit.aic, dp=1),
|
|
805
730
|
"bic": self.rounded_value(fit.bic, dp=1),
|
|
806
731
|
"message": fit.termination_message,
|
|
@@ -817,10 +742,10 @@ class FitResultsCard(BaseComponent):
|
|
|
817
742
|
self.add_body_slot()
|
|
818
743
|
self.table.update()
|
|
819
744
|
|
|
820
|
-
def rounded_value(self, value: float, dp: int = 3) -> str:
|
|
745
|
+
def rounded_value(self, value: float, dp: int = 3, scientific=False) -> str:
|
|
821
746
|
"""Round the value for display."""
|
|
822
|
-
if abs(value) >= 10000 or abs(value) < 0.001:
|
|
823
|
-
return f"{value:.
|
|
747
|
+
if abs(value) >= 10000 or abs(value) < 0.001 or scientific:
|
|
748
|
+
return f"{value:.3e}"
|
|
824
749
|
else:
|
|
825
750
|
return f"{value:.{dp}f}" if isinstance(value, float) else str(value)
|
|
826
751
|
|
|
@@ -239,17 +239,7 @@ def _build_lin_obs_cell4_lines(
|
|
|
239
239
|
return lines
|
|
240
240
|
|
|
241
241
|
|
|
242
|
-
def _build_analytical_lin_obs_lines(lin_obs_param_map: list) -> list[str]:
|
|
243
|
-
"""Return setup lines for the analytical path's linear_obs_param_map.
|
|
244
242
|
|
|
245
|
-
Extracts the param name (or None) from each cell so
|
|
246
|
-
fitfun_analytical_fast_exchange can call calc_analytical_linear_observables.
|
|
247
|
-
"""
|
|
248
|
-
# Serialise as [[name_or_none, ...], ...]
|
|
249
|
-
name_map = [[cell["name"] if cell is not None else None for cell in row] for row in lin_obs_param_map]
|
|
250
|
-
return [
|
|
251
|
-
f"m.analytical_linear_obs_param_map = {name_map!r}",
|
|
252
|
-
]
|
|
253
243
|
|
|
254
244
|
|
|
255
245
|
def export_fit_notebook(
|
|
@@ -382,7 +372,31 @@ def export_fit_notebook(
|
|
|
382
372
|
spec_to_integ_arg = "None"
|
|
383
373
|
|
|
384
374
|
if has_delta:
|
|
385
|
-
|
|
375
|
+
# Build spec_to_dd reconstruction lines dynamically to properly instantiate lmfit.Parameters
|
|
376
|
+
spec_to_dd_lines = [
|
|
377
|
+
"spec_to_dd = np.empty((len(species_names), len(obs_list)), dtype=object)"
|
|
378
|
+
]
|
|
379
|
+
delta_T = delta_to_spec.T
|
|
380
|
+
for i in range(delta_T.shape[0]):
|
|
381
|
+
for j in range(delta_T.shape[1]):
|
|
382
|
+
cell = delta_T[i, j]
|
|
383
|
+
if cell is None:
|
|
384
|
+
spec_to_dd_lines.append(f"spec_to_dd[{i}, {j}] = None")
|
|
385
|
+
elif isinstance(cell, (int, float, np.floating)):
|
|
386
|
+
if np.isnan(cell):
|
|
387
|
+
spec_to_dd_lines.append(f"spec_to_dd[{i}, {j}] = None")
|
|
388
|
+
else:
|
|
389
|
+
spec_to_dd_lines.append(f"spec_to_dd[{i}, {j}] = {float(cell)!r}")
|
|
390
|
+
else: # lmfit.Parameter
|
|
391
|
+
pname = getattr(cell, "name", "")
|
|
392
|
+
pval = getattr(cell, "value", 0.0)
|
|
393
|
+
pmin = getattr(cell, "min", -np.inf)
|
|
394
|
+
pmax = getattr(cell, "max", np.inf)
|
|
395
|
+
pvary = getattr(cell, "vary", True)
|
|
396
|
+
spec_to_dd_lines.append(
|
|
397
|
+
f"spec_to_dd[{i}, {j}] = lmfit.Parameter({pname!r}, value={pval!r}, min={pmin!r}, max={pmax!r}, vary={pvary!r})"
|
|
398
|
+
)
|
|
399
|
+
spec_to_dd_line = "\n".join(spec_to_dd_lines)
|
|
386
400
|
spec_to_dd_arg = "spec_to_dd"
|
|
387
401
|
else:
|
|
388
402
|
spec_to_dd_line = "spec_to_dd = None"
|
|
@@ -390,8 +404,6 @@ def export_fit_notebook(
|
|
|
390
404
|
|
|
391
405
|
is_analytical = bool(getattr(fit, "analytical_fast_exchange", False))
|
|
392
406
|
analytical_topology = getattr(fit, "analytical_topology", None)
|
|
393
|
-
analytical_obs_columns = list(getattr(fit, "analytical_obs_columns", []))
|
|
394
|
-
analytical_obs_components = list(getattr(fit, "analytical_obs_components", []))
|
|
395
407
|
analytical_complex_indices = list(getattr(fit, "analytical_complex_indices", []))
|
|
396
408
|
|
|
397
409
|
has_lin_obs = bool(lin_obs_col_names and lin_obs_param_map)
|
|
@@ -399,15 +411,11 @@ def export_fit_notebook(
|
|
|
399
411
|
if is_analytical:
|
|
400
412
|
analytical_setup_lines: list[str] = [
|
|
401
413
|
"",
|
|
402
|
-
"# Analytical fast-exchange
|
|
414
|
+
"# Analytical fast-exchange speciation",
|
|
403
415
|
"m.analytical_fast_exchange = True",
|
|
404
416
|
f"m.analytical_topology = {analytical_topology!r}",
|
|
405
|
-
f"m.analytical_obs_columns = {analytical_obs_columns!r}",
|
|
406
|
-
f"m.analytical_obs_components = {analytical_obs_components!r}",
|
|
407
417
|
f"m.analytical_complex_indices = {analytical_complex_indices!r}",
|
|
408
418
|
]
|
|
409
|
-
if has_lin_obs:
|
|
410
|
-
analytical_setup_lines += _build_analytical_lin_obs_lines(lin_obs_param_map) # type: ignore[arg-type]
|
|
411
419
|
else:
|
|
412
420
|
analytical_setup_lines = []
|
|
413
421
|
|
|
@@ -1167,6 +1167,9 @@ class StateManager:
|
|
|
1167
1167
|
fittemp = data.get("fits", []) # List of fits
|
|
1168
1168
|
if fittemp:
|
|
1169
1169
|
for fit in fittemp:
|
|
1170
|
+
# backward compatibility to load pre-0.1.7 fits
|
|
1171
|
+
fit.pop("analytical_obs_columns",None)
|
|
1172
|
+
fit.pop("analytical_obs_components",None)
|
|
1170
1173
|
fit_result = FitResult(**fit)
|
|
1171
1174
|
fit_result.fit_speciation = pd.DataFrame(fit.get("fit_speciation", {}))
|
|
1172
1175
|
fit_result.calc_obs = pd.DataFrame(fit.get("calc_obs", {}))
|
|
@@ -1233,7 +1236,7 @@ class StateManager:
|
|
|
1233
1236
|
parameter.vary = bool(cell.get("vary", True))
|
|
1234
1237
|
reconstructed_row.append(parameter)
|
|
1235
1238
|
elif cell is None:
|
|
1236
|
-
reconstructed_row.append(
|
|
1239
|
+
reconstructed_row.append(None)
|
|
1237
1240
|
elif isinstance(cell, (str, int, float)):
|
|
1238
1241
|
try:
|
|
1239
1242
|
reconstructed_row.append(float(cell))
|
|
@@ -1736,16 +1739,12 @@ bd.makeFitResidPlot(fit,plotMask=(0,1),ylabel='Chemical shift (ppm)')"""
|
|
|
1736
1739
|
cfg = {
|
|
1737
1740
|
"topology": fit.analytical_topology,
|
|
1738
1741
|
"complex_indices": list(getattr(fit, "analytical_complex_indices", [])),
|
|
1739
|
-
"obs_columns": list(getattr(fit, "analytical_obs_columns", [])),
|
|
1740
|
-
"obs_components": list(getattr(fit, "analytical_obs_components", [])),
|
|
1741
1742
|
}
|
|
1742
1743
|
|
|
1743
1744
|
if cfg is not None:
|
|
1744
1745
|
model.analytical_fast_exchange = True
|
|
1745
1746
|
model.analytical_topology = str(cfg["topology"])
|
|
1746
1747
|
model.analytical_complex_indices = [int(x) for x in cfg["complex_indices"]] # type: ignore[index]
|
|
1747
|
-
model.analytical_obs_columns = [str(x) for x in cfg["obs_columns"]] # type: ignore[index]
|
|
1748
|
-
model.analytical_obs_components = [int(x) for x in cfg["obs_components"]] # type: ignore[index]
|
|
1749
1748
|
logger.info(
|
|
1750
1749
|
"Using analytical fast-exchange backend (%s model) for fitting.",
|
|
1751
1750
|
model.analytical_topology,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|