Thank you for your interest in contributing to LyoPRONTO! This document provides guidelines and instructions for contributing.
LyoPRONTO uses a modern, robust CI/CD pipeline and a comprehensive test suite. All contributions must pass automated tests and follow the project's testing strategy:
- Fast/Slow Test Separation:
- Fast tests run on every PR and push (under 60 seconds).
- Slow tests (marked with
@pytest.mark.slow) run nightly and on demand.
- Centralized Python Version Management:
- All workflows use the Python version(s) specified in
.github/ci-config/ci-versions.yml.
- All workflows use the Python version(s) specified in
- CI Workflows:
- PRs and pushes: Fast tests (
pr-tests.yml) - Main branch: Full suite (
tests.yml) - Nightly/manual: Slow tests (
slow-tests.yml) - Docs: Build and link check (
docs.yml)
- PRs and pushes: Fast tests (
- Coverage & Linting:
- Coverage is reported for all test runs.
- Linting and formatting are enforced in CI.
Contributor Checklist:
- Mark slow tests with
@pytest.mark.slow. - Add or update tests for all new features and bugfixes.
- Ensure all tests pass locally before submitting a PR (
pytest tests/ -v). - Review
tests/README.mdfor full details on running, writing, and debugging tests, as well as CI workflow explanations.
- Getting Started
- Development Workflow
- Coding Standards
- Testing Requirements
- Submitting Changes
- Pyomo Development
- Python 3.8 or higher
- Git
- Basic understanding of lyophilization process (helpful but not required)
# Clone the repository
git clone https://github.com/SECQUOIA/LyoPRONTO.git
cd LyoPRONTO
# Create virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install in development mode
pip install -e .
# Install development dependencies
pip install -r requirements-dev.txt
# Run tests to verify setup
pytest tests/ -v# Update main branch
git checkout main
git pull origin main
# Create feature branch
git checkout -b feature/your-feature-name
# or
git checkout -b bugfix/issue-description- Write tests first (TDD approach recommended)
- Implement your feature
- Ensure all tests pass
- Update documentation
# Run all tests
pytest tests/ -v
# Run with coverage
pytest tests/ --cov=lyopronto --cov-report=html
# Check specific test
pytest tests/test_functions.py -v
# Run with debugging
pytest tests/ -v --pdb# Format code
black lyopronto/ tests/
# Check linting
flake8 lyopronto/ tests/
# Type checking (optional but recommended)
mypy lyopronto/Write clear, descriptive commit messages:
git add .
git commit -m "Add feature: brief description
Detailed explanation of what changed and why.
- Key change 1
- Key change 2
Closes #issue_number"git push origin feature/your-feature-nameThen create a Pull Request on GitHub with:
- Clear title and description
- Reference to related issues
- Summary of changes
- Test results
- Follow PEP 8 style guide
- Use NumPy-style docstrings
- Include type hints for function signatures
- Maximum line length: 100 characters (flexible for readability)
Use these standard names for consistency:
# Temperatures [degC]
Tsub # Sublimation front temperature
Tbot # Vial bottom temperature
Tsh # Shelf temperature
# Pressures (Torr)
Pch # Chamber pressure
Psub # Vapor pressure at sublimation front
# Lengths (cm)
Lpr0 # Initial product length
Lck # Dried cake length
# Product properties
Rp # Product resistance (cm²-hr-Torr/g)
Kv # Vial heat transfer coefficient [cal/s/K/cm**2])
# Rates
dmdt # Sublimation rate (kg/hr)Every function should have a docstring:
def calculate_vapor_pressure(temperature):
"""Calculate vapor pressure using Antoine equation.
The vapor pressure of ice is calculated using the Antoine
equation with parameters specific to water/ice system.
Args:
temperature (float): Temperature in degrees Celsius.
Returns:
(float): Vapor pressure in Torr.
Notes:
Valid for temperatures between -60°C and 0°C.
Examples:
>>> P = calculate_vapor_pressure(-20.0)
>>> print(f"{P:.3f} Torr")
0.776 Torr
"""- All new functions must have unit tests
- Aim for >80% code coverage
- Include edge cases and error conditions
- Test physical reasonableness of results
class TestMyFeature:
"""Tests for my new feature."""
def test_normal_case(self, standard_setup):
"""Test with typical input values."""
result = my_function(standard_setup)
assert result > 0
assert np.isclose(result, expected, rtol=0.01)
def test_edge_case(self):
"""Test with boundary conditions."""
result = my_function(edge_case_input)
assert is_physically_reasonable(result)
def test_error_handling(self):
"""Test error conditions."""
with pytest.raises(ValueError):
my_function(invalid_input)Leverage existing fixtures from tests/conftest.py:
def test_with_fixtures(self, standard_vial, standard_product):
"""Test using predefined fixtures."""
result = simulate(standard_vial, standard_product)
assert result is not NoneBefore submitting a PR, ensure:
- All tests pass (
pytest tests/ -v) - Code is formatted (
black lyopronto/ tests/) - Documentation is updated
- Docstrings are complete
- CHANGELOG.md is updated (if applicable)
- No unnecessary files are included
- Commit messages are clear and descriptive
## Description
Brief description of what this PR does.
## Motivation
Why is this change needed? What problem does it solve?
## Changes
- Key change 1
- Key change 2
- Key change 3
## Testing
- [ ] Added unit tests
- [ ] Added integration tests
- [ ] All existing tests pass
- [ ] Manually tested with example cases
## Documentation
- [ ] Updated docstrings
- [ ] Updated README if needed
- [ ] Updated CHANGELOG
## Related Issues
Closes #123
Relates to #456When contributing Pyomo-based optimization code:
-
Create Comparison Tests
def test_pyomo_matches_scipy(): """Verify Pyomo results match scipy baseline.""" scipy_result = scipy_optimize(...) pyomo_result = pyomo_optimize(...) np.testing.assert_allclose(scipy_result, pyomo_result, rtol=1e-3)
-
Use Proper Initialization
- Always warmstart with scipy solution
- Document initialization strategy
-
Handle Numerical Issues
- Use log-transforms for exponentials
- Add scaling to improve conditioning
- Document solver options
-
Benchmark Performance
- Compare timing against scipy
- Aim for <3x scipy performance
- Profile bottlenecks
Follow this structure for new Pyomo models:
def create_model(params):
"""Create Pyomo model for [description].
Args:
params: Model parameters
Returns:
Concrete Pyomo model ready to solve
"""
model = pyo.ConcreteModel()
# 1. Sets
# 2. Parameters
# 3. Variables with bounds
# 4. Constraints
# 5. Objective
return model
def solve_model(model, solver='ipopt'):
"""Solve Pyomo model and extract results."""
# Solve
# Check status
# Extract results
# Return formatted output- Check existing documentation in
docs/and*.mdfiles - Review
PYOMO_ROADMAP.mdfor architecture decisions - Search existing issues on GitHub
- Ask questions by opening a new issue
- Be respectful and constructive
- Focus on the code, not the person
- Welcome newcomers and help them learn
- Give credit where credit is due
Contributors will be acknowledged in:
CONTRIBUTORS.mdfile- Release notes
- Academic papers (for significant contributions)
Thank you for contributing to LyoPRONTO! 🚀