Skip to the content.

Refinery Assay and TBP Cut Characterization

OilAssayCharacterisation is the first refinery-specific assay entry point in NeqSim. It converts a pre-binned crude or petroleum-fraction assay into TBP pseudo-components that can be used by the existing thermodynamic and ProcessSystem APIs.

This API is intentionally conservative. It handles assay representation and pseudo-component generation; it does not claim to replace a laboratory assay package or to convert ASTM D86/D1160 data to true boiling point (TBP).

Scope and unit contract

Quantity API basis Notes
Assay mass kg setTotalAssayMass(...)
Molar mass kg/mol Use withMolarMassKgPerMol(...); withMolarMassGramPerMol(...) is provided for assay tables reported in g/mol
Specific gravity dimensionless Numerically equivalent to density in g/cm3 for the petroleum correlations used here
Density kg/m3 Use withDensityKgPerCubicMetre(...) for explicit SI density input
API gravity degrees API Negative API gravity is supported for liquids denser than water
Boiling temperature K, degC, or degF Unit-explicit builder methods are provided
Cut yield mass fraction or liquid-volume fraction One assay must use exactly one basis; mass and volume bases cannot be mixed

The historical withMolarMass(double) method remains available and means kg/mol. Values such as 150.0 therefore mean 150 kg/mol, not 150 g/mol. For a 150 g/mol petroleum cut, use withMolarMassKgPerMol(0.150) or withMolarMassGramPerMol(150.0).

Why the fraction basis is explicit

A refinery assay is commonly reported on either a weight basis or a liquid-volume basis. Combining a 40 wt% cut with a 60 vol% cut does not define one physical composition unless an additional conversion basis is supplied.

NeqSim therefore requires all cuts in one OilAssayCharacterisation to use the same basis:

The declared fractions must close to unity within 0.001. This tolerance permits ordinary assay-table rounding but rejects incomplete assays rather than silently scaling a materially incomplete slate to 100%.

Java: pre-binned mass-basis assay

import neqsim.thermo.characterization.OilAssayCharacterisation;
import neqsim.thermo.characterization.OilAssayCharacterisation.AssayCut;
import neqsim.thermo.system.SystemInterface;
import neqsim.thermo.system.SystemSrkEos;

SystemInterface crude = new SystemSrkEos(298.15, 1.01325);
OilAssayCharacterisation assay = crude.getOilAssayCharacterisation();
assay.clearCuts();
assay.setTotalAssayMass(1.0); // kg basis

assay.addCut(new AssayCut("Naphtha")
    .withWeightPercent(18.0)
    .withSpecificGravity(0.72)
    .withBoilingRangeCelsius(90.0, 180.0));
assay.addCut(new AssayCut("Kerosene")
    .withWeightPercent(22.0)
    .withSpecificGravity(0.79)
    .withBoilingRangeCelsius(180.0, 250.0));
assay.addCut(new AssayCut("GasOil")
    .withWeightPercent(35.0)
    .withSpecificGravity(0.86)
    .withBoilingRangeCelsius(250.0, 380.0));
assay.addCut(new AssayCut("Residue")
    .withWeightPercent(25.0)
    .withApiGravity(12.0)
    .withBoilingRangeCelsius(380.0, 560.0));

assay.apply();

apply() resolves every cut before adding the first component, checks assay closure, calculates cut moles from the configured mass basis, and rejects duplicate/repeated application if the generated pseudo-component already exists. Every strictly positive normalized mass fraction is retained, including trace cuts below ordinary comparison tolerances; only exact-zero cuts are omitted so trace handling remains consistent with the reconstructed-mass closure gate.

Java: cumulative TBP cut boundaries

For a pre-binned true-boiling-point curve, the cumulative liquid-volume yields can be converted directly into assay cuts:

SystemInterface crude = new SystemSrkEos(298.15, 1.01325);
OilAssayCharacterisation assay = crude.getOilAssayCharacterisation();
assay.clearCuts();

// Boundary points: cumulative liquid-volume percent and TBP temperature.
double[] cumulativeVolumePercent = {0.0, 20.0, 55.0, 80.0, 100.0};
double[] tbpCelsius = {90.0, 180.0, 270.0, 380.0, 520.0};

// One specific gravity for each interval.
double[] specificGravity = {0.70, 0.76, 0.84, 0.93};

assay.addTBPCutBoundariesCelsius(
    "TBP", cumulativeVolumePercent, tbpCelsius, specificGravity);
