Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 58 additions & 14 deletions .github/workflows/pr-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ name: PR Tests
# - Draft PRs: Fast tests only (no coverage) for rapid iteration
# - Ready for Review: Full tests with coverage for quality assurance
# - All subsequent commits: Continue with full coverage
# - Pyomo tests run separately and don't block PRs

on:
pull_request:
branches: [ main ]
types: [ opened, synchronize, reopened, ready_for_review, converted_to_draft ]

jobs:
test:
test-scipy:
name: SciPy Tests
runs-on: ubuntu-latest

steps:
Expand All @@ -26,7 +28,7 @@ jobs:
- name: Determine test mode
id: mode
run: |
if [ "${{ github.event.pull_request.draft }}" ]; then
if [ "${{ github.event.pull_request.draft }}" = "true" ]; then
echo "mode=fast" >> $GITHUB_OUTPUT
else
echo "mode=full" >> $GITHUB_OUTPUT
Expand All @@ -43,28 +45,70 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip setuptools wheel
pip install .
pip install .[dev]
pip install -e . --no-build-isolation

pip install -e ".[dev]"

- name: Run tests
# Currently this conditional branching doesn't actually do anything,
# since pyproject.toml adds these coverage arguments to the testing anyway
run: |
if [ "${{ steps.mode.outputs.mode }}" == "fast" ]; then
echo "Skipping notebook tests (marked with @pytest.mark.notebook) - these run separately"
pytest tests/ -n auto -v -m "not notebook" --cov=lyopronto --cov-report=term-missing
echo "Draft PR: Skipping notebook and pyomo tests for fast feedback"
pytest tests/ -n auto -v -m "not notebook and not pyomo"
else
echo "⚡ Skipping notebook tests (marked with @pytest.mark.slow), not running coverage"
pytest tests/ -n auto -v -m "not notebook"
echo "Ready PR: Running full scipy test suite (excluding notebook and pyomo)"
pytest tests/ -n auto -v -m "not notebook and not pyomo" --cov=lyopronto --cov-report=xml:coverage.xml
fi

- name: Upload coverage (if run)
if: steps.mode.outputs.coverage == 'true'
- name: Upload coverage
if: steps.mode.outputs.mode == 'full'
uses: codecov/codecov-action@v4
with:
file: ./coverage.xml
flags: pr-tests
name: pr-coverage
fail_ci_if_error: false
token: ${{ secrets.CODECOV_TOKEN }}

test-pyomo:
name: Pyomo Tests (Optional)
if: ${{ github.event.pull_request.draft == false }}
runs-on: ubuntu-latest
continue-on-error: true # Pyomo tests are brittle, don't block PRs

steps:
- uses: actions/checkout@v4

- name: Read CI version config
id: versions
uses: mikefarah/yq@v4.44.1
with:
cmd: yq eval '.python-version' .github/ci-config/ci-versions.yml

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ steps.versions.outputs.result }}
cache: 'pip'
cache-dependency-path: |
pyproject.toml

- name: Install dependencies with optimization stack
run: |
python -m pip install --upgrade pip setuptools wheel
pip install -e ".[dev]"
pip install pyomo idaes-pse

- name: Install IPOPT solver via IDAES
run: |
echo "Installing IPOPT solver via IDAES extensions"
idaes get-extensions --extra petsc

- name: Run Pyomo tests
run: |
echo "Running Pyomo test suite (continue-on-error enabled)"
# Exit code 5 = no tests collected (pyomo tests added in later PRs)
pytest tests/ -n auto -v -m "pyomo" --cov=lyopronto --cov-report=term-missing || { rc=$?; [ $rc -eq 5 ] && echo "No pyomo-marked tests found yet" && exit 0; exit $rc; }

- name: Pyomo Test Summary
if: always()
run: |
echo "Pyomo tests completed"
echo "Failures here don't block PR merge"
51 changes: 39 additions & 12 deletions .github/workflows/slow-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ on:
options:
- 'true'
- 'false'
include_pyomo:
description: 'Include Pyomo tests (requires IPOPT)'
required: false
default: 'true'
type: choice
options:
- 'true'
- 'false'

jobs:
slow-tests:
Expand All @@ -41,20 +49,39 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip setuptools wheel
pip install .
pip install .[dev]
pip install -e . --no-build-isolation
pip install -e ".[dev]"
if [ "${{ inputs.include_pyomo }}" == "true" ]; then
pip install pyomo idaes-pse
fi

- name: Install IPOPT solver via IDAES (if Pyomo enabled)
if: inputs.include_pyomo == 'true'
run: |
echo "Installing IPOPT solver via IDAES extensions"
idaes get-extensions --extra petsc

