This guide explains how to diagnose and fix solver failures in CoolSolve. It is based on the workflow used to fix the condenser_3zones.eescode model and applies to other models that fail at the solver level.
When a model fails to solve, the typical causes are:
- Convergence problems – Newton solver cannot find a solution (line search failure, max iterations, poor initial guess)
- Algebraic loops – Large strongly connected components (SCCs) that are difficult to solve
- Invalid physics – CoolProp errors from out-of-range temperatures, pressures, or enthalpies
- Procedure/function issues – Bugs or unit mismatches in user-defined procedures
Always start by running the model in debug mode to gather diagnostic information:
./coolsolve -d input.eescodeThis creates a folder <input>_coolsolve/ (or <input>_coolsolve/ next to the input file) containing:
| File | Use for diagnosis |
|---|---|
report.md |
Block summary, solver status, largest block size |
solver_errors.md |
Error type (LineSearchFailed, MaxIterations, EvaluationError) |
solver_trace.md |
Per-block Newton iterations, residual norms |
evaluator.md |
Block evaluation status, max residuals at current state |
coolsolve_residuals.md |
Equation-by-equation LHS, RHS, residual values |
equations.md |
Which equations are in each block |
Before diving into the diagnosis below, click the Try Harder button in the
GUI (shortcut: Ctrl+Enter after a failed solve). It re-runs the model with
the full Deep Search pipeline (deepSearchPipeline), forces structural
tearing and symbolic reduction on, and engages multi-start according
to multiStartMode. If the failure was due to a poor initial guess or an
algebraic loop that tearing can crack, Try Harder recovers it in one click —
no manual diagnosis required.
- If Try Harder succeeds, the model is fine; the default pipeline simply
needed more fire-power. To make Deep Search the default, set
solverPipelineto the full chain incoolsolve.conf. - If Try Harder also fails, proceed to Step 2 to interpret the error and apply the structured workflow below.
- Editing the model, the initials, or the configuration restores the normal Solve button.
Outside the GUI, the equivalent is to send {"deepSearch": true} to the
POST /api/v1/solve endpoint — see
GUI §6.2.
- Meaning: The Newton step direction is valid, but no step size satisfies the line search (e.g. step would push variables into invalid region, or Armijo condition not met).
- Typical causes: Poor initial guess, variables near physical limits, ill-conditioned Jacobian.
- Check:
solver_trace.md– residual at failure, step norm. If residual is small (e.g. < 1), the solution may be very close.
- Meaning: Newton did not converge within the iteration limit (default 100).
- Typical causes: Large algebraic loop, poor initials, slow convergence.
- Check:
solver_trace.md– does the residual decrease or stall? A stall near a small residual suggests a near-solution.
- Meaning: CoolProp rejected an input (e.g. enthalpy outside valid range, pressure too low).
- Typical causes: Incorrect initial values, wrong units (e.g. P=1 interpreted as 1 Pa instead of 1 bar), cascaded errors from a previous block.
- Check: Error message for the exact invalid input. Inspect
coolsolve_residuals.mdfor suspicious values (e.g. enthalpy = 1.0 instead of ~4e5 for air).
- Meaning: Jacobian is rank-deficient; Newton cannot compute a unique step.
- Typical causes: Redundant equations, degenerate system, bad structural analysis.
From report.md and solver_errors.md:
- Note the block index and size (e.g. Block 38, size 62).
- Large blocks (tens of equations) are algebraic loops and are hardest to solve.
From equations.md:
- List the variables and equations in that block.
- Look for coupling: shared variables, procedure calls, thermodynamic functions.
- Variables without values in
.initialsdefault to 1.0, which is often wrong (e.g. enthalpies, mass fractions). - Compare
evaluator.md“Current State” with physically reasonable values. - If you have a reference solution, use those values in
.initials.
For large algebraic loops that fail to converge, a practical approach is to fix one or more variables to reduce the loop size. Use a simplified model to obtain good initials, then solve the full model.
- Copy the original model to
model_simple.eescode. - Pick a variable that strongly couples the loop (e.g. a pressure, temperature, or mass fraction) and fix it to a known value from
.initials. - Replace the equation that originally determined it with an explicit assignment.
Example (condenser-style model):
" Original (in implicit block): P_r_su_cd is unknown "
" Simplified: fix condenser inlet pressure from initials "
P_r_su_cd=949215
- Ensure the system stays square (same number of equations and unknowns). Fixing one variable replaces one implicit equation with one explicit one, so the count stays the same.
cp model.initials model_simple.initials
./coolsolve -g model_simple.eescode- If it solves,
model_simple.initialsis updated with the solution.
cp model_simple.initials model.initials
./coolsolve model.eescode- The full model often converges with these improved initials.
- Pressures (e.g. condenser/evaporator inlet): often good candidates in HVAC/refrigeration.
- Mass/energy fractions (e.g. zone splits): can decouple zone balances.
- Temperatures at key points: useful when they are central to the loop.
Use equations.md and report.md to see which variables appear in the largest blocks.
- Temperatures are in °C, never K. CoolSolve's unit system is fixed (see
the warning at the top of the Language Reference):
every
T=...argument and every value returned bytemperature(...),t_sat(...),T_CRIT, … is in degrees Celsius. CoolProp works internally in Kelvin; the°C ↔ Kconversion is performed automatically by CoolSolve. WritingT=300therefore means 300 °C (≈ 573 K), not 300 K — getting this wrong is a common source ofEvaluationErrorfrom CoolProp. - CoolProp expects Pa for pressure. If the model uses bar or kPa, convert (e.g.
P=101325for 1 atm). - Example:
specheat(cf$, T=t_bar_cf, P=1)may mean 1 bar in some contexts but 1 Pa in CoolProp → useP=101325.
- Procedures are a common source of issues. Check:
- Correct mapping of inputs/outputs in
CALL. - Pressure and temperature units in thermodynamic calls.
- Use of
:=vs=for assignment inside procedures.
- Correct mapping of inputs/outputs in
For difficult models, you can adjust SolverOptions in code (e.g. in runner.cpp or via a future CLI):
maxIterations– increase for large blocks (e.g. 500).tolerance– loosen if near-convergence is acceptable.lsRelaxedTolerance– accept convergence when line search fails but residual is small.
This example shows how to diagnose and fix a failing model when you do not have correct initial values. The repository keeps condenser_3zones.initialsnotok (inadequate) and condenser_3zones.initials (working) for comparison.
-
Run in debug mode:
./coolsolve -d condenser_3zones.eescode
Result: Block 38 (62 equations) fails with
LineSearchFailedorMaxIterations. -
Inspect
coolsolve_residuals.md: equations likeh_cf_su_cd_sc = h_cf_su_cdshow LHS = 1.0 instead of ~4e5. Variables without values in.initialsdefault to 1.0; enthalpies should be ~4e5 J/kg for air. -
Inspect the
two_phase_CDprocedure:specheat(cf$, T=t_bar_cf, P=1)uses P=1. CoolProp expects Pa; 1 Pa is unphysical for air. Some solvers may treat 1 as 1 bar; CoolProp does not.
**Fix: Obtain good initials via a simplified model**
The 62-equation block is a tight algebraic loop. Create a **temporary** simplified model to break it and get usable initials:
1. Copy the model to `condenser_3zones_simple.eescode`.
2. Add an explicit assignment for a key coupling variable, e.g. fix the condenser inlet pressure:
```ees
P_r_su_cd=949215
(Use a value from .initials if available.) Ensure the system stays square.
3. Use existing initials for the simplified model (even partial ones may work once the loop is broken): cp condenser_3zones.initials condenser_3zones_simple.initials, or copy from .initialsnotok if that is all you have.
4. Solve and update initials:
./coolsolve -g condenser_3zones_simple.eescode- Copy the solution to the full model:
cp condenser_3zones_simple.initials condenser_3zones.initials
- Run the full model:
It should converge. You can then delete
./coolsolve condenser_3zones.eescode
condenser_3zones_simple.eescodeandcondenser_3zones_simple.initials; keep the updatedcondenser_3zones.initials.
Alternative: If fixing one variable is not enough, try fixing zone fractions (e.g. alpha_cd_sh, alpha_cd_tp) or other coupling variables. The aim is to shrink the algebraic loop so the simplified model solves, giving you good initials for the full model.
Equation-based dynamic models (containing an INTEGRAL(...) call) take a
different code path: CoolSolve time-marches the state variables and re-solves
the algebraic subsystem at every step. See the
Language Reference §12
for the syntax. When such a model fails or behaves oddly, start in debug mode:
./coolsolve -d integral_model.eescodeThe debug folder gains two extra files for these models:
| File | Use for diagnosis |
|---|---|
integral.md |
Problem summary: state vs algebraic variables, integration variable, interval, chosen method, step count, min/avg/max accepted step, rejected-step count, and a trajectory preview |
integral_table.csv |
Full trajectory (integration variable first, then the $IntegralTable columns) |
MaxSteps reached (integralMaxSteps). The march did not reach tf within
the step budget. Typical causes: the step size collapsed (RK45 rejecting many
steps on a stiff system), or the interval is large relative to the fixed step.
Fixes:
- For RK45 on a stiff system: this is the signal that a stiff (BDF) integrator
is warranted (not yet available — see
docs/solver_roadmap.md§9.4); as a workaround, raiseintegralMaxStepsand tightenintegralRelTol/integralAbsTolonly if accuracy demands it. - For fixed-step methods: set an explicit
integralFixedStep(or pass the 5thINTEGRALargument) so the step count is(tf−t0)/step.
RK45 rejecting many steps. The rejectedSteps count in integral.md is
high. The error estimate exceeds the tolerance repeatedly, so the controller
shrinks h toward integralMinStep. This usually means the system is stiff for
an explicit Runge–Kutta method. Try integralMethod = EulerImplicit (A-stable,
1st order) as a quick check, or use fixed-step RK4 with a small step.
High-index DAE detected warning. A purely algebraic equation constrains a
state variable without going through its derivative. This is emitted as a
warning, not a hard error — legitimate index-1 thermal models routinely have
state variables (e.g. temperatures) appearing in algebraic heat-transfer laws,
so it does not by itself indicate a problem. It only matters if the algebraic
subsystem is structurally non-square: then you see a separate squareness error,
which means a true high-index constraint that needs index reduction (not yet
supported — restructure the model so every state variable is defined through its
derivative).
Inconsistent integration variable or limits. All INTEGRAL(...) calls in a
model must share the same integration variable and the same [t0, tf]
interval. CoolSolve rejects multi-interval / multi-variable integration with a
clear message. The limits must be constants (or constant-foldable expressions);
time-varying limits are not supported.
Non-convergence of the algebraic subsystem at a step. If the per-step
algebraic Solver fails (the same LineSearchFailed / MaxIterations errors
covered in Steps 2–5 above), the march reports it. Apply the same
initials/simplified-model workflow, noting that the algebraic variables must be
well-conditioned at every step, not just the operating point.
Integral-declaring equations are skipped in the post-solve
solution_check.md (their correctness is established by the trajectory). To
sanity-check a result, compare the final tabulated value against a known
analytical answer (the bundled integral_decay.eescode exercises
y(t) = e^{-t}, giving y(4) ≈ 0.0183156), or inspect integral_table.csv
for monotonicity / energy-balance trends.
| Symptom | Likely cause | Workaround |
|---|---|---|
Non-constant integration limits |
INTEGRAL(..., tau_1, tau_2) with non-literal bounds |
Use numeric literals in the INTEGRAL call (0, 604800, …) |
lookup table '…' not found (no table store…) |
INTERPOLATE inside an integral model |
Use analytic expressions, or see Dynamic Solving §7.1 (lookup store not yet wired into IntegralSolver) |
Unknown or unsupported function: IF with 5 arguments |
EES multi-threshold IF |
Rewrite with nested 3-arg IF(cond, true, false) where cond > 0 |
Empty columns in *-integral.csv |
Commas in $IntegralTable directive |
Use space-separated column names: $IntegralTable tau:600 T_in T_out |
PROCEDURE / CALL fails at first step |
Procedure-output block in reduced algebraic subsystem | Inline simple outputs, or debug with -d and integral.md |
- Run with
-dto generate debug output - Identify error type and failing block from
solver_errors.md,report.md - Inspect
solver_trace.md,evaluator.md,coolsolve_residuals.md - Check
.initialsand default values for unphysical guesses - Improve initials: fix missing/wrong values, or use simplified-model workflow (Step 5)
- Fix unit mismatches (especially pressure) in procedures
- Re-run full model with improved initials