From c60cf5edd4c50caa07d24bea0c8ba7d9c390e3f2 Mon Sep 17 00:00:00 2001 From: "David E. Bernal Neira" Date: Wed, 4 Feb 2026 17:48:19 -0500 Subject: [PATCH 1/3] Add Pyomo optional dependencies and module structure This PR adds the foundation for Pyomo integration: pyproject.toml changes: - Add [optimization] extra with pyomo>=6.7.0 and idaes-pse>=2.9.0 - These are optional dependencies, scipy-based code works without them Module structure: - lyopronto/pyomo_models/__init__.py: Placeholder with documentation - tests/test_pyomo_models/__init__.py: Test directory placeholder Installation: pip install .[optimization] # For Pyomo support pip install . # Standard scipy-only installation This enables users to opt-in to Pyomo optimization without affecting existing scipy-based workflows. --- lyopronto/pyomo_models/__init__.py | 49 ++++++++++++++++++ pyproject.toml | 79 ++++++++++++++++++++++++++++- tests/test_pyomo_models/__init__.py | 2 + 3 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 lyopronto/pyomo_models/__init__.py create mode 100644 tests/test_pyomo_models/__init__.py diff --git a/lyopronto/pyomo_models/__init__.py b/lyopronto/pyomo_models/__init__.py new file mode 100644 index 0000000..39ca25f --- /dev/null +++ b/lyopronto/pyomo_models/__init__.py @@ -0,0 +1,49 @@ +# Copyright 2019-2025, Gayathri Shivkumar, Petr S. Kazarin, Alina A. Alexeenko +# Maintained by Isaac S. Wheeler +# +# This file is part of LyoPRONTO. +# LyoPRONTO is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +""" +Pyomo-based optimization models for lyophilization process. + +This module provides Pyomo-based formulations for lyophilization optimization, +complementing the existing scipy-based optimizers. The Pyomo models offer: + +- Mathematical programming formulations with explicit constraints +- Support for IPOPT and other NLP solvers +- Multi-period trajectory optimization +- Orthogonal collocation discretization + +Usage: + # Install optimization dependencies first: + # pip install .[optimization] + + from lyopronto.pyomo_models import optimize_Tsh_pyomo + + result = optimize_Tsh_pyomo(vial, product, ht, Pchamber, Tshelf, dt, eq_cap, nVial) + +The optimizers maintain API compatibility with scipy-based versions: +- optimize_Tsh_pyomo: Optimize shelf temperature trajectory +- optimize_Pch_pyomo: Optimize chamber pressure trajectory +- optimize_Pch_Tsh_pyomo: Optimize both pressure and temperature + +Note: Requires IPOPT solver. Install via: idaes get-extensions --extra petsc +""" + +# Placeholder - actual implementations will be added in subsequent PRs +__all__ = [] + +# Version will be set when implementations are added +__version__ = "0.1.0-dev" diff --git a/pyproject.toml b/pyproject.toml index b9428a7..760a2a6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,10 @@ classifiers = [ ] [project.optional-dependencies] +optimization = [ + "pyomo>=6.7.0", + "idaes-pse>=2.9.0", +] dev = [ "pytest>=7.4.0", "pytest-mock>=3", @@ -58,7 +62,9 @@ docs = [ "mike>=2.1", "mkdocs-ipynb>=0.1.1", ] - +all = [ + "lyopronto[optimization,dev,docs]", +] [project.urls] Homepage = "http://lyopronto.geddes.rcac.purdue.edu" @@ -88,4 +94,75 @@ markers = [ "fast: Quick tests that run in under 1 second", "notebook: Tests that execute Jupyter notebooks for documentation", "main: Tests that cover functionality previously included in main.py", + "pyomo: Tests requiring Pyomo and IPOPT solver (deselect with '-m not pyomo')", + "serial: Tests that must run serially (not in parallel)", +] + +[tool.mypy] +python_version = "3.8" +warn_return_any = true +warn_unused_configs = true +disallow_untyped_defs = false +ignore_missing_imports = true +# Exclude pyomo_models from type checking - Pyomo uses dynamic attribute +# assignment (model.Var = pyo.Var(...)) which generates many false-positive +# mypy errors. The code is tested via pytest instead. +exclude = [ + "lyopronto/pyomo_models/", +] + +[tool.ruff] +# Target Python 3.8+ (matches project.requires-python) +target-version = "py38" +line-length = 88 # Black-compatible +src = ["lyopronto", "tests", "benchmarks"] + +[tool.ruff.lint] +# Enable recommended rules + some extras +select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # Pyflakes + "I", # isort (import sorting) + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "UP", # pyupgrade + "SIM", # flake8-simplify +] +ignore = [ + "E501", # Line too long (handled by formatter) + "B008", # Do not perform function calls in argument defaults + "SIM108", # Use ternary operator (sometimes less readable) ] + +# Allow autofix for all enabled rules +fixable = ["ALL"] +unfixable = [] + +[tool.ruff.lint.per-file-ignores] +# Tests can use assert and have longer lines +"tests/**/*.py" = ["S101", "E501"] +# Pyomo test files have conditional imports at module level (intentional pattern) +"tests/test_pyomo_models/*.py" = ["E402", "E722", "F401"] +# Pyomo models use closures in loops for rule functions (Pyomo pattern) +"lyopronto/pyomo_models/*.py" = ["B023", "B007", "E722", "SIM102", "F811"] +# Legacy scipy optimizers use closures in loops (legacy pattern) +"lyopronto/opt_*.py" = ["B023"] +# Package __init__ re-exports modules for public API +"lyopronto/__init__.py" = ["F401"] +# Benchmarks may have more complex imports +"benchmarks/**/*.py" = ["E501"] +# Legacy examples may have style issues +"examples/legacy/**/*.py" = ["ALL"] +# Notebooks have different import patterns +"**/*.ipynb" = ["E402", "B007"] +# Test design space has complex warning handling +"tests/test_design_space.py" = ["SIM117"] + +[tool.ruff.format] +# Use double quotes (Black-compatible) +quote-style = "double" +# Indent with spaces +indent-style = "space" +# Unix line endings +line-ending = "lf" diff --git a/tests/test_pyomo_models/__init__.py b/tests/test_pyomo_models/__init__.py new file mode 100644 index 0000000..abbbe44 --- /dev/null +++ b/tests/test_pyomo_models/__init__.py @@ -0,0 +1,2 @@ +# Copyright 2019-2025, SECQUOIA +# Tests for Pyomo-based optimization models. From e45351651f77a9df17c82c139b6fa913424ea5a6 Mon Sep 17 00:00:00 2001 From: parkyr Date: Mon, 27 Apr 2026 14:30:02 -0400 Subject: [PATCH 2/3] add dependency installation instruction for conda environments Co-authored-by: Copilot --- lyopronto/pyomo_models/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lyopronto/pyomo_models/__init__.py b/lyopronto/pyomo_models/__init__.py index 39ca25f..924846b 100644 --- a/lyopronto/pyomo_models/__init__.py +++ b/lyopronto/pyomo_models/__init__.py @@ -29,7 +29,11 @@ Usage: # Install optimization dependencies first: # pip install .[optimization] - + + # If using conda environment, install via: + # conda activate [env_name] + # python -m pip install ".[optimization]" + from lyopronto.pyomo_models import optimize_Tsh_pyomo result = optimize_Tsh_pyomo(vial, product, ht, Pchamber, Tshelf, dt, eq_cap, nVial) From 28d0898bc739d60813b9ccdc282dc93aa0487696 Mon Sep 17 00:00:00 2001 From: "David E. Bernal Neira" Date: Thu, 7 May 2026 17:25:47 -0400 Subject: [PATCH 3/3] Address Pyomo dependency review comments --- lyopronto/pyomo_models/__init__.py | 28 +++++++++++++------------ pyproject.toml | 26 ++--------------------- tests/test_pyomo_models/__init__.py | 2 -- tests/test_pyomo_models/test_init.py | 31 ++++++++++++++++++++++++++++ 4 files changed, 48 insertions(+), 39 deletions(-) delete mode 100644 tests/test_pyomo_models/__init__.py create mode 100644 tests/test_pyomo_models/test_init.py diff --git a/lyopronto/pyomo_models/__init__.py b/lyopronto/pyomo_models/__init__.py index 924846b..705a703 100644 --- a/lyopronto/pyomo_models/__init__.py +++ b/lyopronto/pyomo_models/__init__.py @@ -1,5 +1,4 @@ -# Copyright 2019-2025, Gayathri Shivkumar, Petr S. Kazarin, Alina A. Alexeenko -# Maintained by Isaac S. Wheeler +# Copyright (C) 2026, SECQUOIA # # This file is part of LyoPRONTO. # LyoPRONTO is free software: you can redistribute it and/or modify @@ -15,8 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -""" -Pyomo-based optimization models for lyophilization process. +"""Pyomo-based optimization models for lyophilization processes. This module provides Pyomo-based formulations for lyophilization optimization, complementing the existing scipy-based optimizers. The Pyomo models offer: @@ -34,20 +32,24 @@ # conda activate [env_name] # python -m pip install ".[optimization]" - from lyopronto.pyomo_models import optimize_Tsh_pyomo - - result = optimize_Tsh_pyomo(vial, product, ht, Pchamber, Tshelf, dt, eq_cap, nVial) + from lyopronto.pyomo_models import PYOMO_AVAILABLE -The optimizers maintain API compatibility with scipy-based versions: -- optimize_Tsh_pyomo: Optimize shelf temperature trajectory -- optimize_Pch_pyomo: Optimize chamber pressure trajectory -- optimize_Pch_Tsh_pyomo: Optimize both pressure and temperature +Actual optimizer implementations will be added in subsequent PRs. Note: Requires IPOPT solver. Install via: idaes get-extensions --extra petsc """ -# Placeholder - actual implementations will be added in subsequent PRs -__all__ = [] +from importlib.util import find_spec + + +def _is_pyomo_available() -> bool: + """Return whether the optional Pyomo dependency is importable.""" + return find_spec("pyomo") is not None + + +PYOMO_AVAILABLE = _is_pyomo_available() + +__all__ = ["PYOMO_AVAILABLE"] # Version will be set when implementations are added __version__ = "0.1.0-dev" diff --git a/pyproject.toml b/pyproject.toml index 760a2a6..7f0465e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,8 @@ classifiers = [ [project.optional-dependencies] optimization = [ "pyomo>=6.7.0", - "idaes-pse>=2.9.0", + "idaes-pse>=2.5,<2.6; python_version < '3.10'", + "idaes-pse>=2.9.0; python_version >= '3.10'", ] dev = [ "pytest>=7.4.0", @@ -75,29 +76,6 @@ Documentation = "https://lyohub.github.io/LyoPRONTO/" [tool.setuptools.packages.find] include = ["lyopronto*"] -[tool.pytest.ini_options] -pythonpath = "." -testpaths = ["tests"] -python_files = ["test_*.py"] -python_classes = ["Test*"] -python_functions = ["test_*"] -addopts = [ - "-v", - "--strict-markers", - "--tb=short", - "--maxfail=5", - "--cov=lyopronto", - "--cov-report=term-missing", -] -markers = [ - "slow: Tests that take a long time to run", - "fast: Quick tests that run in under 1 second", - "notebook: Tests that execute Jupyter notebooks for documentation", - "main: Tests that cover functionality previously included in main.py", - "pyomo: Tests requiring Pyomo and IPOPT solver (deselect with '-m not pyomo')", - "serial: Tests that must run serially (not in parallel)", -] - [tool.mypy] python_version = "3.8" warn_return_any = true diff --git a/tests/test_pyomo_models/__init__.py b/tests/test_pyomo_models/__init__.py deleted file mode 100644 index abbbe44..0000000 --- a/tests/test_pyomo_models/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# Copyright 2019-2025, SECQUOIA -# Tests for Pyomo-based optimization models. diff --git a/tests/test_pyomo_models/test_init.py b/tests/test_pyomo_models/test_init.py new file mode 100644 index 0000000..9e388fe --- /dev/null +++ b/tests/test_pyomo_models/test_init.py @@ -0,0 +1,31 @@ +# Copyright (C) 2026, SECQUOIA + +"""Tests for the Pyomo models package initializer.""" + +from importlib.machinery import ModuleSpec + +from lyopronto import pyomo_models + + +def test_pyomo_available_is_bool(): + assert isinstance(pyomo_models.PYOMO_AVAILABLE, bool) + + +def test_pyomo_available_exported(): + assert pyomo_models.__all__ == ["PYOMO_AVAILABLE"] + + +def test_pyomo_available_false_when_pyomo_missing(monkeypatch): + monkeypatch.setattr(pyomo_models, "find_spec", lambda name: None) + + assert pyomo_models._is_pyomo_available() is False + + +def test_pyomo_available_true_when_pyomo_present(monkeypatch): + monkeypatch.setattr( + pyomo_models, + "find_spec", + lambda name: ModuleSpec(name="pyomo", loader=None), + ) + + assert pyomo_models._is_pyomo_available() is True