Skip to content

Contributing to JFChemistry

Thank you for your interest in contributing to JFChemistry! This guide will help you get started.

Ways to Contribute

  • ๐Ÿ› Report bugs and issues
  • ๐Ÿ’ก Suggest new features or enhancements
  • ๐Ÿ“ Improve documentation
  • ๐Ÿงช Add examples and tutorials
  • ๐Ÿ”ง Fix bugs or implement features
  • ๐Ÿงช Add new calculators or methods

Getting Started

1. Fork and Clone

# Fork the repository on GitHub, then clone your fork
git clone https://github.com/YOUR-USERNAME/jfchemistry.git
cd jfchemistry

2. Set Up Development Environment

# Install development environment with pixi
pixi install -e dev

# Or install all optional features
pixi install -e aimnet2 -e orb -e dev -e docs

3. Create a Branch

# Create a branch for your changes
git checkout -b feature/your-feature-name
# or
git checkout -b fix/your-bugfix-name

Development Workflow

Code Style

JFChemistry uses Ruff for code formatting and linting:

# Format code
pixi run -e dev fmt

# Run linter
pixi run -e dev lint

# Type checking
pixi run -e dev types

All code must:

  • Follow Google-style docstrings
  • Include type hints
  • Pass ruff formatting and linting
  • Have line length โ‰ค 100 characters

Example Docstring

def optimize_structure(
    self,
    structure: Molecule,
    fmax: float = 0.01
) -> tuple[Molecule, dict[str, Any]]:
    """Optimize a molecular structure.

    Performs geometry optimization using the configured method
    until forces fall below the threshold.

    Args:
        structure: Pymatgen Molecule to optimize.
        fmax: Maximum force threshold in eV/ร….

    Returns:
        Tuple containing:
            - Optimized Molecule structure
            - Dictionary of calculated properties

    Raises:
        CalculationError: If optimization fails to converge.

    Examples:
        >>> from jfchemistry.optimizers import TBLiteOptimizer
        >>> optimizer = TBLiteOptimizer(method="GFN2-xTB")
        >>> opt_mol, props = optimizer.optimize_structure(molecule)
    """

Testing

Write tests for new features:

# Run all tests
pixi run -e dev test

# Run specific test file
pixi run -e dev pytest tests/test_your_feature.py

# Run with coverage
pixi run -e dev pytest --cov=jfchemistry

Tests should:

  • Cover main functionality
  • Include edge cases
  • Use doctests for simple examples
  • Mock external dependencies when appropriate

Documentation

Update documentation for changes:

# Build documentation locally
pixi run -e docs mkdocs serve

# View at http://127.0.0.1:8000

Documentation updates should include:

  • Updated docstrings in code
  • Examples in relevant .md files
  • Updates to User Guide if needed
  • FAQ entries for common questions

Adding New Features

Adding a New Calculator

  1. Create a new file in jfchemistry/calculators/:
# jfchemistry/calculators/my_calculator.py
from jfchemistry.calculators import ASECalculator

class MyCalculator(ASECalculator):
    """Calculator using My Method.

    Attributes:
        parameter1: Description of parameter1.
        parameter2: Description of parameter2.
    """

    def __init__(self, parameter1: str = "default"):
        """Initialize My Calculator.

        Args:
            parameter1: Description and purpose.
        """
        self.parameter1 = parameter1

    def set_calculator(self, atoms, charge=0, spin_multiplicity=1):
        """Set up the ASE calculator."""
        # Implementation
        pass

    def get_properties(self, atoms):
        """Extract properties from calculation."""
        # Implementation
        pass
  1. Add to jfchemistry/calculators/__init__.py:
from .my_calculator import MyCalculator

__all__ = [..., "MyCalculator"]
  1. Add tests in tests/test_calculators.py

  2. Add documentation example

Adding a New Optimizer

Similar process in jfchemistry/optimizers/:

from jfchemistry.optimizers.base import GeometryOptimization

class MyOptimizer(GeometryOptimization):
    """Optimizer using My Method."""

    def optimize_structure(self, structure):
        """Optimize the structure."""
        # Implementation
        pass

