Fix IPOPT result assembly and PCR prediction centering - #137
Conversation
Mazhar331
left a comment
There was a problem hiding this comment.
Maintainer review. This account is the PR author, so GitHub only allows a COMMENT verdict here; the findings below reflect a full review. There are no blocking findings — both fixes are correct, match the fixes prescribed in #78, and are guarded by tests that I verified fail on base.
What I verified
- IPOPT fix (
ParamEstim.py:674) — the baseget_objective(self, params, out_array=False, set_self=True)has noupdate_selfparameter (only theMultipleCurveResolutionsubclass override does), so the pre-fix call raisedTypeErrorright after a successful IPOPT solve. Switching toset_self=Falseboth fixes the crash and correctly avoids overwriting the solved-stateresid_runsthat the finaly_modelassembly atParamEstim.py:672depends on. - PCR fix (
Calibration.py:245-246) — the non-SNV branch now centers/standardizes new inputs with the stored trainingdata_mean/data_stdinstead of recomputing them from the prediction rows, so a single new spectrum no longer hitsstd=0 → divide-by-zero → nan, and multi-row batches project onto the fitted PC basis consistently. - Tests genuinely guard the fixes. I reverted each production fix in a scratch copy (keeping the test) and re-ran: the IPOPT test failed with the exact
TypeError: ... unexpected keyword argument 'update_self', and the PCR test failed with thedivide by zeroRuntimeWarning →nan. With the fixes restored, both pass. The PCR expected value is computed independently of the production expression (manual centering + score projection), not by duplicatingpredict(). - Docstrings/units are accurate — the added Numpy-format docstrings and the units annotations in the tests (
[mol/L],[AU], dimensionless[-],[s],[g/L]) match the actual quantities at each boundary.
Findings
Nonblocking: SNV single-spectrum predict retains the same nan failure mode
The SNV branch (Calibration.py:243) still calls self.__center_data(inputs) with no stats, so for a single new spectrum it recomputes std=0 over that one row and returns nan — the identical failure this PR fixes for the non-SNV branch. I reproduced it directly: an snv=True model predicting one new spectrum yields [[nan]].
This is outside #78's stated scope (the issue prescribes fixing only the non-SNV branch, which this PR does correctly), so it does not block. But it is worth a follow-up: the correct SNV handling likely differs from the non-SNV fix — canonical SNV normalizes each spectrum by its own across-wavelength mean/std (a per-row operation), whereas the current __center_data uses axis=0 (across-sample) statistics, which is really column standardization. Recommend a follow-up issue to define and fix the SNV single-row contract rather than reusing the training-stats approach blindly.
Tests run
pytest tests/test_paramestim_calibration_fit_predict.py -q→2 passed(pharmapy conda env).- Red-on-base confirmation: both tests fail with the documented symptoms when each fix is reverted.
pytest tests/ -m "not assimulo" -q→63 passed, 15 deselected— no regressions.
Merge readiness
- Changed-file set matches
gh pr diff --name-only; reviewed at head1ccf83b. masteris unprotected (no branch-protection approval or status-check gate), so no maintainer-approval gate is enforced by GitHub. This account cannot post a formalAPPROVEbecause it authored the PR; if a formal approval is desired, another maintainer account would need to provide it, but it is not required to merge.Closes #78is accurate — both defects in #78 are fully resolved.
No blocking findings. The single Nonblocking item can be handled as a follow-up.
|
Addressed the review batch at head
|
|
Addressed the monkeypatch review question at head
|
|
Addressed the test documentation/units convention at head
|
Summary
Closes #78.
Refs #67.
ParameterEstimation.optimize_fn(method='IPOPT')result assembly by using the baseget_objective(..., set_self=False)keyword when collecting final weighted residuals.PCR_calibration.predict()so non-SNV prediction inputs are centered and standardized with the training statistics stored during calibration.Acceptance Criteria
ParameterEstimation.get_objective()with the subclass-onlyupdate_selfkeyword.sigma_inv, PCR prediction inputs use the calibration data units, standardized predictors/scores are dimensionless, and predicted responses keep the fitted response units.Review Notes
monkeypatchfor the optional external solver boundary only:have_cyipoptandminimize_ipopt.optimize_fn(method='IPOPT')raises the optionalcyipoptImportErrorbefore calling the finalget_objective(..., set_self=False)path.8917417removed the internalestimator.get_gradientmonkeypatch. The test now supplies a realjac_fun, so PharmaPy's actualParameterEstimation.get_gradient(), final weighted-residual assembly,y_modelassembly, and covariance calculation run after the fake solver returns.34927aaadds module, test, and helper-callback docstrings plus unit comments for each dimensional fixture and derived intermediate intests/test_paramestim_calibration_fit_predict.py.Validation
8c7d14d, the base at implementation time) with the final regression file copied into a temporary worktree:python -m pytest tests/test_paramestim_calibration_fit_predict.py -qTypeError: ParameterEstimation.get_objective() got an unexpected keyword argument 'update_self'.nanprediction from row-local zero standard deviation.8917417, fully unpatched IPOPT probe exits with the optionalcyipoptImportError, confirming that a no-monkeypatch core test would not reach the regression branch.8917417, temporarily reintroduced the oldupdate_self=Falsecall;python -m pytest tests/test_paramestim_calibration_fit_predict.py::test_parameter_estimation_ipopt_result_assembly_uses_base_keyword -qfailed with the originalTypeError, confirming the refined test still guards the bug.34927aa, completed the units/docs/comments audit over the test diff: test/module/helper documentation is present;[s],[mol/L],[mol/L/s],[AU],[g/L], covariance units, and dimensionless[-]predictor/score/component quantities are annotated in the test file.34927aa:python -m pytest tests/test_paramestim_calibration_fit_predict.py -q->2 passedpython -m pytest --collect-only -q->69 tests collectedpython -m pytest tests/ -m "not assimulo" -q->63 passed, 5 skipped, 6 deselectedpython -m py_compile tests/test_paramestim_calibration_fit_predict.pygit diff --checkBranch Hygiene
master8c7d14d(the base at implementation time; currentorigin/masterhas advanced since this PR branch was opened).fix/issue-78-fit-predict-resultsPharmaPy/ParamEstim.py,PharmaPy/Calibration.py, ortests/test_paramestim_calibration_fit_predict.py.