Fix distillation shortcut reflux calculations#121
Conversation
Mazhar331
left a comment
There was a problem hiding this comment.
Maintainer review of the shortcut-design reflux fixes for #72. I authored this PR, so this is a COMMENT review — see the merge-readiness note at the bottom.
Both defects described in the issue are fixed at the right place, and I verified each one independently rather than relying on the green suite.
Verification
Both regression tests are genuinely red without their fix. Because PharmaPy is editable-installed here (a __editable__ import finder wins over PYTHONPATH), a worktree-plus-PYTHONPATH mutation check would have silently exercised the original source, so I mutated the real file in place and restored it afterwards (tree confirmed clean, HEAD still cde248b):
- Reverting
reflux = 1.5 * min_refluxto1.5 * self.min_refluxfailstest_positive_reflux_below_minimum_uses_current_minimumwith exactly the symptom the issue reports —AttributeError: 'DistillationColumn' object has no attribute 'min_reflux'— not a downstream error absorbed by a broadraises. - Reverting
underwood_targetto0failstest_calc_min_reflux_includes_feed_quality_in_underwood_targetwithRmin = 0.5277777vs the expected0.7.
The expected value is independent of the implementation. I derived 0.7 by hand rather than trusting the code: with alpha = [4, 1], z = [0.4, 0.6], q = 0.8, the first Underwood equation 1.6/(4-phi) + 0.6/(1-phi) = 0.2 is solved exactly by phi = 2, giving V_min = 36/2 - 1 = 17 mol/s, L_min = 7 mol/s, Rmin = 0.7 [-]. The pre-fix root phi = 1.8181 likewise reproduces 0.5277 analytically. The assertion pins a value, not a shape or count.
The 1 - q convention matches this repo's q_feed. I checked every consumer rather than assuming the textbook sign: both constructor docstrings define q_feed as "fraction of liquid in the feed stream in molar basis", and calc_plates:412 (Lm = Ln + F + F*(q_feed-1)) and material_balances:725 (Lm = Ln + F*q_feed) both use it as the liquid fraction. So sum(alpha*z/(alpha-phi)) = 1 - q_feed is the correct target, and q_feed = 1 reduces to the previous residual exactly — no behavior change for existing saturated-liquid callers, which tests/test_distillation.py (q_feed=1.0) confirms still passes.
The new residual is well-conditioned across feed qualities. Since the PR materially changes the objective, I swept q from 1.5 to -0.2 on the test fixture: minimize reports success at every point with a residual around 1e-16, and Rmin rises monotonically (0.263 at q=1.5 → 0.700 at q=0.8 → 1.944 at q=0.0 → 2.344 at q=-0.2), which is the physically expected direction as the feed vaporizes. I had considered flagging the unchecked minimize success flag, but it does not appear to be a practical risk on this residual and is pre-existing, so I am not raising it.
No latent second reader of the uninitialized attribute. calc_plates:523 also reads self.min_reflux, but solve_unit assigns it at line 529 before calling calc_plates, so that path is safe.
Findings
One nonblocking finding, posted inline on tests/test_distillation_shortcut_heuristics.py: the assimulo-stub helper is now duplicated from tests/test_cryst_kinetics.py and could be consolidated into tests/conftest.py.
No blocking findings and no open questions.
Scope And Issue Coverage
Closes #72 is the correct link: the issue enumerates exactly two defects and this PR fixes both. #49 (retrieve_results arity) and #59 (dropped gamma_model) are explicitly called out in the issue as related-but-distinct, and this PR correctly leaves them alone. The phi.x[0] and sum → np.sum cleanups are behavior-preserving on the lines already being touched.
The tests target calculate_heuristics and calc_min_reflux directly rather than a full solve_unit column. That is the right scope here — the AttributeError is raised inside calculate_heuristics, which solve_unit calls first, so a heavier end-to-end fixture would add brittleness without covering more of the defect.
Tests Run
All in the repo's pharmapy conda environment, against PR head cde248b:
pytest tests/test_distillation_shortcut_heuristics.py -q→2 passedpytest tests/test_distillation.py -q→1 passedpytest tests/ -q(full suite, assimulo available) →52 passed, 2 pre-existing unrelated warnings- Mutation checks (both reverts) → red, as detailed above
No skipped or deselected tests in the full local run, since assimulo is installed here. CI covers the complementary case: Core tests runs pytest tests/ -m "not assimulo" in an environment without assimulo, which is what actually exercises the new file's stub path — and it passed. No test residue beyond already-gitignored __pycache__.
Merge Readiness
GitHub checks on head cde248b: Core tests SUCCESS, Assimulo integration tests SUCCESS. The head sits directly on origin/master (0 commits behind), so these PR-head results are also the merge-result state; mergeStateStatus is CLEAN and mergeable is MERGEABLE.
There are no blocking findings. This is a COMMENT rather than an APPROVE only because GitHub rejects an approving review from the PR author — this account cannot provide a formal approval. master currently reports no branch protection and reviewDecision is empty, so GitHub is not reporting an approving-review gate on this PR.
|
Addressed the open review feedback on PR #121. Commits pushed:
Changes made:
Tests and checks:
Comments intentionally not addressed:
Remaining risks / gates:
|
Review-resolution verification at
|
| @@ -203,28 +203,57 @@ def kirkbride_correlation(self, material_bce, num_plates, z_feed=None): | |||
|
|
|||
| def calc_min_reflux(self, x_dist, x_bot, dist_flowrate, bot_flowrate, | |||
There was a problem hiding this comment.
Shouldn’t the function name tell you somehow the method used? At the end of the day Underwood’s equations are a heuristic
There was a problem hiding this comment.
Addressed in b6427ba: the Underwood-specific calculation is now calc_underwood_min_reflux(...), while the broader estimate is calculate_shortcut_design(...). The previous names remain as compatibility aliases.
There was a problem hiding this comment.
Addressed in 73e6977: split the Underwood minimum-reflux method from the broader shortcut-design method and retained deprecated aliases.
There was a problem hiding this comment.
Follow-up in c643472: completed the stricter units/docs audit for this branch, normalized touched unit annotations to [...], and updated NumPy-style docs on touched functions. Validation: /home/mlaljee/miniforge3/envs/pharmapy/bin/python /tmp/pharmapy_unit_audit.py PharmaPy/Distillation.py tests/assimulo_helpers.py tests/test_cryst_kinetics.py tests/test_distillation.py tests/test_distillation_shortcut_design.py -> passed; /home/mlaljee/miniforge3/envs/pharmapy/bin/python -m pytest tests/test_distillation.py tests/test_distillation_shortcut_design.py -q -> 4 passed; git diff --cached --check -> passed.
There was a problem hiding this comment.
Addressed in e1a7ce7: kept the method separation documented after the merge-forward update.
| 'with 1.5 * min_reflux') | ||
|
|
||
| reflux = 1.5 * self.min_reflux | ||
| reflux = 1.5 * min_reflux |
There was a problem hiding this comment.
The 1.5 is a magic number. Does it explicitly appear in Underwood’s equation or is it a parameter we should expose to changing and set the default to 1.5?
There was a problem hiding this comment.
Addressed in b6427ba: 1.5 is not part of Underwood's equations. It is now reflux_to_minimum_ratio=1.5 [-], the default shortcut-design multiplier for choosing actual reflux above Rmin, and tests cover changing it.
There was a problem hiding this comment.
Addressed in 73e6977: exposed the default as reflux_to_minimum_ratio=1.5 [-] instead of keeping it as an embedded Underwood constant.
There was a problem hiding this comment.
Follow-up in c643472: completed the stricter units/docs audit for this branch, normalized touched unit annotations to [...], and updated NumPy-style docs on touched functions. Validation: /home/mlaljee/miniforge3/envs/pharmapy/bin/python /tmp/pharmapy_unit_audit.py PharmaPy/Distillation.py tests/assimulo_helpers.py tests/test_cryst_kinetics.py tests/test_distillation.py tests/test_distillation_shortcut_design.py -> passed; /home/mlaljee/miniforge3/envs/pharmapy/bin/python -m pytest tests/test_distillation.py tests/test_distillation_shortcut_design.py -q -> 4 passed; git diff --cached --check -> passed.
There was a problem hiding this comment.
Addressed in e1a7ce7: kept reflux_to_minimum_ratio=1.5 [-] exposed and covered by literal expectations.
|
Addressed the latest review feedback on PR #121. Commits pushed:
Changes made:
Tests and checks:
Comments intentionally not addressed:
Remaining risks / gates:
|
Review-resolution verification at
|
|
Addressed the PR #121 review feedback in commit Changes made:
Tests run:
Post-push CI on |
| raise AssertionError("solve_unit should not retrieve results twice") | ||
|
|
||
| monkeypatch.setattr(column, "calculate_heuristics", fake_calculate_heuristics) | ||
| monkeypatch.setattr( |
There was a problem hiding this comment.
Addressed in 73e6977: the reviewed solve_unit test no longer monkeypatches instance methods. It uses a small subclass to exercise the real orchestration branch and stops before unrelated result retrieval.
There was a problem hiding this comment.
Follow-up in c643472: completed the stricter units/docs audit for this branch, normalized touched unit annotations to [...], and updated NumPy-style docs on touched functions. Validation: /home/mlaljee/miniforge3/envs/pharmapy/bin/python /tmp/pharmapy_unit_audit.py PharmaPy/Distillation.py tests/assimulo_helpers.py tests/test_cryst_kinetics.py tests/test_distillation.py tests/test_distillation_shortcut_design.py -> passed; /home/mlaljee/miniforge3/envs/pharmapy/bin/python -m pytest tests/test_distillation.py tests/test_distillation_shortcut_design.py -q -> 4 passed; git diff --cached --check -> passed.
There was a problem hiding this comment.
Addressed at e1a7ce7: the reviewed tests now use subclass doubles where practical; the remaining alpha-equilibrium stub only isolates the Underwood numeric formula.
|
Pushed Changes:
Checks: docstring audit clean; unit audit clean; targeted tests |
Read-only verification pass at
|
bernalde
left a comment
There was a problem hiding this comment.
Please resolve the merge conflicts and aim to reduce the usage of monkeypatch
# Conflicts: # tests/assimulo_helpers.py
Closes #72.
Summary
min_refluxvalue when a specified positive reflux is belowRmin, avoiding the pre-startupself.min_refluxattribute access.q_feedin the first Underwood equation target:sum(alpha_i z_i / (alpha_i - phi)) = 1 - q.calc_underwood_min_reflux) and the broader shortcut-design estimate (calculate_shortcut_design).calc_min_reflux,calculate_heuristics) as deprecated compatibility aliases that still work and emitDeprecationWarningwith the new method name.reflux_to_minimum_ratio=1.5[-]. This is a shortcut-design operating default, not part of Underwood's equations.Acceptance Criteria
Rminfalls back toreflux_to_minimum_ratio * min_refluxwithout raisingAttributeError; the default remains1.5 * min_reflux.q_feedinstead of assuming saturated-liquid feed.Public API / Compatibility
calc_underwood_min_reflux(...)calculate_shortcut_design(...)reflux_to_minimum_ratio=1.5onDistillationColumn,DynamicDistillation, and_BaseDistillationcalc_min_reflux(...)->calc_underwood_min_reflux(...)calculate_heuristics(...)->calculate_shortcut_design(...)column_startup(time_heuristics=...)->column_startup(time_shortcut_design=...)Units And Dimensional Analysis
alpha,phi, feed/product mole fractions,q_feed,1 - q_feed, reflux ratios, andreflux_to_minimum_ratioare dimensionless[-].dist_flowrate,V_min, andL_minare molar flow rates[mol/s].min_reflux = L_min / Dis dimensionless[-].perc_LKandperc_HKare documented as percentages[%]in both steady-state and dynamic distillation constructor docs.Review Follow-Up Scope
tests/assimulo_helpers.pynow owns a reusableimport_module_with_assimulo_stub(...)helper.tests/test_cryst_kinetics.pyuses that shared helper instead of maintaining its own local Assimulo stub copy.tests/test_distillation_shortcut_design.pyuses the same helper for the distillation Assimulo imports.Related Issues
Branch Hygiene
masterfix/issue-72-distillation-underwoodorigin/masterat0f89b71999daf9b765ff80255a61ed73e9e9f0e535feb029f159b444b6e3988848d8584cee88b96bPharmaPy/Distillation.py,tests/assimulo_helpers.py,tests/test_cryst_kinetics.py, ortests/test_distillation_shortcut_design.py.Local Checks
/home/mlaljee/miniforge3/envs/pharmapy/bin/python -m pytest tests/test_distillation_shortcut_heuristics.py -qfailed with the issue-expectedAttributeErrorandRmin = 0.527777...instead of0.7./home/mlaljee/miniforge3/envs/pharmapy/bin/python -m pytest tests/test_distillation_shortcut_design.py tests/test_distillation.py -q->4 passed/home/mlaljee/miniforge3/envs/pharmapy/bin/python -m pytest tests/ -m "not assimulo" -q->43 passed, 10 deselected/home/mlaljee/miniforge3/envs/pharmapy/bin/python -m pytest --collect-only -q->53 tests collected/home/mlaljee/miniforge3/envs/pharmapy/bin/python -m pytest tests/ -q->53 passedgit diff --checkgit diff --cached --check35feb02:Core testsSUCCESS,Assimulo integration testsSUCCESSReview Follow-Up Metadata
35feb02to existing branchfix/issue-72-distillation-underwood.Distillation.pyconstructors, helpers, the nested Underwood residual, steady/dynamic solve paths, result retrieval, and plotting.11 passed; full suite75 passed, 2 warnings; diff check passed.