|
| 1 | +# Sienna Programming Practices |
| 2 | + |
| 3 | +General programming practices and conventions that apply across all Sienna packages (PowerSystems.jl, PowerSimulations.jl, PowerFlows.jl, PowerNetworkMatrices.jl, InfrastructureSystems.jl, etc.). This file is intended to be **identical across every Sienna repository** — package-specific guidance belongs in that package's `CLAUDE.md`, not here. |
| 4 | + |
| 5 | +## Performance Requirements |
| 6 | + |
| 7 | +**Priority:** Critical. See the [Julia Performance Tips](https://docs.julialang.org/en/v1/manual/performance-tips/). Apply with judgment — focus optimization on hot paths and frequently called code, not every function. |
| 8 | + |
| 9 | +### Anti-Patterns to Avoid |
| 10 | + |
| 11 | +- **Type instability** — functions must return consistent concrete types. Check with `@code_warntype`. Bad: `f(x) = x > 0 ? 1 : 1.0`; good: `f(x) = x > 0 ? 1.0 : 1.0`. |
| 12 | +- **Abstract field types** — struct fields must be concrete or parameterized. Bad: `struct Foo; data::AbstractVector; end`; good: `struct Foo{T<:AbstractVector}; data::T; end`. |
| 13 | +- **Untyped containers** — use `Vector{Float64}()`, not `Vector{Any}()` / `Vector{Real}()`. |
| 14 | +- **Non-const globals** — use `const THRESHOLD = 0.5`. (No type annotation needed on a `const`; the compiler already infers it — annotating gives no precompilation benefit.) |
| 15 | +- **Unnecessary allocations** — use views (`@view`/`@views`), pre-allocate instead of `push!` in loops, use in-place (`!`) operations. |
| 16 | +- **Captured variables** — avoid closures that box captured variables; pass them as arguments instead. |
| 17 | +- **Splatting penalty** — avoid `...` in performance-critical code. |
| 18 | +- **Abstract return types** — avoid returning `Union`s or abstract types. |
| 19 | + |
| 20 | +#### Runtime type checking (`isa` and `<:`) — the canonical rule |
| 21 | + |
| 22 | +**ABSOLUTELY FORBIDDEN unless the user explicitly asks for it.** Never use `isa` or `<:` (subtype) checks to branch on types in a function body — use multiple dispatch instead. Using `<:` to branch is just `isa` with extra steps. |
| 23 | + |
| 24 | +- Bad: `if x isa Float64 ... elseif x isa Int ... end` |
| 25 | +- Bad: `if typeof(x) <: AbstractVector ... end` |
| 26 | +- Bad: `if T <: SomeAbstractType ... else ... end` (branching on a type parameter) |
| 27 | +- Good: `f(x::AbstractVector) = sum(x); f(x::Number) = x` |
| 28 | + |
| 29 | +**Why:** runtime type checks force the compiler to handle multiple paths at runtime, lose type information, prevent specialization, and trigger runtime compilation — defeating Julia's performance model. The only acceptable use of `isa` is filtering inside a `catch` block, where dispatch is unavailable. |
| 30 | + |
| 31 | +### Best Practices |
| 32 | + |
| 33 | +- Use `@inbounds` when bounds are verified; use broadcasting for element-wise ops. |
| 34 | +- Avoid `try-catch` in hot paths; use function barriers to isolate type instability. |
| 35 | + |
| 36 | +## Code Conventions |
| 37 | + |
| 38 | +Style guide: <https://sienna-platform.github.io/InfrastructureSystems.jl/stable/style/> |
| 39 | + |
| 40 | +**Always run the formatter after completing each task — before reporting it done. This is not optional.** Run the package's formatter script (the script self-activates its own environment): |
| 41 | + |
| 42 | +```sh |
| 43 | +julia --project=scripts/formatter -e 'include("scripts/formatter/formatter_code.jl")' |
| 44 | +``` |
| 45 | + |
| 46 | +This applies after any change to `.jl` files. Treat the formatter's output as authoritative; do not manually revert its changes. |
| 47 | + |
| 48 | +Key rules: |
| 49 | + |
| 50 | +- Constructors: use `function Foo()`, not `Foo() = ...` |
| 51 | +- Asserts: prefer `InfrastructureSystems.@assert_op` over `@assert` |
| 52 | +- Globals: `UPPER_CASE` for constants; exports: all in the main module file |
| 53 | +- Comments: complete sentences; describe why, not how |
| 54 | +- Nothing checks: use `isnothing(x)` / `!isnothing(x)`, not `x === nothing` / `x !== nothing` |
| 55 | +- Type checks: use multiple dispatch, never `isa`/`<:` branching — see the runtime type-checking rule above |
| 56 | +- Conditionals: prefer `if/else` over the ternary `? :`, especially in multi-line expressions |
| 57 | +- Cache lookups: use the lazy closure form `get!(dict, key) do ... end` (only evaluates on a miss). Never use 3-arg `get!(dict, key, default)` when `default` is expensive — Julia evaluates arguments eagerly, so `default` runs on every call and silently defeats the cache. |
| 58 | + |
| 59 | +## Documentation Practices and Requirements |
| 60 | + |
| 61 | +Framework: [Diataxis](https://diataxis.fr/). Sienna guides: |
| 62 | + |
| 63 | +- Explanation / best practices: <https://sienna-platform.github.io/InfrastructureSystems.jl/stable/docs_best_practices/explanation/> |
| 64 | +- Tutorials: <https://sienna-platform.github.io/InfrastructureSystems.jl/stable/docs_best_practices/how-to/write_a_tutorial/> (script format via Literate.jl: <https://fredrikekre.github.io/Literate.jl/v2/>) |
| 65 | +- How-to's: <https://sienna-platform.github.io/InfrastructureSystems.jl/stable/docs_best_practices/how-to/write_a_how-to/> |
| 66 | +- API docstrings: <https://sienna-platform.github.io/InfrastructureSystems.jl/stable/docs_best_practices/how-to/write_docstrings_org_api/> |
| 67 | + |
| 68 | +Docstrings: cover all public-interface elements (IS is selective about exports); include signatures + argument lists; automate with `DocStringExtensions.TYPEDSIGNATURES` (`TYPEDFIELDS` sparingly); add "see also" links for same-named (multiple-dispatch) functions. API docs: public in `docs/src/api/public.md` via `@autodocs` (`Public=true, Private=false`); internals in `docs/src/api/internals.md`. |
| 69 | + |
| 70 | +**The documentation must build to succeed.** Before considering any documentation-affecting task complete, confirm the docs build cleanly — a broken docs build is a task failure, not a warning. Documenter treats missing docstring references, broken `@ref` links, and failing doctests as errors: |
| 71 | + |
| 72 | +```sh |
| 73 | +julia --project=docs -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' # first time |
| 74 | +julia --project=docs docs/make.jl # must finish without errors |
| 75 | +``` |
| 76 | + |
| 77 | +Where the package provides a docstring-coverage checker, also run it so every exported symbol is documented (this is enforced in CI): |
| 78 | + |
| 79 | +```sh |
| 80 | +julia --project=test scripts/check_docstrings.jl <PackageName> |
| 81 | +``` |
| 82 | + |
| 83 | +## Design Principles |
| 84 | + |
| 85 | +- Elegance and concision in both interface and implementation |
| 86 | +- Fail fast with actionable error messages rather than hiding problems |
| 87 | +- Validate invariants explicitly in subtle cases |
| 88 | +- Avoid over-adherence to backwards compatibility for internal helpers |
| 89 | + |
| 90 | +## Contribution Workflow |
| 91 | + |
| 92 | +**The default branch for all Sienna packages is `main`, not `master`.** Branch naming: `feature/description` or `fix/description`. Workflow: create a feature branch → follow the style guide and run the formatter → ensure tests pass → submit a pull request. |
| 93 | + |
| 94 | +## Testing Guidelines |
| 95 | + |
| 96 | +**Test custom logic, not language guarantees.** Do not write tests that only verify Julia's built-in behavior. |
| 97 | + |
| 98 | +Avoid: `@test obj isa SomeType` when the type hierarchy makes it tautological; testing that a plain data-holder struct stores the value it was constructed with; testing `==`/`isequal`/`hash` inherited from a parent with no added logic; duplicating a test with trivially different inputs that exercise no new code path. |
| 99 | + |
| 100 | +Instead test: custom dispatch logic and predicates you defined; type-mapping tables and accessors (where typos hide); serialization round-trips; custom `show`/display formatting; validation logic, error paths, and edge cases. |
| 101 | + |
| 102 | +## Julia Environment Best Practices |
| 103 | + |
| 104 | +**CRITICAL: always run Julia with `julia --project=<env>`** — never bare `julia` or `julia --project` without specifying the environment, or required packages won't be available. Each package defines its environments under `test/`, `docs/`, and `scripts/formatter/`. |
| 105 | + |
| 106 | +```sh |
| 107 | +julia --project=test test/runtests.jl # full test suite |
| 108 | +julia --project=test test/runtests.jl test_file_name # a single test file |
| 109 | +julia --project=test -e 'using Pkg; Pkg.instantiate()' # instantiate test env |
| 110 | +julia --project=docs docs/make.jl # build docs |
| 111 | +``` |
| 112 | + |
| 113 | +(See each repo's `CLAUDE.md` for its exact, verified commands and test-runner style.) |
| 114 | + |
| 115 | +## AI Agent Guidance |
| 116 | + |
| 117 | +**Priorities:** read existing patterns first; maintain consistency; use concrete types in hot paths; add docstrings to public API; consider downstream-package impact; ensure tests pass. **Then run the formatter and never edit auto-generated files.** The two rules most often violated: |
| 118 | + |
| 119 | +- **Never use `isa`/`<:` for runtime type branching** — use multiple dispatch (see the canonical rule above). |
| 120 | +- **Always run the formatter** (`julia --project=scripts/formatter -e 'include("scripts/formatter/formatter_code.jl")'`) before reporting a task done. |
| 121 | + |
| 122 | +## Troubleshooting |
| 123 | + |
| 124 | +- **Tests fail unexpectedly / packages missing:** re-instantiate — `julia --project=test -e 'using Pkg; Pkg.instantiate()'`. |
| 125 | +- **Poor performance, many allocations:** run `@code_warntype` on the suspect function (see the performance anti-patterns above). |
0 commit comments