bindtools 0.2.1__tar.gz → 0.2.2__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.
|
@@ -218,16 +218,6 @@ def DoNR(eqMat, K, initComponentConc, guessCompConc):
|
|
|
218
218
|
)
|
|
219
219
|
|
|
220
220
|
|
|
221
|
-
def _analytical_obs_param_token(col_name: str) -> str:
|
|
222
|
-
token = re.sub(r"[^\w]+", "_", str(col_name))
|
|
223
|
-
token = re.sub(r"_+", "_", token).strip("_")
|
|
224
|
-
if not token:
|
|
225
|
-
token = "obs"
|
|
226
|
-
if not (token[0].isalpha() or token[0] == "_"):
|
|
227
|
-
token = f"obs_{token}"
|
|
228
|
-
return token
|
|
229
|
-
|
|
230
|
-
|
|
231
221
|
|
|
232
222
|
def _solve_cubic_vectorized(a: np.ndarray, b: np.ndarray, c: np.ndarray, d: np.ndarray, lower: np.ndarray, upper: np.ndarray, score_fn_list) -> np.ndarray:
|
|
233
223
|
A = b / a
|
|
@@ -437,162 +427,6 @@ def calc_analytical_speciation(
|
|
|
437
427
|
return spec_calc, error
|
|
438
428
|
|
|
439
429
|
|
|
440
|
-
def _analytical_param_value(values: np.ndarray, name_to_idx: dict[str, int], param_name: str) -> float:
|
|
441
|
-
idx = name_to_idx.get(param_name)
|
|
442
|
-
if idx is None or idx >= len(values):
|
|
443
|
-
return 0.0
|
|
444
|
-
return float(values[idx])
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
def calc_analytical_observables(
|
|
448
|
-
spec_calc: np.ndarray,
|
|
449
|
-
comp_concs: np.ndarray,
|
|
450
|
-
eq_mat: np.ndarray,
|
|
451
|
-
obs_components: list[int],
|
|
452
|
-
complex_indices: list[int],
|
|
453
|
-
shift_param_values: np.ndarray,
|
|
454
|
-
shift_param_names: list[str],
|
|
455
|
-
obs_param_map: list[list[str]],
|
|
456
|
-
) -> np.ndarray:
|
|
457
|
-
|
|
458
|
-
n_obs = len(obs_components)
|
|
459
|
-
out = np.zeros((spec_calc.shape[0], n_obs), dtype=float)
|
|
460
|
-
name_to_idx = {name: i for i, name in enumerate(shift_param_names)}
|
|
461
|
-
|
|
462
|
-
for obs_idx in range(n_obs):
|
|
463
|
-
comp_idx = int(obs_components[obs_idx])
|
|
464
|
-
denom = comp_concs[:, comp_idx]
|
|
465
|
-
pnames = obs_param_map[obs_idx]
|
|
466
|
-
baseline = _analytical_param_value(shift_param_values, name_to_idx, pnames[0])
|
|
467
|
-
out[:, obs_idx] = baseline
|
|
468
|
-
|
|
469
|
-
for cidx, complex_idx in enumerate(complex_indices):
|
|
470
|
-
if cidx + 1 >= len(pnames):
|
|
471
|
-
continue
|
|
472
|
-
amp = _analytical_param_value(shift_param_values, name_to_idx, pnames[cidx + 1])
|
|
473
|
-
stoich = float(eq_mat[comp_idx, complex_idx])
|
|
474
|
-
frac = np.zeros_like(denom, dtype=float)
|
|
475
|
-
np.divide(
|
|
476
|
-
stoich * spec_calc[:, complex_idx],
|
|
477
|
-
denom,
|
|
478
|
-
out=frac,
|
|
479
|
-
where=np.isfinite(denom) & (np.abs(denom) > 0),
|
|
480
|
-
)
|
|
481
|
-
out[:, obs_idx] += amp * frac
|
|
482
|
-
|
|
483
|
-
return out
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
def calc_analytical_linear_observables(
|
|
487
|
-
spec_calc: np.ndarray,
|
|
488
|
-
linear_param_values: np.ndarray,
|
|
489
|
-
linear_param_names: list,
|
|
490
|
-
linear_obs_param_map: list,
|
|
491
|
-
) -> np.ndarray:
|
|
492
|
-
"""Compute concentration-weighted linear observables for the analytical fast-exchange path.
|
|
493
|
-
|
|
494
|
-
Used for UV-vis (Beer-Lambert) and fluorescence: A_i = sum_j eps_{ij} * [species_j].
|
|
495
|
-
|
|
496
|
-
Args:
|
|
497
|
-
spec_calc: species concentrations, shape (n_pts, n_species).
|
|
498
|
-
linear_param_values: flat array of current non-binding parameter values.
|
|
499
|
-
linear_param_names: list of param names corresponding to linear_param_values.
|
|
500
|
-
linear_obs_param_map: list of lists; outer index = observable column, inner index = species.
|
|
501
|
-
Each inner entry is a param name string or None (dark/silent species).
|
|
502
|
-
|
|
503
|
-
Returns:
|
|
504
|
-
ndarray of shape (n_pts, n_obs).
|
|
505
|
-
"""
|
|
506
|
-
n_obs = len(linear_obs_param_map)
|
|
507
|
-
out = np.zeros((spec_calc.shape[0], n_obs), dtype=float)
|
|
508
|
-
name_to_idx = {name: i for i, name in enumerate(linear_param_names)}
|
|
509
|
-
|
|
510
|
-
for obs_idx, pnames in enumerate(linear_obs_param_map):
|
|
511
|
-
for species_idx, pname in enumerate(pnames):
|
|
512
|
-
if not pname:
|
|
513
|
-
continue # dark / silent species
|
|
514
|
-
coeff = _analytical_param_value(linear_param_values, name_to_idx, pname)
|
|
515
|
-
out[:, obs_idx] += coeff * spec_calc[:, species_idx]
|
|
516
|
-
|
|
517
|
-
return out
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
def fitfun_analytical_fast_exchange(params, fcn_opts):
|
|
521
|
-
if isinstance(params, lmfit.parameter.Parameters):
|
|
522
|
-
parvals = list(params.valuesdict().values())
|
|
523
|
-
else:
|
|
524
|
-
parvals = params
|
|
525
|
-
|
|
526
|
-
nK = int(fcn_opts["nK"])
|
|
527
|
-
binding_params = np.array([*parvals][:nK], dtype=np.float64)
|
|
528
|
-
comp_concs = np.array(fcn_opts["compConcs"], dtype=float)
|
|
529
|
-
eq_mat = np.array(fcn_opts["eqMat"], dtype=float)
|
|
530
|
-
topology = str(fcn_opts.get("analytical_topology", ""))
|
|
531
|
-
complex_indices = [int(x) for x in fcn_opts.get("analytical_complex_indices", [])]
|
|
532
|
-
obs_components = [int(x) for x in fcn_opts.get("analytical_obs_components", [])]
|
|
533
|
-
obs_param_map = list(fcn_opts.get("analytical_obs_param_map", []))
|
|
534
|
-
linear_obs_param_map = list(fcn_opts.get("analytical_linear_obs_param_map", []))
|
|
535
|
-
|
|
536
|
-
spec_calc, error = calc_analytical_speciation(
|
|
537
|
-
comp_concs=comp_concs,
|
|
538
|
-
eq_mat=eq_mat,
|
|
539
|
-
binding_params=binding_params,
|
|
540
|
-
topology=topology,
|
|
541
|
-
n_comp=int(comp_concs.shape[1]),
|
|
542
|
-
complex_indices=complex_indices,
|
|
543
|
-
)
|
|
544
|
-
|
|
545
|
-
if fcn_opts["optTarget"] == "concs":
|
|
546
|
-
if fcn_opts["ret"] == "residual":
|
|
547
|
-
res = (fcn_opts["exptData"] - spec_calc) / fcn_opts["sigma"]
|
|
548
|
-
if error and fcn_opts["mcmc"] is True:
|
|
549
|
-
return np.nan
|
|
550
|
-
if error:
|
|
551
|
-
res = res * 10
|
|
552
|
-
return res
|
|
553
|
-
if fcn_opts["ret"] == "concs":
|
|
554
|
-
return spec_calc
|
|
555
|
-
return -1
|
|
556
|
-
|
|
557
|
-
shift_params = np.array([*parvals][nK:], dtype=np.float64)
|
|
558
|
-
shift_param_names = fcn_opts["paramNames"][nK:]
|
|
559
|
-
|
|
560
|
-
# Route to linear (UV-vis / fluorescence) or NMR-shift observable calculation.
|
|
561
|
-
if linear_obs_param_map:
|
|
562
|
-
obs_calc = calc_analytical_linear_observables(
|
|
563
|
-
spec_calc=spec_calc,
|
|
564
|
-
linear_param_values=shift_params,
|
|
565
|
-
linear_param_names=shift_param_names,
|
|
566
|
-
linear_obs_param_map=linear_obs_param_map,
|
|
567
|
-
)
|
|
568
|
-
else:
|
|
569
|
-
obs_calc = calc_analytical_observables(
|
|
570
|
-
spec_calc=spec_calc,
|
|
571
|
-
comp_concs=comp_concs,
|
|
572
|
-
eq_mat=eq_mat,
|
|
573
|
-
obs_components=obs_components,
|
|
574
|
-
complex_indices=complex_indices,
|
|
575
|
-
shift_param_values=shift_params,
|
|
576
|
-
shift_param_names=shift_param_names,
|
|
577
|
-
obs_param_map=obs_param_map,
|
|
578
|
-
)
|
|
579
|
-
|
|
580
|
-
if fcn_opts["ret"] == "residual":
|
|
581
|
-
res = (fcn_opts["exptData"] - obs_calc) / fcn_opts["sigma"]
|
|
582
|
-
nan_mask = np.isnan(fcn_opts["exptData"])
|
|
583
|
-
res[nan_mask] = 0
|
|
584
|
-
if error and fcn_opts["mcmc"] is True:
|
|
585
|
-
return np.nan
|
|
586
|
-
if error:
|
|
587
|
-
res = res * 10
|
|
588
|
-
if fcn_opts["mcmc"] is True:
|
|
589
|
-
res = np.nan_to_num(res)
|
|
590
|
-
return res
|
|
591
|
-
if fcn_opts["ret"] == "concs":
|
|
592
|
-
return obs_calc
|
|
593
|
-
return -1
|
|
594
|
-
|
|
595
|
-
|
|
596
430
|
def fitfun(params, fcn_opts): # ,eqMat,specConcs,startShifts=None,sigma=1,ret='residual'):
|
|
597
431
|
|
|
598
432
|
# fcn_opts = {'compConcs': compConcs,
|
|
@@ -604,12 +438,7 @@ def fitfun(params, fcn_opts): # ,eqMat,specConcs,startShifts=None,sigma=1,ret='
|
|
|
604
438
|
# 'sigma': 1,
|
|
605
439
|
# 'ret': 'residual'}
|
|
606
440
|
|
|
607
|
-
|
|
608
|
-
"1:1",
|
|
609
|
-
"1:2",
|
|
610
|
-
"2:1",
|
|
611
|
-
):
|
|
612
|
-
return fitfun_analytical_fast_exchange(params, fcn_opts)
|
|
441
|
+
|
|
613
442
|
|
|
614
443
|
if isinstance(params, lmfit.parameter.Parameters):
|
|
615
444
|
parvals = list(params.valuesdict().values())
|
|
@@ -727,12 +556,12 @@ def concToDelta(concs, specToDd, shiftParams, paramNames):
|
|
|
727
556
|
specToDdTrial = specToDdTrial.astype(np.float64)
|
|
728
557
|
|
|
729
558
|
# normalize concs to mol fractions
|
|
730
|
-
truthMat = np.
|
|
559
|
+
truthMat = ~np.isnan(specToDdTrial)
|
|
731
560
|
shiftCalc = []
|
|
732
561
|
for cc in concs:
|
|
733
562
|
tt = (truthMat.T * cc).T
|
|
734
563
|
moleFracs = tt / tt.sum(axis=0)
|
|
735
|
-
sc = (moleFracs * specToDdTrial
|
|
564
|
+
sc = np.nansum(moleFracs * specToDdTrial, axis=0)
|
|
736
565
|
shiftCalc.append(sc)
|
|
737
566
|
|
|
738
567
|
return shiftCalc
|
|
@@ -924,12 +753,7 @@ class bindingModel:
|
|
|
924
753
|
self.specToLinear: Optional[np.ndarray] = None # (n_species, n_obs) object array for UV-vis / fluorescence
|
|
925
754
|
self.analytical_fast_exchange: bool = False
|
|
926
755
|
self.analytical_topology: Optional[str] = None
|
|
927
|
-
self.analytical_obs_columns: list[str] = []
|
|
928
|
-
self.analytical_obs_components: list[int] = []
|
|
929
756
|
self.analytical_complex_indices: list[int] = []
|
|
930
|
-
self.analytical_obs_param_map: list[list[str]] = []
|
|
931
|
-
self.analytical_linear_obs_columns: list[str] = []
|
|
932
|
-
self.analytical_linear_obs_param_map: list[list] = [] # per-obs list of param names (or None) in species order
|
|
933
757
|
|
|
934
758
|
self.params = lmfit.Parameters()
|
|
935
759
|
self.mini = None
|
|
@@ -1012,91 +836,7 @@ class bindingModel:
|
|
|
1012
836
|
if topology_res is not None:
|
|
1013
837
|
self.analytical_topology, self.analytical_complex_indices = topology_res
|
|
1014
838
|
|
|
1015
|
-
|
|
1016
|
-
# 1. Check slow exchange mapping (specToInteg / colToSpec)
|
|
1017
|
-
has_slow_exchange = False
|
|
1018
|
-
if self.colToSpec is not None and self.colToSpec.size > 0:
|
|
1019
|
-
try:
|
|
1020
|
-
flat_vals = []
|
|
1021
|
-
for x in self.colToSpec.flat:
|
|
1022
|
-
try:
|
|
1023
|
-
val = float(x)
|
|
1024
|
-
except (TypeError, ValueError):
|
|
1025
|
-
val = 0.0
|
|
1026
|
-
flat_vals.append(val)
|
|
1027
|
-
flat_arr = np.array(flat_vals)
|
|
1028
|
-
if np.any(~np.isclose(flat_arr, 0.0)):
|
|
1029
|
-
has_slow_exchange = True
|
|
1030
|
-
except Exception:
|
|
1031
|
-
pass
|
|
1032
|
-
|
|
1033
|
-
# 2. Check types of columns (NMR vs Linear vs Mixed)
|
|
1034
|
-
n_integ = self.colToSpec.shape[1] if (self.colToSpec is not None and self.colToSpec.size > 0) else 0
|
|
1035
|
-
n_linear = self.specToLinear.shape[1] if (self.specToLinear is not None and self.specToLinear.size > 0) else 0
|
|
1036
|
-
n_shift = self.specToDd.shape[1] if (self.specToDd is not None and self.specToDd.size > 0) else 0
|
|
1037
|
-
|
|
1038
|
-
obs_list = self.obsList if self.obsList is not None else []
|
|
1039
|
-
if n_shift == 0:
|
|
1040
|
-
n_shift = max(0, len(obs_list) - n_integ - n_linear)
|
|
1041
|
-
|
|
1042
|
-
if len(obs_list) < n_integ + n_linear + n_shift:
|
|
1043
|
-
obs_list = list(obs_list) + [f"obs_{i}" for i in range(len(obs_list), n_integ + n_linear + n_shift)]
|
|
1044
|
-
|
|
1045
|
-
linear_cols = obs_list[n_integ:n_integ + n_linear]
|
|
1046
|
-
shift_cols = obs_list[n_integ + n_linear:n_integ + n_linear + n_shift]
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
if not has_slow_exchange and not (n_shift > 0 and n_linear > 0) and (n_shift > 0 or n_linear > 0):
|
|
1050
|
-
self.analytical_fast_exchange = True
|
|
1051
|
-
|
|
1052
|
-
if n_linear > 0:
|
|
1053
|
-
self.analytical_linear_obs_columns = linear_cols
|
|
1054
|
-
linear_obs_param_map = []
|
|
1055
|
-
for obs_idx in range(n_linear):
|
|
1056
|
-
pnames = []
|
|
1057
|
-
for species_idx in range(self.specToLinear.shape[0]):
|
|
1058
|
-
cell = self.specToLinear[species_idx, obs_idx]
|
|
1059
|
-
if isinstance(cell, lmfit.Parameter):
|
|
1060
|
-
pnames.append(cell.name)
|
|
1061
|
-
else:
|
|
1062
|
-
pnames.append(None)
|
|
1063
|
-
linear_obs_param_map.append(pnames)
|
|
1064
|
-
self.analytical_linear_obs_param_map = linear_obs_param_map
|
|
1065
|
-
self.analytical_obs_columns = []
|
|
1066
|
-
self.analytical_obs_components = []
|
|
1067
|
-
else:
|
|
1068
|
-
self.analytical_obs_columns = shift_cols
|
|
1069
|
-
|
|
1070
|
-
def is_nonzero_mapping(cell):
|
|
1071
|
-
if cell is None:
|
|
1072
|
-
return False
|
|
1073
|
-
if isinstance(cell, (int, float)):
|
|
1074
|
-
return not np.isclose(cell, 0.0)
|
|
1075
|
-
return True
|
|
1076
|
-
|
|
1077
|
-
obs_components = []
|
|
1078
|
-
for obs_idx in range(n_shift):
|
|
1079
|
-
has_comp0 = False
|
|
1080
|
-
has_comp1 = False
|
|
1081
|
-
if self.specToDd is not None and self.specToDd.shape[0] > 1:
|
|
1082
|
-
has_comp0 = is_nonzero_mapping(self.specToDd[0, obs_idx])
|
|
1083
|
-
has_comp1 = is_nonzero_mapping(self.specToDd[1, obs_idx])
|
|
1084
|
-
|
|
1085
|
-
if has_comp0 and not has_comp1:
|
|
1086
|
-
obs_components.append(0)
|
|
1087
|
-
elif has_comp1 and not has_comp0:
|
|
1088
|
-
obs_components.append(1)
|
|
1089
|
-
else:
|
|
1090
|
-
col_name = shift_cols[obs_idx] if obs_idx < len(shift_cols) else ""
|
|
1091
|
-
tokens = [t for t in re.split(r"[^A-Za-z0-9]+", col_name.lower()) if t]
|
|
1092
|
-
matches = [idx for idx, comp in enumerate(self.compNames) if str(comp).lower() in tokens]
|
|
1093
|
-
if len(matches) == 1:
|
|
1094
|
-
obs_components.append(matches[0])
|
|
1095
|
-
else:
|
|
1096
|
-
obs_components.append(obs_idx % 2)
|
|
1097
|
-
self.analytical_obs_components = obs_components
|
|
1098
|
-
self.analytical_linear_obs_columns = []
|
|
1099
|
-
self.analytical_linear_obs_param_map = []
|
|
839
|
+
|
|
1100
840
|
|
|
1101
841
|
for paramName in self.compNames:
|
|
1102
842
|
self._addParam("log" + paramName, init=0, vary=False)
|
|
@@ -1111,22 +851,7 @@ class bindingModel:
|
|
|
1111
851
|
if isinstance(x, lmfit.Parameter):
|
|
1112
852
|
self._addExistingParam(x)
|
|
1113
853
|
|
|
1114
|
-
|
|
1115
|
-
n_complex = len(self.analytical_complex_indices)
|
|
1116
|
-
if n_complex == 0:
|
|
1117
|
-
n_complex = 1
|
|
1118
|
-
self.analytical_obs_param_map = []
|
|
1119
|
-
for col_name in self.analytical_obs_columns:
|
|
1120
|
-
token = _analytical_obs_param_token(col_name)
|
|
1121
|
-
pname_list = [f"delta0_{token}"]
|
|
1122
|
-
self._addParam(pname_list[0], init=0.0, min=-1000, max=1000, vary=True)
|
|
1123
|
-
for cidx in range(n_complex):
|
|
1124
|
-
pname = f"deltac{cidx + 1}_{token}"
|
|
1125
|
-
pname_list.append(pname)
|
|
1126
|
-
self._addParam(pname, init=0.0, min=-1000, max=1000, vary=True)
|
|
1127
|
-
self.analytical_obs_param_map.append(pname_list)
|
|
1128
|
-
# specToLinear params already registered above; no further action needed.
|
|
1129
|
-
return
|
|
854
|
+
|
|
1130
855
|
|
|
1131
856
|
# add chemical shift fitting params
|
|
1132
857
|
if self.specToDd is not None:
|
|
@@ -1172,11 +897,7 @@ class bindingModel:
|
|
|
1172
897
|
"paramNames": list(self.params.keys()),
|
|
1173
898
|
"analytical_fast_exchange": analytical_mode,
|
|
1174
899
|
"analytical_topology": self.analytical_topology,
|
|
1175
|
-
"analytical_obs_columns": list(self.analytical_obs_columns),
|
|
1176
|
-
"analytical_obs_components": list(self.analytical_obs_components),
|
|
1177
900
|
"analytical_complex_indices": list(self.analytical_complex_indices),
|
|
1178
|
-
"analytical_obs_param_map": [list(x) for x in self.analytical_obs_param_map],
|
|
1179
|
-
"analytical_linear_obs_param_map": [list(x) for x in self.analytical_linear_obs_param_map],
|
|
1180
901
|
}
|
|
1181
902
|
|
|
1182
903
|
# save settings for later plots
|
|
@@ -1228,10 +949,7 @@ class bindingModel:
|
|
|
1228
949
|
"paramNames": list(params.keys()),
|
|
1229
950
|
"analytical_fast_exchange": bool(self.analytical_fast_exchange),
|
|
1230
951
|
"analytical_topology": self.analytical_topology,
|
|
1231
|
-
"analytical_obs_columns": list(self.analytical_obs_columns),
|
|
1232
|
-
"analytical_obs_components": list(self.analytical_obs_components),
|
|
1233
952
|
"analytical_complex_indices": list(self.analytical_complex_indices),
|
|
1234
|
-
"analytical_obs_param_map": [list(x) for x in self.analytical_obs_param_map],
|
|
1235
953
|
}
|
|
1236
954
|
|
|
1237
955
|
return np.array(fitfun(params, fcn_opts))
|
|
File without changes
|
|
File without changes
|