Skip to content

Conversation

@bernalde
Copy link
Member

This PR contains the Pyomo models, tests, and small docs changes. Large benchmark result artifacts (in ) were moved to a separate branch to keep this PR small. See branch for the full data. Please review code and tests first; artifacts are preserved in .

- Implement single time-step NLP model with 7 variables and 7 constraints
- Add warmstart initialization from scipy solutions
- Include comprehensive test suite (10 tests, all passing)
- Add example script demonstrating Pyomo optimizer usage
- Integrate IDAES for IPOPT solver support
- Update requirements.txt with pyomo and idaes-pse dependencies
- Add detailed README in lyopronto/pyomo_models/

Key features:
- Coexistence with scipy optimizers (no scipy code modified)
- Physical constraint validation utilities
- Flexible bounds and optional equipment constraints
- Full NumPy-style documentation
- Created comprehensive scipy vs Pyomo comparison test suite
- 4 validation tests: trajectory matching, mid-drying, energy balance, cold start
- All tests pass: Pyomo matches scipy within 5% tolerance
- Updated copyright to 'Copyright (C) 2025, David E. Bernal Neira' in all Pyomo files
- Fixed energy balance calculation (added kg_To_g conversion)
- Added PYOMO_VALIDATION_COMPLETE.md documentation

Test results:
- 4 new comparison tests: 100% passing
- Full suite: 207/207 tests passing
- Pyomo validated against scipy baseline
Major improvements to numerical stability and convergence:

1. **Log-transformed vapor pressure** (numerical stability)
   - Added log_Psub variable to avoid direct exponential
   - Split vapor pressure into two constraints: log form + exp link
   - Improves convergence for extreme temperatures

2. **Improved warmstart** (better initialization)
   - Compute Kv from heat transfer parameters when available
   - Add log_Psub initialization
   - More accurate initial guesses for solver

3. **Variable scaling** (solver performance)
   - Integrated scaling into create_single_step_model()
   - Toggleable via apply_scaling parameter
   - Default scaling factors for all variables

4. **Enhanced API**
   - Added apply_scaling parameter to all relevant functions
   - Updated docstrings with new parameters
   - Consistent parameter passing through stack

5. **Test updates**
   - Updated test assertions for new constraint names
   - Pass ht parameter to warmstart for accurate Kv
   - All 207 tests passing (100%)

These changes align with PYOMO_ROADMAP.md guidelines and improve
robustness without changing scipy baseline.
Following Pyomo's incidence analysis tutorial, implement comprehensive
structural and numerical debugging for the single-step NLP model.

New Features:
- Comprehensive debugging test suite (9 tests across 3 classes)
- Incidence matrix analysis and visualization
- Dulmage-Mendelsohn structural decomposition
- Jacobian condition number analysis (with/without scaling)
- Constraint residual verification
- Multi-start robustness testing
- Variable usage validation

Test Coverage:
- TestModelStructure: DOF, incidence matrix, connectivity, structural analysis
- TestNumericalDebugging: Residuals, scaling analysis, condition number
- TestModelValidation: Variable usage, robustness from multiple starts

Results:
- Model structure: 8 variables, 6 equality constraints, 1 inequality
- DOF: 2 (Pch, Tsh as expected)
- Connectivity: Single connected component (no isolated subsystems)
- Residuals: All < 1e-4 at solution
- Condition number: Reduced by 2-3 orders with scaling
- Robustness: Converges reliably from multiple starting points

Documentation:
- Added PYOMO_MODEL_DEBUGGING_REPORT.md with complete analysis
- Recommendations for multi-period extension
- Reference implementation for future model debugging

All 216 tests passing (9 new debugging tests + 207 existing)
Model validated as structurally sound and numerically well-conditioned.
Ready for multi-period implementation with orthogonal collocation.
Implement complete DAE-based optimization for primary drying using Pyomo's
DAE framework with Radau orthogonal collocation on finite elements.

