CoolSolve is a parser, structural analyzer, and equation evaluator for the EES (Engineering Equation Solver) language, designed to be the foundation of an open-source equation solver that integrates with CoolProp for thermodynamic property calculations.
-
EES Parser: Parses EES source code (.eescode files) into an Abstract Syntax Tree
- Variables (scalars and arrays like
P[1]) - Equations with operators (
+,-,*,/,^) - Procedural Statements: Support for
=and:=in procedural blocks - Functions and Procedures:
FUNCTIONandPROCEDUREblocks with local scoping - Procedure Calls:
CALL name(inputs : outputs)syntax - Conditional Logic:
IF-THEN-ELSEsupport within procedural blocks - Comments (
"...",{...},//...) - Directives (
$ifnot,$endif, etc.) - Units annotations (
"[Pa]","[kJ/kg]") - Function calls with named arguments (
enthalpy(R134a, T=300, P=1E5))
- Variables (scalars and arrays like
-
Structural Analysis: Decomposes the equation system into solvable blocks
- Hopcroft-Karp algorithm for variable-equation matching
- Tarjan's algorithm for Strongly Connected Components (SCCs)
- Block decomposition (identifies algebraic loops)
-
Automatic Differentiation: Forward-mode AD for efficient Jacobian computation
- Full support for arithmetic operators (
+,-,*,/,^) - Transcendental functions (
sin,cos,exp,log,sqrt, etc.) - Exact analytical derivatives (no finite differences)
- Prepares for Newton-based numerical solvers
- Full support for arithmetic operators (
-
Equation Evaluator: Evaluates residuals and Jacobians for each block
- Block-level evaluation with external variable support
- System-level orchestration for sequential block solving
- CoolProp integration for thermodynamic property calculations
- Automatic temperature conversion (Celsius ↔ Kelvin)
- Configurable Solver Pipeline with multiple algorithms and execution modes
- Explicit solve for size-1 blocks: Bypasses Newton entirely for structurally explicit assignments (one residual evaluation, no Jacobian)
- Newton + Line Search for fast convergence on well-conditioned blocks
- Trust-Region Dogleg for robust convergence on stiff nonlinear blocks
- Levenberg-Marquardt for improved convergence when initial guesses are poor
- Partitioned Block Updates as a fallback for ill-conditioned algebraic loops
- Parallel solver execution (multithreaded, first-to-converge wins)
-
Output Formats:
- JSON (for automated testing and integration)
- EES-compatible residuals format
- LaTeX equations
- Evaluator report (block summary and state)
- Solution/Initials files: Support for loading initial values and comparing solutions
-
Debug Mode: Creates a comprehensive output folder with all analysis information
For details on current performance optimizations and the future roadmap for solver integration, see the Performance Improvement and Solver Integration Plan.
CoolSolve uses several file formats for input and verification:
- .eescode: The EES source code to be parsed and solved.
- .initials: Initial values for variables, used to seed the solver or evaluator. Format:
variable=value(one per line). - coolsolve.conf: Optional solver configuration. Place in the same folder as your .eescode file (not in subfolders). Format:
key = valueper line; lines starting with#are comments. Only the options you set override the defaults (seeinclude/coolsolve/solver.hforSolverOptions). An example with all keys and comments is inexamples/coolsolve.conf. In debug mode (-d), this file is copied into the debug folder. Key pipeline options:solverPipeline: Comma-separated list of solvers to try (e.g.Newton, LM, TrustRegion, Partitioned)pipelineMode:sequential(default) orparallel(first-to-converge wins)
- C++17 compatible compiler (GCC 7+, Clang 5+)
- CMake 3.14 or later
- Git (for fetching dependencies)
CoolSolve is a standalone library that automatically downloads all its dependencies using CMake's FetchContent mechanism:
| Dependency | Purpose |
|---|---|
| CoolProp | Thermodynamic property calculations |
| cpp-peglib | PEG parser generator |
| nlohmann/json | JSON serialization |
| Eigen | Linear algebra |
| Catch2 | Unit testing |
Dependencies are cached in .fetchcontent_cache/ at the project root. This cache persists across build folder deletions, so you won't need to re-download dependencies if you clean and rebuild. The first cmake run will take several minutes to fetch CoolProp and its submodules; subsequent runs take only a few seconds.
mkdir build && cd build
cmake ..
make -j$(nproc)This will build:
coolsolve- The main executablecoolsolve_tests- The test suite
Important: CoolSolve defaults to Release mode for optimal performance. CoolProp property calculations are computationally intensive, and Debug mode can be 10-50x slower than Release.
# Default: Release mode (recommended)
cmake ..
# Explicitly set Release mode
cmake -DCMAKE_BUILD_TYPE=Release ..
# Debug mode (for development only - very slow!)
cmake -DCMAKE_BUILD_TYPE=Debug ..If you experience slow solve times, verify you're building in Release mode:
# Check current build type
grep CMAKE_BUILD_TYPE CMakeCache.txtBy default, CoolSolve fetches the latest master branch of CoolProp. To use a specific version:
# Use a specific tag or commit
cmake -DCOOLSOLVE_COOLPROP_TAG=v6.6.0 ..
# Or a specific commit hash
cmake -DCOOLSOLVE_COOLPROP_TAG=abc123def ..# Clean build artifacts only (keeps dependency cache)
rm -rf build && mkdir build && cd build && cmake .. && make -j$(nproc)
# Full clean including dependency cache (will re-download everything)
rm -rf build .fetchcontent_cache# Parse an EES file and output JSON analysis
./coolsolve input.eescode
# Output in EES residuals format
./coolsolve -f residuals input.eescode
# Output LaTeX equations
./coolsolve -f latex input.eescode
# Save output to a file
./coolsolve -o output.json input.eescodeDebug mode creates a folder containing all analysis information, useful for understanding the equation structure and debugging. For a complete guide on diagnosing and fixing solver failures (including the simplified-model workflow), see Debugging Models.
# Create debug output in <input>_coolsolve/ folder
./coolsolve -d input.eescode
# Specify custom debug output directory
./coolsolve -d my_debug_folder input.eescodeThe debug folder contains:
| File | Description |
|---|---|
README.md |
Index of all generated files |
coolsolve.conf |
Copy of solver config from source folder (if present) |
report.md |
Model statistics and block summary |
variables.md |
Variable mapping table (Markdown) |
variables.csv |
Variable mapping (CSV for external tools) |
equations.md |
Equations grouped by solution block |
analysis.json |
Full JSON analysis data |
residuals.txt |
EES-compatible residuals format |
equations.tex |
LaTeX formatted equations |
incidence.md |
Variable-equation incidence matrix |
evaluator.md |
Evaluator structure and block evaluation tests |
original.eescode |
Copy of the original input |
To validate the structural analysis against EES output:
./coolsolve -c reference.residuals input.eescode| Option | Description |
|---|---|
-o, --output <file> |
Output file (default: stdout) |
-f, --format <fmt> |
Output format: json, residuals, latex (default: json) |
-c, --compare <file> |
Compare with EES .residuals file |
-d, --debug [dir] |
Create debug output folder |
-v, --verbose |
Verbose progress output |
--no-sol |
Disable generation of .sol file |
-g, --guess |
Update .initials file with solution on success |
--no-superancillary |
Disable CoolProp superancillary functions (faster VLE solving) |
-h, --help |
Show help message |
CoolProp's superancillary functions provide high-accuracy VLE (vapor-liquid equilibrium) calculations, especially near the critical point. However, they add computational overhead during solving.
# Default: superancillaries enabled
./coolsolve model.eescode
# superancillaries disabled
./coolsolve --no-superancillary model.eescode
# Alternative: use environment variable
COOLPROP_ENABLE_SUPERANCILLARIES=false ./coolsolve model.eescodecd build
# Run all unit tests
./coolsolve_tests
# Run comprehensive example file tests
./coolsolve_tests "[examples-comprehensive]"Or using CTest:
cd build
ctest --output-on-failureThe comprehensive test runs all .eescode files in the examples/ folder and validates solutions against known expected values (with 1% tolerance). A detailed report is written to examples/test_examples.md.
CoolSolve/
├── CMakeLists.txt # Build configuration (handles all dependencies)
├── README.md # This file
├── main.cpp # CLI entry point
├── .fetchcontent_cache/ # Cached dependencies (auto-generated, git-ignored)
├── include/coolsolve/
│ ├── ast.h # Abstract Syntax Tree definitions
│ ├── parser.h # Parser interface
│ ├── ir.h # Intermediate Representation
│ ├── structural_analysis.h # Analysis algorithms
│ ├── autodiff_node.h # Forward-mode AD types and operations
│ ├── evaluator.h # Block and system evaluators
│ └── solver.h # Solver pipeline, Newton, TR, LM, Partitioned
├── src/
│ ├── parser.cpp # EES parser implementation
│ ├── ir.cpp # IR building and LaTeX generation
│ ├── structural_analysis.cpp # Matching and SCC algorithms
│ ├── autodiff_node.cpp # AD function implementations
│ ├── evaluator.cpp # Evaluator implementations
│ └── solver.cpp # All solver implementations + pipeline orchestrator
├── tests/
│ ├── test_parser.cpp # Parser/IR unit tests (Catch2)
│ ├── test_evaluator.cpp # AD/Evaluator unit tests (Catch2)
│ ├── test_newton.cpp # Newton solver unit tests
│ ├── test_solver_pipeline.cpp # Pipeline, LM, config tests
│ ├── test_solver_integration.cpp # Full-system solver integration tests
│ └── test_examples.cpp # Integration tests with example files
└── examples/ # Example .eescode files for testing
The parser reads EES source code and builds an Abstract Syntax Tree (AST). It handles EES-specific syntax including:
- Case-insensitive keywords
- Multiple comment styles
- Thermodynamic function calls with named parameters
- Array subscript notation
The AST is transformed into an Intermediate Representation (IR) that:
- Extracts all variables from equations
- Builds the incidence matrix (which variables appear in which equations)
- Identifies thermodynamic function calls (first argument is fluid name, not a variable)
The equation system is analyzed using graph algorithms:
-
Matching: The Hopcroft-Karp algorithm finds a maximum bipartite matching, assigning each equation to one "output" variable it will determine.
-
SCC Detection: Tarjan's algorithm finds Strongly Connected Components in the dependency graph. Each SCC becomes a "block" that must be solved simultaneously.
-
Block Ordering: Blocks are topologically sorted so they can be solved in sequence.
Forward-mode automatic differentiation computes exact derivatives alongside values:
-
ADValue: A dual number storing both
valueandgradient(partial derivatives w.r.t. all block variables). -
Propagation Rules: Each operation propagates gradients using the chain rule:
- Addition:
(x + y).grad = x.grad + y.grad - Multiplication:
(x * y).grad = x.grad * y + y.grad * x - Power:
(x^n).grad = n * x^(n-1) * x.grad - Functions:
sin(x).grad = cos(x) * x.grad
- Addition:
-
Jacobian Construction: For each equation residual F_i, the gradient gives row i of the Jacobian matrix.
The evaluator system provides numerical computation:
-
ExpressionEvaluator: Traverses the AST and computes ADValues for any expression.
-
BlockEvaluator: Evaluates a block of equations, returning residuals F(x) and Jacobian J.
-
SystemEvaluator: Orchestrates evaluation across all blocks with proper variable handling.
CoolSolve uses a configurable solver pipeline for algebraic loops. Multiple solver algorithms can be composed into a fallback chain (sequential mode) or launched concurrently (parallel mode, first-to-converge wins).
The pipeline is configured via coolsolve.conf (see below) or programmatically
through SolverOptions::solverPipeline and SolverOptions::pipelineMode.
-
Newton + Line Search (
Newton)- Solves
J(x) * dx = -F(x)and applies backtracking to ensure descent. - Efficient when the Jacobian is well-conditioned and the model is smooth.
- Solves
-
Trust-Region Dogleg (
TrustRegion)- Uses a dogleg step that blends steepest descent with the Newton step to keep updates inside a safe radius.
- Helps avoid oversized steps that drive thermodynamic calls into invalid regions (e.g., non-physical pressure/temperature).
-
Levenberg-Marquardt (
LMorLevenbergMarquardt)- Solves
(J^T J + λ D) dx = -J^T Fwith adaptive damping parameter λ. - When λ is large → gradient descent (safe, slow); when λ is small → Gauss-Newton (fast, quadratic convergence near solution).
- Particularly effective when the initial guess is far from the solution, because the damping prevents oversized steps that would cause divergence.
- Solves
-
Partitioned Block Updates (
Partitioned)- Uses the equation-to-output-variable mapping from structural matching to
apply per-variable diagonal updates inside a block:
x_i <- x_i - w * F_i / (dF_i/dx_i). - This mimics a DAE-style "tear" without changing the block structure: each equation directly updates its matched variable, reducing coupling and improving stability in stiff or highly nonlinear loops.
- Designed as a last-resort stabilizer when full Newton steps are unreliable.
- Uses the equation-to-output-variable mapping from structural matching to
apply per-variable diagonal updates inside a block:
- Sequential (default): Solvers are tried one after another. If the first solver fails, the initial guess is reset and the next solver is tried.
- Parallel: All solvers are launched concurrently in separate threads. The first solver to converge wins and its solution is used. This can save time when it is unclear which algorithm will work best for a given block.
solverPipeline = Newton, TrustRegion, LevenbergMarquardt, Partitioned
pipelineMode = sequential
To use only Levenberg-Marquardt (no fallback):
solverPipeline = LMsolverPipeline = Newton, TrustRegion, LM
pipelineMode = parallelThe analysis results can be exported in various formats for:
- Integration with numerical solvers (JSON)
- Documentation (LaTeX)
Given this EES code:
T_in = 25 // Temperature in Celsius
P = 101325 // Pressure in Pa
h = enthalpy(Water, T=T_in, P=P)
s = entropy(Water, T=T_in, P=P)
CoolSolve will:
- Parse 4 equations
- Identify 4 variables:
T_in,P,h,s - Create 4 blocks (all explicit, no algebraic loops)
- Determine solution order: T_in → P → h, s
- Evaluate thermodynamic properties using CoolProp (with automatic °C→K conversion)
CoolSolve integrates with CoolProp for thermodynamic property calculations:
- Supported functions:
enthalpy,entropy,density,pressure,temperature,quality,cp,cv - Supported input pairs:
T,P,T,x,P,h,P,s,H,P, and others - Temperature units: Automatically converts Celsius (EES convention) to Kelvin (CoolProp)
- Derivatives: Computed using finite differences from CoolProp's
PropsSIfunction - Fluids: All CoolProp fluids are supported (Water, R134a, Nitrogen, etc.)
CoolSolve assumes the following default units:
- Temperature: °C (automatically converted to K for CoolProp calls)
- Pressure: Pa
- Energy: J
- Mass: kg
- Force: N
- Length: m
Built-in constants like g# use these SI units (e.g., g# = 9.80665 m/s^2).
Example usage in EES code:
T_ev = -10 // Celsius
P_ev = pressure(R134a, T=T_ev, x=1) // Saturation pressure
h_1 = enthalpy(R134a, P=P_ev, T=T_1) // Enthalpy at state 1
CoolSolve supports user-defined functions and procedures for modular code:
PROCEDURE single_phase_HX(cf$, hf$, t_cf_su : Q_dot)
"Procedural block with local scope"
cp_cf = specheat(cf$, T=t_cf_su, P=101325)
Q_dot := alpha * cp_cf * (t_hf_su - t_cf_su)
END
CALL single_phase_HX('Air_ha', 'Water', 20 : Q_total)
- Local Scope: Variables inside functions and procedures are local and do not interfere with the main program.
- Assignment: Use
:=for assignment inside procedural blocks (though=is also supported for compatibility). - Control Flow:
IF-THEN-ELSEstatements are supported within these blocks. - Automatic Differentiation: CoolSolve automatically propagates derivatives through procedural calls, ensuring accurate Jacobians.
Important: EES and CoolProp use different reference states for thermodynamic properties:
| Property | EES (IIR Reference) | CoolProp (ASHRAE Reference) |
|---|---|---|
| Enthalpy | h = 200 kJ/kg at 0°C sat. liquid | h = 0 at -40°C |
| Entropy | s = 1 kJ/kg/K at 0°C sat. liquid | s varies |
This means:
- Enthalpy values from EES may differ from CoolProp by ~100-200 kJ/kg
- Entropy values from EES may differ from CoolProp by ~0.8-1 kJ/kg/K
- Differences (e.g., h2 - h1) are the same in both reference states
- When loading initial values from
.initialsfiles, some CoolProp function calls may fail if the enthalpy/entropy is outside the expected range for the given pressure
For new equation systems, use CoolProp-computed values for initial guesses to ensure consistency.
The next steps in the implementation plan include:
- KINSOL (SUNDIALS) integration: For large-scale nonlinear systems requiring robust preconditioning
- alternative solvers: https://www.osti.gov/servlets/purl/876345
- Homotopy / Continuation methods: Gradually transform from an easy problem to the target for highly nonlinear systems
- Analytical Derivatives: Use CoolProp's
AbstractState::first_partial_derivfor exact derivatives - Profiling and optimization: Improve the solving time