# Coding style

OOWL code should be simple, explicit, predictable, and scientifically
interpretable. Prefer the smallest implementation that clearly expresses the
underlying idea.

Ruff controls formatting, imports, and other mechanical style. This guide
covers decisions that tools cannot make. **Must** marks a project requirement,
and **should** marks the normal choice when judgment is involved. The domain
model is described separately in the [design philosophy](design.md).

## Understand the existing code

Before editing, inspect the relevant implementation, public API, tests, and user
documentation. Search for existing concepts and helpers before introducing a
new one. Follow nearby conventions when several approaches are equally clear.

Put behavior with the concept that owns it, so boundary logic belongs under
`oowl.boundaries`, placement under `oowl.layouts`, and so on. Reuse OOWL's
domain objects instead of creating a parallel representation. Research scripts,
fitting workflows, and one-off data processing do not belong in the runtime
package.

Consistency is useful, but it does not justify copying a poor pattern. If the
surrounding code has a broader problem, keep the requested change focused
unless fixing that problem is necessary for a clean implementation.

## Keep implementations simple

Use a function for a calculation or transformation that has no meaningful
state. Use a class for a domain concept with its own data and behavior. Use a
dataclass for a genuine data record, not merely to group unrelated functions.

Do not introduce an interface, factory, registry, plugin system, helper, or
configuration option without a current need. Extract a helper when it removes
duplication, clarifies the main calculation, or isolates logic worth testing.
An explicit five-line calculation is often clearer than a generic abstraction
used once.

Generalize when doing so makes the current implementation simpler. Do not
design around hypothetical future requirements. Prefer existing dependencies
and the Python standard library. A new runtime dependency should replace
substantial or fragile local code, not provide a small convenience.

## Design clear APIs

Names should describe scientific or geometric meaning. Use OOWL's vocabulary
consistently and avoid unexplained abbreviations in public APIs. Short
mathematical names are acceptable inside a small calculation when their meaning
is clear from the equation and context.

Public functions and methods must be type annotated. Type private helpers when
the annotation clarifies a non-obvious input or result, but avoid typing
machinery that obscures straightforward numerical code.

Use ordinary Python values in public APIs when that keeps calls simple. Make
secondary options keyword-only when positional calls would be unclear or
fragile. Avoid boolean arguments whose meaning is hidden at the call site. Do
not expose a parameter unless users can understand what changing it does.

Return an existing OOWL object when the result has OOWL semantics. Internal
calculations should normally use NumPy arrays rather than wrapping every
intermediate value in another class. Convert array-like input near the boundary
of a calculation and then use one representation consistently.

Public objects must be deliberately exported from the appropriate package.
Private implementation details begin with an underscore. Do not make a helper
public only so a test can call it, and avoid mutable default arguments.

## Write clear numerical and scientific code

OOWL uses SI units, except that public angles are expressed in degrees. Convert
angles close to the trigonometric calculation, and document a different
convention when required by an external format.

Important array shapes should be explicit. Turbine positions, for example,
have shape `(N, 2)`. Empty results should preserve the same dimensional meaning
as populated results. Convert numerical input with an explicit floating-point
dtype when the calculation requires it, and reject unexpected NaN or infinity.

Prefer NumPy vectorization when it is clearer or materially faster. A short
Python loop is better than complicated broadcasting that hides the method.
Use SciPy and Shapely for behavior they already implement well rather than
recreating approximate versions locally.

Numerical tolerances must fit the calculation. Geometry, probabilities,
coordinate transforms, and fitted models operate at different scales, so one
universal tolerance is inappropriate. Use `pytest.approx`, `np.allclose`, or
`np.testing.assert_allclose` with explicit tolerances when precision matters.
Never loosen a tolerance merely to make a failing test pass.

The source should reveal the scientific idea. Use names that correspond to the
quantities in the method and keep equations near the explanation or source that
defines them. Explain constants taken from literature, fitting, or numerical
analysis. Cite a paper, standard, dataset, or authoritative implementation
where a future contributor will need it.

Comments should explain why an equation, special case, convention, assumption,
or limitation exists. Do not translate an obvious line of code into English.
Public model parameters should have understandable effects. Internal optimizer
settings and fitted constants should remain internal unless users have a reason
to control them.