- name: Run slow tests
env:
RUN_SLOW_TESTS: "1"
run: |
# Helper: allow exit code 5 (no tests collected) to pass gracefully
run_pytest() { "$@" || { rc=$?; [ $rc -eq 5 ] && echo "No matching tests found (exit 5 is OK)" && return 0; return $rc; }; }
if [ "${{ inputs.run_all }}" == "true" ]; then
echo "🔍 Running ALL tests (including slow optimization tests)"
echo "⏱️ This may take 30-40 minutes on CI"
pytest tests/ -n auto -v --cov=lyopronto --cov-report=xml --cov-report=term-missing
echo "Running ALL tests (including slow optimization tests)"
echo "This may take 30-40 minutes on CI"
if [ "${{ inputs.include_pyomo }}" == "true" ]; then
run_pytest pytest tests/ -n auto -v --cov=lyopronto --cov-report=xml --cov-report=term-missing
else
run_pytest pytest tests/ -n auto -v -m "not pyomo" --cov=lyopronto --cov-report=xml --cov-report=term-missing
fi
else
echo "🐌 Running ONLY slow tests (marked with @pytest.mark.slow)"
echo "⏱️ This focuses on optimization tests that take minutes"
pytest tests/ -n auto -v -m "slow" --cov=lyopronto --cov-report=xml --cov-report=term-missing
echo "Running ONLY slow tests (marked with @pytest.mark.slow)"
echo "This focuses on optimization tests that take minutes"
if [ "${{ inputs.include_pyomo }}" == "true" ]; then
run_pytest pytest tests/ -n auto -v -m "slow" --cov=lyopronto --cov-report=xml --cov-report=term-missing
else
run_pytest pytest tests/ -n auto -v -m "slow and not pyomo" --cov=lyopronto --cov-report=xml --cov-report=term-missing
fi
fi

- name: Upload coverage
Expand All @@ -70,8 +97,8 @@ jobs:
if: always()
run: |
if [ "${{ inputs.run_all }}" == "true" ]; then
echo "Complete test suite finished"
echo "Complete test suite finished"
else
echo "🐌 Slow tests completed"
echo "Slow tests completed"
fi
echo "📊 Coverage uploaded to Codecov"
echo "Coverage uploaded to Codecov"
79 changes: 65 additions & 14 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
name: Main Branch Tests

# Full tests with coverage for main branch
# (PRs are handled by pr-tests.yml)
# Runs both scipy tests and Pyomo tests in separate jobs

on:
push:
branches: [ main, dev-pyomo ]
branches: [ main ]

jobs:
test:
test-scipy:
name: SciPy Tests
runs-on: ubuntu-latest

steps:
Expand All @@ -29,27 +30,77 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip setuptools wheel
pip install .
pip install .[dev]
pip install -e . --no-build-isolation
pip install -e ".[dev]"

- name: Run ALL tests with pytest and coverage (including slow tests)
- name: Run SciPy tests (excluding Pyomo tests)
run: |
echo "🔍 Running complete test suite including slow tests"
echo "⏱️ This may take 30-40 minutes on CI (includes optimization tests)"
pytest tests/ -n auto -v --cov=lyopronto --cov-report=xml --cov-report=term-missing
echo "Running SciPy test suite (excluding Pyomo tests)"
pytest tests/ -n auto -v -m "not pyomo" --cov=lyopronto --cov-report=xml --cov-report=term-missing

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
file: ./coverage.xml
flags: unittests
name: codecov-umbrella
flags: scipy-tests
name: scipy-coverage
fail_ci_if_error: false
token: ${{ secrets.CODECOV_TOKEN }}

- name: Coverage Summary
if: always()
run: |
echo "✅ Full coverage tests completed for main branch"
echo "📊 Coverage metrics updated in Codecov"
echo "SciPy tests completed for main branch"
echo "Coverage metrics updated in Codecov"

test-pyomo:
name: Pyomo Tests
runs-on: ubuntu-latest
continue-on-error: true # Pyomo tests are brittle, don't block merges

steps:
- uses: actions/checkout@v4
- name: Read CI version config
id: versions
uses: mikefarah/yq@v4.44.1
with:
cmd: yq eval '.python-version' .github/ci-config/ci-versions.yml
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ steps.versions.outputs.result }}
cache: 'pip'
cache-dependency-path: |
pyproject.toml

- name: Install dependencies with optimization stack
run: |
python -m pip install --upgrade pip setuptools wheel
pip install -e ".[dev]"
pip install pyomo idaes-pse

- name: Install IPOPT solver via IDAES
run: |
echo "Installing IPOPT solver via IDAES extensions"
idaes get-extensions --extra petsc

- name: Run Pyomo tests
run: |
echo "Running Pyomo test suite"
echo "These tests require IPOPT solver and may be slower"
# Exit code 5 = no tests collected (pyomo tests added in later PRs)
pytest tests/ -n auto -v -m "pyomo" --cov=lyopronto --cov-report=xml --cov-report=term-missing || { rc=$?; [ $rc -eq 5 ] && echo "No pyomo-marked tests found yet" && exit 0; exit $rc; }

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
file: ./coverage.xml
flags: pyomo-tests
name: pyomo-coverage
fail_ci_if_error: false
token: ${{ secrets.CODECOV_TOKEN }}

