Developer guide

This page covers the project layout, testing practices, and contribution guidelines.

Project layout

gensec/
├── src/gensec/              # package source
│   ├── materials/           # constitutive laws and property tables
│   │   ├── base.py
│   │   ├── concrete.py
│   │   ├── steel.py
│   │   ├── tabulated.py
│   │   ├── ec2_properties.py
│   │   ├── en10025_properties.py
│   │   └── ec2_bridge.py
│   ├── geometry/            # section definition and meshing
│   │   ├── fiber.py
│   │   ├── geometry.py
│   │   ├── primitives.py
│   │   └── section.py
│   ├── solver/              # numerical core
│   │   ├── integrator.py
│   │   ├── capacity.py
│   │   └── check.py
│   ├── output/              # plotting, export, reporting
│   │   ├── plots.py
│   │   ├── export.py
│   │   └── report.py
│   ├── io_yaml.py           # YAML input loader
│   ├── cli.py               # command-line interface
│   └── __main__.py          # python -m gensec
├── tests/                   # test suite
├── docs/                    # Sphinx documentation
├── examples/                # YAML input files
└── pyproject.toml

Running the test suite

# With pytest (recommended)
uv run python -m pytest tests/ -v

# With unittest
uv run python -m unittest discover -s tests -v

The suite contains 106 tests organized in four files:

Writing tests

Each development phase must include a validation suite. Follow these principles:

  • Analytical reference cases: whenever possible, compare against hand-calculated or textbook results, not just against GenSec’s own output. Typical cases: pure axial compression/tension (closed-form), symmetric sections (symmetry checks), rebar-only sections (steel stress × area).

  • Tolerances: use relative tolerances of 1–2 % for integration-based results (fiber discretization introduces mesh-dependent error). Use tight tolerances (< 0.01 %) for material law evaluations.

  • Round-trip consistency: for the inverse solver, verify that solve_equilibrium followed by compute_forces recovers the original targets within tolerance.

  • Edge cases: zero curvature, zero axial force, single-fiber sections, sections with only rebars.

Documentation standards

All code is documented in English, in numpydoc style, ready for Sphinx with LaTeX math formulas.

  • Every public class, method, and function must have a complete docstring with Parameters, Returns, and Raises sections as appropriate.

  • Use raw docstrings (r""") when LaTeX math is present.

  • Mathematical formulas use the :math: role for inline expressions and .. math:: directives for display equations.

  • Cross-reference other classes and modules using Sphinx roles: :class:`~gensec.materials.Concrete`, :func:`~gensec.materials.concrete_from_ec2`, etc.

Adding a new material

  1. Create a new module in materials/ that subclasses Material.

  2. Implement stress(eps), stress_array(eps), eps_min, and eps_max. The stress_array method must accept arrays of any shape (1-D, 2-D, …).

  3. Recommended: implement tangent(eps) and tangent_array(eps) with the closed-form derivative of the constitutive law. If omitted, the base class provides a finite-difference fallback, but the analytical form is faster and more accurate.

  4. Optional: if the constitutive law is computationally intensive, consider adding a Numba JIT kernel guarded by a try: import numba block. See concrete.py for the pattern.

  5. Register the new type string in io_yaml.py so that YAML files can reference it.

  6. Add pointwise tests in test_materials.py, including tests for tangent_array consistency with finite differences.

  7. Update the API docs in docs/api/gensec.materials.rst.

Adding a new parametric shape

  1. Write a factory function in primitives that returns a shapely.geometry.Polygon.

  2. Register the shape name in io_yaml.py.

  3. Add a test and an example YAML file.

  4. Update YAML input reference with the new shape parameters.