## Validate behavior at public boundaries

Validate public input before NumPy or Shapely turns it into an obscure error.
Internal helpers may rely on validation already performed by their public
caller.

Use `TypeError` for the wrong kind of object and `ValueError` for an invalid
value, range, or shape. A numeric parameter given a string, `None`, a boolean
or a container raises `TypeError`, and a real number that is non-finite, out of
range or not whole where a count is due raises `ValueError`. The helpers in
`oowl/_validation.py` apply this rule. Error messages should identify the
parameter and its requirement. Do not silently discard invalid values, repair
geometry, reinterpret coordinates, replace NaNs, or substitute a fallback. If
such behavior is useful, make it explicit.

Avoid broad exception handling unless the code can recover in a defined way or
add useful context before re-raising.

## Randomness, metadata, and export

Stochastic code must use `numpy.random.Generator` through OOWL's random helpers,
never global NumPy state or Python's module-level `random` functions. Resolve
randomness once, record the resolved seed, and use deterministic child seeds for
independent operations. An explicit seed must replay the result on the supported
implementation.

Tests should normally verify that the same seed reproduces the same result and,
when meaningful, that a different seed changes it. Do not test an unseeded call
against one particular sample.

Metadata must use JSON-compatible values and contain only information needed to
understand or reproduce a result. Convert NumPy arrays and scalars before
storing them. Do not record every intermediate variable. Exporters must map
data explicitly into windIO or another target schema rather than copying
metadata wholesale.

Packaged fitted models are reviewed scientific outputs. Their model-specific
documentation should describe the source data, preprocessing, assumptions,
fitting, and validation. Runtime artifacts must use a documented, versioned,
pickle-free format. Raw data, builders, studies, and fitting-only dependencies
remain outside the installed package.

## Performance and dependencies

> **Measure before optimizing.** Prefer clear code until measurement identifies
> a meaningful bottleneck.

In code shown to matter for runtime, avoid unnecessary repeated geometry
operations, allocations, conversions, and Python-level work. When an optimized
implementation is difficult to inspect, keep a simple reference calculation in
a test where practical. Add caching only after considering when cached values
become invalid.

## Test and document observable behavior

Tests should describe the public contract, not reproduce the implementation.
Cover the normal case, important invalid inputs, and edge cases introduced by
the change. Useful numerical invariants include shape, finiteness,
normalization, containment, exact turbine count, and reproducibility. A bug fix
needs a regression test that fails for the original bug.

Use exact expected values when they are meaningful, but do not rely only on
snapshots when the real contract is an invariant. Never skip a failing test,
suppress a warning, weaken an assertion, or disable a check unless the old
expectation has been shown to be wrong.

Public modules, classes, functions, and methods need concise docstrings. Begin
with what the object represents or does. Document non-obvious units, shapes,
defaults, randomness, return behavior, and important errors. Tutorials belong
in the user documentation.

Documentation examples must use public APIs and fixed seeds. Each code cell
should teach one main outcome. Canonical notebooks are committed without stored
outputs or execution counts and executed during the documentation build.
Documentation prose uses neither semicolons, em dashes, nor colons. Docstrings
follow the same rule for their prose, keeping colons only where Sphinx needs
them, as in parameter and role syntax. Prose, docstrings, comments and code
names use US spelling (center, color, neighbor, optimize, behavior).

## Keep changes focused

Make the smallest coherent change that solves the problem. Do not use a feature
as an opportunity to rename unrelated APIs, reorganize neighboring modules,
rewrite unrelated tests, regenerate unrelated notebooks, or update
dependencies. A local refactor is appropriate when the implementation needs it
or when it makes the changed behavior substantially clearer or easier to test.

Before version 1.0, prefer a clean, documented API change over an automatic
compatibility alias. Update callers, tests, documentation, and the changelog
together.

Before submitting, check the following.

- the implementation follows OOWL's domain model and is no more complicated
  than necessary
- public inputs, units, errors, randomness, and results are clear
- scientific assumptions and non-obvious constants are explained
- tests cover the changed behavior and relevant invariants
- user-visible behavior is documented
- no unrelated abstractions, dependencies, or files were introduced

The setup, validation commands, and merge-request workflow are listed in
[Contributing](contributing.md).