- name: Pyomo Test Summary
if: always()
run: |
echo "Pyomo tests completed (continue-on-error enabled)"
echo "Coverage metrics updated in Codecov"
23 changes: 23 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,29 @@
#
# Python precompiled files
*.pyc
__pycache__/
*.py[cod]
*$py.class

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# Data files
*.csv

Expand Down
18 changes: 9 additions & 9 deletions lyopronto/calc_knownRp.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def dry(vial,product,ht,Pchamber,Tshelf,dt):
################## Initialization ################

# Initial fill height
Lpr0 = functions.Lpr0_FUN(vial['Vfill'],vial['Ap'],product['cSolid']) # cm
Lpr0 = functions.Lpr0_FUN(vial['Vfill'],vial['Ap'],product['cSolid']) # [cm]

# Time-dependent functions for Pchamber and Tshelf, take time in hours
Pch_t = functions.RampInterpolator(Pchamber)
Expand All @@ -58,12 +58,12 @@ def dry(vial,product,ht,Pchamber,Tshelf,dt):
# Get maximum simulation time based on shelf and chamber setpoints
# This may not really be necessary, but is part of legacy behavior
# Could remove in a future release
max_t = max(Pch_t.max_time(), Tsh_t.max_time()) # hr, add buffer
max_t = max(Pch_t.max_time(), Tsh_t.max_time()) # [hr], add buffer

if Pch_t.max_setpt() > functions.Vapor_pressure(Tsh_t.max_setpt()):
warn("Chamber pressure setpoint exceeds vapor pressure at shelf temperature " +\
"setpoint(s). Drying cannot proceed.")
return np.array([[0.0, Tsh_t(0), Tsh_t(0), Tsh_t(0), Pch_t(0), 0.0, 0.0]])
return np.array([[0.0, Tsh_t(0), Tsh_t(0), Tsh_t(0), Pch_t(0) * constant.Torr_to_mTorr, 0.0, 0.0]])

inputs = (vial, product, ht, Pch_t, Tsh_t, dt, Lpr0)

Expand All @@ -75,21 +75,21 @@ def dry(vial,product,ht,Pchamber,Tshelf,dt):
# taking them as arguments.
def calc_dLdt(t, u):
# Time in hours
Lck = u[0] # cm
Lck = u[0] # [cm]
Tsh = Tsh_t(t)
Pch = Pch_t(t)
Kv = functions.Kv_FUN(ht['KC'],ht['KP'],ht['KD'],Pch) # Vial heat transfer coefficient in cal/s/K/cm^2
Rp = functions.Rp_FUN(Lck,product['R0'],product['A1'],product['A2']) # Product resistance in cm^2-hr-Torr/g
Tsub = fsolve(functions.T_sub_solver_FUN, T0, args = (Pch,vial['Av'],vial['Ap'],Kv,Lpr0,Lck,Rp,Tsh))[0] # Sublimation front temperature array in degC
dmdt = functions.sub_rate(vial['Ap'],Rp,Tsub,Pch) # Total sublimation rate array in kg/hr
Kv = functions.Kv_FUN(ht['KC'],ht['KP'],ht['KD'],Pch) # Vial heat transfer coefficient [cal/s/K/cm^2]
Rp = functions.Rp_FUN(Lck,product['R0'],product['A1'],product['A2']) # Product resistance [cm^2-hr-Torr/g]
Tsub = fsolve(functions.T_sub_solver_FUN, T0, args = (Pch,vial['Av'],vial['Ap'],Kv,Lpr0,Lck,Rp,Tsh))[0] # Sublimation front temperature [degC]
dmdt = functions.sub_rate(vial['Ap'],Rp,Tsub,Pch) # Total sublimation rate [kg/hr]
if dmdt<0:
# print("Shelf temperature is too low for sublimation.")
dmdt = 0.0
dLdt = 0
return [dLdt]
# Tbot = functions.T_bot_FUN(Tsub,Lpr0,Lck,Pch,Rp) # Vial bottom temperature array in degC

dLdt = (dmdt*constant.kg_To_g)/(1-product['cSolid']*constant.rho_solution/constant.rho_solute)/(vial['Ap']*constant.rho_ice)*(1-product['cSolid']*(constant.rho_solution-constant.rho_ice)/constant.rho_solute) # cm/hr
dLdt = (dmdt*constant.kg_To_g)/(1-product['cSolid']*constant.rho_solution/constant.rho_solute)/(vial['Ap']*constant.rho_ice)*(1-product['cSolid']*(constant.rho_solution-constant.rho_ice)/constant.rho_solute) # [cm/hr]
return [dLdt]

### ------ Condition for ending simulation: completed drying
Expand Down
Loading