assay.apply();

This creates TBP1_PC through TBP4_PC. Each AssayCut retains its lower and upper TBP boundaries. The interval midpoint is used as the representative boiling temperature for the existing NeqSim molecular-weight/petroleum-property correlation.

When molecular weight is not supplied, the inverse petroleum correlation first produces a g/mol-sized value and OilAssayCharacterisation converts it to kg/mol before creating the pseudo-component. This unit conversion is part of the assay API contract; generated refinery cuts must not be passed to addTBPfraction(...) with g/mol interpreted as kg/mol.

The input is required to span 0 to 100 liquid-volume percent with strictly increasing yield and temperature boundaries. This is deliberate: the method represents a complete, already-TBP cut table and does not invent unmeasured light or residue tails.

Volume-basis conversion

For cut volume fractions v_i and cut densities rho_i, NeqSim first calculates the normalized mass fraction

\[w_i=\frac{v_i\rho_i}{\sum_j v_j\rho_j}\]

and then calculates the pseudo-component amount on the configured assay mass basis m_assay:

\[n_i=\frac{m_{assay}w_i}{M_i}\]

where M_i is in kg/mol. Before any component is added, the implementation verifies that the reconstructed mass from all generated amounts closes to the configured assay mass.

Density and API gravity

Use an explicit density method when possible:

AssayCut cutA = new AssayCut("CutA").withSpecificGravity(0.85);
AssayCut cutB = new AssayCut("CutB").withDensityKgPerCubicMetre(850.0);
AssayCut cutC = new AssayCut("CutC").withApiGravity(34.97);

withDensity(double) is retained for compatibility. Values up to 1.5 are interpreted as specific gravity/g/cm3; larger values are treated as kg/m3 and divided by 1000. New refinery code should prefer the unit-explicit methods above.

The API-gravity conversion follows the conventional relation

\[SG_{60/60}=\frac{141.5}{API+131.5}\]

and stores the result as dimensionless specific gravity. Negative API gravities are accepted when physically meaningful; the singular range at and below -131.5 degrees API is rejected. This exact dimensionless handling corrects the earlier assay path, which additionally multiplied API-derived specific gravity by the water density and therefore made API- and specific-gravity-origin cuts differ by about 0.0985%.

Reconstructed whole-assay density

For a complete cut table, getBulkSpecificGravity() reconstructs the ideal additive-volume whole-assay specific gravity without creating pseudo-components or mutating the thermodynamic system. getBulkApiGravity() reports the corresponding degrees API. getBulkDensityKgPerCubicMetreAt60F() provides physical density at 60 degF by multiplying the dimensionless result by 999.016 kg/m3. Both mass- and liquid-volume-basis assays use the same resolved relation:

\[SG_{bulk}=\left(\sum_i\frac{w_i}{SG_i}\right)^{-1}\]

where w_i is the resolved mass fraction and SG_i is the cut specific gravity. For volume-basis inputs this reduces to the volume-fraction-weighted cut density after normalization.

These methods are screening calculations at the density reference condition represented by the inputs. They assume ideal additive liquid volumes and do not apply temperature correction, excess-volume, or blend-contraction models. They are not custody-transfer or certified blend-design calculations. The public validation and numerical error boundary are documented in DOE/OEDI COA bulk density and API qualification.

Whole-assay blend screening

RefineryAssayBlend combines already-resolved whole-assay properties on one explicit mass basis. Specific gravity uses ideal additive liquid volumes, while total sulfur and nitrogen use linear mass weighting:

$SG_{blend}=\left(\sum_j\frac{x_j}{SG_j}\right)^{-1},\qquad S_{blend}=\sum_jx_jS_j,\qquad N_{blend}=\sum_jx_jN_j$

Here x_j is the normalized source-assay mass fraction. Callers may supply bulk properties directly, or use fromAssays(...) to resolve complete density, sulfur, and nitrogen inputs from configured OilAssayCharacterisation instances before a result is returned. Property queries do not call apply(), create pseudo-components, or mutate the attached thermodynamic systems.

RefineryAssayBlend densityBlend = RefineryAssayBlend.fromBulkProperties(
    new double[] {60.0, 40.0},
    new double[] {0.847, 0.771});

double blendSpecificGravity = densityBlend.getSpecificGravity();
double blendApi = densityBlend.getApiGravity();
double blendDensityKgM3 = densityBlend.getDensityKgPerCubicMetreAt60F();

