Skip to content

Commit 6fb9483

Browse files
committed
feat: add error handling to FitCalculator.fit()
1 parent 3749935 commit 6fb9483

10 files changed

Lines changed: 99 additions & 53 deletions

File tree

architecture.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ Optional behavior:
331331
FitSettings(errors="collect")
332332
```
333333

334-
In collect mode, failed fits are returned as `FitResult` objects with `success = False` and a failure message.
334+
In collect mode, failed fits are returned as `FitResult` objects with `success = False`.
335335

336336
## Quality control
337337

bindcurve/fitting/calculator.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,20 @@ def fit(
4040
for compound_id in compound_ids:
4141
try:
4242
compound = data.select_compound(compound_id)
43-
for experiment_id in compound.experiments:
43+
except Exception:
44+
if self.settings.errors == "raise":
45+
raise
46+
fits.append(
47+
FitResult.failed(
48+
compound_id=str(compound_id),
49+
model_name=self.model.name,
50+
experiment_id=None,
51+
)
52+
)
53+
continue
54+
55+
for experiment_id in compound.experiments:
56+
try:
4457
fit_data = compound.select_experiment(experiment_id)
4558
fits.append(
4659
self._fit_one(
@@ -50,17 +63,16 @@ def fit(
5063
bounds=bounds,
5164
)
5265
)
53-
except Exception as exc:
54-
if self.settings.errors == "raise":
55-
raise
56-
fits.append(
57-
FitResult.failed(
58-
compound_id=str(compound_id),
59-
model_name=self.model.name,
60-
experiment_id=None,
61-
message=str(exc),
66+
except Exception:
67+
if self.settings.errors == "raise":
68+
raise
69+
fits.append(
70+
FitResult.failed(
71+
compound_id=str(compound_id),
72+
model_name=self.model.name,
73+
experiment_id=experiment_id,
74+
)
6275
)
63-
)
6476

6577
summaries = summarize_fit_parameters(
6678
fits,
@@ -126,7 +138,6 @@ def _fit_one(
126138
experiment_id=experiment_id,
127139
model_name=self.model.name,
128140
success=bool(lmfit_result.success),
129-
message=lmfit_result.message,
130141
parameters=estimates,
131142
metrics=metrics,
132143
lmfit_result=lmfit_result,

bindcurve/results/core.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ class FitResult:
5151
model_name: str
5252
experiment_id: str | None = None
5353
success: bool = True
54-
message: str | None = None
5554
parameters: dict[str, ParameterEstimate] = field(default_factory=dict)
5655
metrics: FitMetrics | None = None
5756
lmfit_result: Any | None = None
@@ -63,15 +62,13 @@ def failed(
6362
compound_id: str,
6463
model_name: str,
6564
experiment_id: str | None,
66-
message: str,
6765
) -> FitResult:
6866
"""Create a failed result container."""
6967
return cls(
7068
compound_id=compound_id,
7169
model_name=model_name,
7270
experiment_id=experiment_id,
7371
success=False,
74-
message=message,
7572
)
7673

7774
def parameter(self, name: str) -> ParameterEstimate:
@@ -176,7 +173,6 @@ def fit_summary(self) -> pd.DataFrame:
176173
"experiment_id": fit.experiment_id,
177174
"model": fit.model_name,
178175
"success": fit.success,
179-
"message": fit.message,
180176
}
181177
if fit.metrics is not None:
182178
row.update(

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ dependencies = [
1919
"pandas",
2020
"matplotlib",
2121
"lmfit",
22+
"scipy",
2223
]
2324
classifiers = [
2425
"Development Status :: 4 - Beta",
@@ -41,7 +42,6 @@ Documentation = "https://choutkaj.github.io/bindcurve/"
4142
[dependency-groups]
4243
test = [
4344
"pytest",
44-
"scipy",
4545
]
4646
lint = [
4747
"ruff",

tests/test_ic50_skeleton.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,29 @@ def test_collect_errors_returns_failed_result():
156156

157157
assert len(results.failed()) == 1
158158
assert results.failed()[0].success is False
159+
160+
161+
def test_collect_errors_continues_after_experiment_failure():
162+
class FailsForExp2Model(bc.IC50Model):
163+
name = "fails_for_exp2"
164+
165+
def guess(self, compound: bc.CompoundData) -> dict[str, float]:
166+
experiment_id = str(compound.table["experiment_id"].iloc[0])
167+
if experiment_id == "exp2":
168+
raise RuntimeError("synthetic experiment failure")
169+
return super().guess(compound)
170+
171+
data = make_synthetic_data().keep_only("cmpd_a")
172+
results = bc.fit(
173+
data,
174+
model=FailsForExp2Model(),
175+
settings=bc.FitSettings(errors="collect"),
176+
fixed={"ymin": 0.0, "ymax": 100.0},
177+
)
178+
179+
successful_experiments = {fit.experiment_id for fit in results.successful()}
180+
failed_experiments = {fit.experiment_id for fit in results.failed()}
181+
182+
assert successful_experiments == {"exp1", "exp3"}
183+
assert failed_experiments == {"exp2"}
184+
assert len(results.fit_results) == 3

tests/test_quality_dashboards.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@ def test_results_quality_dashboard_handles_failed_fit_and_preserves_order():
103103
compound_id="cmpd_a",
104104
model_name="ic50",
105105
experiment_id="exp4",
106-
message="failed",
107106
)
108107
)
109108
merged = bc.FitResults(

tests/test_quality_reports.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,6 @@ def test_results_quality_report_orange_for_partial_fit_failure():
174174
compound_id="cmpd_a",
175175
model_name="ic50",
176176
experiment_id="exp4",
177-
message="failed",
178177
)
179178
)
180179

@@ -192,13 +191,11 @@ def test_results_quality_report_red_for_all_failed_fits():
192191
compound_id="cmpd_a",
193192
model_name="ic50",
194193
experiment_id="exp1",
195-
message="failed",
196194
),
197195
bc.FitResult.failed(
198196
compound_id="cmpd_a",
199197
model_name="ic50",
200198
experiment_id="exp2",
201-
message="failed",
202199
),
203200
],
204201
summaries=[],

tests/test_results_reporting.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,13 @@ def test_report_omits_missing_uncertainty_for_single_experiment():
8484
assert "±" not in report.loc[0, "report"]
8585

8686

87+
def test_fit_summary_omits_message_column():
88+
data = make_single_experiment_data()
89+
results = bc.fit(data, model="ic50", fixed={"ymin": 0.0, "ymax": 100.0})
90+
91+
assert "message" not in results.fit_summary().columns
92+
93+
8794
def test_report_auto_raises_for_multiple_reportable_quantities():
8895
fit = bc.FitResult(compound_id="cmpd_a", model_name="mock", success=True)
8996
summaries = [

tutorials/00_basics.ipynb

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@
144144
" format=\"wide\",\n",
145145
")\n",
146146
"\n",
147-
"data.summary()\n"
147+
"data.summary()"
148148
]
149149
},
150150
{
@@ -892,7 +892,6 @@
892892
" <th>experiment_id</th>\n",
893893
" <th>model</th>\n",
894894
" <th>success</th>\n",
895-
" <th>message</th>\n",
896895
" <th>n_data</th>\n",
897896
" <th>n_varying_parameters</th>\n",
898897
" <th>chisqr</th>\n",
@@ -917,7 +916,6 @@
917916
" <td>exp_1</td>\n",
918917
" <td>ic50</td>\n",
919918
" <td>True</td>\n",
920-
" <td>Fit succeeded.</td>\n",
921919
" <td>11</td>\n",
922920
" <td>2</td>\n",
923921
" <td>4.98</td>\n",
@@ -940,7 +938,6 @@
940938
" <td>exp_2</td>\n",
941939
" <td>ic50</td>\n",
942940
" <td>True</td>\n",
943-
" <td>Fit succeeded.</td>\n",
944941
" <td>11</td>\n",
945942
" <td>2</td>\n",
946943
" <td>15.27</td>\n",
@@ -963,7 +960,6 @@
963960
" <td>exp_3</td>\n",
964961
" <td>ic50</td>\n",
965962
" <td>True</td>\n",
966-
" <td>Fit succeeded.</td>\n",
967963
" <td>11</td>\n",
968964
" <td>2</td>\n",
969965
" <td>12.66</td>\n",
@@ -986,7 +982,6 @@
986982
" <td>exp_1</td>\n",
987983
" <td>ic50</td>\n",
988984
" <td>True</td>\n",
989-
" <td>Fit succeeded.</td>\n",
990985
" <td>11</td>\n",
991986
" <td>2</td>\n",
992987
" <td>5.69</td>\n",
@@ -1009,7 +1004,6 @@
10091004
" <td>exp_2</td>\n",
10101005
" <td>ic50</td>\n",
10111006
" <td>True</td>\n",
1012-
" <td>Fit succeeded.</td>\n",
10131007
" <td>11</td>\n",
10141008
" <td>2</td>\n",
10151009
" <td>16.06</td>\n",
@@ -1032,7 +1026,6 @@
10321026
" <td>exp_3</td>\n",
10331027
" <td>ic50</td>\n",
10341028
" <td>True</td>\n",
1035-
" <td>Fit succeeded.</td>\n",
10361029
" <td>11</td>\n",
10371030
" <td>2</td>\n",
10381031
" <td>7.35</td>\n",
@@ -1055,7 +1048,6 @@
10551048
" <td>exp_1</td>\n",
10561049
" <td>ic50</td>\n",
10571050
" <td>True</td>\n",
1058-
" <td>Fit succeeded.</td>\n",
10591051
" <td>11</td>\n",
10601052
" <td>2</td>\n",
10611053
" <td>8.78</td>\n",
@@ -1078,7 +1070,6 @@
10781070
" <td>exp_2</td>\n",
10791071
" <td>ic50</td>\n",
10801072
" <td>True</td>\n",
1081-
" <td>Fit succeeded.</td>\n",
10821073
" <td>11</td>\n",
10831074
" <td>2</td>\n",
10841075
" <td>10.57</td>\n",
@@ -1101,7 +1092,6 @@
11011092
" <td>exp_3</td>\n",
11021093
" <td>ic50</td>\n",
11031094
" <td>True</td>\n",
1104-
" <td>Fit succeeded.</td>\n",
11051095
" <td>11</td>\n",
11061096
" <td>2</td>\n",
11071097
" <td>14.00</td>\n",
@@ -1123,16 +1113,16 @@
11231113
"</div>"
11241114
],
11251115
"text/plain": [
1126-
" compound_id experiment_id model success message n_data n_varying_parameters chisqr redchi aic bic r_squared ymin ymin_stderr ymax ymax_stderr IC50 IC50_stderr hill_slope \\\n",
1127-
"0 cmpd_1 exp_1 ic50 True Fit succeeded. 11 2 4.98 0.55 -4.72 -3.92 1.0 0.0 0.0 100.0 0.0 0.02 3.42e-04 -1.00 \n",
1128-
"1 cmpd_1 exp_2 ic50 True Fit succeeded. 11 2 15.27 1.70 7.61 8.40 1.0 0.0 0.0 100.0 0.0 0.02 7.15e-04 -0.99 \n",
1129-
"2 cmpd_1 exp_3 ic50 True Fit succeeded. 11 2 12.66 1.41 5.55 6.34 1.0 0.0 0.0 100.0 0.0 0.02 6.24e-04 -0.98 \n",
1130-
"3 cmpd_2 exp_1 ic50 True Fit succeeded. 11 2 5.69 0.63 -3.26 -2.46 1.0 0.0 0.0 100.0 0.0 0.19 3.92e-03 -1.01 \n",
1131-
"4 cmpd_2 exp_2 ic50 True Fit succeeded. 11 2 16.06 1.78 8.16 8.96 1.0 0.0 0.0 100.0 0.0 0.20 6.98e-03 -0.97 \n",
1132-
"5 cmpd_2 exp_3 ic50 True Fit succeeded. 11 2 7.35 0.82 -0.43 0.37 1.0 0.0 0.0 100.0 0.0 0.22 5.17e-03 -1.02 \n",
1133-
"6 cmpd_3 exp_1 ic50 True Fit succeeded. 11 2 8.78 0.98 1.52 2.31 1.0 0.0 0.0 100.0 0.0 2.09 5.54e-02 -0.99 \n",
1134-
"7 cmpd_3 exp_2 ic50 True Fit succeeded. 11 2 10.57 1.17 3.57 4.36 1.0 0.0 0.0 100.0 0.0 1.95 5.74e-02 -0.97 \n",
1135-
"8 cmpd_3 exp_3 ic50 True Fit succeeded. 11 2 14.00 1.56 6.65 7.45 1.0 0.0 0.0 100.0 0.0 1.81 6.13e-02 -0.97 \n",
1116+
" compound_id experiment_id model success n_data n_varying_parameters chisqr redchi aic bic r_squared ymin ymin_stderr ymax ymax_stderr IC50 IC50_stderr hill_slope \\\n",
1117+
"0 cmpd_1 exp_1 ic50 True 11 2 4.98 0.55 -4.72 -3.92 1.0 0.0 0.0 100.0 0.0 0.02 3.42e-04 -1.00 \n",
1118+
"1 cmpd_1 exp_2 ic50 True 11 2 15.27 1.70 7.61 8.40 1.0 0.0 0.0 100.0 0.0 0.02 7.15e-04 -0.99 \n",
1119+
"2 cmpd_1 exp_3 ic50 True 11 2 12.66 1.41 5.55 6.34 1.0 0.0 0.0 100.0 0.0 0.02 6.24e-04 -0.98 \n",
1120+
"3 cmpd_2 exp_1 ic50 True 11 2 5.69 0.63 -3.26 -2.46 1.0 0.0 0.0 100.0 0.0 0.19 3.92e-03 -1.01 \n",
1121+
"4 cmpd_2 exp_2 ic50 True 11 2 16.06 1.78 8.16 8.96 1.0 0.0 0.0 100.0 0.0 0.20 6.98e-03 -0.97 \n",
1122+
"5 cmpd_2 exp_3 ic50 True 11 2 7.35 0.82 -0.43 0.37 1.0 0.0 0.0 100.0 0.0 0.22 5.17e-03 -1.02 \n",
1123+
"6 cmpd_3 exp_1 ic50 True 11 2 8.78 0.98 1.52 2.31 1.0 0.0 0.0 100.0 0.0 2.09 5.54e-02 -0.99 \n",
1124+
"7 cmpd_3 exp_2 ic50 True 11 2 10.57 1.17 3.57 4.36 1.0 0.0 0.0 100.0 0.0 1.95 5.74e-02 -0.97 \n",
1125+
"8 cmpd_3 exp_3 ic50 True 11 2 14.00 1.56 6.65 7.45 1.0 0.0 0.0 100.0 0.0 1.81 6.13e-02 -0.97 \n",
11361126
"\n",
11371127
" hill_slope_stderr \n",
11381128
"0 0.02 \n",
@@ -1530,4 +1520,4 @@
15301520
},
15311521
"nbformat": 4,
15321522
"nbformat_minor": 5
1533-
}
1523+
}

0 commit comments

Comments
 (0)