Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# https://pre-commit.com/
# https://github.com/pre-commit/pre-commit
minimum_pre_commit_version: 4.5.1
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version
rev: v0.15.5
hooks:
# Run the linter
- id: ruff
name: ruff linter
alias: lint
# Run the formatter
# - id: ruff-format
# name: ruff formatter
# alias: format

- repo: https://github.com/jendrikseipp/vulture
# Vulture version
rev: v2.15
hooks:
# Run dead code detection
# Paths are configured under [tool.vulture] in pyproject.toml
- id: vulture
name: vulture dead code
alias: vulture

# ty has no official pre-commit hook yet (https://github.com/astral-sh/ty/issues/269).
# Run it as a local hook using the system-installed binary
- repo: local
hooks:
- id: ty
name: ty type checker
alias: ty
entry: ty check --color always --project pyhope pyhope
language: system
pass_filenames: false
types: [python]
99 changes: 99 additions & 0 deletions docs/developer-guide/get-the-source.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Environment Setup

Get the source and start developing

---

## Obtaining the Source

PyHOPE uses [git](https://git-scm.com) to sync down source code. Some larger mesh files are stored in [Git LFS](https://git-lfs.com). `git-lfs` must be installed before cloning, otherwise those files will be replaced by pointer stubs.

!!! info
PyHOPE developers commonly put their virtual environment in `venv` within the git repository. All commands in this document assume that your virtual environment is in `venv`.

```bash
git clone https://github.com/hopr-framework/PyHOPE.git
cd PyHOPE
python -m venv venv
source venv/bin/activate
python -m pip install --upgrade -e .
```

!!! info
Changes in the source code are immediately reflected within the virtual environment without any explicit sync requirement.

## Linting

PyHOPE enforces code quality through three static analysis tools which are wired up as [pre-commit](https://pre-commit.com/) hooks and run as the first stage of the CI pipeline.

### Tools

**[Ruff](https://docs.astral.sh/ruff/)** is a fast Python linter that consolidates the roles of `flake8`, `black`, `isort`, and many others in a single tool. It checks style and common errors across all `.py` files.

!!! note
Some linter errors can be fixed automatically:
```bash
ruff check --fix
```
Note that `--unsafe-fixes` may silently change program behaviour; `--fix` is guaranteed to be behaviour-preserving.

**[ty](https://github.com/astral-sh/ty)** is a static type checker by Astral. It resolves all imports against the full installed dependency set.

**[vulture](https://github.com/jendrikseipp/vulture)** detects dead code such as unused functions, unreachable branches, and variables that are assigned but never read.

### Pre-commit Integration

All three tools are configured as hooks in `.pre-commit-config.yaml`. Install them once after cloning.

```bash
pip install pre-commit
pre-commit install
```
From that point on, every `git commit` runs ruff, vulture, and ty against the staged files automatically. To run all hooks explicitly against the full codebase.

```bash
pre-commit run --all-files
```
To skip the hooks for a specific commit, pass `--no-verify` to `git commit`. Use this sparingly as the CI pipeline enforces the same rules and will catch anything skipped locally.

!!! note
[ty](https://github.com/astral-sh/ty) does not yet have an official pre-commit hook ([astral-sh/ty#269](https://github.com/astral-sh/ty/issues/269)). The local hook in `.pre-commit-config.yaml` calls the system-installed `ty` binary directly. Make sure `ty` is installed in your virtual environment (`pip install ty`) before committing.

### Ruff Configuration

Ruff's rules, exclusions, and per-file overrides are managed through `pyproject.toml` in the project root. To suppress a specific violation on a single line, use a `# noqa` comment with the rule code:

```python
x = 1 # noqa: F841 # suppress unused-variable for this line only
i = 1 # noqa: E741, F841 # suppress multiple rules
x = 1 # noqa # suppress all violations on this line
```

!!! note
Use inline suppressions only when the violation is a known false positive or an intentional deviation. Document the reason in a comment where it is not obvious.

## Continuous Integration

PyHOPE runs a CI pipeline on every pull request. Only one pipeline run executes at a time per branch; a newer push cancels any run still in progress. The pipeline is structured in three stages that run in order.

**1. Lint**

The three tools described above run in parallel. All three must pass before the compatibility stage begins. Running `git commit` locally triggers the same checks automatically via pre-commit.

**2. Compatibility**

A matrix job runs the mesh generation pipeline against five Python versions: **3.10 – 3.14**. All versions run in parallel, so a failure on one version does not abort the others. Two representative tutorials are executed end-to-end:

```bash
pyhope tutorials/1-04-cartbox_multiple_stretch/parameter.ini
pyhope tutorials/2-02-external_mesh_CGNS_mixed/parameter.ini
```

**3. Verification**

| Job | Description |
| --- | --- |
| **examples** | Runs the full tutorial suite with coverage instrumentation |
| **healthcheck** | Runs PyHOPE's internal health checks: `coverage run -m pyhope --verify tutorials` |
| **convergence** | Runs FLEXI solver convergence tests in a dedicated Fedora container (`ghcr.io/hopr-framework/nrg-fedora`). Requires `GITHUB_TOKEN` to pull from the GitHub Container Registry |
| **coverage** | Merges `coverage.xml` artifacts from `examples` and `healthcheck`. On PRs targeting `main`, a bot posts a summary comment. Thresholds: **85% overall**, **90% for new code** |
52 changes: 52 additions & 0 deletions docs/developer-guide/github-workflow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# GitHub workflow

Overview of the development workflow

---

Code development is performed on [GitHub](https://github.com), with a protected `main` branch. The actual development is performed on feature branches, which are merged to `main` following a pull request and the completion of a code review. Continuous Integration (CI) tests are automatically performed on pull requests with a successful pass as a prerequisite for merging into `main`.

## Issues & Milestones
Issues are created for bugs, improvements, features, regression testing and documentation. The issues should be named with a few keywords. Try to avoid describing the complete issue already in the title. The issue can be assigned to a certain milestone (if appropriate).

Milestones are created based on planned releases (e.g. Release 1.2.1) or as a grouping of multiple related issues (e.g. Documentation Version 1, Clean-up Emission Routines). A deadline can be given if applicable. The milestone should contain a short summary of the work performed (bullet-points) as its contents will be added to the description of the releases. Generally, pull requests should be associated with a milestone containing a release tag, while issues should be associated with the grouping milestones.

As soon as a developer wants to start working on an issue, she/he shall assign himself to the issue and a branch and pull request denoted as work in progress ("draft") should be created to allow others to contribute and track the progress. Ideally, issues should be created for every code development for documentation purposes. Branches without an issue should be avoided to reduce the number of orphaned/stale branches. Where a branch must be created outside the issue context, its name should carry a prefix that matches one of the existing GitHub labels

```
bugfix.connect.internal
feature.sort.hilbertmorton
improvement.extrude.zones
```

Pull requests should include a concise description of what changed and why, a reference to the issue being resolved (e.g. `Closes #42`), and a note on anything that reviewers should pay particular attention to.

## Code Review
Every pull request requires at least one approving review before it can be merged. Reviewers should check at least the following points.

- The implementation is correct and consistent with the existing architecture
- New code follows the conventions in the [Style Guide](style-guide.md)
- Public functions carry type annotations and docstrings
- New functionality is covered by tests or tutorials
- The CI pipeline passes all checks without errors or warnings

Authors should respond to review comments promptly. If a comment is addressed by a code change, mark it as resolved. If it is addressed by explanation, reply in the thread and leave it for the reviewer to close.

## Commit Messages
Commit messages should be written in the imperative mood and describe *what* the commit does, not *what was done*:

```
# Good
Add convergence test for CGNS mixed mesh

# Avoid
Added convergence test
Fixed stuff
```


## Release and Deploy

Releases follow [Semantic Versioning](https://semver.org): `MAJOR.MINOR.PATCH`. A new release is created from `main` by opening a pull request associated with the corresponding release milestone. Once merged, navigate to [Releases](https://github.com/hopr-framework/PyHOPE/releases) and select `Draft a new release`. Set the tag to `vMAJOR.MINOR.PATCH` and the title to `Release MAJOR.MINOR.PATCH`. The milestone description supplies the release notes.

Tagged releases are automatically packaged as a Python wheel and deployed to [PyPI](https://pypi.org/project/PyHOPE) by the CI pipeline.
16 changes: 16 additions & 0 deletions docs/developer-guide/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Contributing to PyHOPE

How to get started as a developer

---

This guide describes how to develop PyHOPE. It covers the workflow, tooling, and conventions that keep the codebase consistent as the project grows.


The project is developed openly on GitHub under the GPL-3.0 license. Contributions of all kinds are welcome - bug reports, documentation improvements, new features, and convergence tests.

**Contents**

- [GitHub Workflow](github-workflow.md): Branch strategy, pull requests, code review, and release process
- [Environment Setup](get-the-source.md): Cloning, virtual environment, linting tools, and the CI pipeline
- [Style Guide](style-guide.md): Coding conventions, naming rules, and documentation standards
145 changes: 145 additions & 0 deletions docs/developer-guide/style-guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# Style Guide

Rules and recommendations for code style

---

A consistent style across the codebase reduces friction when reading and reviewing code written by others. Where a rule is marked _must_, it is mandatory; where it is marked _should_, it is a strong recommendation that may be deviated from only with good reason.

**Why do we need a style guide?**

- It creates a unified appearance and coding structure
- It makes the code more understandable and therefore important information is understood more easily
- It forces the developers to think more actively about their work

**General rules**

- Coding language: English
- A maximum of 132 characters are allowed per line (incl. comments)
- Indentation: 4 spaces (no tabs!)
- Line breaks in comments _must_ be followed by the next line indented appropriately

## Nested Functions
Code _should_ avoid nested functions. Nesting a function inside another function creates a new scope that is difficult to test in isolation, obscures the control flow, and can introduce subtle bugs through closure over mutable variables. Instead, helper logic _should_ be extracted into a separate, named function at module or class level.

**Avoid:**
```python
def GenerateMesh() -> None:
""" Generate the mesh
"""
def _build_cartesian():
# ... builds the mesh ...
pass

mesh = _build_cartesian()
```

**Prefer:**
```python
def MeshCartesian() -> meshio.Mesh:
""" Build and return a Cartesian mesh
"""
# ... builds the mesh ...

def GenerateMesh() -> None:
""" Generate the mesh
Mode 1 - Use internal mesh generator
Mode 2 - Readin external mesh through GMSH
"""
mesh = MeshCartesian()
```

## Object-Oriented Programming
Code _should_ follow object-oriented programming principles. Related data and the functions that operate on it _should_ be grouped into classes rather than kept as loose collections of module-level variables and free functions. Use `@dataclass` for lightweight data containers and reserve full class definitions for objects that carry behavior.

**Example:** Data container using `@dataclass`
```python
from dataclasses import dataclass
from typing import Optional
import numpy.typing as npt

@dataclass(init=True, repr=False, eq=False, slots=True)
class ELEM:
type : Optional[int] = None
zone : Optional[int] = None
elemID : Optional[int] = None
sides : Optional[list] = None
nodes : Optional[npt.NDArray] = None

# Comparison operator for bisect
def __lt__(self, other) -> bool:
return self.elemID < other.elemID
```

The `slots=True` argument instructs Python to use `__slots__` instead of the default per-instance `__dict__`. Normally, each instance stores its attributes in a dictionary, which is flexible but carries memory and access overhead. With `__slots__`, Python instead allocates a fixed, compact block of memory whose layout is fixed at class definition time.

**Example:** Class with properties and methods
```python
@singleton
class Common():
def __init__(self: Self) -> None:
self._program: Final[str] = self.__program__
self._version: Final = self.__version__

@property
@cache
def __version__(self) -> Version:
""" Retrieve the package version from installed metadata
"""
...
```

## Global Variables
Code _must_ avoid global variables if possible. Global mutable state makes the execution order matter in non-obvious ways, hinders testing, and creates hidden dependencies between modules. Where a value must be shared across modules, it _should_ be encapsulated in a dedicated variables module (e.g. `mesh_vars.py`) with a clear type annotation, so that the origin and type of each value is always explicit.

**Avoid:**
```python
# At the top of a file (hard to trace, easy to accidentally overwrite)
nGeo = 1
mesh = None
```

**Prefer**: Explicit module-level declarations with type annotations
```python
nGeo : int # Order of spline-reconstruction for curved surfaces
mesh : meshio.Mesh # MeshIO object holding the mesh
bcs : list[Optional['BC']] # Boundary conditions
```

Consumers then import the module by name, making the provenance of each value clear at every call site. Import _must_ use the full namespace path.
```python
import pyhope.mesh.mesh_vars as mesh_vars

mesh_vars.nGeo = GetInt('NGeo')
```

## Function Signatures
Functions _should_ always contain type annotations for the arguments and the return values. Functions _should_ contain a brief description in the header.
```python
def change_basis_3D(Vdm: npt.NDArray[np.float64], x3D_In: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]:
""" Interpolate a 3D tensor product Lagrange basis defined by (N_in+1) 1D interpolation point positions xi_In(0:N_In)
to another 3D tensor product node positions (number of nodes N_out+1)
defined by (N_out+1) interpolation point positions xi_Out(0:N_Out)
xi is defined in the 1D reference element xi=[-1,1]
"""
```
## Functions and Control Structures
User-defined functions and subroutines _should_ carry meaning in their name. If their name is composed of multiple words, they are to be joined together without underscores (`_`) and the first letter of each word _should_ be capitalised (camelCase). Furthermore, the words `Get`, `get`, `Is`, `is`, `Do`, `do` at the start of a function name signal the function's intent.

**Examples:**
```python
# "Get" — retrieves a typed parameter value from the configuration
def GetInt(name: str, default: Optional[str] = None, number: Optional[int] = None) -> int:
...

# "Is" — queries an environmental condition, returns bool
def IsInteractive() -> bool:
""" Check if the program is running in an interactive terminal
"""
...
```

Functions that neither retrieve a value nor test a condition _should_ use a descriptive verb that names the action performed, such as `Build`, `Write`, `Connect`, `Generate`, or `Check`.

## Workflow Description
In addition to the header description, a short workflow table of contents at the beginning of the file or function _should_ be included for longer subroutines in which multiple tasks are completed. The table of contents lists each logical step as a numbered comment, so a reader can grasp the overall structure before reading the body. Furthermore, the steps _must_ be found at the appropriate position within the code as a matching numbered comment. The step number _must not_ just appear once in the header and then disappear.
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ PyHOPE (Python High-Order Preprocessing Environment) is an open-source Python fr
<div class="text-center" style="margin-bottom: 2em;">
<a href="getting-started/" class="btn btn-primary btn-spacing" role="button">Getting Started</a>
<a href="user-guide/" class="btn btn-primary btn-spacing" role="button">User Guide</a>
<a href="developer-guide/" class="btn btn-primary btn-spacing" role="button">Developer Guide</a>
<a href="mesh-format/" class="btn btn-primary btn-spacing" role="button">Mesh Format</a>
</div>

Expand Down
5 changes: 5 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ nav:
- External: user-guide/mesh-generators/external.md
- Parameter File Format: user-guide/parameter-file.md
# - Issue Tracker: 'https://github.com/hopr-framework/PyHOPE/issues'
- Developer Guide:
- Introduction: developer-guide/index.md
- GitHub Workflow: developer-guide/github-workflow.md
- Environment Setup: developer-guide/get-the-source.md
- Style Guide: developer-guide/style-guide.md
- Mesh Format: mesh-format.md
- About:
- Authors: 'AUTHORS.md'
Expand Down
6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ enableExperimentalFeatures = false
python-preference = 'system'

[tool.ruff]
line-length = 132
indent-width = 4
force-exclude = true
# Exclude a variety of commonly ignored directories.
exclude = [
".git",
Expand All @@ -156,6 +159,9 @@ exclude = [
"site-packages",
"venv"
]
# Required Ruff version
required-version = ">=0.7.1"
show-fixes = true
# PyHOPE requires Python 3.10 or newer
target-version = "py310"

Expand Down
Loading