Skill: Design Flash Benchmark
Purpose
Create a structured test matrix for comparing flash algorithm performance
across fluid types, thermodynamic conditions, and difficulty levels.
When to Use
- Starting a new flash algorithm comparison study
- Extending an existing benchmark to new fluid families
- Designing stress tests for near-critical or multi-phase regions
Benchmark Design Procedure
Step 1: Select Fluid Families
Choose from these standard families:
| Family |
Components |
Mole Fractions |
Characteristics |
| Lean gas |
CH4(0.90), C2(0.05), C3(0.03), N2(0.01), CO2(0.01) |
Fixed or ±10% |
Easy, mostly single-phase |
| Rich gas |
CH4(0.70), C2(0.10), C3(0.08), nC4(0.05), nC5(0.03), N2(0.02), CO2(0.02) |
Fixed or ±15% |
Moderate, clear two-phase |
| Gas condensate |
CH4(0.65), C2(0.08), C3(0.06), nC4(0.04), nC5(0.03), nC6(0.02), nC7(0.02), nC10(0.05), N2(0.02), CO2(0.03) |
±20% |
Near-critical behavior |
| CO2-rich |
CO2(0.80), CH4(0.10), N2(0.05), H2S(0.03), C2(0.02) |
±15% |
Strong non-ideality |
| Wide-boiling |
CH4(0.50), nC4(0.15), nC10(0.15), nC16(0.10), nC20(0.10) |
±20% |
Large volatility range |
| Sour gas |
CH4(0.60), CO2(0.15), H2S(0.10), C2(0.08), C3(0.05), N2(0.02) |
±15% |
Acid gas behavior |
Step 2: Define PT Space
For each family, define the pressure-temperature sampling grid:
import numpy as np
def generate_pt_grid(T_min_K, T_max_K, P_min_bara, P_max_bara, n_T=20, n_P=20):
"""Generate a regular PT grid."""
T_values = np.linspace(T_min_K, T_max_K, n_T)
P_values = np.logspace(np.log10(P_min_bara), np.log10(P_max_bara), n_P)
cases = []
for T in T_values:
for P in P_values:
cases.append({"T_K": float(T), "P_bara": float(P)})
return cases
Standard ranges by family:
| Family |
T range (K) |
P range (bara) |
Focus region |
| Lean gas |
200–400 |
1–200 |
Dew point region |
| Rich gas |
220–450 |
5–300 |
Two-phase dome |
| Gas condensate |
250–500 |
10–500 |
Near cricondenbar |
| CO2-rich |
250–400 |
10–200 |
CO2 critical region |
| Wide-boiling |
300–600 |
1–100 |
Large T range |
Step 3: Add Stress Cases
Add cases specifically designed to challenge the algorithm:
- Near bubble point: T slightly above Tbub at given P
- Near dew point: T slightly below Tdew at given P
- Near critical: T ≈ Tc ± 5K, P ≈ Pc ± 5 bar
- Very low vapor fraction: β ≈ 0.001
- Very high vapor fraction: β ≈ 0.999
- Single phase (verification): Known single-phase conditions
- Trace components: One component at < 1e-6 mole fraction
Step 4: Define Composition Perturbation
Use Dirichlet sampling to generate composition variants:
from numpy.random import dirichlet
def perturb_composition(base_comp, n_variants=10, concentration=50):
"""Generate composition variants around a base composition.
Higher concentration = less perturbation.
"""
names = list(base_comp.keys())
alpha = np.array([base_comp[n] for n in names]) * concentration
variants = []
for _ in range(n_variants):
x = dirichlet(alpha)
variants.append(dict(zip(names, x.tolist())))
return variants
Step 5: Define Metrics
Every benchmark case must record:
| Metric |
Type |
Unit |
How to Measure |
converged |
bool |
— |
Did the flash converge? |
iterations |
int |
— |
Total iterations (SS + NR) |
ss_iterations |
int |
— |
Successive substitution iterations only |
nr_iterations |
int |
— |
Newton-Raphson iterations only |
cpu_time_ms |
float |
ms |
Wall-clock time (median of 3 runs) |
residual_norm |
float |
— |
Final norm of equilibrium residuals |
stability_tested |
bool |
— |
Was stability analysis triggered? |
stability_iters |
int |
— |
TPD minimization iterations |
n_phases |
int |
— |
Number of phases at equilibrium |
beta_vapor |
float |
— |
Vapor phase fraction |
phase_id_correct |
bool |
— |
Correct phase identification? |
Step 6: Estimate Total Cases
Target: 500–2000 cases per algorithm version.
| Component |
Cases |
| 6 families × 20 PT points |
120 base cases |
| 10 composition variants each |
1200 cases |
| 50 stress cases |
50 cases |
| Total |
~1250 cases |
Step 7: Generate Config File
Output benchmark_config.json:
{
"benchmark_id": "tpflash_2026_01",
"created": "2026-03-31",
"algorithms": ["baseline", "candidate_eigenvalue_switch"],
"eos_models": ["SRK"],
"timing_repeats": 3,
"families": [
{
"name": "lean_gas",
"base_composition": {"methane": 0.90, "ethane": 0.05, "propane": 0.03, "nitrogen": 0.01, "CO2": 0.01},
"n_composition_variants": 10,
"dirichlet_concentration": 50,
"T_range_K": [200, 400],
"P_range_bara": [1, 200],
"n_T": 20,
"n_P": 20
}
],
"stress_cases": {
"near_critical": 20,
"near_bubble": 10,
"near_dew": 10,
"trace_component": 10
}
}
Checklist
1---2name: design-flash-benchmark3description: Create a structured test matrix for comparing flash algorithm performance4---56# Skill: Design Flash Benchmark78## Purpose910Create a structured test matrix for comparing flash algorithm performance11across fluid types, thermodynamic conditions, and difficulty levels.1213## When to Use1415- Starting a new flash algorithm comparison study16- Extending an existing benchmark to new fluid families17- Designing stress tests for near-critical or multi-phase regions1819## Benchmark Design Procedure2021### Step 1: Select Fluid Families2223Choose from these standard families:2425| Family | Components | Mole Fractions | Characteristics |26|--------|-----------|----------------|-----------------|27| **Lean gas** | CH4(0.90), C2(0.05), C3(0.03), N2(0.01), CO2(0.01) | Fixed or ±10% | Easy, mostly single-phase |28| **Rich gas** | CH4(0.70), C2(0.10), C3(0.08), nC4(0.05), nC5(0.03), N2(0.02), CO2(0.02) | Fixed or ±15% | Moderate, clear two-phase |29| **Gas condensate** | CH4(0.65), C2(0.08), C3(0.06), nC4(0.04), nC5(0.03), nC6(0.02), nC7(0.02), nC10(0.05), N2(0.02), CO2(0.03) | ±20% | Near-critical behavior |30| **CO2-rich** | CO2(0.80), CH4(0.10), N2(0.05), H2S(0.03), C2(0.02) | ±15% | Strong non-ideality |31| **Wide-boiling** | CH4(0.50), nC4(0.15), nC10(0.15), nC16(0.10), nC20(0.10) | ±20% | Large volatility range |32| **Sour gas** | CH4(0.60), CO2(0.15), H2S(0.10), C2(0.08), C3(0.05), N2(0.02) | ±15% | Acid gas behavior |3334### Step 2: Define PT Space3536For each family, define the pressure-temperature sampling grid:3738```python39import numpy as np4041def generate_pt_grid(T_min_K, T_max_K, P_min_bara, P_max_bara, n_T=20, n_P=20):42 """Generate a regular PT grid."""43 T_values = np.linspace(T_min_K, T_max_K, n_T)44 P_values = np.logspace(np.log10(P_min_bara), np.log10(P_max_bara), n_P)45 cases = []46 for T in T_values:47 for P in P_values:48 cases.append({"T_K": float(T), "P_bara": float(P)})49 return cases50```5152Standard ranges by family:5354| Family | T range (K) | P range (bara) | Focus region |55|--------|------------|----------------|--------------|56| Lean gas | 200–400 | 1–200 | Dew point region |57| Rich gas | 220–450 | 5–300 | Two-phase dome |58| Gas condensate | 250–500 | 10–500 | Near cricondenbar |59| CO2-rich | 250–400 | 10–200 | CO2 critical region |60| Wide-boiling | 300–600 | 1–100 | Large T range |6162### Step 3: Add Stress Cases6364Add cases specifically designed to challenge the algorithm:65661. **Near bubble point**: T slightly above Tbub at given P672. **Near dew point**: T slightly below Tdew at given P683. **Near critical**: T ≈ Tc ± 5K, P ≈ Pc ± 5 bar694. **Very low vapor fraction**: β ≈ 0.001705. **Very high vapor fraction**: β ≈ 0.999716. **Single phase (verification)**: Known single-phase conditions727. **Trace components**: One component at < 1e-6 mole fraction7374### Step 4: Define Composition Perturbation7576Use Dirichlet sampling to generate composition variants:7778```python79from numpy.random import dirichlet8081def perturb_composition(base_comp, n_variants=10, concentration=50):82 """Generate composition variants around a base composition.8384 Higher concentration = less perturbation.85 """86 names = list(base_comp.keys())87 alpha = np.array([base_comp[n] for n in names]) * concentration88 variants = []89 for _ in range(n_variants):90 x = dirichlet(alpha)91 variants.append(dict(zip(names, x.tolist())))92 return variants93```9495### Step 5: Define Metrics9697Every benchmark case must record:9899| Metric | Type | Unit | How to Measure |100|--------|------|------|----------------|101| `converged` | bool | — | Did the flash converge? |102| `iterations` | int | — | Total iterations (SS + NR) |103| `ss_iterations` | int | — | Successive substitution iterations only |104| `nr_iterations` | int | — | Newton-Raphson iterations only |105| `cpu_time_ms` | float | ms | Wall-clock time (median of 3 runs) |106| `residual_norm` | float | — | Final norm of equilibrium residuals |107| `stability_tested` | bool | — | Was stability analysis triggered? |108| `stability_iters` | int | — | TPD minimization iterations |109| `n_phases` | int | — | Number of phases at equilibrium |110| `beta_vapor` | float | — | Vapor phase fraction |111| `phase_id_correct` | bool | — | Correct phase identification? |112113### Step 6: Estimate Total Cases114115Target: **500–2000 cases per algorithm version**.116117| Component | Cases |118|-----------|-------|119| 6 families × 20 PT points | 120 base cases |120| 10 composition variants each | 1200 cases |121| 50 stress cases | 50 cases |122| Total | ~1250 cases |123124### Step 7: Generate Config File125126Output `benchmark_config.json`:127128```json129{130 "benchmark_id": "tpflash_2026_01",131 "created": "2026-03-31",132 "algorithms": ["baseline", "candidate_eigenvalue_switch"],133 "eos_models": ["SRK"],134 "timing_repeats": 3,135 "families": [136 {137 "name": "lean_gas",138 "base_composition": {"methane": 0.90, "ethane": 0.05, "propane": 0.03, "nitrogen": 0.01, "CO2": 0.01},139 "n_composition_variants": 10,140 "dirichlet_concentration": 50,141 "T_range_K": [200, 400],142 "P_range_bara": [1, 200],143 "n_T": 20,144 "n_P": 20145 }146 ],147 "stress_cases": {148 "near_critical": 20,149 "near_bubble": 10,150 "near_dew": 10,151 "trace_component": 10152 }153}154```155156## Checklist157158- [ ] All components available in NeqSim database159- [ ] PT ranges cover the two-phase region for each family160- [ ] Stress cases are well-defined161- [ ] Metrics list is complete162- [ ] Total case count is between 500 and 2000163- [ ] Random seed is fixed for reproducibility