Skip to content

Fix recuperator effectiveness via pinch-point analysis#65

Merged
dyreby merged 2 commits into
mainfrom
fix-effectiveness
Mar 5, 2026
Merged

Fix recuperator effectiveness via pinch-point analysis#65
dyreby merged 2 commits into
mainfrom
fix-effectiveness

Conversation

@dyreby

@dyreby dyreby commented Mar 5, 2026

Copy link
Copy Markdown
Contributor

The recuperator effectiveness values from twine-models are incorrect for variable-property fluids like supercritical CO₂. The underlying computation sums per-segment C_min * ΔT to get q_max, but picks the minimum-capacitance stream independently per segment — producing a denominator that doesn't correspond to any physically realizable heat transfer. The approximation diverges rather than converges as segment count increases. See twine-models#73.

This PR adds a standalone effectiveness module that computes correct effectiveness via pinch-point analysis: bisect on the cold-side outlet temperature using RecuperatorGivenOutlet to find q_max where min_delta_t reaches zero, then effectiveness = q_actual / q_max. The facades use this instead of the twine-models values. The cycle solvers are untouched.

The effectiveness values from twine-models are incorrect for
variable-property fluids — the per-segment C_min approach diverges
as segment count increases (twine-models#73).

Add src/effectiveness.rs: a standalone module that computes correct
effectiveness by bisecting on the cold-side outlet temperature using
RecuperatorGivenOutlet to find q_max where min_delta_t reaches zero.
Effectiveness is then q_actual / q_max.

The facades (simple and recomp) now use this instead of the twine-models
values. The cycle solvers are untouched — the fix is entirely in how
the facade reports effectiveness.
@dyreby dyreby requested a review from gdtroszak March 5, 2026 04:09

@gdtroszak gdtroszak left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean fix. The pinch-point bisection is the right approach, and the separation from the solvers keeps the change well-scoped. A few minor notes inline.

Comment thread src/effectiveness.rs Outdated
Thermo: EffectivenessThermo<Fluid>,
{
let q_actual_w = q_actual.get::<watt>();
if q_actual_w == 0.0 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exact float comparison — if q_actual picks up a tiny negative from solver noise, this falls through to bisection and could return a nonsensical result. q_actual_w <= 0.0 (or < ε) would be safer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — changed to <= 0.0 so tiny negatives from solver noise get caught. (384d51d)

Comment thread src/effectiveness.rs Outdated
bisection::solve_from_bracket(&recup, &problem, bracket, &config, observer).ok()?;

let q_max_w = solution.snapshot.output.q_dot.magnitude().get::<watt>();
if q_max_w == 0.0 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same concern here. If bisection converges to a degenerate case where q_max is near-zero but not exactly zero, q_actual / tiny clamps to 1.0. Probably fine in practice (q_max ≈ 0 implies q_actual ≈ 0), but a comment explaining why this is safe would help.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Changed to <= 0.0 for consistency with the first guard, and added a comment explaining why near-zero is safe: q_max ≈ 0 implies q_actual ≈ 0, and .clamp(0.0, 1.0) handles any residual overshoot. (384d51d)

Comment thread src/facade/simple.rs Outdated
config.hx.recuperator.segments,
thermo,
)
.unwrap_or(f64::NAN);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Silent NaN in a result struct is worth a deliberate decision. Downstream consumers (especially WASM/JS) may not handle NaN well — JSON serializes it as null in some libraries, others choke.

Options:

  • Document on the output struct fields that effectiveness can be NaN
  • Return an error from the facade if the computation fails
  • Fall back to the (known-incorrect) twine-models value with a flag

Not blocking, but worth deciding explicitly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed all effectiveness fields to Option<f64> — on both simple and recomp output structs. None is explicit, serializes cleanly to JSON null, and doesn't require downstream consumers to handle NaN. (384d51d)

…veness

- Use <= 0.0 instead of == 0.0 for q_actual and q_max guards to handle
  solver noise producing tiny negatives.
- Add comment explaining why near-zero q_max is safe.
- Change effectiveness fields from f64 to Option<f64> on both simple and
  recomp output structs. None is explicit and JSON-safe, unlike NaN.
@dyreby dyreby merged commit fcfcb2e into main Mar 5, 2026
1 check passed
@dyreby dyreby deleted the fix-effectiveness branch March 5, 2026 13:42
dyreby added a commit that referenced this pull request Mar 5, 2026
Remove Effectiveness type and fields from solution structs.
twine-models 0.4 drops built-in effectiveness from recuperator
outputs; brayton already computes it independently via pinch-point
analysis (added in #65).
dyreby added a commit that referenced this pull request Mar 5, 2026
Remove Effectiveness type and fields from solution structs.
twine-models 0.4 drops built-in effectiveness from recuperator
outputs; brayton already computes it independently via pinch-point
analysis (added in #65).
@dyreby dyreby mentioned this pull request Mar 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants