Thank you for considering a contribution to CoolSolve! This guide describes the workflow that every new feature, enhancement, or bug fix should follow so the codebase stays clean, consistent, and easy to maintain.
CoolSolve is a tightly integrated stack — a single new feature usually touches the parser, the solver, the GUI, the configuration file, the tests, the documentation, and the LaTeX/debug reports. Skipping any of these layers leads to silent regressions or to a feature that "works on the CLI but is invisible in the GUI". The checklists below are designed to prevent that.
Before writing any code, make sure you accept the following constraints. They are non-negotiable and apply to every contribution.
- Prefer simple solutions. Avoid clever code; readability beats brevity.
- No code duplication. Before adding new logic, search for similar functionality elsewhere in the codebase and reuse or extend it.
- Only make changes that are requested. Do not refactor unrelated code along the way; submit refactors as separate, focused contributions.
- Keep the codebase clean and organised. Files have well-defined responsibilities (see §3) — respect them.
- Document everything that is non-obvious. Use Doxygen-style comments on public C++ APIs and JSDoc-style comments on TypeScript exports. Avoid redundant comments that just restate the code.
- Never decrease computational efficiency. CoolSolve is performance
critical: CoolProp calls dominate runtime, and any new feature must be
either zero-overhead by default or hidden behind an opt-in flag in
SolverOptions. Profile before merging if you have any doubt. - Backward compatibility. New configuration keys must have sensible
defaults so that existing models and
coolsolve.conffiles keep working unchanged.
Every contribution should follow these phases in order. Do not skip phases.
┌──────────────┐ ┌─────────┐ ┌────────────────┐ ┌──────────────┐ ┌─────────────────┐
│ 1. Plan │ → │ 2. Code │ → │ 3. Targeted │ → │ 4. Full test │ → │ 5. Docs & │
│ (checklist) │ │ │ │ tests │ │ suite │ │ review │
└──────────────┘ └─────────┘ └────────────────┘ └──────────────┘ └─────────────────┘
Walk through the integration checklist in §3 and write down, for each applicable item, what needs to change and why. A typical contribution touches between four and eight items in that list. The output of this phase is a short Markdown plan you can paste into the pull request description.
Make the code changes. Follow §1 strictly. When introducing new public identifiers (functions, options, REST endpoints, GUI components), keep names consistent with the existing convention in the affected module.
Add or update Catch2 tests covering the new behaviour. Also add negative tests, where the feature should not work, and proper error reporting messages should be provided. Then run only those tests first:
cd build && cmake --build . -j$(nproc)
./coolsolve_tests "[my-new-feature]"Iterate on the implementation until the targeted tests pass. This loop is much faster than the full suite.
Once the targeted tests pass, run the full suite to catch regressions:
# Unit tests (parser, evaluator, solvers, config, …)
./coolsolve_tests
# Comprehensive example-file tests (solves all .eescode examples and
# verifies expected values + LHS≈RHS for every equation)
./coolsolve_tests "[examples-comprehensive]"
# Solver robustness suite (every example × every pipeline configuration)
./coolsolve_tests "[solver-robustness]"A contribution is not ready for review until all three of these pass on
your machine. Pay special attention to examples/test_examples.md and
examples/solver_robustness_report.md — large changes in iteration count or
runtime are red flags worth investigating before merging.
Even purely internal changes usually require a documentation pass — at the
very least the README.md features list and the language reference if a new
keyword/function/option is added. See §3 for the full list.
For every contribution, walk through this list and explicitly note which items apply. Mark each item as needed, not applicable, or already covered, and address every "needed" item before submitting the PR.
The order below roughly follows the data flow: source code → parser → solver → outputs → user-facing surfaces. Following this order when implementing a feature minimises rework.
If the feature introduces new syntax, keywords, operators, built-in functions, fluids, or thermophysical properties:
-
src/parser.cpp— extend the PEG grammar / lexer; add the new production and any keyword recognition (case-insensitive). -
include/coolsolve/ast.h— add new AST node kinds if needed. -
src/ir.cpp— handle the new AST node when building the IR (variable extraction, incidence matrix, LaTeX rendering). -
src/autodiff_node.cpp+include/coolsolve/autodiff_node.h— every new mathematical function MUST also implement its analytical derivative (forward-mode AD). Finite-difference fallbacks are considered a regression — see the principle in §1.7. -
src/variable_inference.cpp— if the feature implies a unit or a CoolProp property, extend the inference table. -
src/solution_checker.cpp— make sure new equation types are re-evaluated correctly during post-solve verification. -
tests/test_parser.cppandtests/test_evaluator.cpp— add unit tests for parsing and AD propagation.
If the feature changes the solver pipeline, adds a new solver, or modifies how blocks are decomposed:
-
include/coolsolve/solver.h— add new fields toSolverOptionswith sensible defaults (zero-overhead when disabled). -
src/solver.cpp— extendloadSolverOptionsFromFile()to parse the new keys fromcoolsolve.conf. Forgetting this step makes the option silently ignored. -
src/solver_*.cpp— new solvers go in their own translation unit (solver_<name>.cpp) following the pattern ofsolver_newton.cpp,solver_lm.cpp, … -
src/structural_analysis.cpp/src/solver_symbolic.cpp— update if block decomposition or symbolic reduction is affected. -
tests/test_solver_pipeline.cpp,tests/test_newton.cpp,tests/test_solver_robustness.cpp— add coverage. New solvers must be exercised by the robustness suite.
The canonical example file is examples/coolsolve.conf. Whenever
SolverOptions gains a new field:
- Add the new key to
examples/coolsolve.confcommented out (so it shows the default), with a clear comment block explaining: what it does, when to enable it, and any trade-offs. - Make sure
loadSolverOptionsFromFile()parses it (§3.2). - Add a regression test in
tests/test_config.cppto verify the key round-trips correctly.
The HTML configuration editor is the user-facing mirror of coolsolve.conf:
-
gui/src/components/ConfigEditor.tsx— add aConfigFieldentry for the new option, inside the appropriateConfigGroup. Use a clear label, the correct type (number/boolean/string), the same default value asSolverOptions, and a description matching the comment inexamples/coolsolve.conf. - If the option belongs to the solver pipeline, update
PIPELINE_PRESETSso the relevant presets remain consistent. - Verify the change in dev mode (
npm run dev) and in the built binary (coolsolve --gui).
Beyond the config editor, several other GUI surfaces may need updates:
-
gui/src/api/types.ts— extend the TypeScript types if the REST API response shape changes. -
gui/src/api/client.ts— add the corresponding API call. -
gui/src/stores/modelStore.ts/uiStore.ts— store new state fields (Zustand). -
gui/src/components/CodeEditor.tsx— only changes if editor behaviour itself changes. -
gui/src/languages/ees.ts— add new keywords, built-in functions, or fluid names so Monaco highlights them properly. -
gui/src/components/VariableTable.tsx,ArrayTable.tsx,ParametricStudy.tsx,ThermoDiagram.tsx,PlotlyChart.tsx,DebugViewer.tsx,Toolbar.tsx,Tooltip.tsx— update only the components that actually expose the feature.
If the feature has to be reachable from the GUI:
-
src/server.cpp— add or extend an HTTP route. Keep the handler thin: it should call intoCoolSolveRunnerexactly likemain.cppdoes, never reimplement core logic. -
include/coolsolve/server.h— update only if the publicServerOptionssurface changes. - Update SSE progress events if the feature changes solver progress semantics.
- Add at least one targeted test (Python or C++) that hits the new
route end-to-end. Existing example:
tests/test_parametric_api.py.
The GUI exchanges complete model state as a ZIP bundle through
/api/v1/files/upload and /api/v1/files/bundle:
-
src/server.cpp— if the feature persists user-editable data, add it to bothcreateZipBundle(export) andextractZipBundle(import). Keep file extensions stable; add new ones rather than reusing existing ones. - Confirm round-trip: download a bundle, upload it back, verify the session state is identical.
- If you change the bundle layout, document the new convention here
and in
docs/gui.md.
CoolSolve's debug mode is the contract by which advanced internals become inspectable. Anything that produces analytical output must integrate into the debug folder rather than printing to stdout.
-
src/runner.cpp→CoolSolveRunner::generateDebugOutput()— write a new<feature>.mdfile in the debug folder. Follow the existing pattern: a# Title, a brief explanation paragraph, then tables/sections with the analytical content. - Update the debug index (the table written into
README.mdof the debug folder at the bottom ofgenerateDebugOutput()) so the new file appears with a one-line description. - Also update the public debug-folder table in the main
README.md("The debug folder contains:" section) so users can discover the new file from the project landing page. - Markdown output in debug mode must be deterministic when possible (sorted keys, fixed precision) so debug folders can be diffed across runs.
CoolSolve uses three message channels with distinct rules:
| Channel | Use for |
|---|---|
stdout |
The actual program output (JSON / LaTeX / text). |
stderr |
Progress, warnings, and human-readable errors. |
Debug-mode .md files |
Detailed analytical artefacts (see §3.8). |
Add user-facing messages with the right channel:
- User-visible errors must be emitted as diagnostics
(
include/coolsolve/diagnostic.h→DiagnosticCollector) so they appear in both the CLI stderr stream and the GUI Console with the correct severity. - In the GUI, the
Consolecomponent (gui/src/components/Console.tsx) classifies lines based on keywords (ERROR,[Warning],SUCCESS, …). Use these markers consistently so colour-coding works. - Never print verbose internals to stdout — that would corrupt the
JSON/LaTeX output. Verbose internals belong in
-ddebug files.
The comprehensive LaTeX report is generated after a successful solve.
-
src/latex_report.cpp+include/coolsolve/latex_report.h— if the feature contributes new analytical content (new equations, new variable categories, new diagnostic plots), include it in the report. The report must compile with plainpdflatexand degrade gracefully when optional plot files are missing (use\IfFileExists). -
tests/test_latex_report.cpp— add a regression test that the generated.texcontains the new section/macro and is well-formed. - When working on the report locally, compile with:
pdflatex -synctex=1 -interaction=nonstopmode <file>.tex.
CoolSolve's strongest regression net is the suite of .eescode examples.
- If the feature unlocks a new modelling pattern, add a minimal
examples/<feature_name>.eescodethat exercises it. - Add an entry to
EXPECTED_SOLUTIONSintests/test_examples.cppwith a known target value and a 1 % tolerance. Without this entry the example is parsed but its result is not validated. - If the example needs initial guesses, add a
examples/<feature_name>.initialsfile.
The README.md and the docs/ folder are served verbatim by the GUI
binary (see docs/documentation_strategy.md), so any change there is
immediately visible to users.
-
README.md— update the Features bullet list, the Command Line Options table, the Project Structure tree (if files were added), the Documentation table (if a new doc page was added), and the File Formats / Debug folder tables when applicable. -
docs/language_reference.md— required for any new keyword, operator, built-in function, fluid, or thermophysical property. Only document features that are implemented and tested. -
docs/debugging_models.md— extend if the feature introduces a new failure mode or a new diagnostic file. -
docs/solver_roadmap.md— update if a roadmap item is now delivered, or if the new feature replaces a planned approach. -
docs/gui.md— required for any GUI-visible change. -
docs/symbolic_redecomposition.md— update only if the symbolic-reduction algorithm itself changes. -
docs/docs.html— if you add a brand-new.mdpage todocs/, append it to the sidebar nav so the in-app docs viewer can find it.
-
CMakeLists.txt—src/*.cppandtests/*.cppare picked up automatically byGLOB_RECURSE, so most contributions need no build-system change. Only update CMake when adding a new dependency, a new build option, or non-glob sources (e.g. resource files). - If you add a dependency, prefer
FetchContentand pin a tag. Update the Dependencies table inREADME.md. - Verify the Windows build path still works:
build_installer.batandcoolsolve.nsishould not need changes for normal feature additions, but should be regenerated and tested for installer-level changes.
- Follow the existing file structure: declarations in
include/coolsolve/*.h, definitions insrc/*.cpp. - Public APIs are commented with Doxygen blocks (
/** … */). - Prefer
constandnoexceptwhere they apply. - Avoid raw pointers for ownership; use
std::unique_ptr/std::shared_ptr. - Use the existing
coolsolve::namespace for all new symbols. - New numerical algorithms must include a one-paragraph comment naming
the source paper or textbook reference (see
solver_lm.cppfor the pattern).
- Functional components with hooks; no class components.
- State lives in Zustand stores (
gui/src/stores/), never in component internal state if it crosses component boundaries. - API calls go through
gui/src/api/client.ts; do not callfetchdirectly from components. - Prefer existing CSS variables (light/dark theme support) over hard-coded colours.
- Use sentence case in headings.
- Wrap lines at ~80 characters where reasonable.
- Use code fences with language tags.
- Reference files with backticks (
`src/solver.cpp`).
CoolSolve must remain a fast solver. The following rules apply to every contribution:
- No new allocations in inner loops. The Newton, LM, and
TrustRegion solvers are called millions of times — any new code path
reachable from
BlockEvaluator::evaluateBlock()or the residual evaluation routines must be benchmarked. - Default off for any new analysis pass. If your feature adds
pre-processing or post-processing work, gate it behind a
SolverOptionsflag that defaults tofalse(seeenableSymbolicReductionfor the canonical pattern). Document explicitly that "when disabled, zero overhead is added". - CoolProp calls are expensive. Cache derived quantities; use the
thread-local
AbstractStatecache (coolpropCacheEnabled) rather than going throughPropsSI. - Compare runtimes before and after. Run
./coolsolve_tests "[solver-robustness]"and inspectexamples/solver_robustness_report.mdfor per-example iteration and timing changes. Anything beyond ±10 % on the established models needs justification in the PR description. - Release builds only for benchmarks. Debug builds are 10–50× slower (see Build Type: Release vs Debug in the README); never benchmark in Debug mode.
Copy the block below into your PR description and tick each box.
### Integration checklist (see docs/contributing.md §3)
- [ ] §3.1 Language / parser / AST / IR / AD / inference / solution checker
- [ ] §3.2 Solver pipeline / SolverOptions / loadSolverOptionsFromFile
- [ ] §3.3 examples/coolsolve.conf entry + test_config coverage
- [ ] §3.4 GUI ConfigEditor entry + presets
- [ ] §3.5 GUI components, API types & client, stores, EES Monaco language
- [ ] §3.6 REST endpoints in src/server.cpp
- [ ] §3.7 ZIP bundle round-trip (createZipBundle / extractZipBundle)
- [ ] §3.8 Debug-folder Markdown file + index entry
- [ ] §3.9 stdout / stderr / GUI Console wiring via Diagnostics
- [ ] §3.10 LaTeX report contribution + test_latex_report
- [ ] §3.11 New example .eescode + EXPECTED_SOLUTIONS entry
- [ ] §3.12 README + language_reference + relevant docs/*.md
- [ ] §3.13 CMakeLists.txt / dependency table (only if needed)
### Tests run locally
- [ ] `./coolsolve_tests` passes
- [ ] `./coolsolve_tests "[examples-comprehensive]"` passes
- [ ] (if GUI changed) Manual smoke test in `coolsolve --gui`
### Performance discipline (see §5)
- [ ] No new allocations in inner solver loops
- [ ] Any new pass is opt-in with default off
- [ ] Robustness report iteration counts within ±10 % of baselineWhen in doubt, the table below tells you where the canonical owner of a concern lives.
| Concern | Canonical file(s) |
|---|---|
| Parsing CoolSolve syntax | src/parser.cpp, include/coolsolve/ast.h |
| Equation graph / blocks | src/ir.cpp, src/structural_analysis.cpp |
| Automatic differentiation | src/autodiff_node.cpp |
| Block evaluation | src/evaluator.cpp |
| Solver pipeline | src/solver.cpp |
| Per-strategy solvers | src/solver_<name>.cpp |
coolsolve.conf parsing |
loadSolverOptionsFromFile() in src/solver.cpp |
| Static config example | examples/coolsolve.conf |
| Solver options struct | include/coolsolve/solver.h |
| Post-solve verification | src/solution_checker.cpp |
| Debug-folder generation | CoolSolveRunner::generateDebugOutput() in src/runner.cpp |
| LaTeX report | src/latex_report.cpp |
| HTTP server / REST endpoints | src/server.cpp |
| ZIP bundle import/export | createZipBundle / extractZipBundle in src/server.cpp |
| GUI config editor | gui/src/components/ConfigEditor.tsx |
| GUI Monaco syntax highlighting | gui/src/languages/ees.ts |
| GUI Zustand stores | gui/src/stores/modelStore.ts, uiStore.ts |
| GUI REST client | gui/src/api/client.ts, types.ts |
| GUI Console | gui/src/components/Console.tsx |
| Comprehensive example tests | tests/test_examples.cpp (EXPECTED_SOLUTIONS) |
| Solver robustness tests | tests/test_solver_robustness.cpp |
| Configuration parsing tests | tests/test_config.cpp |
| Documentation strategy | docs/documentation_strategy.md |
| In-app docs sidebar | docs/docs.html |
If you find a bug but do not have time to fix it, please file an issue that includes:
- The CoolSolve version (
./coolsolve --helpshows it on the first line). - The minimal
.eescodereproducer (and.initialsif relevant). - The output of
./coolsolve -d <model>.eescode— the debug folder is the single most useful artefact for triage. Attach it as a ZIP. - The expected vs. observed behaviour.
For solver-convergence bugs, also attach the relevant section of
solver_robustness_report.md if available, and read
Debugging Models before opening the issue.
Thanks again for contributing! Following this guide keeps CoolSolve fast, correct, and pleasant to maintain.
This section documents the steps to publish a new numbered release of
CoolSolve (e.g. v0.3, v1.0, …). Follow them in order.
CoolSolve uses semantic versioning with two components: MAJOR.MINOR.
- Increment MINOR for any release that adds new features without breaking
existing
.eescodemodels orcoolsolve.conffiles. - Increment MAJOR only if the release breaks backward compatibility (e.g. the language syntax changes in an incompatible way).
The version is declared in one authoritative place and propagated everywhere else at build time. Edit only the files below.
CMakeLists.txt — change the VERSION field in project():
project(CoolSolve VERSION 0.3.0 CXX)coolsolve.nsi — change the !define VERSION line:
!define VERSION "0.3"
!define COOLPROP_VERSION "<new-version>"coolsolve.rc (Windows resource file) — change both the binary version
fields and the string values (use four-component format):
FILEVERSION 0,3,0,0
PRODUCTVERSION 0,3,0,0
…
VALUE "FileVersion", "0.3.0.0"
VALUE "ProductVersion", "0.3.0.0"
All other places (--help output, Windows installer title, Add/Remove
Programs entry) derive the version automatically from these three files.
Check which CoolProp commit is included in this build:
cd .fetchcontent_cache/coolprop-src
git log --oneline -1
git describe --tagsNote the commit hash and version string — you will need them in §9.5.
cd build
cmake --build . -j$(nproc)
./coolsolve_tests
./coolsolve_tests "[examples-comprehensive]"
./coolsolve_tests "[solver-robustness]"All three must pass. Check examples/solver_robustness_report.md for
unexpected changes in iteration counts or runtimes (see §5).
Edit docs/versions.md:
- Add a new
## vX.Y — Month Year (current)section at the top with:- The Windows installer download link (GitHub Releases URL — see §9.7).
- The CoolProp commit hash and version string from §9.3.
- A bullet list of the main changes since the previous release.
- Remove the
(current)label from the previous version's heading.
Keep the v0.1 entry permanently at the bottom as the historical baseline.
Also update the Try CoolSolve section in README.md to point to the new
version's installer link.
The download URL follows a fixed pattern once the tag is chosen:
https://github.com/CoolProp/CoolSolve/releases/download/vX.Y/CoolSolve_vX.Y_Installer.exe
You can set this link in the docs before publishing the release (the URL is deterministic). The installer built in §9.6 will not embed the updated docs — that is expected and harmless.
On a Windows machine with Visual Studio 2022, Python 3, Node.js, and NSIS:
build_installer.batThis produces CoolSolve_vX.Y_Installer.exe in the project root. Do not
commit the .exe to git; it is attached to the GitHub Release in §9.7.
Commit the documentation updates from §9.5, then create and push the tag:
git add docs/versions.md README.md
git commit -m "docs: add vX.Y GitHub release download link"
git tag -a vX.Y -m "CoolSolve vX.Y"
git push origin main
git push origin vX.YCreate the release and upload the installer with the GitHub CLI
(gh must be installed and authenticated — gh auth login):
gh release create vX.Y \
--repo CoolProp/CoolSolve \
--title "CoolSolve vX.Y" \
--notes-file docs/versions.md \
CoolSolve_vX.Y_Installer.exeOn Windows (PowerShell), use backtick line continuations or a single line:
gh release create vX.Y --repo CoolProp/CoolSolve --title "CoolSolve vX.Y" `
--notes-file docs/versions.md CoolSolve_vX.Y_Installer.exeThe release page will be at
https://github.com/CoolProp/CoolSolve/releases/tag/vX.Y.
Older releases (v0.1, v0.2) may still point to the legacy dox.uliege.be host; new releases should use GitHub Releases exclusively.
- [ ] Version bumped in CMakeLists.txt, coolsolve.nsi, coolsolve.rc
- [ ] Full test suite passes (unit + examples-comprehensive + solver-robustness)
- [ ] docs/versions.md updated (new section, CoolProp version, changelog)
- [ ] README.md "Try CoolSolve" section updated to the new version
- [ ] Windows installer built with build_installer.bat (not committed to git)
- [ ] Git tag created and pushed
- [ ] GitHub Release published with installer attached (gh release create)