Contributing

We welcome contributions to crabML! This document provides guidelines for contributing.

Development Setup

  1. Clone the repository:

git clone https://github.com/yourusername/crabml.git
cd crabml
  1. Install with development dependencies:

uv sync --all-extras --reinstall-package crabml-rust --group dev --group docs
  1. Verify installation:

uv run pytest

Code Style

Python Code

We use ruff for linting and formatting:

# Format code
uv run ruff format src/ tests/

# Check for issues
uv run ruff check src/ tests/

Guidelines:

  • Follow PEP 8 style guide

  • Use Google-style docstrings

  • Maximum line length: 100 characters

  • Type hints for function signatures

Rust Code

Rust code should follow standard Rust conventions:

cd rust/
cargo fmt
cargo clippy

Running Tests

Run all tests:

uv run pytest -n 8

Run specific test file:

uv run pytest tests/test_api.py -v -n 8

Run tests with coverage:

uv run pytest --cov=crabml --cov-report=html

PAML validation tests (slower):

uv run pytest tests/test_paml_examples/ -v -n 4

Building Documentation

Build Sphinx documentation:

cd docs/
make html

View documentation:

open _build/html/index.html  # macOS
xdg-open _build/html/index.html  # Linux

Contributing Guidelines

  1. Create an issue first to discuss major changes

  2. Fork the repository and create a feature branch:

git checkout -b feature/your-feature-name
  1. Make your changes following code style guidelines

  2. Add tests for new functionality

  3. Update documentation if needed

  4. Run tests to ensure nothing breaks:

uv run pytest -n 8
  1. Commit your changes with clear messages:

git add .
git commit -m "Add feature: brief description

More detailed explanation of changes, why they were needed,
and any relevant context."
  1. Push to your fork and create a pull request

Pull Request Process

  1. Ensure all tests pass

  2. Update CHANGELOG.md with your changes

  3. Update documentation if you changed the API

  4. Fill out the PR template with:

    • Description of changes

    • Issue number (if applicable)

    • Testing performed

    • Screenshots (if UI changes)

  5. Wait for review - maintainers will review and provide feedback

Code Review Checklist

Reviewers will check for:

☐ All tests pass

☐ Code follows style guidelines

☐ Documentation updated

☐ CHANGELOG.md updated

☐ No unnecessary dependencies added

☐ PAML validation tests still pass (for model changes)

☐ Performance hasn’t regressed

Reporting Bugs

Use GitHub Issues to report bugs. Include:

  1. Environment information:

    • Python version

    • Rust version

    • Operating system

    • crabML version

  2. Steps to reproduce the bug

  3. Expected behavior

  4. Actual behavior

  5. Minimal example that demonstrates the issue

  6. Error messages and stack traces

Example bug report:

**Bug**: M2a optimizer fails on large alignments

**Environment:**
- Python 3.11.5
- Rust 1.73.0
- Ubuntu 22.04
- crabML 0.2.0

**Steps to reproduce:**
1. Load alignment with 200 sequences
2. Run `optimize_model("M2a", align, tree)`

**Expected:** Should complete successfully

**Actual:** Crashes with "out of memory" error

**Error message:**
```
MemoryError: Unable to allocate array with shape (200, 1000, 10)
```

Feature Requests

We welcome feature requests! Please:

  1. Check existing issues to avoid duplicates

  2. Describe the feature in detail

  3. Explain the use case and why it’s needed

  4. Provide examples of how it would be used

Adding New Models

To add a new codon model:

  1. Implement optimizer class in src/crabml/optimize/

class MyModelOptimizer(BaseOptimizer):
    def __init__(self, alignment, tree, **kwargs):
        super().__init__(alignment, tree, **kwargs)

    def optimize(self, **kwargs):
        # Implement optimization logic
        pass
  1. Add parser function to src/crabml/api.py

def _parse_mymodel_result(result_tuple, ...):
    # Parse optimizer output into ModelResult
    pass
  1. Add to optimize_model() function

OPTIMIZER_MAP = {
    ...
    "mymodel": MyModelOptimizer,
}

PARSER_MAP = {
    ...
    "mymodel": _parse_mymodel_result,
}
  1. Add tests in tests/test_api.py

  2. Add PAML validation in tests/test_paml_examples/

  3. Update documentation in docs/user_guide/models.rst

Testing Guidelines

All code should be tested. We use pytest.

Test structure:

class TestMyFeature:
    def test_basic_functionality(self):
        """Test basic use case."""
        result = my_function()
        assert result == expected

    def test_edge_case(self):
        """Test edge case behavior."""
        # ...

    def test_error_handling(self):
        """Test that errors are raised appropriately."""
        with pytest.raises(ValueError):
            my_function(invalid_input)

PAML validation tests:

def test_mymodel_vs_paml():
    """Test MyModel against PAML reference."""
    result = optimize_model("MyModel", align, tree)
    paml_lnL = -1234.567890  # From PAML output

    assert abs(result.lnL - paml_lnL) < 0.01

Documentation Guidelines

Docstring format (Google style):

def my_function(param1: str, param2: int = 5) -> bool:
    """
    Brief one-line description.

    Longer description with more details about what the function
    does, when to use it, and any important notes.

    Args:
        param1: Description of param1
        param2: Description of param2. Defaults to 5.

    Returns:
        Description of return value

    Raises:
        ValueError: When param1 is empty
        RuntimeError: When computation fails

    Examples:
        >>> my_function("test", 10)
        True

        >>> my_function("")
        ValueError: param1 cannot be empty
    """
    pass

Getting Help

  • GitHub Issues: For bugs and feature requests

  • GitHub Discussions: For questions and general discussion

  • Email: adkern@uoregon.edu for other inquiries

License

By contributing, you agree that your contributions will be licensed under the GPL-3.0-or-later license.