Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/development/TASK_LOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ requirement`, or `confidential compressor route`.

<!-- Add new entries at the top. Most recent first. -->

### 2026-10-14 — Coordinate shared-slate characterization with mixing-rule isolation
**Type:** A (Property)
**Keywords:** shared reference slate, quantile projection, shared mixing handler, accepted-point replay, 4330, 4333
**Solution:** Stack PR #4330 on the shared classic mixing-rule accumulator foundation while preserving its characterization implementation, regression tests and mathematical documentation byte-for-byte. Regenerate engineering coverage for the combined source tree.
**Notes:** The unchanged field-to-facility acceptance class passed three fresh-JVM runs on both the original head and the foundation overlay (six tests per variant, none failed or skipped). A read-only fixture probe confirmed the parallel HP/LP branches share phase-specific mixing handlers. Java 8 dependencies and release-8 bytecode were tested on Linux Java 17. This validates the shared dependency; it does not establish the cause of the earlier Windows Java 8 accepted-point replay failure, which remains a hosted CI gate. No solver tolerance or execution setting changed.

### 2026-10-14 — Isolate shared classic mixing-rule accumulators
**Type:** A (Property)
**Keywords:** classic mixing rule, concurrency, cloned fluids, heat capacity, compressor pressure replay, SRK, PR, 4326, 4306
Expand Down
10 changes: 7 additions & 3 deletions docs/pvtsimulation/fluid_characterization_mathematics.md
Original file line number Diff line number Diff line change
Expand Up @@ -966,7 +966,7 @@ SystemInterface matched = PseudoComponentCombiner.characterizeToReference(
| `inheritReferenceProperties` | Inherit the reference lump properties (molar mass, density, critical constants). When `false`, lump properties are recomputed from the source mass on the reference cut grid | `true` |
| `delumpBeforeRecharacterization` | Split each coarse source lump into finer single-carbon-number sub-fractions before re-binning onto the reference cuts (Pedersen Ch. 5 delumping). Conserves source moles and mass exactly | `false` |
| `delumpResolution` | Number of sub-fractions per source lump when delumping (also the fine grid for equal-mass reference boundaries). Values ≤ 1 disable splitting | `12` |
| `sharedImaginaryBoundaries` | Place reference cut boundaries as carbon-number **equal-mass** cut points on the reference's imaginary (delumped) composition (Pedersen Ch. 5.6, Eqs. 5.58–5.59) instead of boiling-point midpoints | `false` |
| `sharedImaginaryBoundaries` | Use discrete mass-quantile candidates from the reference's imaginary (delumped) composition, projected into adjacent reference gaps; final cut masses need not be equal | `false` |

#### Property inheritance vs. recomputation (`inheritReferenceProperties`)

Expand All @@ -978,7 +978,11 @@ When a field's native lumps already sit close to the reference grid, the naive s

#### Equal-mass reference boundaries (`sharedImaginaryBoundaries`)

By default the reference cut boundaries are the arithmetic boiling-point midpoints between adjacent reference lumps, which ignores how much mass each lump represents. With `sharedImaginaryBoundaries(true)` the reference is first rebuilt into a fine single-carbon-number "imaginary" composition (using `delumpResolution`) and the cut points are placed so each cut carries an **equal mass fraction** — the reference-only (NFLUID = 1) form of the Pedersen Ch. 5.6 common-slate rule (Eqs. 5.58–5.59 with §5.3 cut criterion). Each equal-mass cut is then *clamped* into the gap between the two adjacent reference pseudo-components, guaranteeing every reference lump stays inside its own cut so the one-to-one property inheritance is preserved even when the reference lumps carry unequal mass. With `delumpResolution ≤ 1` it falls back to boiling-point midpoints.
By default the reference cut boundaries are the arithmetic boiling-point midpoints between adjacent reference lumps. With `sharedImaginaryBoundaries(true)`, the reference is first rebuilt into a fine imaginary composition using `delumpResolution`. The discrete cumulative-mass quantiles provide candidate boundaries on the sorting-key axis (normal boiling point, falling back to molar mass).

Each finite candidate is projected into the gap between the corresponding adjacent reference keys: a candidate below the gap moves to the next representable value above its lower key, while a candidate above the gap moves to the next representable value below its upper key. A candidate already inside the gap is retained. This keeps each distinct reference lump in its own cut when a representable interior exists, without discarding an out-of-gap candidate in favor of the midpoint. Missing or non-finite candidates, equal/non-finite keys, gaps with no representable interior, and `delumpResolution ≤ 1` retain the legacy midpoint fallback. Equal keys cannot define separate ordered cuts.

These are **constrained mass-quantile boundaries**, not a guarantee of equal final cut masses: both projection and the finite imaginary grid can prevent exact equality. Source lumps are assigned whole unless source delumping is enabled, so changing a boundary only changes the result when a source contribution crosses it. With `inheritReferenceProperties(false)`, redistribution preserves source moles and mass; inheriting reference molar masses can change source mass. The synthetic delumping distribution is an approximation and is not independently validated by the cut-placement regression tests.

> **Why reference-only here?** `characterizeToReference` inherits the trusted reference's lump properties one-to-one, so the reference fluid is the correct authority for the grid. The pooled multi-fluid imaginary composition of Eqs. 5.58–5.59 remains used by `characterizeToCommonSlate` (free new slate); applying it to the inherit-from-fixed-reference path would break the inherit alignment.

Expand Down Expand Up @@ -1051,7 +1055,7 @@ System.out.println(report.toReportString());
When matching a source fluid to a reference PC structure:

1. **Component Mapping**: Discrete components (C1, C2, CO₂, etc.) are mapped directly
2. **Cut boundaries**: The reference lumps define cut boundaries on the carbon-number (boiling-point) axis — boiling-point midpoints by default, or carbon-number **equal-mass** cut points with `sharedImaginaryBoundaries(true)`
2. **Cut boundaries**: The reference lumps define cut boundaries on the carbon-number (boiling-point) axis — boiling-point midpoints by default, or constrained mass-quantile cut points with `sharedImaginaryBoundaries(true)`
3. **PC binning**: Each source pseudo-component (optionally first delumped into single-carbon-number sub-fractions, see `delumpBeforeRecharacterization`) is assigned to the reference cut whose boundary range contains its boiling point; the lump moles and mass are accumulated per cut
4. **Lump properties**: Either inherited from the reference (`inheritReferenceProperties(true)`) or recomputed as molar mass = mass / moles per cut (`inheritReferenceProperties(false)`)
5. **Mass Conservation**: Total source pseudo-fraction moles and mass are preserved through the binning
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,8 @@ Given a reference fluid with $N$ pseudo-components having boiling points $T_{b,1
**Step 1**: Calculate boundary boiling points:
$$T_{boundary,k} = \frac{T_{b,k} + T_{b,k+1}}{2}, \quad k = 1, ..., N-1$$

The optional `sharedImaginaryBoundaries` setting instead uses discrete mass-quantile candidates from a delumped reference, projected strictly inside each adjacent reference gap when possible. Projection preserves reference ordering but does not guarantee equal final cut masses. See [constrained mass-quantile reference boundaries](../../pvtsimulation/fluid_characterization_mathematics.md#equal-mass-reference-boundaries-sharedimaginaryboundaries) for the fallback cases and conservation limits.

**Step 2**: Assign source pseudo-components to bins:
- Bin 1: all components with $T_b \leq T_{boundary,1}$
- Bin $k$: all components with $T_{boundary,k-1} < T_b \leq T_{boundary,k}$
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,20 +157,18 @@ public int getDelumpResolution() {
}

/**
* Whether the reference cut boundaries should be placed as carbon-number-based equal-mass cuts on the reference
* fluid's imaginary (fine-resolution) composition, instead of the simple boiling-point midpoints between adjacent
* reference pseudo-components.
* Whether the reference cut boundaries should use constrained mass quantiles of the reference fluid's imaginary
* composition instead of boiling-point midpoints.
*
* <p>
* Disabled by default (boiling-point midpoints are used). When {@code true}, the reference is the single
* representative composition (NFLUID = 1) of the Pedersen et al. (Chapter 5.6) common-slate scheme: each reference
* pseudo-component is delumped into {@link #getDelumpResolution()} single-carbon-number sub-fractions to rebuild the
* imaginary molar composition (Eqs. 5.58-5.59), and the cut points are then placed so each cut carries an equal mass
* fraction (Section 5.3). Each boundary is clamped to lie strictly between the two adjacent reference
* pseudo-component sorting keys, so the strict one-to-one property-inheritance mapping is preserved even when the
* reference lumps are not equal in mass.
* Disabled by default. When enabled, each reference pseudo-component is delumped into {@link #getDelumpResolution()}
* synthetic sub-fractions. Discrete mass quantiles provide the candidate cut points. A finite candidate outside its
* reference gap is projected to the nearest representable value strictly inside that gap, preserving the one-to-one
* property-inheritance mapping for distinct keys with a representable interior. These constraints and the discrete
* sub-fractions mean the final cuts need not contain equal masses. Missing/non-finite candidates, degenerate gaps and
* resolutions of 1 or less retain the boiling-point midpoint fallback.
*
* @return true if equal-mass cut points on the reference imaginary composition should be used
* @return true if constrained mass-quantile reference boundaries should be used
*/
public boolean isSharedImaginaryBoundaries() {
return sharedImaginaryBoundaries;
Expand Down Expand Up @@ -336,18 +334,15 @@ public Builder delumpResolution(int resolution) {
}

/**
* Set whether the reference cut boundaries should be placed as carbon-number-based equal-mass cuts on the reference
* fluid's imaginary (fine-resolution) composition, instead of the simple boiling-point midpoints between adjacent
* reference pseudo-components.
* Set whether reference cut boundaries use constrained mass quantiles instead of boiling-point midpoints.
*
* <p>
* Disabled by default. Enable to follow the Pedersen et al. (Chapter 5.6) common-slate cut-point rule (Eqs.
* 5.58-5.59 with NFLUID = 1, Section 5.3 equal-mass lumping) for the reference-only path. The
* {@link #delumpResolution(int) delump resolution} controls how finely each reference lump is split when rebuilding
* the imaginary composition. Boundaries are clamped between adjacent reference pseudo-component sorting keys so the
* property-inheritance mapping stays one-to-one.
* Disabled by default. The {@link #delumpResolution(int) delump resolution} controls the reference grid used to
* rebuild the imaginary composition. Finite candidates are projected into the representable interior of each
* reference gap; clamping may prevent equal final cut masses. See
* {@link CharacterizationOptions#isSharedImaginaryBoundaries()} for the ordering constraint and fallback cases.
*
* @param shared true to use equal-mass cut points on the reference imaginary composition
* @param shared true to use constrained mass-quantile reference boundaries
* @return this builder
*/
public Builder sharedImaginaryBoundaries(boolean shared) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,8 @@ public static SystemInterface characterizeToReference(SystemInterface source, Sy
* reference cuts
* @param delumpResolution number of sub-fractions per source lump when {@code delump} is {@code true}; values of 1 or
* less disable splitting
* @param sharedImaginaryBoundaries whether the reference cut boundaries are placed as equal-mass cuts on the
* reference's imaginary (delumped) composition instead of boiling-point midpoints
* @param sharedImaginaryBoundaries whether the reference cut boundaries use mass quantiles from the reference's
* imaginary (delumped) composition projected into reference gaps instead of boiling-point midpoints
* @return characterized fluid containing pseudo components compatible with the reference fluid
*/
private static SystemInterface characterizeToReferenceCore(SystemInterface source, SystemInterface reference,
Expand Down Expand Up @@ -1056,29 +1056,23 @@ private static List<Double> determineReferenceBoundaries(List<PseudoComponentCon
}

/**
* Place the reference cut boundaries as carbon-number-based equal-mass cut points on the reference fluid's imaginary
* (fine-resolution) composition.
* Place reference cut boundaries using constrained mass quantiles of the reference's imaginary composition.
*
* <p>
* This is the reference-only (NFLUID = 1) form of the Pedersen et al. (Chapter 5.6) common-slate cut-point rule.
* Because the reference is supplied already lumped into pseudo-components, its fine carbon-number distribution is
* first rebuilt by delumping each lump into {@code resolution} single-carbon-number sub-fractions (the imaginary
* composition of Eqs. 5.58-5.59 with the reference as the sole weighted fluid). Equal-mass cut points are then placed
* on that fine composition (Section 5.3 lumping criterion), so each cut carries an equal mass fraction instead of
* being split at the arithmetic boiling-point midpoint, which ignores how much mass each lump represents.
* Delumping each reference lump into {@code resolution} synthetic sub-fractions supplies the fine distribution.
* Discrete cumulative-mass quantiles on the sorting-key axis (normal boiling point, falling back to molar mass)
* provide candidate cut points.
*
* <p>
* Each equal-mass cut is finally clamped to lie strictly between the sorting keys of the two adjacent reference
* pseudo-components. This guarantees that every reference pseudo-component remains inside its own cut, preserving the
* strict one-to-one ordering on which the property inheritance in {@link #characterizeToReferenceCore} relies, even
* when the reference lumps are not equal in mass (in which case an unclamped equal-mass cut could fall across a lump
* and mis-bin the source). When clamping cannot recover a valid in-gap value the boiling-point midpoint is used as a
* fallback.
* Each finite candidate is projected to the nearest representable value strictly between its adjacent reference keys.
* This preserves one-to-one ordering for distinct reference keys with a representable interior. Projection and the
* discrete grid mean that final cuts need not contain equal masses. Missing/non-finite candidates and gaps with no
* representable interior retain the legacy midpoint fallback; equal keys cannot define separate ordered cuts.
*
* @param referenceContributions the reference pseudo-components, sorted ascending by sorting key
* @param resolution number of single-carbon-number sub-fractions per reference lump used to rebuild the imaginary
* composition; values of 1 or less fall back to boiling-point midpoints
* @return the clamped equal-mass cut boundaries (size {@code referenceContributions.size() - 1})
* @param resolution number of synthetic sub-fractions per reference lump used to rebuild the imaginary composition;
* values of 1 or less fall back to boiling-point midpoints
* @return the projected mass-quantile cut boundaries (size {@code referenceContributions.size() - 1})
*/
private static List<Double> determineReferenceEqualMassBoundaries(
List<PseudoComponentContribution> referenceContributions, int resolution) {
Expand All @@ -1103,8 +1097,10 @@ private static List<Double> determineReferenceEqualMassBoundaries(
double boundary = midpoint;
if (Double.isFinite(lower) && Double.isFinite(upper) && upper > lower) {
double candidate = i < equalMass.size() ? equalMass.get(i) : Double.NaN;
if (Double.isFinite(candidate) && candidate > lower && candidate < upper) {
boundary = candidate;
double firstInterior = Math.nextUp(lower);
double lastInterior = Math.nextDown(upper);
if (Double.isFinite(candidate) && firstInterior <= lastInterior) {
boundary = Math.max(firstInterior, Math.min(candidate, lastInterior));
}
}
boundaries.add(boundary);
Expand Down
Loading
Loading