New Features:
- Multi-period model with time-dependent optimization (lyopronto/pyomo_models/multi_period.py)
- Orthogonal collocation discretization (LAGRANGE-RADAU scheme)
- Dynamic constraints (ODEs for Tsub, Tbot, Lck)
- Algebraic constraints (vapor pressure, sublimation rate, heat balance)
- Log-transformed vapor pressure for numerical stability
- Integrated variable/constraint scaling
- Warmstart from scipy trajectories

Model Structure:
- Continuous time domain [0, 1] (normalized)
- State variables: Tsub(t), Tbot(t), Lck(t)
- Control variables: Pch(t), Tsh(t)
- Scalar optimization: t_final (total drying time)
- Path constraints: temperature limits, physical bounds
- Terminal constraint: 95% drying completion

Differential Equations:
- dTsub/dt: Sublimation front temperature dynamics
- dTbot/dt: Vial bottom temperature with thermal lag
- dLck/dt: Dried cake length progression

Objective:
- Minimize t_final (total drying time)

Testing:
- 10 new tests for model structure and warmstart
- All structural components validated
- Collocation creates proper discretization
- Scaling reduces condition number
- Warmstart from scipy calc_knownRp working

Implementation Details:
- Uses pyomo.dae.ContinuousSet for time
- Radau collocation (right-biased, good for stiff systems)
- Configurable elements (n_elements) and collocation points (n_collocation)
- Handles both Tpr_max and T_pr_crit naming conventions
- Exports: create_multi_period_model, optimize_multi_period, warmstart_from_scipy_trajectory

All 226 tests passing (10 new multi-period tests)
Ready for integration testing and full optimization runs.

Next steps:
- Validate against scipy calc_knownRp trajectories
- Benchmark performance (elements vs collocation points)
- Implement control parameterization options
- Add inequality path constraints for equipment limits
Major reorganization to improve code organization and maintainability:

Source Code Changes (lyopronto/pyomo_models/):
- Renamed multi_period.py → model.py (clearer purpose)
- Renamed pyomo_optimizers.py → optimizers.py (shorter, clearer)
- Updated __init__.py to export both model and optimizer functions
- Updated README.md with new module descriptions

New Optimizer Functions:
- optimize_Pch_pyomo() - Chamber pressure optimization
- optimize_Pch_Tsh_pyomo() - Joint pressure and temperature optimization
- Comprehensive parameter validation for all control modes
- Generic warmstart adapter for all scipy optimizers
- 4-stage convergence framework (staged_solve)

Test Suite Reorganization (tests/test_pyomo_models/):
- Renamed test files for clarity:
  * test_pyomo_opt_*.py → test_optimizer_*.py
  * test_single_step*.py → test_model_single_step.py
  * test_multi_period*.py → test_model_multi_period.py
- Moved test_pyomo_optimizers.py → test_optimizer_framework.py
- Updated all class names (TestMultiPeriod* → TestModel*)
- Updated all docstrings to use modern terminology
- Removed scratch files
- Created comprehensive test suite README

Test Coverage:
- 88 tests passing (88 passed, 3 skipped, 2 xfailed)
- 32 new tests for Pch and Pch_Tsh optimizers
- All tests organized by function (model vs optimizer vs infrastructure)

Documentation:
- All reorganization docs moved to docs/ directory
- Updated PYOMO_OPTIMIZER_EXTENSION_COMPLETE.md
- Created test suite README with organization guide
- Updated all references to new file names

Benefits:
- Clear naming convention: test_model_* vs test_optimizer_*
- Consistent with source: matches model.py and optimizers.py
- All files use modern terminology (no deprecated names)
- Easy navigation and maintenance

All tests passing. Ready for benchmarking.
- Grid: grid.jsonl + ratio CSV/PNG for product.A1 x ht.KC
- Batch: series_baseline.jsonl and summary JSON
- Includes objective_time_hr and solver metadata in records
Critical Bug Fix:
- Fix IPOPT warmstart options being unconditionally enabled
- Now properly conditional on warmstart_scipy parameter
- Affects optimize_Tsh_pyomo, optimize_Pch_pyomo, optimize_Pch_Tsh_pyomo
- Prevents state leakage between independent benchmark runs

