COMMODITY_GROUP GetKCoeffsGroup(COMMODITY_GROUP grp, double api60) { if (grp != COMMODITY_GROUP.GENERALIZED_REFINED_PRODUCT) { return(grp); } // Try to match density range var uom = uoms["api"]; COMMODITY_GROUP ret = COMMODITY_GROUP.ANY; foreach (var limKv in uom.Limits) { if (((int)limKv.Key) >= 100) // Limit to refined product subgroups { if (limKv.Value.ValueIsWithin(api60)) { // Found it! ret = limKv.Key; break; } } } if (ret == COMMODITY_GROUP.ANY) { throw (new ArgumentException("Density is not in the range of any of the Generalized Refined Products")); } return(ret); }
// API 12.1 - Tank Roof Corrections public double GetBarrelsDueToTankRoof(COMMODITY_GROUP grp, double api60, double tempF, double presPsi = 0, double vapPresPsi = 0, double roofWgtLb = 0, double bblPerApi = 0, double refApi = 0) { // Get CTPL double CTPL = GetCTPLFromApiDegFPsig(grp, api60, tempF, presPsi, vapPresPsi); return(GetBarrelsDueToTankRoof(api60, CTPL, roofWgtLb, bblPerApi, refApi)); }
void checkRange(double val, string units, COMMODITY_GROUP grp = COMMODITY_GROUP.ANY) { // Validate and get UoM that contains the limits UnitOfMeasure uom = null; if (!uoms.TryGetValue(units.ToLower(), out uom)) { throw (new ArgumentException("Units {0} not recognized or supported")); } // If not limits defined, there is nothig to check if (uom.Limits == null || uom.Limits.Count < 1) { return; } bool isLiquidGas = (grp == COMMODITY_GROUP.LPG_NGL); bool isGeneralizedRefinedProduct = ((int)grp >= 100); // Ignore commodity if limits are for ANY commodity if (!isLiquidGas && uom.Limits.ContainsKey(COMMODITY_GROUP.ANY)) { try { uom.Limits[COMMODITY_GROUP.ANY].CheckLimits(val); } catch (ArgumentOutOfRangeException e) { throw(new ArgumentOutOfRangeException(String.Format("{0} with units={1}", e.Message, units))); } return; } // Check limit for commodity ValueLimit limit = null; var type = grp; if (isGeneralizedRefinedProduct) { type = COMMODITY_GROUP.GENERALIZED_REFINED_PRODUCT; // Needed to allow density variations within refined products group } if (!uom.Limits.TryGetValue(type, out limit)) { throw (new ArgumentException("Limits for commodity group {0} not supported")); } try { limit.CheckLimits(val); } catch (ArgumentOutOfRangeException e) { throw (new ArgumentOutOfRangeException(String.Format("{0} with units={1} and commodity group={2}", e.Message, units, grp))); } return; }
// Convenient method to handle all in a single call public double GetCTPLFromApiDegFPsig(COMMODITY_GROUP grp, double api60, double tempF, double?presPsig = null, double?vapPress = null) { if (grp == COMMODITY_GROUP.LPG_NGL) { if (vapPress == null) { vapPress = GetVapPressPsia(api60, tempF) + Conversions.pressAtmPsi; // From API 11.2.5 estimation } return(GetCTPLFromAPIDegFPsigLiqGas(api60, tempF, presPsig == null ? 0 : presPsig.Value, vapPress.Value)); } return(GetCTPLFromApiDegFPsigNONLiqGas(grp, api60, tempF, presPsig == null ? 0 : presPsig.Value)); }
// API 11.1. - Equation (7) public double GetDensityFromDensity60(COMMODITY_GROUP grp, double api60, double tempF, double presPsi = 0, double vapPresPsi = 0) { if (tempF == 60.0 && presPsi == 0.0) { return(api60); } // Get CTPL double CTPL = GetCTPLFromApiDegFPsig(grp, api60, tempF, presPsi, vapPresPsi); // Use to computed API - Remember API is inverse of density return(api60 / CTPL); }
// Section 11.1.6.1 Step 3 Ki Table public KCoeffs GetKCoeffs(COMMODITY_GROUP cgroup, double api60 = 0.0) { // If density is passed, try to match commodity group to density if (api60 > 0.0) { cgroup = GetKCoeffsGroup(cgroup, api60); } KCoeffs coeffs = null; if (!kCoeffs.TryGetValue(cgroup, out coeffs)) { throw (new ArgumentException(String.Format("Coefficients for COMMODITY_GROUP {0} not found", cgroup))); } return(coeffs); }
// Section 11.1.6.1 CTPL (commonly known as VCF) double GetCTPLFromApiDegFPsigNONLiqGas(COMMODITY_GROUP grp, double api60, double tempF, double presPsig = 0) { // Step 1 - Check range for density,temperature and pressure checkRange(api60, "API", grp); checkRange(tempF, "degF"); if (presPsig < 0) { presPsig = 0; } checkRange(presPsig, "psig"); KCoeffs coeffs = GetKCoeffs(grp, api60); // Step 2 and 3 - Get corrected temp and density at ITP68 basis double tempITPS68 = Conversions.TempITS90toITPS68(tempF, "degF"); double densITSP68 = Conversions.Api60ITS90tokgm3ITPS68(api60, coeffs); // kg/m3 // Step 4 - Compute coefficient of thermal expansion double thermExpCoeff60 = GetThermExpCoeff60(densITSP68, coeffs); // Step 5 - Compute temperature correction factor double CTL = GetCTL(thermExpCoeff60, tempITPS68); double CPL = 1.0; // No compensations // If not ATM pressure correct for pressure if (presPsig > 0) { // Step 6 - Compute compressibility factor double Fp = GetCompressFactor(densITSP68, tempITPS68); // Step 7 - Compute pressure correction CPL = GetCPL(Fp, presPsig); } // Step 8 double CTPL = CTL * CPL; return(RoundUpAPI11_1(CTPL, "CTPL")); }
public double GetDensity60FromDensity(COMMODITY_GROUP grp, double api, double tempF, out double CTPL, double presPsi = 0, double vapPresPsi = 0) { CTPL = 1.0; if (tempF == 60.0 && presPsi == 0.0) { return(api); } // Guess desired initially set to the same as input density double api60 = api; double apiGuessed = api; // Iterate int i = 0; do { if (i > MAXITERATIONS) { throw(new OperationCanceledException("Maximum iterations {0} have been exceeded when trying to compute api60 from api")); } api60 = api60 - 0.61803 * (apiGuessed - api); // Using golden ratio for simplicity // Get CTPL using guessed number CTPL = GetCTPLFromApiDegFPsig(grp, api60, tempF, presPsi, vapPresPsi); // Get guessed api apiGuessed = api60 / CTPL; i++; }while (CTPL * Math.Abs(api - apiGuessed) > 0.1 * uoms["api"].Precision); // A little better than precision // Check ranges checkRange(api60, "api", grp); // Return reached at api60 return(api60); }
// API 11.2.4 Section 5.1.1.3 // Temperature compensation CTL public double GetCTLLiqGas(double tempF, double api60) { // Step T24/3 // Check Density and Temperature range COMMODITY_GROUP grp = COMMODITY_GROUP.LPG_NGL; checkRange(api60, "API", grp); checkRange(tempF, "degF", grp); // Group must be specified // Step T24/2 // Convert temp to Kelvin and roundup tempF = RoundUp(tempF, -1); double Tx = Conversions.DegFtoDegK(tempF); // Convert to relative density and roundup double relDens60 = Conversions.APItoSG(api60); relDens60 = RoundUp(relDens60, -4); // Step T24/4 // Chose reference fluid subscripts (1,2) LiqGasProperties f1 = null; LiqGasProperties f2 = null; LiqGasProperties prev = null; foreach (var lgProp in lgProps) { if (prev != null) { if (lgProp.Value.relDens60 >= relDens60) { f2 = lgProp.Value; f1 = prev; break; } } prev = lgProp.Value; } if (f2 == null || f1 == null) { throw (new ArgumentException(String.Format("Relative density {0} is out of the range of API 11.2.4 Table 1", api60))); } // Step T24/5 // Compute interpolation variable double delta = (relDens60 - f1.relDens60) / (f2.relDens60 - f1.relDens60); // Step T24/6 // Compute interpolated critical temperature double Tc = f1.tempCritK + delta * (f2.tempCritK - f1.tempCritK); // Step T24/7 // Compute reduced temperature ratio double Trx = Tx / Tc; if (Trx > 1) { throw (new ArgumentException(String.Format("Temperature {0} will result in supercritical conditions which are not supported by this computation", tempF))); } // Step 24/8 // Compute reduced temperature at 60F double t60K = Conversions.DegFtoDegK(60); double Tr60 = t60K / Tc; // Step 24/9 // Compute scaling factor double h2 = (f1.comprFactCrit * f1.densCrit) / (f2.comprFactCrit * f2.densCrit); // Step 24/10 // Compute saturation density of both fluids at 60F double dens60_1 = getSatDensityAtTemp(Tr60, f1); double dens60_2 = getSatDensityAtTemp(Tr60, f2); // Step 24/11 // Calculate interpolating factor Func <double, double, double> ratio = (dens1, dens2) => dens1 / (1 + delta * (dens1 / (h2 * dens2) - 1)); double X = ratio(dens60_1, dens60_2); // Step 24/12 // Opbtain saturation density of both fluids at Trx double densX_1 = getSatDensityAtTemp(Trx, f1); double densX_2 = getSatDensityAtTemp(Trx, f2); // Step 24/13 // Obtain CTL double CTL = ratio(densX_1, densX_2) / X; return(CTL); }