The two endpoint specific gravities above are published DOE/OEDI COA whole-crude values for samples 50146 and 56337. They provide public endpoint evidence for an independently recomputable 60/40 screening calculation; no measured property for that hypothetical blend is available or claimed. Source: DOE/OEDI COA summary workbook.

All positive-mass sources must provide each property requested by the selected factory. Null, non-finite, negative, zero-total, length-mismatched, or incomplete positive-mass inputs fail closed. Zero-mass sources do not contribute. Returned normalized mass fractions are defensive copies.

This is bulk bookkeeping, not a thermodynamic or empirical blend model. It does not predict temperature/pressure effects, excess volume or contraction, viscosity, cloud/pour point, asphaltene stability, phase equilibrium, TBP/pseudo-component compatibility, product specifications, or blend optimization.

Kinematic-viscosity blend screening

RefineryViscosityBlend supplies a separate empirical Refutas screen for source viscosities already resolved at one common temperature. It transforms kinematic viscosity nu in cSt to a dimensionless viscosity blending number, mixes that number by normalized mass fraction, and applies the analytical inverse:

$VBN_i=14.534\ln(\ln(\nu_i+0.8))+10.975$

$VBN_{blend}=\sum_i x_iVBN_i$

$\nu_{blend}=\exp\left(\exp\left(\frac{VBN_{blend}-10.975}{14.534}\right)\right)-0.8$

The double logarithm requires every positive-mass source viscosity to be finite and greater than 0.2 cSt. A zero-mass source is ignored and may leave its viscosity unresolved. The temperature is stored with the immutable result but is not used to extrapolate viscosity.

Equal-viscosity sources are valid when their masses are supplied: blending three equal masses at 20 cSt returns 20 cSt. Floating-point summation can put the blended VBN just outside the source interval, so the result is clamped to that interval after input and finiteness checks. This also preserves blends of nearly equal viscosities without rejecting valid recipes.

RefineryViscosityBlend viscosityBlend = RefineryViscosityBlend.fromMassBasis(
    new double[] {5000.0, 12000.0},
    new double[] {550.0, 375.0},
    50.0);

double blendViscosityCSt = viscosityBlend.getKinematicViscosityCSt();
double blendNumber = viscosityBlend.getViscosityBlendingNumber();
double temperatureCelsius = viscosityBlend.getTemperatureCelsius();
double[] massFractions = viscosityBlend.getMassFractions();
double[] sourceBlendNumbers = viscosityBlend.getSourceViscosityBlendingNumbers();
double directBlendNumber =
    RefineryViscosityBlend.calculateViscosityBlendingNumber(550.0);
double directViscosityCSt =
    RefineryViscosityBlend.calculateKinematicViscosityCSt(directBlendNumber);

RefineryViscosityBlend plannedBlend =
    RefineryViscosityBlend.fromBinaryTargetKinematicViscosity(
        550.0, 375.0, 418.68738293612904, 50.0);
double[] plannedMassFractions = plannedBlend.getMassFractions();

For two distinct source viscosities, the target factory solves the unique mass fractions in VBN space:

$x_1=\frac{VBN_{target}-VBN_2}{VBN_1-VBN_2}$

$x_2=1-x_1$

The target must lie inside the closed interval formed by the two source viscosities. Exact endpoint targets return a pure-source result. Equal-viscosity sources fail closed because their mass ratio is not uniquely determined.

The equations and mass-weighting basis follow Centeno et al., DOI 10.1016/j.fuel.2011.02.028. The fixed binary example above is an independently recomputed arithmetic reference, not measured blend evidence. This API does not claim physical prediction accuracy, uncertainty, ASTM compliance, dynamic-viscosity conversion, viscosity-temperature extrapolation, non-Newtonian behavior, pressure correction, phase behavior, compatibility, multi-source or multi-property optimization, economics, or control.

Binary quality-constrained blend envelope

RefineryBinaryBlendEnvelope combines the already-qualified bulk-property and viscosity screens for exactly two resolved sources. For first-source mass fraction x, API gravity, sulfur, nitrogen, and VBN are affine in x:

$P_{blend}=xP_1+(1-x)P_2$

For API gravity this follows from the ideal-additive-volume relation because $API=141.5/SG-131.5$. For viscosity, $P$ is VBN and the final value is obtained through the published Refutas inverse. The implementation intersects every inclusive property interval with $0\leq x\leq1$ and fails closed when the intersection is empty.

RefineryBinaryBlendEnvelope envelope =
    RefineryBinaryBlendEnvelope.fromQualityConstraints(
        new double[] {0.847, 0.771},
        new double[] {0.020, 0.005},
        new double[] {0.0020, 0.0005},
        new double[] {550.0, 375.0},
        50.0,
        35.56021251475798,
        52.0278858625162,
        0.014,
        0.01,
        411.7708156677767,
        550.0);

double minimumFirstFraction = envelope.getMinimumFirstSourceMassFraction();
double maximumFirstFraction = envelope.getMaximumFirstSourceMassFraction();
RefineryBinaryBlendEnvelope.Plan minimumCost = envelope.planMinimumCost(1.0, 2.0);
double selectedFirstFraction = minimumCost.getFirstSourceMassFraction();
double selectedCost = minimumCost.getUnitCostPerMass();
double selectedApi = minimumCost.getAssayBlend().getApiGravity();
double selectedViscosity = minimumCost.getViscosityBlend().getKinematicViscosityCSt();

The documented arithmetic case gives a closed first-source interval of 0.25-0.60. With source costs 1 and 2 per common mass unit, the unique minimum-cost endpoint is 0.60 and the blended unit cost is 1.40. Reversing the costs selects 0.25. Equal costs fail closed when the feasible interval contains more than one point; a single-point feasible interval remains valid.

The specific-gravity endpoints 0.847 and 0.771 are preserved public DOE/OEDI assay values used by the existing bulk-blend qualification. The viscosities and quality values are arithmetic integration evidence, not measured blend data. The viscosity relation retains the Centeno et al. provenance, DOI 10.1016/j.fuel.2011.02.028.

This bounded analytical planner is not a generic optimizer. It does not predict excess volume, viscosity-temperature behavior, phase or asphaltene compatibility, measured product quality, uncertainty, nonlinear economics, multi-source feasibility, or control actions. Every source property must already be resolved on the documented basis, and costs must use one common currency-per-mass basis.

Multi-source linear blend optimization

RefineryLinearBlendOptimizer extends the qualified binary envelope to two or more resolved sources. It minimizes a linear source cost on normalized, non-negative mass fractions:

\[\min_x\sum_i c_i x_i,\qquad \sum_i x_i=1,\qquad x_i\geq0\]

API gravity is affine in the fractions because ideal additive volume makes reciprocal specific gravity mass-linear. Sulfur and nitrogen are mass-linear. Viscosity constraints are linearized in the published Refutas VBN space at one explicit common temperature, then reconstructed through RefineryViscosityBlend.

RefineryLinearBlendOptimizer.Result optimum =
    RefineryLinearBlendOptimizer.optimizeMinimumCost(
        new double[] {1.0, 2.0, 3.0},
        new double[] {0.85, 0.80, 0.75},
        new double[] {0.030, 0.010, 0.002},
        new double[] {0.003, 0.001, 0.0002},
        new double[] {600.0, 300.0, 50.0},
        50.0,
        30.0,
        60.0,
        0.015,
        0.01,
        50.0,
        600.0);

double[] sourceMassFractions = optimum.getSourceMassFractions();
double unitCost = optimum.getUnitCostPerMass();
double blendApi = optimum.getAssayBlend().getApiGravity();
double blendViscosityCSt = optimum.getViscosityBlend().getKinematicViscosityCSt();

RefineryLinearBlendOptimizer.QualityConstraintReceipt quality =
    optimum.getQualityConstraintReceipt();
double sulfurMargin = quality.getSulfurMargin();
boolean sulfurConstraintIsBinding = quality.isSulfurBinding();

The documented analytical case selects source fractions 0.25, 0.75, and 0.0, with unit cost 1.75 and sulfur exactly at the 0.015 mass-fraction limit. A binary regression reproduces the qualified RefineryBinaryBlendEnvelope endpoint. Input reversal preserves the physical optimum. An infeasible problem, invalid property, non-finite solver result, failed mass closure, or failed property reconstruction stops without returning a recipe.

The optimizer uses NeqSim’s existing Apache Commons Math simplex dependency. It returns one minimum-cost feasible vertex; it does not claim that the recipe is unique when costs or constraints are degenerate. Costs must share one currency-per-mass basis, source properties must already be resolved on their documented bases, and all viscosities must refer to the supplied common temperature.

The SG/API, sulfur, and nitrogen rules retain the existing public DOE/OEDI assay provenance. The viscosity rule retains the Centeno et al. provenance, DOI 10.1016/j.fuel.2011.02.028. The numerical examples are transparent arithmetic integration cases, not measured multi-crude blend data.