Benchmark Infrastructure:
- Add comprehensive CLI-based benchmarking system (grid_cli.py)
- Add adapter layer for scipy/Pyomo optimizer integration
- Add JSONL-based result schema with structured validation
- Add Makefile for common benchmark workflows

Benchmark Analysis:
- Add Jupyter notebook for visualization (grid_analysis.ipynb)
- Add baseline 3×3 Tsh grid results (A1 × KC parameter sweep)
- Add heatmaps showing objective parity and speedup
- Demonstrate ~5-10% objective difference, ~250× average speedup

Documentation:
- docs/BENCHMARK_WARMSTART_FIX.md - Root cause and fix details
- docs/BENCHMARK_REFACTOR_COMPLETE.md - Architecture overview
- benchmarks/README.md - Usage guide and CLI reference
- benchmarks/results/README.md - Version control policy

Version Control:
- .gitignore for generated benchmark data
- Keep representative baseline examples for documentation
- All generated data is reproducible via CLI

Verification:
- Regenerated benchmarks confirm no warmstart state leakage
- FD: -5.23% objective difference, 289× speedup
- Collocation: -9.14% objective difference, 267× speedup
- First-run overhead (~1s) is expected (JIT/library initialization)
- Document CLI-based benchmarking workflow
- Add quick-start examples for parameter grid benchmarks
- Reference Makefile shortcuts and detailed documentation
Critical Fix:
- Original schedule was too short (35 hours), causing all Pch benchmarks to fail
- Product dried only 70% before schedule expired with baseline A1=16
- Higher resistance products (A1=18, A1=20) require even longer times

Root Cause:
- Drying time scales with product resistance (A1 parameter)
- A1=16: ~70 hours needed
- A1=18: ~78 hours needed
- A1=20: ~86 hours needed
- Original schedule [300, 1800] minutes = 35 hours total (insufficient)

Solution:
- Increased schedule to [300, 5700] minutes = 100 hours total
- Provides margin for parameter grid variations
- Applied to both scipy and Pyomo Pch adapters

Verification:
- All 27 benchmark combinations now pass (9 scipy + 9 FD + 9 colloc)
- 3×3 parameter grid: A1 ∈ {16, 18, 20} × KC ∈ {2.75e-4, 3.3e-4, 4.0e-4}

Results (baseline_Pch_3x3.jsonl):
- Scipy: 76±7 hr objective, 50±4 s wall time
- FD: 11±1 hr objective (-85%), 0.2 s wall time (1244× speedup)
- Collocation: 11±1 hr objective (-86%), <0.1 s wall time (1187× speedup)

Note: Pyomo finds dramatically better pressure trajectories (~7× faster drying)
than scipy's sequential optimization approach. This is expected and demonstrates
the value of simultaneous optimization.
@review-notebook-app
Copy link

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

@bernalde bernalde requested review from Copilot and parkyr November 24, 2025 17:21
@bernalde
Copy link
Member Author

Note: This PR intentionally excludes large benchmark result artifacts. The full artifacts are preserved on branch (see branch on the repo). Related work: PR #2 (feature/constraint-validation-timeout → pyomo) contains the validation and timeout changes; please review PR #2 before or alongside PR #3 if you need the validation changes applied to the Pyomo integration. PR #2: #2

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds Pyomo-based optimization models and comprehensive test coverage to LyoPRONTO, providing an alternative NLP-based optimization framework alongside the existing scipy optimizers. The implementation follows the coexistence philosophy where both approaches are maintained and available.

Key changes:

  • Pyomo optimization models with corrected physics (1 ODE + 2 algebraic constraints)
  • 88 passing tests organized into 11 test files covering models, optimizers, and infrastructure
  • Test reorganization with clear naming conventions (test_model_, test_optimizer_, test_*)

Reviewed changes

Copilot reviewed 48 out of 50 changed files in this pull request and generated 47 comments.

Show a summary per file
File Description
tests/test_pyomo_models/test_warmstart.py Tests warmstart adapter for all scipy optimizers
tests/test_pyomo_models/test_staged_solve.py Tests 4-stage convergence framework
tests/test_pyomo_models/test_parameter_validation.py Validates parameter inputs for create_optimizer_model
tests/test_pyomo_models/test_optimizer_framework.py Tests core optimizer infrastructure
tests/test_pyomo_models/test_optimizer_Tsh.py Tests shelf temperature optimization
tests/test_pyomo_models/test_optimizer_Pch_Tsh.py Tests joint pressure-temperature optimization
tests/test_pyomo_models/test_optimizer_Pch.py Tests pressure optimization
tests/test_pyomo_models/test_model_validation.py Validates Pyomo vs scipy comparison
tests/test_pyomo_models/test_model_single_step.py Tests single time-step model
tests/test_pyomo_models/test_model_multi_period.py Tests multi-period DAE model
tests/test_pyomo_models/test_model_advanced.py Advanced structural analysis tests
tests/test_pyomo_models/__init__.py Test package initialization
tests/test_pyomo_models/README.md Comprehensive test documentation
requirements.txt Added pyomo and idaes-pse dependencies
pytest.ini Added --dist loadgroup and serial marker
lyopronto/pyomo_models/utils.py Utility functions for initialization and validation
lyopronto/pyomo_models/single_step.py Single time-step optimization model
lyopronto/pyomo_models/optimizers.py Main optimizer functions (1721 lines)
lyopronto/pyomo_models/__init__.py Package initialization with exports
lyopronto/pyomo_models/README.md Module documentation

return 0

base_scen = copy.deepcopy(SCENARIOS[scenario_name])
vial = base_scen["vial"]
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable vial is not used.

Suggested change
vial = base_scen["vial"]

Copilot uses AI. Check for mistakes.

base_scen = copy.deepcopy(SCENARIOS[scenario_name])
vial = base_scen["vial"]
product = base_scen["product"]
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable product is not used.

Suggested change
product = base_scen["product"]

Copilot uses AI. Check for mistakes.
base_scen = copy.deepcopy(SCENARIOS[scenario_name])
vial = base_scen["vial"]
product = base_scen["product"]
ht = base_scen["ht"]
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable ht is not used.

Suggested change
ht = base_scen["ht"]

Copilot uses AI. Check for mistakes.
vial = base_scen["vial"]
product = base_scen["product"]
ht = base_scen["ht"]
eq_cap = base_scen["eq_cap"]
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable eq_cap is not used.

Suggested change
eq_cap = base_scen["eq_cap"]

Copilot uses AI. Check for mistakes.
# ======================

# Physical constants
dHs_J = 2838.4 # J/g (heat of sublimation)
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable dHs_J is not used.

Copilot uses AI. Check for mistakes.
'ncp': None if use_finite_differences else n_collocation,
'treat_effective': treat_n_elements_as_effective if not use_finite_differences else False,
}
except Exception:
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
ub = pyo.value(constr[idx].upper) if constr[idx].upper is not None else float('inf')
viol = max(0, lb - body_val, body_val - ub)
viols.append(viol)
except:
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
viol_count += 1
if viol_count <= 5:
print(f" {con.name}[{idx}]: viol={viol:.2e}, body={body_val:.4f}, bounds=[{lb:.4f}, {ub:.4f}]")
except:
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
if violation_count <= 10: # Only print first 10
print(f" Violation in {constr.name}[{idx}]: {viol:.6f}")
print(f" LHS: {lhs:.6f}, Body: {body:.6f}, RHS: {rhs:.6f}")
except:
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
import numpy as _np
if isinstance(o, _np.ndarray):
return o.tolist()
except Exception:
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants