Superancillaries for pure fluids (#2511)

* Expansions are fully wrapped, looking good. Next step is the set of expansions that is the 1D approximation

* Get 1D approx working via cython

* Count solutions

* SuperAncillary class is working

>1000x speedup for water
Time for C++!

* Superancillaries are working!

In C++, speedup is more than 2000x. In Python, more like 150x because of Python <-> C++ overhead

* Add pmax check for PQ superancillary calls

* Update tests

* Allow T limits to be obtained

* Implement get_fluid_parameter_double for getting superanc value

* Add tests for getting parameters from superanc

* Script for testing superancillaries for sphinx

* Microoptimizations; don't help speed

The limiting factor remains the clear function, which takes about 30 ns

* Add R125 superancillary

* Use the release from fastchebpure for the files

* Drop a .gitignore in the unzipped folder

* Update superancillary injection script

* Turn on superancillaries by default

* Missing header

* Many int conversions in superancillary

* Another int cast

* More annoying solution for boost iter max

* Fix warnings

* One more warning

* Clear up the calculation of rho

* Update docs_docker-build.yml

Use arm64 since the containers were built on mac

* Superfluous ;

* Update backend.py

* Get the critical points working for superancillaries

* Fix wrapping changes of xmin&xmax methods

* squelch warnings

* Version 0 of jupyter notebook for docs

* Try to add the notebook to the docs

* Add jupyter notebook for superancillary

* Lots of updates to superancillary notebook

* More updates to docs

* Skip pseudo-pure for superancillary docs

* Fix output of superancillary figures

* Add superancillary plots to docs for the page for each fluid

* Make a placeholder figure for fluids without superancillary

* Add superancillary plots to task list

* Bump to release fixing m-xylene

* Relax the location of the REFPROP stuff

* Change default name for R-1336mzz(E)

* No need for figures to be so large

* Don't need REFPROP setting

* Bump to fastchebpure release with methanol

* Benchmark caching options

* Benchmark more granularly

* Add the fast methods to public API for HEOS class

* Back to memset - can memset with 0 but no other value

* Fix how caching is managed in Helmholtz class

* Close to final implementation

Perhaps a tiny bit more optimization possible?

* Update function name

* Make message more accurate

* Fix init order

* Expose update_QT_pure_superanc to Python

* Fix when _reducing is set for pures

* Fix the post_update

* Indent

* Notebook

* Notebook

* Make ln(p) construction lazy

Only really matters for debug builds

* Also make reference non-const

* Inject superancillary for methanol

* Make the superancillary loading entirely lazy in debug

* Fix PH bug for Nitrogen

 Closes #2470

* Force the clear to be called on SatL and SatV

To invalidate them at start

* Default is non-lazy superancillary loading

* Add CMake option to have lazy-loading superancillaries [skip ci]

Not a good idea unless doing very narrow testing
This commit is contained in:
Ian Bell
2025-05-17 20:27:19 -04:00
committed by GitHub
parent 629dab9646
commit 267d64533a
34 changed files with 35302 additions and 2986 deletions

View File

@@ -24,7 +24,7 @@ env:
jobs:
build:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04-arm
permissions:
contents: read
packages: write

View File

@@ -306,6 +306,8 @@ list(APPEND APP_INCLUDE_DIRS
"${CMAKE_CURRENT_SOURCE_DIR}/externals/msgpack-c/include")
list(APPEND APP_INCLUDE_DIRS
"${CMAKE_CURRENT_SOURCE_DIR}/externals/miniz-3.0.2")
list(APPEND APP_INCLUDE_DIRS
"${CMAKE_CURRENT_SOURCE_DIR}/externals/nlohmann-json")
list(APPEND APP_INCLUDE_DIRS
"${CMAKE_CURRENT_SOURCE_DIR}/externals/incbin")
list(APPEND APP_INCLUDE_DIRS
@@ -2103,6 +2105,10 @@ if(COOLPROP_CATCH_MODULE)
set_property(TARGET CatchTestRunner PROPERTY CXX_INCLUDE_WHAT_YOU_USE
${iwyu_path})
endif()
if(COOLPROP_LAZY_LOAD_SUPERANCILLARIES)
target_compile_definitions(CatchTestRunner PRIVATE LAZY_LOAD_SUPERANCILLARIES)
endif()
endif()
if(COOLPROP_CPP_EXAMPLE_TEST)

View File

@@ -18,6 +18,8 @@
# built documents.
#
import subprocess
import sys
from pathlib import Path
import urllib.request
import zipfile
@@ -76,6 +78,14 @@ else:
'cpapi': ('_static/doxygen/CoolPropDoxyLink.tag', 'http://www.coolprop.org/dev/_static/doxygen/html')
}
# Execute all the notebooks
for dirpath, dirnames, filenames in Path(__file__).parent.walk():
for file in filenames:
if file.endswith('.ipynb') and '.ipynb_checkpoints' not in str(dirpath):
cmd = f'jupyter nbconvert --allow-errors --to notebook --output "{file}" --execute "{file}"'
subprocess.check_output(cmd, shell=True, cwd=dirpath)
# -- General configuration -----------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be extensions
@@ -103,6 +113,7 @@ extensions = ['IPython.sphinxext.ipython_console_highlighting',
# 'inheritance_diagram',
# 'numpydoc',
# 'breathe'
"nbsphinx",
]
# set path to issue tracker:

View File

@@ -0,0 +1,434 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "ed8d1cd8-0c5b-4f4e-8668-ef653075ec02",
"metadata": {},
"source": [
"# Superancillaries \n",
"\n",
"## Motivation\n",
"\n",
"VLE calculations for pure fluids require to solve the system of equations \n",
"\n",
"$$\n",
"p(T,\\rho') = p(T,\\rho'')\n",
"$$\n",
"$$\n",
"g(T,\\rho') = g(T,\\rho'')\n",
"$$\n",
"which is a complicated non-linear rootfinding problem. For a specified $T$, one must get guess values for $\\rho'(T)$ and $\\rho''(T)$ which are commonly obtained from ancillary functions that give \"good enough\" estimates of the densities from which a nonlinear rootfinding algorithm launches to solve for the co-existing densities.\n",
"\n",
"These calculations, while not very slow (order of microseconds per call), often, if not usually, represent a computational pinchpoint. So it would be nice to be able to use a numerical function that can represent the results of these iterative calculations so well that the iterative calculation itself can be avoided. The superancillary functions described here satisfy that goal.\n",
"\n",
"## Theory and Approach\n",
"\n",
"The development of superancillary functions have been laid out in a series of publications:\n",
"\n",
"* [Exceptionally reliable density-solving algorithms for multiparameter mixture models from Chebyshev expansion rootfinding](https://scholar.google.com/citations?view_op=view_citation&hl=en&user=WNn0e_4AAAAJ&cstart=20&pagesize=80&sortby=pubdate&citation_for_view=WNn0e_4AAAAJ:hMod-77fHWUC)\n",
"* [Efficient and Precise Representation of Pure Fluid Phase Equilibria with Chebyshev Expansions](https://scholar.google.com/citations?view_op=view_citation&hl=en&user=WNn0e_4AAAAJ&cstart=20&pagesize=80&sortby=pubdate&citation_for_view=WNn0e_4AAAAJ:OU6Ihb5iCvQC)\n",
"* [Superancillary Equations for the Multiparameter Equations of State in REFPROP 10.0](https://scholar.google.com/citations?view_op=view_citation&hl=en&user=WNn0e_4AAAAJ&sortby=pubdate&citation_for_view=WNn0e_4AAAAJ:AXPGKjj_ei8C)\n",
"\n",
"The term \"superancillary\" was coined by Ulrich Deiters to differentiate it from the ancillary functions that are commonly provided alongside reference equations of state.\n",
"\n",
"At their core, a superancillary function is constructed from a series of Chebyshev expansions. When considering the entire set of Chebyshev expansions, they span the entire range of the independent variable. In their current form, they support only 1D function approximation. Chebyshev expansions, and orthogonal polynomials more generally, are a well-studied numerical tool for function approximation and can permit function approximation to the level of numerical precision.\n",
"\n",
"To build the superancillary equations, one does vapor-liquid equilibrium (VLE) calculations at carefully selected temperatures and *does some math* to construct the Chebyshev expansion. If the expansion is not accurate enough, the domain is subdivided into two halves and the process is then carried out in each section.\n",
"\n",
"To ensure highly accurate and reliable results in the very near vicinity of the critical point, as well as at very pressures (e.g., near the triple point of propane), it is necessary to do the phase equilibrium calculations in extended precision arithmetic. The ``boost::multiprecision`` library is used in C++ to do the extended precision calculations, in concert with the new ``teqp`` library for the equation of state part. The code used to do this exercise is in a fork at https://github.com/CoolProp/fastchebpure and [the releases](https://github.com/CoolProp/fastchebpure/releases) contain the obtained functions.\n",
"\n",
"### Caveats:\n",
"* When superancillaries are enabled, the \"critical point\" is the numerical one that is obtained by enforcing $\\left(\\frac{\\partial p}{\\partial \\rho}\\right)_T = \\left(\\frac{\\partial^2 p}{\\partial \\rho^2}\\right)_T = 0$, rather than the one reported by the EOS developers. This is because the superancillaries are developed to try to fix the critical region as well as possible. The differences can be sometimes not too small: [An Analysis of the Critical Region of Multiparameter Equations of State](https://scholar.google.com/citations?view_op=view_citation&hl=en&user=WNn0e_4AAAAJ&sortby=pubdate&citation_for_view=WNn0e_4AAAAJ:1qzjygNMrQYC)\n",
"* When pressure is provided, the inverse superancillary for $T(p)$ is used, which introduces a very small error"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ab07d156-01ad-43c2-902c-6f45ee76dfa6",
"metadata": {},
"outputs": [],
"source": [
"import json\n",
"import timeit\n",
"import CoolProp.CoolProp as CP\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"import urllib\n",
"from zipfile import ZipFile\n",
"from pathlib import Path"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bad6e729-92f7-42d2-a53e-ff1c11263af1",
"metadata": {},
"outputs": [],
"source": [
"AS = CP.AbstractState('HEOS','Water')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0d7a3783-b7fa-471b-902b-539fe3f8769a",
"metadata": {},
"outputs": [],
"source": [
"# See caveat noted above. The use of superancillary functions necessarily changes the critical point for the fluid\n",
"# Not usually by an amount that will be meaningful in practical applications\n",
"for superanc in [False, True]:\n",
" CP.set_config_bool(CP.ENABLE_SUPERANCILLARIES, superanc)\n",
" print(superanc, AS.T_critical())"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "58913d10-19c7-4b47-858c-60c7e9a66ec8",
"metadata": {},
"outputs": [],
"source": [
"# The JSON data for the expansions can be accessed from CoolProp\n",
"jSuper = json.loads(CP.get_fluid_param_string(\"WATER\", \"JSON\"))[0][\"EOS\"][0][\"SUPERANCILLARY\"]\n",
"SA = CP.SuperAncillary(json.dumps(jSuper))\n",
"\n",
"AS = CP.AbstractState('HEOS','Water')\n",
"Tt = AS.Ttriple()\n",
"Tc = AS.T_critical()\n",
"\n",
"Ts = np.linspace(Tt, 647.0959999999873, 5*10**5)\n",
"ps = np.zeros_like(Ts)\n",
"SA.eval_sat_many(Ts, 'P', 0, ps)\n",
"plt.plot(1/Ts, ps)\n",
"plt.yscale('log'); plt.gca().set(xlabel='$1/T$ / 1/K', ylabel='$p$ / Pa');"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "418b1527-9d80-44ca-9461-5cf1cb5ad35d",
"metadata": {},
"outputs": [],
"source": [
"# At the lower level, calling with a buffer of points\n",
"tic = timeit.default_timer()\n",
"SA.eval_sat_many(Ts, 'P', 0, ps)\n",
"toc = timeit.default_timer()\n",
"print((toc-tic)/len(Ts)*1e6, 'μs/call')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "54f6ad37-0813-4f8a-b7ba-8752b550587e",
"metadata": {},
"outputs": [],
"source": [
"# With superancillaries disabled\n",
"CP.set_config_bool(CP.ENABLE_SUPERANCILLARIES, False)\n",
"QT_INPUTS = CP.QT_INPUTS\n",
"tic = timeit.default_timer()\n",
"for T_ in Ts:\n",
" AS.update(QT_INPUTS, 0, T_)\n",
"toc = timeit.default_timer()\n",
"print((toc-tic)/len(Ts)*1e6, 'μs/call')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5d254da7-0080-4549-9222-0f0b276fd6d5",
"metadata": {},
"outputs": [],
"source": [
"# With superancillaries enabled, three superancillary functions are evaluated\n",
"CP.set_config_bool(CP.ENABLE_SUPERANCILLARIES, True)\n",
"QT_INPUTS = CP.QT_INPUTS\n",
"tic = timeit.default_timer()\n",
"for T_ in Ts:\n",
" AS.update(QT_INPUTS, 0, T_)\n",
"toc = timeit.default_timer()\n",
"print((toc-tic)/len(Ts)*1e6, 'μs/call')"
]
},
{
"cell_type": "markdown",
"id": "f8731b27-90f9-413a-8cfc-363d6feea0f1",
"metadata": {},
"source": [
"If pressure is specified, the speedup is even greater because one must also iterate for the pressure of interest:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "72969532-5b98-4c44-adc9-204d16127e9b",
"metadata": {},
"outputs": [],
"source": [
"pssmall = np.geomspace(ps[0], ps[-1]*(1-1e-10), 10**4) # The full equilibrium is slow\n",
"CP.set_config_bool(CP.ENABLE_SUPERANCILLARIES, False)\n",
"PQ_INPUTS = CP.PQ_INPUTS\n",
"tic = timeit.default_timer()\n",
"for p_ in pssmall:\n",
" AS.update(PQ_INPUTS, p_, 0)\n",
"toc = timeit.default_timer()\n",
"print((toc-tic)/len(pssmall)*1e6, 'μs/call')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "960463cb-9574-4ab7-b3be-23a932470d30",
"metadata": {},
"outputs": [],
"source": [
"# With superancillaries enabled, one evaluates T(p) from an inverse function and then uses that T\n",
"CP.set_config_bool(CP.ENABLE_SUPERANCILLARIES, True)\n",
"PQ_INPUTS = CP.PQ_INPUTS\n",
"tic = timeit.default_timer()\n",
"for p_ in pssmall:\n",
" AS.update(PQ_INPUTS, p_, 0)\n",
"toc = timeit.default_timer()\n",
"print((toc-tic)/len(pssmall)*1e6, 'μs/call')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "56dee520-1a03-45dc-bd2e-c8343da27e59",
"metadata": {},
"outputs": [],
"source": [
"CP.set_config_bool(CP.ENABLE_SUPERANCILLARIES, True)"
]
},
{
"cell_type": "markdown",
"id": "cecc435c-89ad-47f5-a368-3daba910a294",
"metadata": {},
"source": [
"## Other validation checks to confirm accuracy"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d7635fe7-94f7-4415-bcb3-403c5efb5d42",
"metadata": {},
"outputs": [],
"source": [
"def get_hfg(T):\n",
" \"\"\" Latent heat \"\"\"\n",
" AS.update(CP.QT_INPUTS, 0, T)\n",
" return AS.saturated_vapor_keyed_output(CP.iHmolar)-AS.saturated_liquid_keyed_output(CP.iHmolar)\n",
" \n",
"CP.set_config_bool(CP.ENABLE_SUPERANCILLARIES, False)\n",
"HFG_SA = np.array([get_hfg(T_) for T_ in Ts])\n",
"CP.set_config_bool(CP.ENABLE_SUPERANCILLARIES, True)\n",
"HFG_NON = np.array([get_hfg(T_) for T_ in Ts])\n",
"\n",
"plt.plot(Ts, np.abs(HFG_NON/HFG_SA-1))\n",
"CP.set_config_bool(CP.ENABLE_SUPERANCILLARIES, True)\n",
"plt.yscale('log')\n",
"plt.ylabel(r'$|\\Delta h_{fg, non}/\\Delta h_{fg, sa} - 1|$')\n",
"plt.xlabel('$T$ / K');"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b7eb7d11-f782-46b4-a245-07d05aa038f8",
"metadata": {},
"outputs": [],
"source": [
"def get_sf(T):\n",
" \"\"\" liquid entropy \"\"\"\n",
" AS.update(CP.QT_INPUTS, 0, T)\n",
" return AS.saturated_liquid_keyed_output(CP.iSmolar)\n",
" \n",
"CP.set_config_bool(CP.ENABLE_SUPERANCILLARIES, False)\n",
"SF_SA = np.array([get_sf(T_) for T_ in Ts])\n",
"CP.set_config_bool(CP.ENABLE_SUPERANCILLARIES, True)\n",
"SF_NON = np.array([get_sf(T_) for T_ in Ts])\n",
"\n",
"plt.plot(Ts, np.abs(SF_NON - SF_SA))\n",
"plt.yscale('log')\n",
"plt.ylabel(r\"$|s'_{non} - s'_{sa}|$ / J/mol\")\n",
"plt.xlabel('$T$ / K');\n",
"\n",
"plt.figure()\n",
"plt.plot(Ts, SF_NON)\n",
"plt.title(r'Note value goes towards zero at $T\\to 273.16$ K')\n",
"plt.gca().set(ylabel=r\"$s'_{non}$ / J/mol\", xlabel='$T$ / K')\n",
"\n",
"CP.set_config_bool(CP.ENABLE_SUPERANCILLARIES, True)"
]
},
{
"cell_type": "markdown",
"id": "f05b9de1-27d7-42ce-a35c-08cbb5b99d25",
"metadata": {},
"source": [
"## Checks against extended precision"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "933cb2e5-d31e-4fac-a1d9-b4aa82e079a0",
"metadata": {},
"outputs": [],
"source": [
"outputversion = '2025.04.27'\n",
"if not Path(f'{outputversion}.zip').exists():\n",
" print('Downloading the chebyshev output file to ', Path('.').absolute())\n",
" urllib.request.urlretrieve(f'https://github.com/CoolProp/fastchebpure/archive/refs/tags/{outputversion}.zip', f'{outputversion}.zip')\n",
"\n",
" with ZipFile(f'{outputversion}.zip') as z:\n",
" print(Path(f'{outputversion}.zip').parent.absolute())\n",
" z.extractall(Path(f'{outputversion}.zip').parent.absolute())\n",
"\n",
" with (Path('.') / f'fastchebpure-{outputversion}' / '.gitignore').open('w') as fp:\n",
" fp.write(\"*\")\n",
"\n",
"outputcheck = (Path('.') / f'fastchebpure-{outputversion}' / 'outputcheck').absolute()\n",
"assert outputcheck.exists()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e26150cd-0e6a-477a-90c9-4aa11e076d7e",
"metadata": {},
"outputs": [],
"source": [
"import matplotlib\n",
"import matplotlib.pyplot as plt\n",
"import json\n",
"import numpy as np\n",
"import pandas as pd\n",
"import CoolProp.CoolProp as CP\n",
"\n",
"fluid = 'Water'\n",
"\n",
"CP.set_config_bool(CP.ENABLE_SUPERANCILLARIES, False)\n",
"\n",
"AS = CP.AbstractState('HEOS', f\"{fluid}\")\n",
"jSuper = json.loads(CP.get_fluid_param_string(f\"{fluid}\", \"JSON\"))[0]['EOS'][0]['SUPERANCILLARY']\n",
"superanc = CP.SuperAncillary(json.dumps(jSuper))\n",
"RPname = AS.fluid_param_string(\"REFPROP_name\")\n",
"\n",
"# Load extended precision calcs from the release on github\n",
"chk = json.load(open(f'{outputcheck}/{fluid}_check.json'))\n",
"df = pd.DataFrame(chk['data'])\n",
"# df.info() # uncomment to see what fields are available\n",
"\n",
"Tcrit_num = AS.get_fluid_parameter_double(0, \"SUPERANC::Tcrit_num\")\n",
"T = df['T / K'].to_numpy()\n",
"Theta = (Tcrit_num-T)/Tcrit_num\n",
"\n",
"fig, axes = plt.subplots(3, 1, sharex=True, figsize=(6,10))\n",
"\n",
"\n",
"plt.sca(axes[0])\n",
"rhoL_anc = np.zeros_like(T)\n",
"superanc.eval_sat_many(T, 'D', 0, rhoL_anc)\n",
"err = np.abs(df[\"rho'(mp) / mol/m^3\"]/rhoL_anc-1)\n",
"plt.plot(Theta, err, 'o', label=r'$\\Upsilon$:SA')\n",
"\n",
"errCP = np.abs(df[\"rho'(mp) / mol/m^3\"]/CP.PropsSI('Dmolar', 'T', T, 'Q', 0, f'HEOS::{fluid}')-1)\n",
"plt.plot(Theta, errCP, '^', label=r'$\\Upsilon$:HEOS')\n",
"\n",
"try:\n",
" errRP = np.abs(df[\"rho'(mp) / mol/m^3\"]/CP.PropsSI('Dmolar', 'T', T, 'Q', 0, f'REFPROP::{RPname}')-1)\n",
" plt.plot(Theta, errRP, 'x', label=r'$\\Upsilon$:REFPROP')\n",
"except BaseException as BE:\n",
" print(BE)\n",
"\n",
"plt.legend(loc='best')\n",
"plt.ylabel(r\"$|\\rho_{{\\rm \\Upsilon}}'/\\rho_{{\\rm ep}}'-1|$\")\n",
"plt.yscale('log')\n",
"\n",
"\n",
"\n",
"plt.sca(axes[1])\n",
"rhoV_anc = np.zeros_like(T)\n",
"superanc.eval_sat_many(T, 'D', 1, rhoV_anc)\n",
"err = np.abs(df[\"rho''(mp) / mol/m^3\"]/rhoV_anc-1)\n",
"plt.plot(Theta, err, 'o', label=r'$\\Upsilon$:SA')\n",
"\n",
"errCP = np.abs(df[\"rho''(mp) / mol/m^3\"]/CP.PropsSI('Dmolar', 'T', T, 'Q', 1, f'HEOS::{fluid}')-1)\n",
"plt.plot(Theta, errCP, '^', label=r'$\\Upsilon$:HEOS')\n",
"\n",
"try:\n",
" errRP = np.abs(df[\"rho''(mp) / mol/m^3\"]/CP.PropsSI('Dmolar', 'T', T, 'Q', 1, f'REFPROP::{RPname}')-1)\n",
" plt.plot(Theta, errRP, 'x', label=r'$\\Upsilon$:REFPROP')\n",
"except BaseException as BE:\n",
" print(BE)\n",
"\n",
"plt.legend(loc='best')\n",
"plt.ylabel(r\"$|\\rho_{{\\rm \\Upsilon}}''/\\rho_{{\\rm ep}}''-1|$\")\n",
"plt.yscale('log')\n",
"\n",
"\n",
"\n",
"plt.sca(axes[2])\n",
"p_anc = np.zeros_like(T)\n",
"superanc.eval_sat_many(T, 'P', 1, p_anc)\n",
"err = np.abs(df[\"p(mp) / Pa\"]/p_anc-1)\n",
"plt.plot(Theta, err, 'o', label=r'$\\Upsilon$:SA')\n",
"\n",
"errCP = np.abs(df[\"p(mp) / Pa\"]/CP.PropsSI('P', 'T', T, 'Q', 1, f'HEOS::{fluid}')-1)\n",
"plt.plot(Theta, errCP, '^', label=r'$\\Upsilon$:HEOS')\n",
"\n",
"try:\n",
" errRP = np.abs(df[\"p(mp) / Pa\"]/CP.PropsSI('P', 'T', T, 'Q', 1, f'REFPROP::{RPname}')-1)\n",
" plt.plot(Theta, errRP, 'x', label=r'$\\Upsilon$:REFPROP')\n",
"except BaseException as BE:\n",
" print(BE)\n",
"plt.legend(loc='best')\n",
"\n",
"# print(CP.PropsSI('gas_constant', 'T', T[0], 'Q', 1, f'HEOS::{fluid}'))\n",
"# print(CP.PropsSI('gas_constant', 'T', T[0], 'Q', 1, f'REFPROP::{fluid}'))\n",
"\n",
"plt.ylabel(r\"$|p_{{\\rm \\Upsilon}}/p_{{\\rm ep}}-1|$\")\n",
"plt.yscale('log')\n",
"\n",
"plt.sca(axes[2])\n",
"plt.xlabel(r'$\\Theta=(T_{{\\rm crit,num}}-T)/T_{{\\rm crit,num}}$')\n",
"plt.xscale('log')\n",
"\n",
"for ax in axes:\n",
" ax.axhline(1e-12, dashes=[2,2])\n",
" ax.axvline(1e-6, dashes=[2,2])\n",
"\n",
"plt.tight_layout(pad=0.2)\n",
"plt.show()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -17,3 +17,4 @@ This section includes information about the CoolProp software, listings of input
PCSAFT.rst
examples.rst
changelog.rst
SuperAncillary.ipynb

View File

@@ -49,6 +49,17 @@ In this figure, we start off with a state point given by T,P and then we calcula
.. image:: Consistencyplots/{fluid:s}.png
Superancillary Plots
====================
The following figure shows the accuracy of the superancillary functions relative to extended precision calculations carried out in C++ with the teqp library. The results of the iterative calculations with REFPROP and CoolProp are also shown.
.. note::
You can download the script that generated the following figure here: :download:`(link to script)<Superancillaryplots/{fluid:s}.py>`, right-click the link and then save as... or the equivalent in your browser. You can also download this figure :download:`as a PDF<Superancillaryplots/{fluid:s}.pdf>`.
.. image:: Superancillaryplots/{fluid:s}.png
"""
table_template = """ Parameter, Value

View File

@@ -84,6 +84,7 @@ add_to_task_list("coolprop.tabular.speed.py")
add_to_task_list("fluid_properties.phase_envelope.py")
add_to_task_list("fluid_properties.PurePseudoPure.py")
add_to_task_list("fluid_properties.Mixtures.py")
add_to_task_list("fluid_properties.Superancillary.py")
add_to_task_list("coolprop.parametric_table.py")
add_to_task_list("coolprop.configuration.py")
add_to_task_list("logo_2014.py")

View File

@@ -0,0 +1,159 @@
from __future__ import print_function
import os.path
import CoolProp
import urllib.request
import subprocess
import sys
from pathlib import Path
from zipfile import ZipFile
web_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
root_dir = os.path.abspath(os.path.join(web_dir, '..'))
fluids_path = os.path.join(web_dir, 'fluid_properties', 'fluids')
plots_path = os.path.join(web_dir, 'fluid_properties', 'fluids', 'Superancillaryplots')
outputversion = '2025.04.27'
if not Path(f'{outputversion}.zip').exists():
print('Downloading the chebyshev output file to ', Path('.').absolute())
urllib.request.urlretrieve(f'https://github.com/CoolProp/fastchebpure/archive/refs/tags/{outputversion}.zip', f'{outputversion}.zip')
with ZipFile(f'{outputversion}.zip') as z:
z.extractall('.')
with (Path('.') / f'fastchebpure-{outputversion}' / '.gitignore').open('w') as fp:
fp.write("*")
outputcheck = (Path('.') / f'fastchebpure-{outputversion}' / 'outputcheck').absolute()
template = r"""
import matplotlib
matplotlib.use('Agg') # Force mpl to use a non-GUI backend
import matplotlib.pyplot as plt
import json
import numpy as np
import pandas as pd
import CoolProp.CoolProp as CP
CP.set_config_bool(CP.ENABLE_SUPERANCILLARIES, False)
AS = CP.AbstractState('HEOS', "{fluid}")
# Skip pseudo-pure fluids, pure fluids only; pseudo-pure do not have superancillaries
if AS.fluid_param_string("pure") != "true":
quit()
jEOS = json.loads(CP.get_fluid_param_string("{fluid}", "JSON"))[0]['EOS'][0]
if 'SUPERANCILLARY' not in jEOS:
fig = plt.figure()
fig.text(0.5, 0.5, 'Superancillary not available')
plt.savefig('{fluid:s}.png', dpi = 300)
plt.savefig('{fluid:s}.pdf')
plt.close()
quit()
else:
jSuper = jEOS['SUPERANCILLARY']
superanc = CP.SuperAncillary(json.dumps(jSuper))
RPname = AS.fluid_param_string("REFPROP_name")
# Load extended precision calcs from the release on github
chk = json.load(open('{outputcheck}/{fluid}_check.json'))
df = pd.DataFrame(chk['data'])
# df.info() # uncomment to see what fields are available
Tcrit_num = AS.get_fluid_parameter_double(0, "SUPERANC::Tcrit_num")
T = df['T / K'].to_numpy()
Theta = (Tcrit_num-T)/Tcrit_num
fig, axes = plt.subplots(3, 1, sharex=True, figsize=(3.5,7))
plt.sca(axes[0])
rhoL_anc = np.zeros_like(T)
superanc.eval_sat_many(T, 'D', 0, rhoL_anc)
err = np.abs(df["rho'(mp) / mol/m^3"]/rhoL_anc-1)
plt.plot(Theta, err, 'o', label=r'$\Upsilon$:SA')
errCP = np.abs(df["rho'(mp) / mol/m^3"]/CP.PropsSI('Dmolar', 'T', T, 'Q', 0, f'HEOS::{fluid}')-1)
plt.plot(Theta, errCP, '^', label=r'$\Upsilon$:HEOS')
try:
errRP = np.abs(df["rho'(mp) / mol/m^3"]/CP.PropsSI('Dmolar', 'T', T, 'Q', 0, f'REFPROP::{{RPname}}')-1)
plt.plot(Theta, errRP, 'x', label=r'$\Upsilon$:REFPROP')
except BaseException as BE:
print(BE)
plt.legend(loc='best')
plt.ylabel(r"$|\rho_{{\rm \Upsilon}}'/\rho_{{\rm ep}}'-1|$")
plt.yscale('log')
plt.sca(axes[1])
rhoV_anc = np.zeros_like(T)
superanc.eval_sat_many(T, 'D', 1, rhoV_anc)
err = np.abs(df["rho''(mp) / mol/m^3"]/rhoV_anc-1)
plt.plot(Theta, err, 'o', label=r'$\Upsilon$:SA')
errCP = np.abs(df["rho''(mp) / mol/m^3"]/CP.PropsSI('Dmolar', 'T', T, 'Q', 1, f'HEOS::{fluid}')-1)
plt.plot(Theta, errCP, '^', label=r'$\Upsilon$:HEOS')
try:
errRP = np.abs(df["rho''(mp) / mol/m^3"]/CP.PropsSI('Dmolar', 'T', T, 'Q', 1, f'REFPROP::{{RPname}}')-1)
plt.plot(Theta, errRP, 'x', label=r'$\Upsilon$:REFPROP')
except BaseException as BE:
print(BE)
plt.legend(loc='best')
plt.ylabel(r"$|\rho_{{\rm \Upsilon}}''/\rho_{{\rm ep}}''-1|$")
plt.yscale('log')
plt.sca(axes[2])
p_anc = np.zeros_like(T)
superanc.eval_sat_many(T, 'P', 1, p_anc)
err = np.abs(df["p(mp) / Pa"]/p_anc-1)
plt.plot(Theta, err, 'o', label=r'$\Upsilon$:SA')
errCP = np.abs(df["p(mp) / Pa"]/CP.PropsSI('P', 'T', T, 'Q', 1, f'HEOS::{fluid}')-1)
plt.plot(Theta, errCP, '^', label=r'$\Upsilon$:HEOS')
try:
errRP = np.abs(df["p(mp) / Pa"]/CP.PropsSI('P', 'T', T, 'Q', 1, f'REFPROP::{{RPname}}')-1)
plt.plot(Theta, errRP, 'x', label=r'$\Upsilon$:REFPROP')
except BaseException as BE:
print(BE)
plt.legend(loc='best')
# print(CP.PropsSI('gas_constant', 'T', T[0], 'Q', 1, f'HEOS::{fluid}'))
# print(CP.PropsSI('gas_constant', 'T', T[0], 'Q', 1, f'REFPROP::{fluid}'))
plt.ylabel(r"$|p_{{\rm \Upsilon}}/p_{{\rm ep}}-1|$")
plt.yscale('log')
plt.sca(axes[2])
plt.xlabel(r'$\Theta=(T_{{\rm crit,num}}-T)/T_{{\rm crit,num}}$')
plt.xscale('log')
plt.suptitle('Superancillary v. Extended Precision')
plt.tight_layout(pad=0.2, rect=(0,0,1,0.95))
plt.savefig('{fluid:s}.png', dpi = 300)
plt.savefig('{fluid:s}.pdf')
plt.close()
"""
if not os.path.exists(plots_path):
os.makedirs(plots_path)
from pathlib import Path
for fluid in CoolProp.__fluids__:
print('fluid:', fluid)
file_string = template.format(fluid=fluid, outputcheck=outputcheck)
file_path = os.path.join(plots_path, fluid + '.py')
print('Writing to', file_path)
with open(file_path, 'w') as fp:
fp.write(file_string)
print('calling:', 'python "' + fluid + '.py"', 'in', plots_path)
subprocess.check_call('python -u "' + fluid + '.py"', cwd=plots_path, stdout=sys.stdout, stderr=sys.stderr, shell=True)

View File

@@ -4,10 +4,12 @@ import os
import sys
from pathlib import Path
#
def build_sdist(wheel_directory, config_settings=None):
return _orig.build_sdist(wheel_directory, config_settings)
def build_wheel(wheel_directory, config_settings=None, metadata_directory=None):
sys.path.append(str(Path(__file__).parent))
os.chdir('wrappers/Python')
return _orig.build_wheel(wheel_directory, config_settings, metadata_directory)
return _orig.build_wheel(wheel_directory, config_settings, metadata_directory)

View File

@@ -6,14 +6,14 @@ FROM ubuntu:24.04
RUN apt-get -y -m update && DEBIAN_FRONTEND=noninteractive apt-get install -y cmake g++ git zip wget xz-utils
RUN mkdir /boost && \
wget -c --no-check-certificate https://boostorg.jfrog.io/artifactory/main/release/1.87.0/source/boost_1_87_0_rc1.tar.gz -O - | tar -xz -C /boost && \
wget -c --no-check-certificate https://archives.boost.io/release/1.87.0/source/boost_1_87_0.tar.gz -O - | tar -xz -C /boost && \
cd /boost/boost_1_87_0/ && \
./bootstrap.sh && \
./b2 tools/bcp
WORKDIR /boost/boost_1_87_0/
RUN mkdir /boost_CoolProp && \
./bin.v2/tools/bcp/gcc-13/debug/x86_64/link-static/threading-multi/visibility-hidden/bcp predef/other/endian.h boost/fusion/sequence/intrinsic/size.hpp boost/fusion/algorithm/iteration/for_each.hpp boost/fusion/include/mpl.hpp boost/fusion/sequence/intrinsic/at.hpp boost/utility/string_ref.hpp boost/utility/string_view.hpp boost/mpl/size.hpp boost/variant.hpp boost/assert.hpp boost/preprocessor.hpp boost/fusion/support/is_sequence.hpp boost/optional.hpp boost/operators.hpp boost/version.hpp /boost_CoolProp && \
dist/bin/bcp predef/other/endian.h boost/fusion/sequence/intrinsic/size.hpp boost/fusion/algorithm/iteration/for_each.hpp boost/fusion/include/mpl.hpp boost/fusion/sequence/intrinsic/at.hpp boost/utility/string_ref.hpp boost/utility/string_view.hpp boost/mpl/size.hpp boost/variant.hpp boost/assert.hpp boost/preprocessor.hpp boost/fusion/support/is_sequence.hpp boost/optional.hpp boost/operators.hpp boost/version.hpp math/tools/toms748_solve.hpp /boost_CoolProp && \
zip -r /boost_CoolProp.zip /boost_CoolProp && \
tar cJf /boost_CoolProp.tar.xz /boost_CoolProp

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -3798,7 +3798,8 @@
"R1336mzz(E)",
"(E)-1,1,1,4,4,4-Hexafluoro-2-butene",
"R1336MZZ(E)",
"(e)-1,1,1,4,4,4-hexafluoro-2-butene"
"(e)-1,1,1,4,4,4-hexafluoro-2-butene",
"R1336MZZE"
],
"CAS": "66711-86-2",
"CHEMSPIDER_ID": -1,
@@ -3816,7 +3817,7 @@
"FORMULA": "C_{4}H_{2}F_{6}",
"INCHI_KEY": "NLOLSXYRJFEOTA-OWOJBTEDSA-N",
"INCHI_STRING": "InChI=1S/C4H2F6/c5-3(6,7)1-2-4(8,9)10/h1-2H/b2-1+",
"NAME": "R1336MZZE",
"NAME": "R1336mzz(E)",
"REFPROP_NAME": "R1336MZZE",
"SMILES": "?"
},

View File

@@ -1,13 +1,16 @@
from pathlib import Path
import json
FLUIDS = Path(__file__).parent / 'fluids'
FLUIDS = Path(__file__).parent.parent / 'fluids'
print(FLUIDS)
assert(FLUIDS.exists())
FASTCHEB_OUT = Path(r"D:\Code\fastchebpure")
FASTCHEB_OUT = Path(r"/Users/ianbell/Documents/Code/fastchebpure/output")
assert(FASTCHEB_OUT.exists())
for path in FASTCHEB_OUT.glob('*.json'):
print(path)
name = path.stem.replace('_exps','')
contents = json.load(path.open())
destpath = FLUIDS / f'{name}.json'
dest = json.load(destpath.open())
dest['EOS'][0]['SUPERANCILLARY'] = contents
with destpath.open('w') as fp:
fp.write(json.dumps(dest, indent=2))
fp.write(json.dumps(dest, indent=2))

24765
externals/nlohmann-json/nlohmann/json.hpp vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -838,6 +838,11 @@ class AbstractState
/// Update the state using two state variables
virtual void update(CoolProp::input_pairs input_pair, double Value1, double Value2) = 0;
/// Update the state for QT inputs for pure fluids when using the superancillary functions
virtual void update_QT_pure_superanc(double Q, double T){
throw NotImplementedError("update_QT_pure_superanc is not implemented for this backend");
};
/// Update the state using two state variables and providing guess values
/// Some or all of the guesses will be used - this is backend dependent
@@ -867,7 +872,6 @@ class AbstractState
virtual const double get_fluid_constant(std::size_t i, parameters param) const {
throw NotImplementedError("get_fluid_constant is not implemented for this backend");
};
;
/// Set binary mixture floating point parameter (EXPERT USE ONLY!!!)
virtual void set_binary_interaction_double(const std::string& CAS1, const std::string& CAS2, const std::string& parameter, const double value) {

View File

@@ -93,8 +93,8 @@ class CacheArrayElement
{
private:
bool& is_cached;
NumType& value;
bool& is_cached;
public:
@@ -153,7 +153,7 @@ private:
std::array<bool, N> m_cached;
public:
void clear(){
std::fill_n(m_values.data(), m_values.size(), _HUGE);
memset(m_values.data(), 0, sizeof(m_values));
memset(m_cached.data(), false, sizeof(m_cached));
}
auto factory(std::size_t i){

View File

@@ -75,6 +75,7 @@
X(VTPR_ALWAYS_RELOAD_LIBRARY, "VTPR_ALWAYS_RELOAD_LIBRARY", false, \
"If true, the library will always be reloaded, no matter what is currently loaded") \
X(FLOAT_PUNCTUATION, "FLOAT_PUNCTUATION", ".", "The first character of this string will be used as the separator between the number fraction.") \
X(ENABLE_SUPERANCILLARIES, "ENABLE_SUPERANCILLARIES", true, "If true, the superancillary functions will be used for VLE of pure fluids") \
X(LIST_STRING_DELIMITER, "LIST_STRING_DELIMITER", ",", "The delimiter to be used when converting a list of strings to a string")
// Use preprocessor to create the Enum

View File

@@ -16,11 +16,14 @@
#include <string>
#include <vector>
#include <map>
#include <variant>
#include <optional>
#include <cassert>
#include <iterator>
#include "Eigen/Core"
#include "PolyMath.h"
#include "Ancillaries.h"
#include "superancillary/superancillary.h"
namespace CoolProp {
@@ -417,6 +420,13 @@ struct Ancillaries
*/
class EquationOfState
{
public:
using SuperAncillary_t = superancillary::SuperAncillary<std::vector<double>>;
private:
std::string superancillaries_str;
std::optional<SuperAncillary_t> superancillaries; ///< The superancillaries
public:
EquationOfState(){};
~EquationOfState(){};
@@ -439,6 +449,32 @@ class EquationOfState
BibTeX_CP0; ///< The bibtex key for the ideal gas specific heat correlation
CriticalRegionSplines
critical_region_splines; ///< A cubic spline in the form T = f(rho) for saturated liquid and saturated vapor curves in the near-critical region
/// Get the optional of the populated superancillary
std::optional<SuperAncillary_t> & get_superanc_optional(){
if (!superancillaries){
if (!superancillaries_str.empty()){
auto start = std::chrono::high_resolution_clock::now(); // Start time
// Now do the parsing pass and replace with the actual superancillary
superancillaries.emplace(SuperAncillary_t(superancillaries_str));
auto end = std::chrono::high_resolution_clock::now(); // End time
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
std::cout << "Execution time: " << duration.count() << " microseconds for " << BibTeX_EOS << std::endl;
}
}
return superancillaries;
}
/// Set the placeholder string for the superancillaries to allow for lazy construction, particularly important in debug builds
void set_superancillaries_str(const std::string &s){
superancillaries_str = s;
// Do the construction greedily by default, but allow it to be lazy if you want
#if !defined(LAZY_LOAD_SUPERANCILLARIES)
get_superanc_optional();
#endif
}
/// Validate the EOS that was just constructed
void validate() {

View File

@@ -741,6 +741,7 @@ class BaseHelmholtzContainer
{
protected:
std::array<double, 16> cache;
std::array<bool, 16> is_cached;
constexpr static std::size_t i00 = 0, i01 = 1, i02 = 2, i03 = 3, i04 = 4,
i10 = 5, i11 = 6, i12 = 7, i13 = 8,
i20 = 9, i21 = 10, i22 = 11,
@@ -748,12 +749,13 @@ class BaseHelmholtzContainer
i40 = 14;
bool cache_valid(std::size_t i) const {
return std::isfinite(cache[i]);
return is_cached[i];
}
public:
void clear() {
std::fill_n(cache.data(), cache.size(), _HUGE);
memset(cache.data(), 0, sizeof(cache));
memset(is_cached.data(), false, sizeof(is_cached));
};
virtual void empty_the_EOS() = 0;
@@ -874,6 +876,7 @@ class ResidualHelmholtzContainer : public BaseHelmholtzContainer
cache[i03] = derivs.d3alphar_dtau3;
cache[i21] = derivs.d3alphar_ddelta2_dtau;
cache[i12] = derivs.d3alphar_ddelta_dtau2;
memset(is_cached.data(), true, sizeof(is_cached));
}
return derivs;
};
@@ -1456,6 +1459,7 @@ class IdealHelmholtzContainer : public BaseHelmholtzContainer
cache[i03] = derivs.d3alphar_dtau3 * _prefactor;
cache[i21] = derivs.d3alphar_ddelta2_dtau * _prefactor;
cache[i12] = derivs.d3alphar_ddelta_dtau2 * _prefactor;
memset(is_cached.data(), true, sizeof(is_cached));
}
return derivs * _prefactor;
};

File diff suppressed because it is too large Load Diff

View File

@@ -176,19 +176,18 @@ std::vector<std::string> AbstractState::fluid_names(void) {
bool AbstractState::clear_comp_change() {
// Reset all instances of CachedElement and overwrite
// the internal double values with -_HUGE
this->_gas_constant.clear();
this->_molar_mass.clear();
this->_critical.fill(_HUGE);
this->_reducing.fill(_HUGE);
calc_reducing_state();
this->_gas_constant = calc_gas_constant();
return true;
}
bool AbstractState::clear() {
// Reset all instances of CachedElement and overwrite
// the internal double values with -_HUGE
// Reset all instances of CachedElement
cache.clear();
this->_critical.fill(_HUGE);
this->_reducing.fill(_HUGE);
/// Bulk values
this->_rhomolar = -_HUGE;

View File

@@ -341,7 +341,31 @@ void FlashRoutines::QS_flash(HelmholtzEOSMixtureBackend& HEOS) {
}
void FlashRoutines::QT_flash(HelmholtzEOSMixtureBackend& HEOS) {
CoolPropDbl T = HEOS._T;
CoolPropDbl Q = HEOS._Q;
if (HEOS.is_pure_or_pseudopure) {
if (get_config_bool(ENABLE_SUPERANCILLARIES) && HEOS.is_pure()){
auto& optsuperanc = HEOS.get_superanc_optional();
if (optsuperanc){
auto& superanc = optsuperanc.value();
CoolPropDbl Tcrit_num = superanc.get_Tcrit_num();
if (T > Tcrit_num){
throw ValueError(format("Temperature to QT_flash [%0.8Lg K] may not be above the numerical critical point of %0.15Lg K", T, Tcrit_num));
}
auto rhoL = superanc.eval_sat(T, 'D', 0);
auto rhoV = superanc.eval_sat(T, 'D', 1);
auto p = superanc.eval_sat(T, 'P', 1);
HEOS.SatL->update_TDmolarP_unchecked(T, rhoL, p);
HEOS.SatV->update_TDmolarP_unchecked(T, rhoV, p);
HEOS._p = p;
HEOS._rhomolar = 1 / (Q / rhoV + (1 - Q) / rhoL);
HEOS._phase = iphase_twophase;
return;
}
}
// The maximum possible saturation temperature
// Critical point for pure fluids, slightly different for pseudo-pure, very different for mixtures
CoolPropDbl Tmax_sat = HEOS.calc_Tmax_sat() + 1e-13;
@@ -353,9 +377,9 @@ void FlashRoutines::QT_flash(HelmholtzEOSMixtureBackend& HEOS) {
// Get a reference to keep the code a bit cleaner
const CriticalRegionSplines& splines = HEOS.components[0].EOS().critical_region_splines;
// If exactly(ish) at the critical temperature, liquid and vapor have the critial density
if ((get_config_bool(CRITICAL_WITHIN_1UK) && std::abs(T - Tmax_sat) < 1e-6) || std::abs(T - Tmax_sat) < 1e-12) {
// If exactly(ish) at the critical temperature, liquid and vapor have the critial density
HEOS.SatL->update(DmolarT_INPUTS, HEOS.rhomolar_critical(), HEOS._T);
HEOS.SatV->update(DmolarT_INPUTS, HEOS.rhomolar_critical(), HEOS._T);
HEOS._rhomolar = HEOS.rhomolar_critical();
@@ -567,6 +591,29 @@ void get_Henrys_coeffs_FP(const std::string& CAS, double& A, double& B, double&
}
void FlashRoutines::PQ_flash(HelmholtzEOSMixtureBackend& HEOS) {
if (HEOS.is_pure_or_pseudopure) {
if (get_config_bool(ENABLE_SUPERANCILLARIES) && HEOS.is_pure()){
auto& optsuperanc = HEOS.get_superanc_optional();
if (optsuperanc){
auto& superanc = optsuperanc.value();
CoolPropDbl pmax_num = superanc.get_pmax();
if (HEOS._p > pmax_num){
throw ValueError(format("Pressure to PQ_flash [%0.8Lg Pa] may not be above the numerical critical point of %0.15Lg Pa", HEOS._p, pmax_num));
}
auto T = superanc.get_T_from_p(HEOS._p);
auto rhoL = superanc.eval_sat(T, 'D', 0);
auto rhoV = superanc.eval_sat(T, 'D', 1);
auto p = HEOS._p;
HEOS.SatL->update_TDmolarP_unchecked(T, rhoL, p);
HEOS.SatV->update_TDmolarP_unchecked(T, rhoV, p);
HEOS._T = T;
HEOS._p = p;
HEOS._rhomolar = 1 / (HEOS._Q / HEOS.SatV->rhomolar() + (1 - HEOS._Q) / HEOS.SatL->rhomolar());
HEOS._phase = iphase_twophase;
return;
}
}
if (HEOS.components[0].EOS().pseudo_pure) {
// It is a pseudo-pure mixture
@@ -1551,6 +1598,19 @@ void FlashRoutines::HSU_P_flash(HelmholtzEOSMixtureBackend& HEOS, parameters oth
if (HEOS._p < HEOS.p_triple()) {
Tmin = std::max(HEOS.Tmin(), HEOS.Ttriple());
} else {
if (get_config_bool(ENABLE_SUPERANCILLARIES) && HEOS.is_pure()){
auto& optsuperanc = HEOS.get_superanc_optional();
if (optsuperanc){
auto& superanc = optsuperanc.value();
CoolPropDbl pmax_num = superanc.get_pmax();
if (HEOS._p > pmax_num){
throw ValueError(format("Pressure to PQ_flash [%0.8Lg Pa] may not be above the numerical critical point of %0.15Lg Pa", HEOS._p, pmax_num));
}
Tmin = superanc.get_T_from_p(HEOS._p)-1e-12;
break;
}
}
if (saturation_called) {
Tmin = HEOS.SatV->T();
} else {

View File

@@ -391,6 +391,12 @@ class JSONFluidLibrary
// BibTex keys
EOS.BibTeX_EOS = cpjson::get_string(EOS_json, "BibTeX_EOS");
EOS.BibTeX_CP0 = cpjson::get_string(EOS_json, "BibTeX_CP0");
if (EOS_json.HasMember("SUPERANCILLARY")){
// This is inefficient as we do JSON(rapidjson) -> string -> JSON(nlohmann)
// which implies two large parsing passes
EOS.set_superancillaries_str(cpjson::json2string(EOS_json["SUPERANCILLARY"]));
}
EOS.alphar = parse_alphar(EOS_json["alphar"]);
EOS.alpha0 = parse_alpha0(EOS_json["alpha0"]);

View File

@@ -100,6 +100,8 @@ void HelmholtzEOSMixtureBackend::set_components(const std::vector<CoolPropFluid>
mole_fractions = std::vector<CoolPropDbl>(1, 1);
std::vector<std::vector<double>> ones(1, std::vector<double>(1, 1));
Reducing = shared_ptr<ReducingFunction>(new GERG2008ReducingFunction(components, ones, ones, ones, ones));
_reducing = calc_reducing_state_nocache(mole_fractions);
_gas_constant = calc_gas_constant();
} else {
// Set the mixture parameters - binary pair reducing functions, departure functions, F_ij, etc.
set_mixture_parameters();
@@ -113,8 +115,10 @@ void HelmholtzEOSMixtureBackend::set_components(const std::vector<CoolPropFluid>
SatL.reset(get_copy(false));
SatL->specify_phase(iphase_liquid);
linked_states.push_back(SatL);
SatL->clear();
SatV.reset(get_copy(false));
SatV->specify_phase(iphase_gas);
SatV->clear();
linked_states.push_back(SatV);
}
}
@@ -257,6 +261,47 @@ std::string HelmholtzEOSMixtureBackend::fluid_param_string(const std::string& Pa
}
}
std::optional<EquationOfState::SuperAncillary_t>& HelmholtzEOSMixtureBackend::get_superanc_optional(){
if (!is_pure()){
throw CoolProp::ValueError("Only available for pure (and not pseudo-pure) fluids");
}
return components[0].EOS().get_superanc_optional();
}
double HelmholtzEOSMixtureBackend::get_fluid_parameter_double(const size_t i, const std::string& parameter){
if (i >= N) {
throw ValueError(format("Index i [%d] is out of bounds. Must be between 0 and %d.", i, N-1));
}
auto& optsuperanc = get_superanc_optional();
if (parameter.find("SUPERANC::") == 0){
auto& superanc = optsuperanc.value();
if (optsuperanc){
std::string key = parameter.substr(10);
if (key == "pmax"){
return superanc.get_pmax();
}
else if (key == "pmin"){
return superanc.get_pmin();
}
else if (key == "Tmin"){
return superanc.get_Tmin();
}
else if (key == "Tcrit_num"){
return superanc.get_Tcrit_num();
}
else {
throw ValueError(format("Superancillary parameter [%s] is invalid", key.c_str()));
}
}
else{
throw ValueError(format("Superancillary not available for this fluid"));
}
} else {
throw ValueError(format("fluid parameter [%s] is invalid", parameter.c_str()));
}
}
void HelmholtzEOSMixtureBackend::apply_simple_mixing_rule(std::size_t i, std::size_t j, const std::string& model) {
// bound-check indices
if (i < 0 || i >= N) {
@@ -1040,6 +1085,12 @@ CoolPropDbl HelmholtzEOSMixtureBackend::calc_T_critical(void) {
throw ValueError(format("critical point finding routine found %d critical points", critpts.size()));
}
} else {
if (get_config_bool(ENABLE_SUPERANCILLARIES) && is_pure()){
auto& optsuperanc = get_superanc_optional();
if (optsuperanc){
return optsuperanc.value().get_Tcrit_num(); // from the numerical critical point satisfying dp/drho|T = d2p/drho2|T = 0
}
}
return components[0].crit.T;
}
}
@@ -1053,6 +1104,12 @@ CoolPropDbl HelmholtzEOSMixtureBackend::calc_p_critical(void) {
throw ValueError(format("critical point finding routine found %d critical points", critpts.size()));
}
} else {
if (get_config_bool(ENABLE_SUPERANCILLARIES) && is_pure()){
auto& optsuperanc = get_superanc_optional();
if (optsuperanc){
return optsuperanc.value().get_pmax(); // from the numerical critical point satisfying dp/drho|T = d2p/drho2|T = 0
}
}
return components[0].crit.p;
}
}
@@ -1066,6 +1123,12 @@ CoolPropDbl HelmholtzEOSMixtureBackend::calc_rhomolar_critical(void) {
throw ValueError(format("critical point finding routine found %d critical points", critpts.size()));
}
} else {
if (get_config_bool(ENABLE_SUPERANCILLARIES) && is_pure()){
auto& optsuperanc = get_superanc_optional();
if (optsuperanc){
return optsuperanc.value().get_rhocrit_num(); // from the numerical critical point satisfying dp/drho|T = d2p/drho2|T = 0
}
}
return components[0].crit.rhomolar;
}
}
@@ -1142,6 +1205,54 @@ CoolPropDbl HelmholtzEOSMixtureBackend::calc_pmax(void) {
return summer;
}
void HelmholtzEOSMixtureBackend::update_QT_pure_superanc(CoolPropDbl Q, CoolPropDbl T) {
clear();
_Q = Q;
_T = T;
if (!is_pure()){
throw ValueError(format("Is not a pure fluid"));
}
if (!get_config_bool(ENABLE_SUPERANCILLARIES)){
throw ValueError(format("Superancillaries are not enabled"));
}
auto& optsuperanc = get_superanc_optional();
if (!optsuperanc){
throw ValueError(format("Superancillaries not available for this fluid"));
}
auto& superanc = optsuperanc.value();
CoolPropDbl Tcrit_num = superanc.get_Tcrit_num();
if (T > Tcrit_num){
throw ValueError(format("Temperature to QT_flash [%0.8Lg K] may not be above the numerical critical point of %0.15Lg K", T, Tcrit_num));
}
auto rhoL = superanc.eval_sat(T, 'D', 0);
auto rhoV = superanc.eval_sat(T, 'D', 1);
auto p = superanc.eval_sat(T, 'P', 1);
SatL->update_TDmolarP_unchecked(T, rhoL, p);
SatV->update_TDmolarP_unchecked(T, rhoV, p);
_p = p;
_rhomolar = 1 / (Q / rhoV + (1 - Q) / rhoL);
_phase = iphase_twophase;
post_update(false /*optional_checks*/);
}
void HelmholtzEOSMixtureBackend::update_TDmolarP_unchecked(CoolPropDbl T, CoolPropDbl rhomolar, CoolPropDbl p) {
clear();
_rhomolar = rhomolar;
_T = T;
_p = p;
// Cleanup
bool optional_checks = false;
post_update(optional_checks);
}
void HelmholtzEOSMixtureBackend::update_DmolarT_direct(CoolPropDbl rhomolar, CoolPropDbl T) {
// TODO: This is just a quick fix for #878 - should be done more systematically
const CoolPropDbl rhomolar_min = 0;
@@ -1411,7 +1522,9 @@ void HelmholtzEOSMixtureBackend::post_update(bool optional_checks) {
_delta = _rhomolar / _reducing.rhomolar;
// Update the terms in the excess contribution
residual_helmholtz->Excess.update(_tau, _delta);
if (!is_pure_or_pseudopure){
residual_helmholtz->Excess.update(_tau, _delta);
}
}
CoolPropDbl HelmholtzEOSMixtureBackend::calc_Bvirial() {

View File

@@ -104,6 +104,8 @@ class HelmholtzEOSMixtureBackend : public AbstractState
std::vector<CoolProp::CriticalState> _calc_all_critical_points(bool find_critical_points = true);
static void set_fluid_enthalpy_entropy_offset(CoolPropFluid& component, double delta_a1, double delta_a2, const std::string& ref);
std::optional<EquationOfState::SuperAncillary_t>& get_superanc_optional();
public:
HelmholtzEOSMixtureBackend();
@@ -194,6 +196,7 @@ class HelmholtzEOSMixtureBackend : public AbstractState
virtual void set_fluid_parameter_double(const size_t i, const std::string& parameter, const double value) {
throw ValueError("set_fluid_parameter_double only defined for cubic backends");
};
virtual double get_fluid_parameter_double(const size_t i, const std::string& parameter);
phases calc_phase(void) {
return _phase;
@@ -356,6 +359,8 @@ class HelmholtzEOSMixtureBackend : public AbstractState
*/
void update_TP_guessrho(CoolPropDbl T, CoolPropDbl p, CoolPropDbl rho_guess);
void update_DmolarT_direct(CoolPropDbl rhomolar, CoolPropDbl T);
void update_TDmolarP_unchecked(CoolPropDbl T, CoolPropDbl rhomolarL, CoolPropDbl p);
void update_QT_pure_superanc(CoolPropDbl Q, CoolPropDbl T);
void update_HmolarQ_with_guessT(CoolPropDbl hmolar, CoolPropDbl Q, CoolPropDbl Tguess);
/** \brief Set the components of the mixture

View File

@@ -4,6 +4,8 @@
#include "DataStructures.h"
#include "../Backends/Helmholtz/HelmholtzEOSMixtureBackend.h"
#include "../Backends/Helmholtz/HelmholtzEOSBackend.h"
#include "superancillary/superancillary.h"
// ############################################
// TESTS
// ############################################
@@ -2315,7 +2317,14 @@ TEST_CASE("Github issue #2470", "[pureflash]") {
auto pressure = 3368965.046; //Pa
std::shared_ptr<CoolProp::AbstractState> AS;
AS.reset(AbstractState::factory("HEOS", fluide));
AS->update(PQ_INPUTS, pressure, 1);
auto Ts = AS->T();
AS->specify_phase(iphase_gas);
CHECK_NOTHROW(AS->update(PT_INPUTS, pressure, Ts));
AS->unspecify_phase();
CHECK_NOTHROW(AS->update(HmassP_INPUTS, enthalpy, pressure));
auto Tfinal = AS->T();
CHECK(Tfinal > AS->T_critical());
}
TEST_CASE("Github issue #2467", "[pureflash]") {
@@ -2411,6 +2420,214 @@ TEST_CASE("Check that indexes for mixtures are assigned correctly, especially fo
CHECK(abs((p_extra - p)/ p * 100) < 1e-1);
}
/// A fixture class to enable superancillaries just for a given test
class SuperAncillaryOnFixture{
private:
const configuration_keys m_key = ENABLE_SUPERANCILLARIES;
const bool initial_value;
public:
SuperAncillaryOnFixture() : initial_value(CoolProp::get_config_bool(m_key)) {
CoolProp::set_config_bool(m_key, true);
}
~SuperAncillaryOnFixture(){
CoolProp::set_config_bool(m_key, initial_value);
}
};
/// A fixture class to enable superancillaries just for a given test
class SuperAncillaryOffFixture{
private:
const configuration_keys m_key = ENABLE_SUPERANCILLARIES;
const bool initial_value;
public:
SuperAncillaryOffFixture() : initial_value(CoolProp::get_config_bool(m_key)) {
CoolProp::set_config_bool(m_key, false);
}
~SuperAncillaryOffFixture(){
CoolProp::set_config_bool(m_key, initial_value);
}
};
TEST_CASE_METHOD(SuperAncillaryOnFixture, "Check superancillary for water", "[superanc]") {
auto json = nlohmann::json::parse(get_fluid_param_string("WATER", "JSON"))[0].at("EOS")[0].at("SUPERANCILLARY");
superancillary::SuperAncillary<std::vector<double>> anc{json};
shared_ptr<CoolProp::AbstractState> AS(CoolProp::AbstractState::factory("HEOS", "Water"));
shared_ptr<CoolProp::AbstractState> IF97(CoolProp::AbstractState::factory("IF97", "Water"));
auto& rHEOS = *dynamic_cast<HelmholtzEOSMixtureBackend*>(AS.get());
BENCHMARK("HEOS.clear()"){
return rHEOS.clear();
};
BENCHMARK("HEOS rho(T)"){
return AS->update(QT_INPUTS, 1.0, 300.0);
};
BENCHMARK("HEOS update_QT_pure_superanc(Q,T)"){
return rHEOS.update_QT_pure_superanc(1.0, 300.0);
};
BENCHMARK("superanc rho(T)"){
return anc.eval_sat(300.0, 'D', 1);
};
BENCHMARK("IF97 rho(T)"){
return IF97->update(QT_INPUTS, 1.0, 300.0);
};
double Tmin = AS->get_fluid_parameter_double(0, "SUPERANC::Tmin");
double Tc = AS->get_fluid_parameter_double(0, "SUPERANC::Tcrit_num");
double pmin = AS->get_fluid_parameter_double(0, "SUPERANC::pmin");
double pmax = AS->get_fluid_parameter_double(0, "SUPERANC::pmax");
CHECK_THROWS(AS->get_fluid_parameter_double(1, "SUPERANC::pmax"));
BENCHMARK("HEOS rho(p)"){
return AS->update(PQ_INPUTS, 101325, 1.0);
};
BENCHMARK("superanc T(p)"){
return anc.get_T_from_p(101325);
};
BENCHMARK("IF97 rho(p)"){
return IF97->update(PQ_INPUTS, 101325, 1.0);
};
}
TEST_CASE_METHOD(SuperAncillaryOffFixture, "Check superancillary-like calculations with superancillary disabled for water", "[superanc]") {
auto json = nlohmann::json::parse(get_fluid_param_string("WATER", "JSON"))[0].at("EOS")[0].at("SUPERANCILLARY");
superancillary::SuperAncillary<std::vector<double>> anc{json};
shared_ptr<CoolProp::AbstractState> AS(CoolProp::AbstractState::factory("HEOS", "Water"));
shared_ptr<CoolProp::AbstractState> IF97(CoolProp::AbstractState::factory("IF97", "Water"));
auto& approxrhoL = anc.get_approx1d('D', 0);
BENCHMARK("HEOS rho(T)"){
return AS->update(QT_INPUTS, 1.0, 300.0);
};
BENCHMARK("superanc rho(T)"){
return anc.eval_sat(300.0, 'D', 1);
};
BENCHMARK("superanc rho(T) with expansion directly"){
return approxrhoL.eval(300.0);
};
BENCHMARK("superanc get_index rho(T)"){
return approxrhoL.get_index(300.0);
};
BENCHMARK("IF97 rho(T)"){
return IF97->update(QT_INPUTS, 1.0, 300.0);
};
BENCHMARK("HEOS rho(p)"){
return AS->update(PQ_INPUTS, 101325, 1.0);
};
BENCHMARK("superanc T(p)"){
return anc.get_T_from_p(101325);
};
BENCHMARK("IF97 rho(p)"){
return IF97->update(PQ_INPUTS, 101325, 1.0);
};
}
TEST_CASE_METHOD(SuperAncillaryOnFixture, "Check superancillary functions are available for all pure fluids", "[ancillary]") {
for (auto & fluid : strsplit(CoolProp::get_global_param_string("fluids_list"), ',')){
shared_ptr<CoolProp::AbstractState> AS(CoolProp::AbstractState::factory("HEOS", fluid));
auto& rHEOS = *dynamic_cast<HelmholtzEOSMixtureBackend*>(AS.get());
if (rHEOS.is_pure()){
CAPTURE(fluid);
CHECK_NOTHROW(rHEOS.update_QT_pure_superanc(1, rHEOS.T_critical()*0.9999));
}
}
};
TEST_CASE_METHOD(SuperAncillaryOnFixture, "Check out of bound for superancillary", "[superanc]") {
shared_ptr<CoolProp::AbstractState> AS(CoolProp::AbstractState::factory("HEOS", "Water"));
CHECK_THROWS(AS->update(PQ_INPUTS, 100000000001325, 1.0));
CHECK_THROWS(AS->update(QT_INPUTS, 1.0, 1000000));
}
TEST_CASE_METHOD(SuperAncillaryOnFixture, "Check throws for R410A", "[superanc]") {
shared_ptr<CoolProp::AbstractState> AS(CoolProp::AbstractState::factory("HEOS", "R410A"));
auto& rHEOS = *dynamic_cast<HelmholtzEOSMixtureBackend*>(AS.get());
CHECK_THROWS(rHEOS.update_QT_pure_superanc(1.0, 300.0));
}
TEST_CASE_METHOD(SuperAncillaryOnFixture, "Check throws for REFPROP", "[superanc]") {
shared_ptr<CoolProp::AbstractState> AS(CoolProp::AbstractState::factory("REFPROP", "WATER"));
CHECK_THROWS(AS->update_QT_pure_superanc(1.0, 300.0));
}
TEST_CASE_METHOD(SuperAncillaryOnFixture, "Check Tc & pc", "[superanccrit]") {
shared_ptr<CoolProp::AbstractState> AS(CoolProp::AbstractState::factory("HEOS", "Water"));
set_config_bool(ENABLE_SUPERANCILLARIES, true);
auto TcSA = AS->T_critical();
auto pcSA = AS->p_critical();
auto rhocSA = AS->rhomolar_critical();
set_config_bool(ENABLE_SUPERANCILLARIES, false);
auto TcnonSA = AS->T_critical();
auto pcnonSA = AS->p_critical();
auto rhocnonSA = AS->rhomolar_critical();
CHECK(TcSA != TcnonSA);
CHECK(pcSA != pcnonSA);
CHECK(rhocSA != rhocnonSA);
}
TEST_CASE_METHOD(SuperAncillaryOnFixture, "Check h_fg", "[superanc]") {
shared_ptr<CoolProp::AbstractState> AS(CoolProp::AbstractState::factory("HEOS", "Water"));
CHECK_THROWS(AS->saturated_vapor_keyed_output(iHmolar) - AS->saturated_liquid_keyed_output(iHmolar));
AS->update_QT_pure_superanc(1, 300);
CHECK_NOTHROW(AS->saturated_vapor_keyed_output(iHmolar) - AS->saturated_liquid_keyed_output(iHmolar));
}
TEST_CASE_METHOD(SuperAncillaryOnFixture, "Benchmarking caching options", "[caching]") {
std::array<double, 16> buf15; buf15.fill(0.0);
std::array<double, 100> buf100; buf100.fill(0.0);
std::array<bool, 100> bool100; bool100.fill(false);
std::vector<CachedElement> cache100(100);
for (auto i = 0; i < cache100.size(); ++i){ cache100[i] = _HUGE; }
std::vector<std::optional<double>> opt100(100);
for (auto i = 0; i < opt100.size(); ++i){ opt100[i] = _HUGE; }
BENCHMARK("memset array15 w/ 0"){
std::memset(buf15.data(), 0, sizeof(buf15));
return buf15;
};
BENCHMARK("std::fill_n array15"){
std::fill_n(buf15.data(), 15, _HUGE);
return buf15;
};
BENCHMARK("std::fill array15"){
std::fill(buf15.begin(), buf15.end(), _HUGE);
return buf15;
};
BENCHMARK("array15.fill()"){
buf15.fill(_HUGE);
return buf15;
};
BENCHMARK("memset array100 w/ 0"){
memset(buf100.data(), 0, sizeof(buf100));
return buf100;
};
BENCHMARK("memset bool100 w/ 0"){
memset(bool100.data(), false, sizeof(bool100));
return buf100;
};
BENCHMARK("std::fill_n array100"){
std::fill_n(buf100.data(), 100, _HUGE);
return buf100;
};
BENCHMARK("fill array100"){
buf100.fill(_HUGE);
return buf100;
};
BENCHMARK("fill cache100"){
for (auto i = 0; i < cache100.size(); ++i){ cache100[i] = _HUGE; }
return cache100;
};
BENCHMARK("fill opt100"){
for (auto i = 0; i < opt100.size(); ++i){ opt100[i] = _HUGE; }
return opt100;
};
}
/*
TEST_CASE("Test that HS solver works for a few fluids", "[HS_solver]")
{

View File

@@ -47,6 +47,7 @@ cdef class PySpinodalData:
cdef class AbstractState:
cdef cAbstractState.AbstractState *thisptr # hold a C++ instance which we're wrapping
cpdef update(self, constants_header.input_pairs iInput1, double Value1, double Value2)
cpdef update_QT_pure_superanc(self, double Q, double T)
cpdef update_with_guesses(self, constants_header.input_pairs iInput1, double Value1, double Value2, PyGuessesStructure guesses)
cpdef set_mole_fractions(self, vector[double] z)
cpdef set_mass_fractions(self, vector[double] z)

View File

@@ -106,6 +106,9 @@ cdef class AbstractState:
cpdef update(self, constants_header.input_pairs ipair, double Value1, double Value2):
""" Update function - wrapper of c++ function :cpapi:`CoolProp::AbstractState::update` """
self.thisptr.update(ipair, Value1, Value2)
cpdef update_QT_pure_superanc(self, double Q, double T):
""" Update function - wrapper of c++ function :cpapi:`CoolProp::AbstractState::update` """
self.thisptr.update_QT_pure_superanc(Q, T)
cpdef update_with_guesses(self, constants_header.input_pairs ipair, double Value1, double Value2, PyGuessesStructure guesses):
""" Update function - wrapper of c++ function :cpapi:`CoolProp::AbstractState::update` """
cdef cAbstractState.GuessesStructure _guesses

View File

@@ -108,6 +108,103 @@ from .constants import *
from .constants_header cimport *
from . cimport constants_header
from . cimport superancillary as supanc
from cpython cimport array
from libcpp.utility cimport move
ctypedef vector[double] ArrayType
ctypedef supanc.ChebyshevExpansion[ArrayType] ChebExp
cdef class ChebyshevExpansion:
cdef ChebExp* m_exp
def __cinit__(self, double xmin, double xmax, vector[double] coef):
self.m_exp = new ChebExp(xmin, xmax, coef)
def __dealloc__(self):
del self.m_exp
def xmin(self):
return self.m_exp.xmin()
def xmax(self):
return self.m_exp.xmax()
def coeff(self):
np_array = np.asarray(self.m_exp.coeff() )
cdef double[:] view = np_array
return view
def eval_many(self, double[::1] x, double[::1] y):
assert x.size == y.size
self.m_exp.eval_manyC(&x[0], &y[0], x.shape[0])
def solve_for_x(self, double y, double a, double b, unsigned int bits, size_t max_iter, double boundstytol):
return self.m_exp.solve_for_x(y, a, b, bits, max_iter, boundstytol)
def solve_for_x_many(self, double[::1] y, double a, double b, unsigned int bits, size_t max_iter, double boundstytol, double[::1] x, double [::1] counts):
cdef size_t N = y.shape[0]
return self.m_exp.solve_for_x_manyC(&y[0], N, a, b, bits, max_iter, boundstytol, &x[0], &counts[0])
ctypedef supanc.ChebyshevApproximation1D[ArrayType] ChebApprox1D
from cython.operator cimport dereference as deref
cdef class ChebyshevApproximation1D:
cdef supanc.ChebyshevApproximation1D[vector[double]]* thisptr
def __cinit__(self, expansions):
cdef vector[supanc.ChebyshevExpansion[vector[double]]] expansions_copy
cdef ChebyshevExpansion expansion
for expansion in expansions:
expansions_copy.push_back(deref(expansion.m_exp))
self.thisptr = new ChebApprox1D(move(expansions_copy))
def xmin(self):
return self.thisptr.xmin()
def xmax(self):
return self.thisptr.xmax()
def is_monotonic(self):
return self.thisptr.is_monotonic()
def eval_many(self, double[::1] x, double[::1] y):
assert x.size == y.size
self.thisptr.eval_manyC(&x[0], &y[0], x.shape[0])
def get_x_for_y(self, double y, unsigned int bits, size_t max_iter, double boundstytol):
return self.thisptr.get_x_for_y(y, bits, max_iter, boundstytol)
def count_x_for_y_many(self, double[::1] y, unsigned int bits, size_t max_iter, double boundstytol, double[::1] counts):
assert y.shape[0] == counts.shape[0]
cdef size_t N = y.shape[0]
return self.thisptr.count_x_for_y_manyC(&y[0], N, bits, max_iter, boundstytol, &counts[0])
def monotonic_intervals(self):
return self.thisptr.get_monotonic_intervals()
def __dealloc__(self):
del self.thisptr
ctypedef supanc.SuperAncillary[ArrayType] SuperAncillary_t
cdef class SuperAncillary:
cdef SuperAncillary_t* thisptr
def __cinit__(self, str json_as_string):
self.thisptr = new SuperAncillary_t(json_as_string)
def eval_sat(self, double T, str prop, short Q):
cdef char prop_ = prop[0]
return self.thisptr.eval_sat(T, prop_, Q)
def eval_sat_many(self, double[::1] T, str prop, short Q, double[::1] y):
assert T.shape[0] == y.shape[0]
cdef char prop_ = prop[0]
cdef size_t N = y.shape[0]
return self.thisptr.eval_sat_manyC(&T[0], N, prop_, Q, &y[0])
cdef bint iterable(object a):
"""
If numpy is supported, this function returns true if the argument is a
@@ -127,6 +224,9 @@ cdef ndarray_or_iterable(object input):
include "HumidAirProp.pyx"
include "AbstractState.pyx"
def set_reference_state(FluidName, *args):
"""
Accepts one of two signatures:

View File

@@ -103,6 +103,8 @@ cdef extern from "AbstractState.h" namespace "CoolProp":
## Property updater
## Uses the indices in CoolProp for the input parameters
void update(constants_header.input_pairs iInput1, double Value1, double Value2) except +ValueError
## QT inputs for pure fluids with the superancillary functions
void update_QT_pure_superanc(double Q, double T) except +ValueError
## Uses the indices in CoolProp for the input parameters
void update_with_guesses(constants_header.input_pairs iInput1, double Value1, double Value2, GuessesStructure) except +ValueError

View File

@@ -0,0 +1,48 @@
from libcpp cimport bool
from libcpp.vector cimport vector
from libcpp.pair cimport pair
from libcpp.string cimport string
cdef extern from "superancillary/superancillary.h" namespace "CoolProp::superancillary":
cdef struct MonotonicExpansionMatch:
const size_t idx # The index of the expansion that has been matched
const double ymin, ymax, xmin, xmax
# Check if a value of the dependent variable is within this match
# bool contains_y (double) const
# Data associated with a monotonic interval
cdef struct IntervalMatch:
const vector[MonotonicExpansionMatch] expansioninfo # The information about the expansions for this interval
const double xmin, xmax, ymin, ymax
# Check if a value of the dependent variable is within this interval
# bool contains_y (double) const
cdef cppclass ChebyshevExpansion[ArrayType]:
ChebyshevExpansion() except +ValueError
ChebyshevExpansion(double, double, ArrayType) except +ValueError
const double xmin() const
const double xmax() const
const ArrayType& coeff() const
U eval[U](const U& x) const
void eval_manyC[U](const U x[], U v[], size_t) const
double solve_for_x(double, double, double, unsigned int, size_t, double) const
void solve_for_x_manyC[U](const U[], size_t, double, double, unsigned int, size_t, double, U[], U[]) const
cdef cppclass ChebyshevApproximation1D[ArrayType]:
ChebyshevApproximation1D(vector[ChebyshevExpansion[ArrayType]]&&) except +ValueError
bool is_monotonic() const
double xmin()
double xmax()
void eval_manyC[U](const U x[], U v[], size_t) const
const vector[ChebyshevExpansion[ArrayType]] m_expansions # The collection of expansions forming the approximation
const vector[double] m_x_at_extrema # The values of the independent variable at the extrema of the expansions
const vector[IntervalMatch] get_monotonic_intervals() # The intervals that are monotonic
vector[pair[double, int]] get_x_for_y(double, unsigned int, size_t, double) const
void count_x_for_y_manyC[U](const U[], size_t, unsigned int, size_t, double, U[]) const
cdef cppclass SuperAncillary[ArrayType]:
SuperAncillary(const string&) except +ValueError
double eval_sat(double, char, short) except +ValueError
void eval_sat_manyC[U](const U[], size_t, char, short, U[]) except +ValueError

View File

@@ -438,6 +438,7 @@ if __name__ == '__main__':
os.path.join(CProot, 'externals', 'fmtlib', 'include'),
os.path.join(CProot, 'boost_CoolProp'),
os.path.join(CProot, 'externals', 'incbin'),
os.path.join(CProot, 'externals', 'nlohmann-json'),
os.path.join(CProot, 'externals', 'miniz-3.0.2'),
os.path.join(CProot, 'externals', 'msgpack-c', 'include')]