This is a screening optimizer. It does not model excess volume, blend contraction, viscosity-temperature extrapolation, phase or asphaltene compatibility, uncertainty, nonlinear or integer economics, inventory, scheduling, control actions, or certified product compliance.

Optimization quality-constraint receipts

Every successful result retains an immutable QualityConstraintReceipt. It reports the realized API gravity, sulfur and nitrogen mass fractions, Refutas-blended kinematic viscosity, the exact bounds used by the solve, and the common viscosity temperature. Lower/upper or upper-only margins are reported on the same basis as their properties. A constraint is marked binding when its margin is within the optimizer’s documented numerical tolerance.

The receipt is generated only after the optimizer reconstructs and validates the physical blend. Negative margins larger than tolerance therefore fail closed; tolerance-scale negative roundoff is reported as zero. This is auditable optimization evidence, not sensitivity or uncertainty analysis. It does not expose dual prices, predict nonlinear quality behavior, calibrate a plant, or certify a product specification.

Auditable blend-batch receipts

RefineryBlendBatch scales a normalized mass-fraction recipe to one requested batch mass and returns immutable source mass and ideal-additive source-volume receipts. The source volumes retain the existing 60 degrees Fahrenheit basis:

$V_i=\frac{m_i}{SG_i\rho_{w,60F}},\qquad V_{blend}=\sum_iV_i,\qquad SG_{blend}=\frac{m_{blend}}{V_{blend}\rho_{w,60F}}$

Here $\rho_{w,60F}=999.016\ \mathrm{kg/m^3}$, matching RefineryAssayBlend. A recipe may be supplied directly or taken from a qualified RefineryLinearBlendOptimizer.Result; the latter also closes total cost from the optimizer’s common currency-per-mass basis.

RefineryBlendBatch batch = RefineryBlendBatch.fromMassFractions(
    10000.0,
    new double[] {0.60, 0.40},
    new double[] {0.847, 0.771});

double[] sourceMassesKg = batch.getSourceMassesKg();
double[] sourceVolumesM3At60F = batch.getSourceVolumesM3At60F();
double totalVolumeM3At60F = batch.getTotalAdditiveVolumeM3At60F();
double batchSpecificGravity = batch.getSpecificGravity();

Pair the qualified batch with unique caller identifiers when downstream evidence must be label-addressable:

RefineryBlendSourceLedger ledger = RefineryBlendSourceLedger.fromBatch(
    new String[] {"DOE/OEDI sample 50146", "DOE/OEDI sample 56337"},
    batch);

RefineryBlendSourceLedger.SourceReceipt first =
    ledger.getSourceReceipt("DOE/OEDI sample 50146");
double firstSourceMassKg = first.getMassKg();
double firstSourceVolumeM3At60F = first.getAdditiveVolumeM3At60F();

The identifiers are caller metadata in exact batch-array order. They must be nonblank, free of surrounding whitespace, and unique. The ledger defensively preserves source order, mass fraction, mass, specific gravity, additive volume, and zero-contribution sources. It does not infer assay identity, query a source database, or attest provenance.

The specific-gravity endpoints are the published DOE/OEDI COA values for samples 50146 and 56337 from the DOE/OEDI COA summary workbook. The 60/40 receipt is transparent arithmetic integration evidence, not a measured multi-crude blend. It preserves the same ideal-additive-volume result as RefineryAssayBlend.

The receipt and source ledger do not model blend contraction, temperature correction, tank gauging, viscosity extrapolation, phase or asphaltene compatibility, inventory or scheduling decisions, control actions, or certified product compliance. The ledger does not create tanks or thermodynamic streams. Zero-contribution sources need no fabricated specific gravity; every positive contribution fails closed unless its fraction and density basis are finite and valid.

Complete optimized blend-plan receipts

Use RefineryBlendOptimizationPlan.fromOptimization(...) when one downstream record must retain the qualified optimizer result, scaled batch, quality-constraint evidence, exact source labels, and source costs together:

RefineryBlendOptimizationPlan plan = RefineryBlendOptimizationPlan.fromOptimization(
    new String[] {"DOE/OEDI sample 50146", "DOE/OEDI sample 56337"},
    10000.0,
    sourceSpecificGravities,
    sourceCostsPerMass,
    optimization);

