Contributing#
Thank you for your interest in contributing to PyPSA-GB!
Getting Started#
Prerequisites#
Fork the repository on GitHub
Clone your fork:
git clone https://github.com/YOUR-USERNAME/PyPSA-GB.git cd PyPSA-GB
Set up the development environment:
conda env create -f envs/pypsa-gb.yaml conda activate pypsa-gb
Add the upstream remote:
git remote add upstream https://github.com/andrewlyden/PyPSA-GB.git
Development Workflow#
1. Create a Branch#
git checkout -b feature/my-new-feature
# or
git checkout -b fix/bug-description
2. Make Changes#
Follow the code style guidelines below
Add tests for new functionality
Update documentation as needed
3. Test Your Changes#
# Validate scenarios
python scripts/validate_scenarios.py
# Run a test scenario
snakemake resources/network/Test_reduced_solved.nc -j 4
# Check for errors
python -m pytest tests/ -v
4. Commit Your Changes#
Use clear commit messages:
git add .
git commit -m "feat: Add support for new storage technology"
Commit message prefixes:
feat:- New featurefix:- Bug fixdocs:- Documentation onlydata:- Data file updatesrefactor:- Code restructuringtest:- Adding testschore:- Maintenance tasks
5. Push and Create PR#
git push origin feature/my-new-feature
Then create a Pull Request on GitHub.
Code Style#
Python Style#
Follow PEP 8
Use type hints for function signatures
Maximum line length: 100 characters
Use descriptive variable names
# Good
def calculate_curtailment(
network: pypsa.Network,
carrier: str = "wind_offshore"
) -> float:
"""Calculate curtailment for a carrier."""
...
# Bad
def calc(n, c="wind"):
...
Docstrings#
Use NumPy-style docstrings:
def map_sites_to_buses(
network: pypsa.Network,
sites_df: pd.DataFrame,
method: str = 'nearest'
) -> pd.DataFrame:
"""
Map generator sites to nearest network buses.
Parameters
----------
network : pypsa.Network
PyPSA network with bus coordinates
sites_df : pd.DataFrame
Sites with 'lat' and 'lon' columns
method : str, optional
Mapping method: 'nearest' (default) or 'voronoi'
Returns
-------
pd.DataFrame
Input DataFrame with 'bus' column added
Raises
------
ValueError
If sites_df lacks coordinate columns
Examples
--------
>>> mapped = map_sites_to_buses(network, wind_farms)
>>> mapped.groupby('bus').p_nom.sum()
"""
Logging#
Use the centralized logging configuration:
from scripts.logging_config import setup_logging
# At module level (for imports)
logger = setup_logging("module_name")
# In main block (for Snakemake)
if __name__ == "__main__":
snk = globals().get('snakemake')
log_path = snk.log[0] if snk and snk.log else "module_name"
logger = setup_logging(log_path)
Adding New Features#
New Data Source#
Add raw data to
data/directoryCreate processing script in
scripts/Add Snakemake rule in
rules/Update documentation
Add validation checks
New Technology/Carrier#
Add to
scripts/carrier_definitions.py:'my_new_tech': { 'nice_name': 'My New Technology', 'color': '#HEXCODE', 'co2_emissions': 0.0, 'renewable': True }
Update integration modules to handle the carrier
Add test cases
Document in data reference
New Network Model#
Create topology files in
data/network/Update
scripts/build_network.pyto load itAdd to configuration options
Document in network models guide
Snakemake Rules#
Rule Style#
rule my_rule:
"""Brief description of what this rule does."""
input:
network="resources/network/{scenario}_network.nc",
data="data/my_data.csv"
output:
"resources/network/{scenario}_with_feature.nc"
log:
"logs/my_rule/{scenario}.log"
params:
param1=lambda wildcards: config['scenarios'][wildcards.scenario].get('param1', 'default')
script:
"../scripts/my_script.py"
Rule Guidelines#
Include
log:directive for every ruleUse wildcards consistently
Document complex rules
Keep scripts focused on one task
Testing#
Running Tests#
# All tests
python -m pytest tests/ -v
# Specific test file
python -m pytest tests/test_scenario_detection.py -v
# With coverage
python -m pytest tests/ --cov=scripts
Writing Tests#
# tests/test_my_feature.py
import pytest
from scripts.my_module import my_function
def test_my_function_basic():
"""Test basic functionality."""
result = my_function(input_data)
assert result == expected_output
def test_my_function_edge_case():
"""Test edge case handling."""
with pytest.raises(ValueError):
my_function(invalid_input)
Documentation#
Building Docs Locally#
cd docs/readthedocs
make html
# Open build/html/index.html
Documentation Style#
Use clear, concise language
Include code examples
Add cross-references with
{doc}and{ref}Include diagrams for complex concepts
Reporting Issues#
Bug Reports#
Include:
PyPSA-GB version (git commit hash)
Python and key package versions
Operating system
Full error message and traceback
Minimal reproduction steps
Relevant configuration snippets
Feature Requests#
Include:
Clear description of the feature
Use case / motivation
Proposed implementation (if any)
Impact on existing functionality
Code Review#
All contributions go through code review:
Functionality: Does it work as intended?
Tests: Are there adequate tests?
Documentation: Is it documented?
Style: Does it follow guidelines?
Performance: Any performance concerns?
License#
By contributing, you agree that your contributions will be licensed under the MIT License.
Contact#
Lead Developer: Dr Andrew Lyden
Email: andrew.lyden@ed.ac.uk
GitHub Issues: For bug reports and feature requests