Contributing#
Thank you for your interest in contributing to EngiAI! This document provides guidelines for contributing to the project.
Getting Started#
Fork the repository on GitHub
Clone your fork:
git clone https://github.com/YOUR_USERNAME/engiai.git cd EngiAI
Create environment and install dependencies:
conda create -n engiai python=3.13 -y conda activate engiai pip install -e ".[dev,test]"
Development Workflow#
1. Create a Feature Branch#
git checkout -b feature/your-feature-name
Use descriptive branch names:
feature/for new featuresfix/for bug fixesdocs/for documentationtest/for test improvements
2. Make Your Changes#
Write clean, readable code
Follow the existing code style
Add docstrings to new functions and classes
Update documentation as needed
3. Write Tests#
Add tests for your changes:
# Create test file
touch tests/test_your_feature.py
# Run tests
make test
# Run specific test
pytest tests/test_your_feature.py
4. Check Code Quality#
# Format code
make format
# Lint code — ruff check, ruff format --check, and mypy
make lint
# The full pre-push gate: everything make lint runs, plus the whole test suite
make check-all
make lint covers all three checks CI gates separately, so a green make lint
means the lint job will pass. make check-all adds pytest on top.
5. Commit Your Changes#
Write clear commit messages:
git add .
git commit -m "feat: add new ArXiv search feature"
Commit message format:
feat:new featurefix:bug fixdocs:documentationtest:testsrefactor:code refactoringstyle:formattingchore:maintenance
6. Push and Create Pull Request#
git push origin feature/your-feature-name
Then create a Pull Request on GitHub with:
Clear description of changes
Link to related issues
Screenshots if applicable
Adding New Engineering Problems#
The system uses a unified problem registry that makes adding new EngiBench problems simple:
Step-by-Step Guide#
Both edits are in src/tools/problems.py.
Update the ProblemId Type:
ProblemId = Literal["beams2d", "photonics2d", "yournewproblem"]
Add the engibench import path to
_PROBLEM_IMPORTS:_PROBLEM_IMPORTS: dict[str, tuple[str, str]] = { "beams2d": ("engibench.problems.beams2d.v1", "Beams2D"), "photonics2d": ("engibench.problems.photonics2d.v1", "Photonics2D"), "yournewproblem": ("engibench.problems.yours.v0", "YourNewProblem"), }
The module path and class name go here as strings. Do not add a top-level
from engibench import ...to this file: the classes are imported lazily on first lookup, because importing engibench eagerly puts ~2 s onto every Streamlit start and every agent process. A guard test (tests/test_tools/test_import_cost.py) fails if that regresses.That’s It! The system automatically:
✅ Enables the problem in all tools
✅ Updates documentation in prompts
✅ Extracts objectives dynamically
✅ Handles rendering and constraints
✅ Works with training and sampling
What NOT to Modify#
You don’t need to edit:
src/tools/engibench.py- Already problem-agnosticsrc/tools/engiopt.py- Uses registry automaticallysrc/utils/prompts.py- Auto-generates documentationAgent files - They adapt automatically
Testing Your New Problem#
@pytest.mark.parametrize("problem_id", ["beams2d", "yournewproblem"])
def test_new_problem(problem_id):
from src.tools.problems import PROBLEM_CLASSES
problem_class = PROBLEM_CLASSES[problem_id]
problem = problem_class()
assert problem is not None
assert hasattr(problem, "objectives")
Code Style#
Python#
Follow PEP 8 and use type hints:
from typing import List, Optional
def process_data(
data: List[float], threshold: float = 0.5, normalize: bool = True
) -> Optional[List[float]]:
"""Process input data with optional normalization.
Args:
data: Input data points
threshold: Minimum value threshold
normalize: Whether to normalize results
Returns:
Processed data or None if invalid
"""
# Implementation
pass
Docstrings#
Use Google-style docstrings:
def my_function(arg1: str, arg2: int) -> bool:
"""Short description.
Longer description if needed.
Args:
arg1: Description of arg1
arg2: Description of arg2
Returns:
Description of return value
Raises:
ValueError: When invalid input is provided
"""
pass
Testing#
Writing Tests#
Use pytest fixtures and parametrization:
import pytest
from src.agents import EngineeringAgent
@pytest.fixture
def agent():
return EngineeringAgent()
def test_optimization(agent):
# Works with any problem type from the registry
result = agent.optimize(problem="beams2d")
assert result is not None
@pytest.mark.parametrize("volfrac", [0.3, 0.4, 0.5])
def test_volume_fractions(agent, volfrac):
result = agent.optimize(config={"volfrac": volfrac})
assert result["volfrac"] == volfrac
@pytest.mark.parametrize("problem_type", ["beams2d", "photonics2d"])
def test_all_problem_types(problem_type):
# System automatically adapts to any registered problem
from src.tools.problems import PROBLEM_CLASSES
problem_class = PROBLEM_CLASSES[problem_type]
problem = problem_class()
assert problem is not None
Running Tests#
# All tests
make test
# Everything except the slow ones — this is what CI runs on a PR
make test-fast
# Specific file
pytest tests/test_agents/test_arxiv_agent.py
# With coverage
make test-cov
# Verbose output
pytest -v
Three workflows gate a pull request, and between them they run everything:
Workflow |
Runs |
|---|---|
|
|
|
the |
|
the Playwright suite |
The two expensive tests and test_baseline_reproduction.py are the exceptions:
the first two move to a weekly schedule, and the third needs MPS and a live
W&B session, so it stays a manual pre-release check.
Markers#
Four markers are registered, in [tool.pytest.ini_options] markers in
pyproject.toml. That list is the whole set — anything else is a typo:
Marker |
Meaning |
|---|---|
|
Fast, isolated. Applied by hand; never auto-applied. |
|
Spans multiple components |
|
Excluded by |
|
Full-browser Playwright tests; needs a running Streamlit server |
tests/conftest.py also applies some of these automatically, by substring
match on the test’s node id — which is worth knowing before you name a file:
node id contains
integration→integrationnode id contains
slow,long_runningorperformance→slownode id contains
/e2e/→ bothe2eandslow
Warning
The match is against the whole node id, path included. The second rule is the
one to watch: a test named test_performance_of_x is silently marked slow,
which drops it out of make test-fast and out of the linux-test job — a much
quieter failure than being mislabelled integration. It would still run on a
PR, in slow-tests.yml’s quick job, so the test is not lost; it just stops
running where you expect it. No test currently trips the rule, so it is dormant
rather than gone.
Note also that the directory tests/integration/ contributes none of the
integration-marked tests; those marks come from the substring rule matching
paths elsewhere.
Note
A typo’d marker is a collection error, so you will find out immediately. This
relies on pyproject.toml declaring strict_markers = true as an ini key
under [tool.pytest.ini_options], rather than --strict-markers in addopts:
the addopts form is silently inert on pytest 9, where an unregistered marker
only raises PytestUnknownMarkWarning and the test still passes.
strict_config = true has the same requirement. Both are pinned in place by
tests/test_scripts/test_pytest_config.py, so please do not “tidy” them back
into addopts.
The two forms are mutually exclusive across the major version — on pytest 8 the
ini keys are unknown options and only addopts enforces; on pytest 9 it is the
reverse — so the test extra requires pytest>=9.0. If you are on 8.x, upgrade:
the config still runs, but marker validation is silently off and the two guard
tests fail with ValueError: unknown configuration value.
Note that markers applied programmatically by the auto-marking rules above are
added after the point where strict_markers validates decorators, so those are
covered by a separate assertion in the same test file.
Documentation#
Building Docs#
# Build documentation
make docs
# Live preview
make docs-watch
Writing Documentation#
Use Markdown for content pages
Add examples and code snippets
Include links to API reference
Update the index when adding new pages
Pull Request Guidelines#
Before Submitting#
[ ] All tests pass
[ ] Code is formatted (
make format)[ ] No linting errors (
make lint)[ ] Documentation updated
PR Checklist#
Your PR should:
Have a clear title and description
Link to related issues (e.g., “Fixes #123”)
Include tests for new features
Update documentation
Follow the code style guide
Have passing CI checks
Code Review Process#
Maintainers review your PR
Address feedback by pushing new commits
Once approved, your PR will be merged
Your contribution will be credited in releases
Questions?#
Open an issue for bugs
Start a discussion for questions
Join our community chat (if available)
License#
By contributing, you agree that your contributions will be licensed under the same license as the project.
Thank you for contributing! 🎉