Thank you for your interest in contributing to PyFVS! This document provides guidelines for contributing to the project.
-
Clone the repository
git clone https://github.com/mihiarc/pyfvs.git cd pyfvs -
Create a virtual environment with uv (recommended)
uv venv source .venv/bin/activate # On Windows: .venv\Scripts\activate
-
Install in development mode with all dependencies
uv pip install -e ".[dev,test,docs]" -
Verify the setup
uv run pytest -v --tb=short
pyfvs/
├── src/fvs_python/ # Main source code
│ ├── tree.py # Individual tree growth models
│ ├── stand.py # Stand-level management
│ ├── cli.py # Command-line interface
│ └── ...
├── cfg/ # Configuration files
│ ├── species/ # Species-specific parameters
│ └── *.json # Model coefficients
├── tests/ # Test suite
├── docs/ # Documentation
└── test_output/ # Generated test outputs
-
Create a feature branch
git checkout -b feature/your-feature-name
-
Make your changes, following the code style guidelines
-
Run tests
uv run pytest
-
Format your code
uv run black src/fvs_python tests
-
Run linters
uv run flake8 src/fvs_python tests uv run mypy src/fvs_python
-
Commit your changes
git add . git commit -m "Brief description of changes"
-
Push and create a pull request
git push origin feature/your-feature-name
# Run all tests with coverage
uv run pytest
# Run specific test file
uv run pytest tests/test_tree.py
# Run with verbose output
uv run pytest -v --tb=short
# Run only unit tests
uv run pytest -m unit
# Run only integration tests
uv run pytest -m integration
# Skip slow tests
uv run pytest -m "not slow"Tests generate outputs in test_output/ for manual verification. These include:
- Growth trajectory plots
- Validation reports (markdown)
- Yield table CSVs
- Formatter: Black (line length 88)
- Linter: Flake8
- Type Checker: MyPy with strict settings
- Import Sorting: isort (black profile)
# Variables and functions: snake_case
def calculate_basal_area(trees: list) -> float:
total_ba = 0.0
...
# Classes: PascalCase
class StandMetricsCalculator:
...
# Constants: UPPER_SNAKE_CASE
MAX_CROWN_RATIO = 0.95
MIN_DBH_THRESHOLD = 0.1Use Google-style docstrings for all public functions and classes:
def grow(self, site_index: float, basal_area: float, time_step: int = 5) -> None:
"""Grow the tree for the specified time period.
Applies the appropriate growth model (small-tree or large-tree)
based on the tree's current DBH.
Args:
site_index: Site index (base age 25) in feet.
basal_area: Stand basal area in square feet per acre.
time_step: Growth period in years. Defaults to 5.
Raises:
ValueError: If site_index is not between 30 and 100.
Example:
>>> tree = Tree(species='LP', dbh=6.0, height=45.0)
>>> tree.grow(site_index=70, basal_area=120)
>>> print(f"New DBH: {tree.dbh:.1f}")
New DBH: 6.8
"""Use type hints for all function signatures:
from typing import Optional, List, Dict
def calculate_metrics(
trees: List[Tree],
site_index: float,
ecounit: Optional[str] = None
) -> Dict[str, float]:
...import pytest
from fvs_python import Stand, Tree
class TestTree:
"""Tests for the Tree class."""
def test_small_tree_growth(self):
"""Small trees should use height-driven growth model."""
tree = Tree(species='LP', dbh=1.5, height=12.0)
initial_dbh = tree.dbh
tree.grow(site_index=70, basal_area=100)
assert tree.dbh > initial_dbh
def test_large_tree_growth(self):
"""Large trees should use diameter-driven growth model."""
tree = Tree(species='LP', dbh=8.0, height=55.0)
initial_dbh = tree.dbh
tree.grow(site_index=70, basal_area=100)
assert tree.dbh > initial_dbh
@pytest.mark.parametrize("species", ["LP", "SP", "LL", "SA"])
def test_all_species_grow(self, species):
"""All supported species should grow without errors."""
tree = Tree(species=species, dbh=6.0, height=40.0)
tree.grow(site_index=70, basal_area=100)
assert tree.dbh > 6.0Mark tests appropriately:
@pytest.mark.unit
def test_crown_ratio_bounds():
"""Unit test for crown ratio validation."""
...
@pytest.mark.integration
def test_stand_growth_simulation():
"""Integration test for full simulation."""
...
@pytest.mark.slow
def test_long_term_projection():
"""Slow test that runs 50-year simulation."""
...Before modifying growth models, familiarize yourself with:
- CLAUDE.md - Architecture overview and known issues
- SINGLE_TREE_GROWTH_GUIDE.md - Growth equation details
- docs/FVS_PYTHON_VALIDATION_SPEC.md - Validation requirements
The critical DBH transition between small and large tree models:
DBH < 1.0": Small-tree model only (height-driven)1.0" <= DBH <= 3.0": Weighted blend of both modelsDBH > 3.0": Large-tree model only (diameter-driven)
Growth model changes must:
- Pass all existing tests
- Not degrade validation metrics beyond acceptance criteria:
- Diameter growth: within 5% of expected
- Height growth: within 10% of expected
- Basal area: within 3% of expected
- Include new tests for the changed behavior
- Update validation outputs in
test_output/
Species configurations are in cfg/species/*.yaml:
# cfg/species/lp_loblolly_pine.yaml
code: LP
name: Loblolly Pine
scientific_name: Pinus taeda
diameter_growth:
model: ln_dds
coefficients:
b1: 0.222214
b2: 1.163040
...Changes to species parameters should:
- Include a reference to the source (publication, FVS documentation)
- Be validated against known yield tables
- Update CLAUDE.md if significant
- Create a new YAML file in
cfg/species/ - Add coefficients to relevant JSON files in
cfg/ - Add test coverage
- Update documentation
# Install docs dependencies
uv pip install -e ".[docs]"
# Build Sphinx documentation
cd docs
sphinx-build -b html . _build/htmlWhen making code changes:
- Update docstrings for changed functions
- Update CLAUDE.md for significant fixes or known issues
- Update README.md if architecture changes
- Add examples for new features
-
Ensure all tests pass
uv run pytest
-
Format code
uv run black src/fvs_python tests
-
Update documentation as needed
-
Write a clear PR description including:
- What the change does
- Why the change is needed
- How it was tested
- Any related issues
-
Request review from maintainers
Use conventional commit format:
feat: Add longleaf pine crown width equationsfix: Correct bark ratio calculation for slash pinedocs: Update getting started guidetest: Add integration tests for thinningrefactor: Simplify Stand.grow() time step handling
Include:
- Python version and OS
- Steps to reproduce
- Expected vs actual behavior
- Relevant code or configuration
- Error messages and stack traces
Include:
- Use case description
- Proposed solution (if any)
- Alternatives considered
- Be respectful and inclusive
- Focus on constructive feedback
- Help others learn and grow
- Assume good intentions
- Open a GitHub Issue
- Check existing documentation in
docs/ - Review the CLAUDE.md for project context
Thank you for contributing to PyFVS!