RefineryBlendOptimizationPlan.SourceCostReceipt first =
    plan.getSourceCostReceipt("DOE/OEDI sample 50146");
double firstSourceCost = first.getTotalCost();

Each ordered receipt copies the source index, identifier, mass fraction, mass, 60 degF specific gravity, ideal-additive volume, unit cost, and source total cost. The source-level cost closes to the optimizer unit cost and batch total cost. The plan exposes the original immutable QualityConstraintReceipt, preserves zero-contribution identity with zero mass, volume, and total cost, and fails closed on mismatched identifiers, gravities, costs, or cost closure.

This composition layer does not re-solve the optimization or alter any property result. Source identifiers remain caller metadata, and the DOE/OEDI labels are public-data regression identifiers, not automatic provenance attestation. The plan does not add blend contraction, scheduling, or compliance logic; it also does not model temperature correction, tank gauging, control, phase compatibility, or thermodynamic stream creation.

Per-cut UOP/Watson characterization factor

AssayCut.getWatsonCharacterizationFactor() calculates the dimensionless UOP/Watson factor from the same authoritative density and representative-boiling-point inputs used by the assay workflow:

\[K_W=\frac{(1.8T_b)^{1/3}}{SG}\]

Here $T_b$ is in K and $SG$ is dimensionless specific gravity. This is equivalent to using boiling point in degrees Rankine. A finite boiling interval uses its arithmetic midpoint. Kelvin, Celsius and Fahrenheit inputs therefore share one calculation, and exact-equivalent SG/API inputs give the same result. Missing density or boiling-point information fails closed.

The public DOE Big Hill Watson-factor qualification covers four bounded 375-1050 degF cuts with SG 0.8297-0.9336. The maximum absolute difference from DOE’s one-decimal UOP K values is 0.0122. The method is qualified for assay screening inside that matrix; it is not a whole-crude aggregation, ASTM conversion or design-certification method.

Watson-factor input for terminal cuts

When a source reports a Watson factor but no representative boiling point, use withWatsonCharacterizationFactor(...) together with specific gravity or API gravity. NeqSim inverts the same authoritative relation:

$T_b=\frac{(K_W SG)^3}{1.8}$

The result is a representative normal boiling point in K; it does not create a finite terminal boundary. An explicit representative boiling point and an explicit Watson factor are mutually exclusive. A derived value outside a stored one-sided boundary fails before any thermodynamic component is added.

The DOE Big Hill terminal-Watson qualification uses the reported 1050 degF+ residue values SG60/60 = 1.0089 and UOP K = 11.7. They imply 913.7543263804 K (1185.0877874847 degF), above the published lower boundary. This supplies a source-derived representative temperature for the existing pseudo-component path; it does not independently validate the molecular weight or downstream properties produced by that path.

Composition-resolved standard light ends

When an assay source reports known light molecules, mark each mass-basis cut with withStandardComponent(componentName). NeqSim then uses the attached Java thermodynamic system’s standard-component database and authoritative molar mass instead of creating a petroleum _PC component. Preparation is performed on a clone so unknown or inconsistent inputs fail before the original system is mutated.

The DOE Big Hill composition-resolved light-end qualification normalizes DOE’s reported ethane, propane, i-butane, and n-butane debutanization weights over the reported C2-C4 subset and applies the independently reported 1.70 mass% whole-crude gas yield. It qualifies standard-component identity plus exact mass/mole bookkeeping. It does not qualify VLE, flash recovery, or C5-175 degF properties.

PIANO-derived assay-cut molar mass

When a public assay reports hydrocarbon family and carbon number on a mass basis, use calculatePianoMolarMassKgPerMol(...) to obtain the number-average molar mass for an explicit pseudo-component. The helper supports paraffin, iso-paraffin, aromatic, and naphthene groups using their ideal homologous-series formulas and conventional C/H atomic weights. It normalizes small source-table rounding errors but fails closed for invalid formulas, weights, or closure.

The DOE Big Hill C5-175 degF PIANO qualification freezes the public family/carbon-number table and derives 0.0791538366563 kg/mol. Together with the reported 5.22 mass% yield, SG60/60 = 0.6731, and one-sided 175 degF upper boundary, this supplies a reproducible explicit-molar-mass cut without inventing a lower or representative boiling point. It does not resolve isomers or validate critical properties, VLE, or fractionation yields.

Complete public reference slate