Adding Examples

Add examples to examples/ directory:

# examples/my_example.py
"""
Example: Doing Something Cool

This example demonstrates how to...
"""

from jfchemistry.inputs import Smiles
# ... rest of example

if __name__ == "__main__":
    # Runnable code
    pass

Pull Request Process

1. Before Submitting

  • [ ] Code passes all tests
  • [ ] Code is formatted with ruff
  • [ ] Docstrings are complete and accurate
  • [ ] Documentation is updated
  • [ ] CHANGELOG is updated (if applicable)
  • [ ] Commits are clear and descriptive

2. Submit Pull Request

  1. Push your branch to your fork:
git push origin feature/your-feature-name
  1. Open a Pull Request on GitHub

  2. Fill out the PR template:

## Description

Brief description of changes

## Type of Change

-   [ ] Bug fix
-   [ ] New feature
-   [ ] Documentation update
-   [ ] Performance improvement

## Testing

How was this tested?

## Checklist

-   [ ] Code follows style guidelines
-   [ ] Tests pass
-   [ ] Documentation updated
-   [ ] Self-reviewed code

3. Code Review

  • Address reviewer feedback
  • Keep discussion focused and professional
  • Be open to suggestions

4. Merging

Once approved:

  • Maintainer will merge your PR
  • Your changes will be included in the next release

Commit Message Guidelines

Use clear, descriptive commit messages:

# Good
git commit -m "Add AimNet2 calculator for neural network potentials"
git commit -m "Fix conformer generation memory leak"
git commit -m "Update documentation for TBLite optimizer"

# Bad
git commit -m "fix stuff"
git commit -m "wip"
git commit -m "asdf"

Format:

<type>: <subject>

[optional body]

[optional footer]

Types:

  • feat: New feature
  • fix: Bug fix
  • docs: Documentation changes
  • style: Code style (formatting, etc.)
  • refactor: Code refactoring
  • test: Adding/updating tests
  • chore: Maintenance tasks

Project Structure

jfchemistry/
โ”œโ”€โ”€ jfchemistry/           # Source code
โ”‚   โ”œโ”€โ”€ calculators/       # Calculator implementations
โ”‚   โ”œโ”€โ”€ conformers/        # Conformer generation
โ”‚   โ”œโ”€โ”€ generation/        # 3D structure generation
โ”‚   โ”œโ”€โ”€ inputs/            # Input parsers
โ”‚   โ”œโ”€โ”€ modification/      # Structure modification
โ”‚   โ”œโ”€โ”€ optimizers/        # Geometry optimizers
โ”‚   โ””โ”€โ”€ jfchemistry.py     # Core classes
โ”œโ”€โ”€ tests/                 # Test files
โ”œโ”€โ”€ docs/                  # Documentation source
โ”œโ”€โ”€ examples/              # Example scripts
โ””โ”€โ”€ pyproject.toml         # Project configuration

Best Practices

1. Keep Changes Focused

  • One feature/fix per PR
  • Keep PRs reasonably sized
  • Split large changes into multiple PRs

2. Write Good Tests

def test_optimizer_convergence():
    """Test that optimizer converges for simple molecule."""
    # Arrange
    molecule = create_test_molecule()
    optimizer = TBLiteOptimizer(fmax=0.01)

    # Act
    job = optimizer.make(molecule)
    result = job.output

    # Assert
    assert result["structure"] is not None
    assert result["properties"]["converged"] is True

3. Document Public APIs

Every public class, method, and function needs:

  • Concise summary line
  • Detailed description
  • Args documentation
  • Returns documentation
  • Examples (when helpful)

4. Handle Errors Gracefully

try:
    result = calculation()
except CalculationError as e:
    logger.error(f"Calculation failed: {e}")
    raise

Questions?

  • Open an issue for questions
  • Join discussions on GitHub
  • Check existing issues and PRs

Code of Conduct

Be respectful and constructive. We're all here to advance computational chemistry!

Thank you for contributing! ๐ŸŽ‰