DoeBigHillSweetAssay.create(system, totalMassKg) composes the qualified DOE Big Hill inputs into a complete source-specific mass-basis assay. It configures four standard C2-C4 components plus eight petroleum cuts without automatically mutating the thermodynamic component list. The caller inspects the returned OilAssayCharacterisation and invokes apply() explicitly.

The complete modeled-slate contract and provenance document the 2021 workbook values, the normalized C2-C4 allocation assumption, the blank sulfur/nitrogen screening assumptions, exact mass closure, and the boundary between reproducibility and property/process validation.

Constrained Sarir pseudo-component input

SarirAtmosphericAssay.create(...) converts the public Sarir cumulative TBP evidence into 18 volume-basis cuts while preserving the 70 degC- and 550 degC+ terminal intervals as one-sided. The source does not publish cut density or molar-mass profiles, so both are required caller inputs. The factory accepts a profile only when it reconciles the reported 841.5 kg/m3 whole-crude density within 1.0 kg/m3 and the reported 0.2447 kg/mol average molar mass within 0.001 kg/mol.

This capability makes the missing-property boundary executable instead of silently filling it. It does not resolve Sarir light-end composition, distribute whole-crude sulfur, or qualify fractionation yields. The Sarir reference and input contract provide provenance, Java/JPype usage, and the process-validation stop boundary.

Open-ended terminal boiling boundaries

Terminal assay cuts often publish only one boiling limit. Use withLowerBoilingPointKelvin/Celsius/Fahrenheit(...) for a heavy-end “plus” cut and withUpperBoilingPointKelvin/Celsius/Fahrenheit(...) for a light-end “minus” cut. hasLowerBoilingPoint() and hasUpperBoilingPoint() distinguish one-sided metadata from a complete finite interval.

A one-sided limit is provenance, not a representative boiling point. NeqSim never turns it into a finite midpoint. Pseudo-component generation therefore still requires either an explicit molar mass or an independently supported representative boiling point together with density. Contradictory bounds or representative values fail before the cut is changed.

The public DOE Big Hill terminal-cut qualification freezes the workbook’s 11.56 mass% 1050 degF+ residue boundary. It verifies exact unit conversion, one-sided retention, clone behavior, fail-closed incomplete characterization and exact mass closure when an explicit engineering molar mass is supplied. DOE does not publish that molar mass; the test does not present its control value as source data.

Assay-carried total sulfur

AssayCut.withSulfurMassFraction(...) and withSulfurMassPercent(...) carry total sulfur explicitly on a mass basis. getBulkSulfurMassFraction() and getBulkSulfurMassPercent() apply the linear mass-basis rule to the same resolved assay mass fractions used for pseudo-component bookkeeping. Positive-yield cuts without sulfur data fail closed; zero-yield cuts do not contribute.

The public DOE Big Hill assay sulfur qualification covers a complete non-overlapping crude slate. With the documented zero-sulfur screening assumption for the 1.70 mass% gas cut that lacks a reported value, the reconstructed 0.40867518 mass% agrees with DOE’s 0.409 mass% whole-crude result within 0.00032482 mass%. This is linear assay bookkeeping, not sulfur-species thermodynamics, emissions prediction or hydrotreating chemistry.

Assay-carried total nitrogen

AssayCut.withNitrogenMassFraction(...) and withNitrogenMassPercent(...) carry total nitrogen explicitly on a mass basis. getBulkNitrogenMassFraction() and getBulkNitrogenMassPercent() apply the linear mass-basis rule to the same resolved assay mass fractions used for pseudo-component bookkeeping. Positive-yield cuts without nitrogen data fail closed; zero-yield cuts do not contribute.

The public DOE Big Hill assay nitrogen qualification covers the same complete non-overlapping crude slate. DOE omits nitrogen for the four lightest cuts, so the documented screening case assigns zero to those cuts. The reconstructed 0.1095129 mass% agrees with DOE’s 0.11 mass% whole-crude result within 0.0004871 mass%. This is linear assay bookkeeping, not nitrogen-species thermodynamics, emissions prediction or hydrotreating chemistry.

Existing petroleum-property correlations

This refinery-assay API deliberately reuses the existing NeqSim TBP/pseudo-component property framework. It does not add or retune critical-property, acentric-factor, or molecular-weight coefficients.

The current implementation and its literature lineage are documented in:

Relevant published foundations already used by NeqSim include petroleum-fraction characterization work by Watson, Nelson and Murphy (1935), Winn (1957), Riazi and Daubert (1980/1987), Pedersen, Thomassen and Fredenslund (1984), and Whitson (1983). This increment changes the assay input contract and validation, not those scientific coefficients.

Python accessibility

OilAssayCharacterisation and AssayCut are public Java classes and are therefore reachable through the normal NeqSim Python/JVM gateway. The unit and basis contracts are identical from Python. A dedicated refinery notebook is planned after the characterization API has passed the full Java CI matrix and a public refinery benchmark dataset has been selected.

Validation boundary

The regression suite for this API currently verifies:

These tests establish software/bookkeeping correctness, qualify ideal-additive-volume SG/API screening over the frozen COA matrix (published crude SG 0.765-0.847; maximum observed errors 0.006 SG and 1.5 degrees API), qualify per-cut Watson factors over the frozen DOE matrix (375-1050 degF; maximum observed error 0.0122), qualify the DOE residue’s Watson-derived representative temperature, qualify the DOE C2-C4 standard-component split, qualify the DOE C5-175 degF PIANO aggregate molar mass, and qualify linear assay sulfur and nitrogen bookkeeping over one complete DOE crude slate. They do not qualify temperature correction, contraction, generic molecular-weight or critical-property correlations, sulfur/nitrogen species or reactions, or atmospheric/vacuum fractionation; those remain separate campaign gates in issue #3305.

Refinery capability inventory after this increment

Capability Current NeqSim foundation Campaign status
Pre-binned crude/petroleum assay cuts OilAssayCharacterisation Foundation hardened in #3305
Mass- and volume-basis cut yields Unit/basis-explicit assay API Foundation hardened in #3305
Pre-binned cumulative TBP cut boundaries addTBPCutBoundariesCelsius/Kelvin Initial implementation in #3305
Open-ended terminal boiling limits Unit-explicit one-sided AssayCut boundaries DOE-qualified for metadata and fail-closed preparation
Watson-derived terminal representative point withWatsonCharacterizationFactor(...) DOE-qualified for the Big Hill 1050 degF+ residue
Composition-resolved standard light ends AssayCut.withStandardComponent(...) DOE-qualified for the Big Hill C2-C4 composition and mass/mole bookkeeping
PIANO family/carbon-number molar mass calculatePianoMolarMassKgPerMol(...) DOE-qualified for the Big Hill C5-175 degF aggregate and explicit-molar-mass bookkeeping
Per-cut UOP/Watson characterization factor AssayCut.getWatsonCharacterizationFactor() DOE-qualified for assay screening over 375-1050 degF
Assay-carried total sulfur getBulkSulfurMassFraction/Percent() DOE-qualified for linear bookkeeping over one complete crude slate
Assay-carried total nitrogen getBulkNitrogenMassFraction/Percent() DOE-qualified for linear bookkeeping over one complete crude slate
Complete public reference slate DoeBigHillSweetAssay Reproducible modeled 2021 DOE composition; property and process validation remain separate gates
TBP pseudo-component properties Pedersen, Lee-Kesler, Riazi-Daubert, Twu, Cavett, Standing and related models Existing; needs refinery-range independent validation
Plus-fraction splitting/lumping Characterise, plus-fraction and lumping models Existing; refinery assay integration still to be qualified
Oil density/API and volatility standards Oil-quality standards package, RVP/TVP workflows Whole-assay SG/API, per-cut Watson and linear sulfur/nitrogen bookkeeping qualified over frozen public matrices; broader stream properties remain open
Rigorous distillation columns DistillationColumn, SimpleTray, Naphtali-Sandholm solver, side-draw support Existing foundation; broad-boiling atmospheric/vacuum refinery benchmark remains open
Crude preheat/fired heater General heater/heat-exchanger process equipment Refinery workflow and fuel/emission integration remain open
Whole-assay bulk blend screening RefineryAssayBlend Qualified for ideal-volume SG/API and mass-linear sulfur/nitrogen bookkeeping; nonlinear properties and optimization remain open
ASTM D86/D1160 to TBP conversion No qualified refinery conversion API in this increment Open; requires public correlation provenance and validation
Atmospheric crude-unit benchmark No campaign benchmark yet Next high-value validation milestone
Vacuum tower benchmark No campaign benchmark yet Open after atmospheric case
Hydrotreating/reforming/FCC/hydrocracking No campaign-qualified refinery conversion models Deliberately later phase

Next campaign step

The next dependency-ready process increment should advance the bounded DOE atmospheric integration case to this complete modeled slate and compare product yields and boiling ranges against independent public evidence before vacuum fractionation.