CoolProp 8.0.0
An open-source fluid property and humid air property database
AbstractState.cpp
Go to the documentation of this file.
1/*
2 * AbstractState.cpp
3 *
4 * Created on: 21 Dec 2013
5 * Author: jowr
6 */
7
8#ifndef _CRT_SECURE_NO_WARNINGS
9#define _CRT_SECURE_NO_WARNINGS
10#endif
11
12#include <cmath>
13#include <cstdlib>
14#include "boost/math/tools/toms748_solve.hpp"
18#include "qmass_conversions.h"
24
25#if !defined(NO_TABULAR_BACKENDS)
28#endif
30
31namespace CoolProp {
32
35
37{
38 private:
39 std::map<backend_families, shared_ptr<AbstractStateGenerator>> backends;
40
41 public:
42 void add_backend(const backend_families& bg, const shared_ptr<AbstractStateGenerator>& asg) {
43 backends[bg] = asg;
44 };
46 std::map<backend_families, shared_ptr<AbstractStateGenerator>>::const_iterator& generator,
47 std::map<backend_families, shared_ptr<AbstractStateGenerator>>::const_iterator& end) {
48 generator = backends.find(bg);
49 end = backends.end();
50 };
51 std::size_t size() {
52 return backends.size();
53 };
54};
56 static BackendLibrary the_library;
57 return the_library;
58}
59
60void register_backend(const backend_families& bf, const shared_ptr<AbstractStateGenerator>& gen) {
62};
63
64// Default implementation of the options-aware factory entry-point.
65// Forwards to the no-options overload when `options_json` is empty or
66// is the canonical empty object "{}". Anything else means the caller
67// passed `?<options>` but the backend hasn't opted in to consume them —
68// throw with a clear message so silent option-dropping never happens.
69AbstractState* AbstractStateGenerator::get_AbstractState(const std::vector<std::string>& fluid_names, const std::string& options_json) {
70 // Trim surrounding whitespace; treat ""/"{}" as "no options".
71 std::size_t lo = options_json.find_first_not_of(" \t\r\n");
72 if (lo == std::string::npos) {
73 return get_AbstractState(fluid_names);
74 }
75 std::size_t hi = options_json.find_last_not_of(" \t\r\n");
76 const std::string trimmed = options_json.substr(lo, hi - lo + 1);
77 if (trimmed == "{}") {
78 return get_AbstractState(fluid_names);
79 }
80 throw NotImplementedError("This backend does not accept factory-string options. Drop the '?<...>' suffix or opt the backend in (see "
81 "docs/superpowers/specs/2026-05-16-backend-options-string-design.md).");
82}
83
85{
86 public:
87 AbstractState* get_AbstractState(const std::vector<std::string>& fluid_names) override {
88 if (fluid_names.size() == 1) { // Check that fluid_names[0] has only one component
89 const std::string& str = fluid_names[0]; // Check that the fluid name is an alias for "Water"
90 if ((upper(str) == "WATER") || (upper(str) == "H2O")) {
91 return new IF97Backend();
92 } else {
93 throw ValueError(format("The IF97 backend returns Water props only; fluid name [%s] not allowed", fluid_names[0].c_str()));
94 }
95 } else {
96 throw ValueError(format("The IF97 backend does not support mixtures, only Water"));
97 };
98 };
99};
100// This static initialization will cause the generator to register.
101// GeneratorInitializer's constructor only inserts a single entry into a
102// std::map keyed by backend_family; in practice this cannot throw under
103// normal startup, and if std::bad_alloc fires here the process is
104// unrecoverable anyway. NOLINT rather than convert to call_once + lazy
105// registration (option 1 from #2874) which would be the cleaner refactor
106// but is out of this PR's scope.
107// NOLINTNEXTLINE(cert-err58-cpp)
108static GeneratorInitializer<IF97BackendGenerator> if97_gen(IF97_BACKEND_FAMILY);
110{
111 public:
112 AbstractState* get_AbstractState(const std::vector<std::string>& fluid_names) override {
113 return new SRKBackend(fluid_names, get_config_double(R_U_CODATA));
114 };
115};
116// NOLINTNEXTLINE(cert-err58-cpp)
117static GeneratorInitializer<SRKGenerator> srk_gen(CoolProp::SRK_BACKEND_FAMILY);
119{
120 public:
121 AbstractState* get_AbstractState(const std::vector<std::string>& fluid_names) override {
122 return new PengRobinsonBackend(fluid_names, get_config_double(R_U_CODATA));
123 };
124};
125// NOLINTNEXTLINE(cert-err58-cpp)
126static GeneratorInitializer<PRGenerator> pr_gen(CoolProp::PR_BACKEND_FAMILY);
128{
129 public:
130 AbstractState* get_AbstractState(const std::vector<std::string>& fluid_names) override {
131 if (fluid_names.size() != 1) {
132 throw ValueError(format("For INCOMP backend, name vector must be one element long"));
133 }
134 return new IncompressibleBackend(fluid_names[0]);
135 };
136};
137// This static initialization will cause the generator to register
138// NOLINTNEXTLINE(cert-err58-cpp)
139static GeneratorInitializer<IncompressibleBackendGenerator> incomp_gen(INCOMP_BACKEND_FAMILY);
141{
142 public:
143 CoolProp::AbstractState* get_AbstractState(const std::vector<std::string>& fluid_names) override {
144 return new CoolProp::VTPRBackend(fluid_names, CoolProp::get_config_double(R_U_CODATA));
145 };
146};
147// This static initialization will cause the generator to register
148// NOLINTNEXTLINE(cert-err58-cpp)
150
152{
153 public:
154 CoolProp::AbstractState* get_AbstractState(const std::vector<std::string>& fluid_names) override {
155 return new CoolProp::PCSAFTBackend(fluid_names);
156 };
157};
158// This static initialization will cause the generator to register
159// NOLINTNEXTLINE(cert-err58-cpp)
161
162// SVDSBTL has no AbstractStateGenerator because the standard
163// generator API only knows about (backend_family, fluid_names) — it
164// can't carry the source-backend slot SVDSBTL needs. Instead,
165// factory() handles SVDSBTL inline below, the same pattern TTSE and
166// BICUBIC already use.
167
168AbstractState* AbstractState::factory(const std::string& backend, const std::vector<std::string>& fluid_names) {
169 if (get_debug_level() > 0) {
170 std::cout << "AbstractState::factory(" << backend << "," << stringvec_to_string(fluid_names) << ")" << '\n';
171 }
172
173 // Split off any "?<options>" suffix once at the entry-point so the
174 // rest of the dispatch operates on the unadorned backend / fluid
175 // strings. PropsSI calls extract_backend() first, which splits
176 // "BACKEND::FLUID?<options>" on "::" without touching '?' — so
177 // the suffix arrives on the *fluid* side. Direct factory()
178 // callers may put it on either side. Strip from wherever it
179 // lives (rejecting the ambiguous both-sides case) and thread the
180 // raw options JSON through to the backend generator via the
181 // options-aware overload below. See
182 // docs/superpowers/specs/2026-05-16-backend-options-string-design.md.
183 auto parsed = parse_factory_options(backend);
184 std::string clean_backend = parsed.clean_string;
185 std::string options_json = parsed.options_json;
186 std::vector<std::string> clean_fluid_names = fluid_names;
187 // The `?<options>` suffix on the fluid side can land on ANY
188 // element of fluid_names — for mixtures, the
189 // factory(string, string) convenience overload calls
190 // strsplit('&') before forwarding, so e.g.
191 // "HEOS::R32&R125?{...}" arrives as ["R32", "R125?{...}"] with
192 // the suffix on the LAST token. Scan the whole vector; throw
193 // if two different tokens carry a suffix (typo); always strip
194 // the trailing '?' so downstream fluid lookup sees the bare
195 // component name.
196 std::size_t fluid_options_index = clean_fluid_names.size(); // sentinel = none
197 for (std::size_t i = 0; i < clean_fluid_names.size(); ++i) {
198 if (clean_fluid_names[i].find('?') == std::string::npos) {
199 continue;
200 }
201 if (fluid_options_index != clean_fluid_names.size()) {
202 throw ValueError("factory: '?<options>' supplied in multiple fluid components; encode it once.");
203 }
204 fluid_options_index = i;
205 }
206 if (fluid_options_index != clean_fluid_names.size()) {
207 auto parsed_fluid = parse_factory_options(clean_fluid_names[fluid_options_index]);
208 if (!parsed_fluid.options_json.empty() && !options_json.empty()) {
209 throw ValueError("factory: '?<options>' supplied on both the backend and fluid sides; pick one side.");
210 }
211 // Always replace the affected fluid name with the stripped
212 // form — even when the suffix carried no options (bare '?'
213 // or whitespace) — so the downstream fluid lookup doesn't
214 // see a trailing '?'.
215 clean_fluid_names[fluid_options_index] = parsed_fluid.clean_string;
216 if (!parsed_fluid.options_json.empty()) {
217 options_json = parsed_fluid.options_json;
218 }
219 }
220
222 std::string f2;
223 extract_backend_families_string(clean_backend, f1, f2);
224
225 std::map<backend_families, shared_ptr<AbstractStateGenerator>>::const_iterator gen, end;
227
228 if (get_debug_level() > 0) {
229 std::cout << "AbstractState::factory backend_library size: " << get_backend_library().size() << '\n';
230 }
231
232 if (gen != end) {
233 // One of the registered backends was able to match the given backend family
234 return gen->second->get_AbstractState(clean_fluid_names, options_json);
235 }
236#if !defined(NO_TABULAR_BACKENDS)
237 else if (f1 == TTSE_BACKEND_FAMILY) {
238 // Will throw if there is a problem with this backend. These
239 // tabular backends don't accept options today; if the caller
240 // supplied any, fail loud so the suffix isn't silently dropped.
241 if (!options_json.empty()) {
242 throw NotImplementedError("TTSE backend does not yet accept factory-string options");
243 }
244 const shared_ptr<AbstractState> AS(factory(f2, clean_fluid_names));
245 return new TTSEBackend(AS);
246 } else if (f1 == BICUBIC_BACKEND_FAMILY) {
247 if (!options_json.empty()) {
248 throw NotImplementedError("BICUBIC backend does not yet accept factory-string options");
249 }
250 const shared_ptr<AbstractState> AS(factory(f2, clean_fluid_names));
251 return new BicubicBackend(AS);
252 } else if (f1 == SVDSBTL_BACKEND_FAMILY) {
253 // SVDSBTL requires an explicit source-of-truth backend in
254 // its name (e.g. "SVDSBTL&HEOS"). No default — see
255 // SVDSBTLBackend.h for rationale. factory("SVDSBTL", ...)
256 // without the '&' lands here with f2 empty and throws.
257 if (f2.empty()) {
258 throw ValueError(format(R"(SVDSBTL requires an explicit source backend, e.g. factory("SVDSBTL&HEOS", "%s"))",
259 clean_fluid_names.empty() ? "<fluid>" : clean_fluid_names[0].c_str()));
260 }
261 if (clean_fluid_names.size() != 1) {
262 throw ValueError("SVDSBTL backend is pure-fluid only; expected exactly one fluid name");
263 }
264 // SVDSBTL is opted in to factory-string options: the JSON
265 // payload is forwarded verbatim to the constructor, which
266 // validates against kSVDSBTLOptionsSchemaJson before applying.
267 return new SVDSBTLBackend(clean_fluid_names[0], f2, options_json);
268 }
269#endif
270 else if (clean_backend == "?" || clean_backend.empty()) {
271 // Backend has not been specified, and we have to figure out what
272 // the backend is by parsing the (already-stripped) fluid string.
273 // Options accumulated above are re-attached to the inner
274 // backend half so the recursive factory() call's own parse pass
275 // picks them up — keeps the dispatch logic in one place.
276 const std::size_t idel = clean_fluid_names[0].find("::");
277 const std::string suffix = options_json.empty() ? std::string{} : ("?" + options_json);
278 if (idel == std::string::npos) {
279 // No '::' found, default to HEOS.
280 return factory("HEOS" + suffix, clean_fluid_names);
281 } else {
282 std::string inner_backend = clean_fluid_names[0].substr(0, idel) + suffix;
283 std::vector<std::string> inner_fluid = {clean_fluid_names[0].substr(idel + 2)};
284 return factory(inner_backend, inner_fluid);
285 }
286 } else {
287 throw ValueError(format("Invalid backend name [%s] to factory function", clean_backend.c_str()));
288 }
289}
290std::vector<std::string> AbstractState::fluid_names() {
291 return calc_fluid_names();
292}
294 // Reset all instances of CachedElement and overwrite
295 // the internal double values with -_HUGE
296 this->_molar_mass.clear();
297 this->_critical.fill(_HUGE);
298 this->_reducing.fill(_HUGE);
301 return true;
302}
304 // Reset all instances of CachedElement
305 cache.clear();
306
307 this->_critical.fill(_HUGE);
308
310 this->_rhomolar = -_HUGE;
311 this->_T = -_HUGE;
312 this->_p = -_HUGE;
313 this->_Q = -_HUGE;
314
315 return true;
316}
318 // Check if a mass based input, convert it to molar units
319
320 // Pure / pseudo-pure: Qmass == Qmolar exactly. Rewrite the Qmass pair to
321 // its molar sibling without touching values. Mixture cases (size > 1) are
322 // handled by the iterative update_Qmass_pair path; leave them unchanged here.
323 if (get_mole_fractions().size() == 1) {
324 switch (input_pair) {
325 case QmassT_INPUTS:
326 input_pair = QT_INPUTS;
327 break;
328 case PQmass_INPUTS:
329 input_pair = PQ_INPUTS;
330 break;
332 input_pair = QSmolar_INPUTS;
333 break;
335 input_pair = QSmass_INPUTS;
336 break;
338 input_pair = HmolarQ_INPUTS;
339 break;
341 input_pair = HmassQ_INPUTS;
342 break;
344 input_pair = DmolarQ_INPUTS;
345 break;
347 input_pair = DmassQ_INPUTS;
348 break;
349 default:
350 break;
351 }
352 }
353
354 switch (input_pair) {
355 case DmassT_INPUTS:
356 //case HmassT_INPUTS: ///< Enthalpy in J/kg, Temperature in K (NOT CURRENTLY IMPLEMENTED)
357 case SmassT_INPUTS:
358 //case TUmass_INPUTS: ///< Temperature in K, Internal energy in J/kg (NOT CURRENTLY IMPLEMENTED)
359 case DmassP_INPUTS:
360 case DmassQ_INPUTS:
361 case HmassQ_INPUTS:
362 case QSmass_INPUTS:
363 case HmassP_INPUTS:
364 case PSmass_INPUTS:
365 case PUmass_INPUTS:
366 case HmassSmass_INPUTS:
367 case SmassUmass_INPUTS:
368 case DmassHmass_INPUTS:
369 case DmassSmass_INPUTS:
370 case DmassUmass_INPUTS:
371 {
372 // Set the cache value for the molar mass if it hasn't been set yet
373 molar_mass();
374
375 // Molar mass (just for compactness of the following switch)
376 const auto mm = static_cast<CoolPropDbl>(_molar_mass);
377
378 switch (input_pair) {
379 case DmassT_INPUTS:
380 input_pair = DmolarT_INPUTS;
381 value1 /= mm;
382 break;
383 //case HmassT_INPUTS: input_pair = HmolarT_INPUTS; value1 *= mm; break; (NOT CURRENTLY IMPLEMENTED)
384 case SmassT_INPUTS:
385 input_pair = SmolarT_INPUTS;
386 value1 *= mm;
387 break;
388 //case TUmass_INPUTS: input_pair = TUmolar_INPUTS; value2 *= mm; break; (NOT CURRENTLY IMPLEMENTED)
389 case DmassP_INPUTS:
390 input_pair = DmolarP_INPUTS;
391 value1 /= mm;
392 break;
393 case DmassQ_INPUTS:
394 input_pair = DmolarQ_INPUTS;
395 value1 /= mm;
396 break;
397 case HmassQ_INPUTS:
398 input_pair = HmolarQ_INPUTS;
399 value1 *= mm;
400 break;
401 case QSmass_INPUTS:
402 input_pair = QSmolar_INPUTS;
403 value2 *= mm;
404 break;
405 case HmassP_INPUTS:
406 input_pair = HmolarP_INPUTS;
407 value1 *= mm;
408 break;
409 case PSmass_INPUTS:
410 input_pair = PSmolar_INPUTS;
411 value2 *= mm;
412 break;
413 case PUmass_INPUTS:
414 input_pair = PUmolar_INPUTS;
415 value2 *= mm;
416 break;
418 input_pair = HmolarSmolar_INPUTS;
419 value1 *= mm;
420 value2 *= mm;
421 break;
423 input_pair = SmolarUmolar_INPUTS;
424 value1 *= mm;
425 value2 *= mm;
426 break;
428 input_pair = DmolarHmolar_INPUTS;
429 value1 /= mm;
430 value2 *= mm;
431 break;
433 input_pair = DmolarSmolar_INPUTS;
434 value1 /= mm;
435 value2 *= mm;
436 break;
438 input_pair = DmolarUmolar_INPUTS;
439 value1 /= mm;
440 value2 *= mm;
441 break;
442 default:
443 break;
444 }
445 break;
446 }
447 default:
448 return;
449 }
450}
452 if (get_debug_level() >= 50)
453 std::cout << format("AbstractState: trivial_keyed_output called for %s ", get_parameter_information(key, "short").c_str()) << '\n';
454 switch (key) {
455 case imolar_mass:
456 return molar_mass();
457 case iacentric_factor:
458 return acentric_factor();
459 case igas_constant:
460 return gas_constant();
461 case iT_min:
462 return Tmin();
463 case iT_triple:
464 return Ttriple();
465 case iT_max:
466 return Tmax();
467 case iP_max:
468 return pmax();
469 case iP_min:
470 case iP_triple:
471 return this->p_triple();
472 case iT_reducing:
473 return calc_T_reducing();
475 return calc_rhomolar_reducing();
476 case iP_reducing:
477 return calc_p_reducing();
478 case iP_critical:
479 return this->p_critical();
480 case iT_critical:
481 return this->T_critical();
483 return this->rhomolar_critical();
485 return this->rhomass_critical();
486 case iODP:
487 return this->calc_ODP();
488 case iGWP100:
489 return this->calc_GWP100();
490 case iGWP20:
491 return this->calc_GWP20();
492 case iGWP500:
493 return this->calc_GWP500();
494 case ifraction_min:
495 return this->calc_fraction_min();
496 case ifraction_max:
497 return this->calc_fraction_max();
498 case iT_freeze:
499 return this->calc_T_freeze();
500 case iFH:
501 return this->calc_flame_hazard();
502 case iHH:
503 return this->calc_health_hazard();
504 case iPH:
505 return this->calc_physical_hazard();
506 case idipole_moment:
507 return this->calc_dipole_moment();
508 default:
509 throw ValueError(
510 format("This input [%d: \"%s\"] is not valid for trivial_keyed_output", key, get_parameter_information(key, "short").c_str()));
511 }
512}
514 if (get_debug_level() >= 50)
515 std::cout << format("AbstractState: keyed_output called for %s ", get_parameter_information(key, "short").c_str()) << '\n';
516 // Handle trivial inputs
517 if (is_trivial_parameter(key)) {
518 return trivial_keyed_output(key);
519 }
520 switch (key) {
521 case iQ:
522 return Q();
523 case iQmass:
524 return Qmass();
525 case iT:
526 return T();
527 case iP:
528 return p();
529 case iDmolar:
530 return rhomolar();
531 case iDmass:
532 return rhomass();
533 case iHmolar:
534 return hmolar();
535 case iHmolar_residual:
536 return hmolar_residual();
537 case iHmolar_idealgas:
538 return hmolar_idealgas();
539 case iHmass:
540 return hmass();
541 case iHmass_idealgas:
542 return hmass_idealgas();
543 case iSmolar:
544 return smolar();
545 case iSmolar_residual:
546 return smolar_residual();
547 case iSmolar_idealgas:
548 return smolar_idealgas();
549 case iSmass:
550 return smass();
551 case iSmass_idealgas:
552 return smass_idealgas();
553 case iUmolar:
554 return umolar();
555 case iUmolar_idealgas:
556 return umolar_idealgas();
557 case iUmass:
558 return umass();
559 case iUmass_idealgas:
560 return umass_idealgas();
561 case iGmolar:
562 return gibbsmolar();
563 case iGmolar_residual:
564 return gibbsmolar_residual();
565 case iGmass:
566 return gibbsmass();
567 case iHelmholtzmolar:
568 return helmholtzmolar();
569 case iHelmholtzmass:
570 return helmholtzmass();
571 case iCvmolar:
572 return cvmolar();
573 case iCvmass:
574 return cvmass();
575 case iCpmolar:
576 return cpmolar();
577 case iCp0molar:
578 return cp0molar();
579 case iCpmass:
580 return cpmass();
581 case iCp0mass:
582 return cp0mass();
583 case imolar_mass:
584 return molar_mass();
585 case iT_reducing:
586 return get_reducing_state().T;
589 case ispeed_sound:
590 return speed_sound();
591 case ialphar:
592 return alphar();
593 case ialpha0:
594 return alpha0();
596 return dalpha0_dDelta();
598 return d2alpha0_dDelta2();
600 return d3alpha0_dDelta3();
602 return dalpha0_dTau();
604 return dalphar_dDelta();
606 return dalphar_dTau();
607 case iBvirial:
608 return Bvirial();
609 case idBvirial_dT:
610 return dBvirial_dT();
611 case iCvirial:
612 return Cvirial();
613 case idCvirial_dT:
614 return dCvirial_dT();
621 case iviscosity:
622 return viscosity();
623 case iconductivity:
624 return conductivity();
625 case iPrandtl:
626 return Prandtl();
627 case isurface_tension:
628 return surface_tension();
629 case iPhase:
630 return phase();
631 case iZ:
632 return compressibility_factor();
633 case iPIP:
634 return PIP();
637 case iTau:
638 return _reducing.T / _T;
639 case iDelta:
641 default:
642 throw ValueError(format("This input [%d: \"%s\"] is not valid for keyed_output", key, get_parameter_information(key, "short").c_str()));
643 }
644}
645
648 return _tau;
649}
652 return _delta;
653}
655 return calc_Tmin();
656}
658 return calc_Tmax();
659}
661 return calc_Ttriple();
662}
664 return calc_pmax();
665}
667 return calc_T_critical();
668}
670 if (!ValidNumber(_reducing.T)) {
672 }
673 return _reducing.T;
674}
676 return calc_p_critical();
677}
679 return calc_p_triple();
680}
682 return calc_rhomolar_critical();
683}
686}
690 }
691 return _reducing.rhomolar;
692}
694 return rhomolar_reducing() * molar_mass();
695}
697 if (!_hmolar) _hmolar = calc_hmolar();
698 return _hmolar;
699}
702 return _hmolar_residual;
703}
705 return gas_constant() * T() * (1 + tau() * dalpha0_dTau());
706}
708 return hmolar_idealgas() / molar_mass();
709}
712 return _hmolar_excess;
713}
715 if (!_smolar) _smolar = calc_smolar();
716 return _smolar;
717}
720 return _smolar_residual;
721}
723 return gas_constant() * (tau() * dalpha0_dTau() - alpha0());
724}
726 return smolar_idealgas() / molar_mass();
727}
729 const double tau = calc_T_reducing() / _T;
730 const double delta = _rhomolar / calc_rhomolar_reducing();
731 const double Ar01 = delta * dalphar_dDelta();
732 const double Ar11 = tau * delta * d2alphar_dDelta_dTau();
733 const double Ar20 = tau * tau * d2alphar_dTau2();
734 return -3.0 * (Ar01 - Ar11) / Ar20;
735}
738 return _smolar_excess;
739}
741 if (!_umolar) _umolar = calc_umolar();
742 return _umolar;
743}
746 return _umolar_excess;
747}
749 return gas_constant() * T() * (tau() * dalpha0_dTau());
750}
752 return umolar_idealgas() / molar_mass();
753}
756 return _gibbsmolar;
757}
761}
764 return _gibbsmolar_excess;
765}
768 return _helmholtzmolar;
769}
773}
776 return _volumemolar_excess;
777}
780 return _cpmolar;
781}
783 return calc_cpmolar_idealgas();
784}
787 return _cvmolar;
788}
791 return _speed_sound;
792}
795 return _viscosity;
796}
799 return _conductivity;
800}
801double AbstractState::melting_line(int param, int given, double value) {
802 return calc_melting_line(param, given, value);
803}
805 return calc_acentric_factor();
806}
807double AbstractState::saturation_ancillary(parameters param, int Q, parameters given, double value) {
808 return calc_saturation_ancillary(param, Q, given, value);
809}
812 return _surface_tension;
813}
816 return _molar_mass;
817}
819 if (!_Qmass) _Qmass = calc_Qmass();
820 return _Qmass;
821}
823 if (!ValidNumber(_Q) || _Q < 0 || _Q > 1) {
824 throw ValueError("Qmass requires a two-phase state (0 <= Q <= 1)");
825 }
826 // Endpoints: Qmass equals Qmolar by definition (no phase composition needed).
827 // This avoids NaN propagation when one phase is unpopulated by the backend
828 // at a saturation-curve endpoint (e.g. Q=0 on the bubble line).
829 if (_Q == 0.0 || _Q == 1.0) return static_cast<CoolPropDbl>(_Q);
830 const auto MM = calc_phase_molar_masses();
831 return static_cast<CoolPropDbl>(detail::Qmolar_to_Qmass(_Q, MM.liquid, MM.vapor));
832}
834 // For a pure or pseudo-pure fluid the liquid and vapor phases share the same
835 // molar mass as the bulk fluid. A mixture backend must override this method.
836 if (get_mole_fractions().size() == 1) {
837 const double mm = molar_mass();
838 return {mm, mm};
839 }
840 throw NotImplementedError("calc_phase_molar_masses must be overridden by mixture backends");
841}
843 // Map Qmass-pair to its molar sibling and identify which slot (1=value1, 2=value2)
844 // holds the Qmass value.
845 struct Mapping
846 {
848 int qmass_slot;
849 };
850 Mapping m;
851 switch (pair) {
852 case QmassT_INPUTS:
853 m = {QT_INPUTS, 1};
854 break;
855 case PQmass_INPUTS:
856 m = {PQ_INPUTS, 2};
857 break;
859 m = {QSmolar_INPUTS, 1};
860 break;
862 m = {QSmass_INPUTS, 1};
863 break;
865 m = {HmolarQ_INPUTS, 2};
866 break;
868 m = {HmassQ_INPUTS, 2};
869 break;
871 m = {DmolarQ_INPUTS, 2};
872 break;
874 m = {DmassQ_INPUTS, 2};
875 break;
876 default:
877 throw ValueError("update_Qmass_pair called with non-Qmass pair");
878 }
879 const double Qmass_target = (m.qmass_slot == 1) ? v1 : v2;
880 const double partner = (m.qmass_slot == 1) ? v2 : v1;
881
882 if (Qmass_target < 0 || Qmass_target > 1) {
883 throw ValueError(format("Qmass out of range [0,1]: %g", Qmass_target));
884 }
885
886 auto run_molar = [&](double Qmolar) {
887 if (m.qmass_slot == 1)
888 update(m.molar, Qmolar, partner);
889 else
890 update(m.molar, partner, Qmolar);
891 };
892
893 // Endpoints: bypass iteration entirely.
894 if (Qmass_target == 0.0 || Qmass_target == 1.0) {
895 run_molar(Qmass_target);
896 _Qmass = Qmass_target;
897 return;
898 }
899
900 auto residual = [&](double Qmolar) -> double {
901 run_molar(Qmolar);
902 const auto MM = calc_phase_molar_masses();
903 return detail::Qmolar_to_Qmass(Qmolar, MM.liquid, MM.vapor) - Qmass_target;
904 };
905
906 // TOMS748 needs a strict bracket on (0, 1). Qmass(Qmolar) is monotonically
907 // increasing on this interval and goes 0 -> 1, so [eps, 1-eps] always brackets.
908 constexpr double eps = 1e-12;
909 constexpr int bits = 48; // ~14 significant digits
910 boost::math::uintmax_t max_iter = 50;
911 const double a = eps;
912 const double b = 1.0 - eps;
913 const double fa = residual(a);
914 const double fb = residual(b);
915 if (fa * fb > 0) {
916 throw SolutionError(format("update_Qmass_pair: cannot bracket Qmolar for Qmass=%g (fa=%g, fb=%g)", Qmass_target, fa, fb));
917 }
918 const auto [lo, hi] = boost::math::tools::toms748_solve(residual, a, b, fa, fb, boost::math::tools::eps_tolerance<double>(bits), max_iter);
919 const double Qmolar_solution = 0.5 * (lo + hi);
920 if (!ValidNumber(Qmolar_solution)) {
921 throw SolutionError(format("update_Qmass_pair: did not converge for Qmass=%g", Qmass_target));
922 }
923 // Final flash at converged Qmolar (makes the public state unambiguous).
924 run_molar(Qmolar_solution);
925 _Qmass = Qmass_target;
926}
929 return _gas_constant;
930}
932 // TODO: Cache the fug. coeff for each component
934}
936 // TODO: Cache the fug. coeff for each component
938}
939double AbstractState::fugacity(std::size_t i) {
940 // TODO: Cache the fug. coeff for each component
941 return calc_fugacity(i);
942}
944 // TODO: Cache the chemical potential for each component
945 return calc_chemical_potential(i);
946}
947void AbstractState::build_phase_envelope(const std::string& type) {
949}
951 return 1.0 / _rhomolar * first_partial_deriv(iDmolar, iP, iT);
952}
954 return -1.0 / _rhomolar * first_partial_deriv(iDmolar, iT, iP);
955}
958}
960 return calc_Bvirial();
961}
963 return calc_Cvirial();
964}
966 return calc_dBvirial_dT();
967}
969 return calc_dCvirial_dT();
970}
973}
974
976 // See Colonna, FPE, 2010, Eq. 1
977 return 1 + this->second_partial_deriv(iP, iDmass, iSmolar, iDmass, iSmolar) * this->rhomass() / (2 * powInt(speed_sound(), 2));
978};
979
980// Get the derivatives of the parameters in the partial derivative with respect to T and rho
982 const CoolPropDbl T = AS.T(), rho = AS.rhomolar(), rhor = AS.rhomolar_reducing(), Tr = AS.T_reducing(), dT_dtau = -pow(T, 2) / Tr,
983 R = AS.gas_constant(), delta = rho / rhor, tau = Tr / T;
984
985 switch (index) {
986 case iT:
987 dT = 1;
988 drho = 0;
989 break;
990 case iDmolar:
991 dT = 0;
992 drho = 1;
993 break;
994 case iDmass:
995 dT = 0;
996 drho = AS.molar_mass();
997 break;
998 case iP: {
999 // dp/drho|T
1000 drho = R * T * (1 + 2 * delta * AS.dalphar_dDelta() + pow(delta, 2) * AS.d2alphar_dDelta2());
1001 // dp/dT|rho
1002 dT = rho * R * (1 + delta * AS.dalphar_dDelta() - tau * delta * AS.d2alphar_dDelta_dTau());
1003 break;
1004 }
1005 case iHmass:
1006 case iHmolar: {
1007 // dh/dT|rho
1008 dT = R
1009 * (-pow(tau, 2) * (AS.d2alpha0_dTau2() + AS.d2alphar_dTau2())
1010 + (1 + delta * AS.dalphar_dDelta() - tau * delta * AS.d2alphar_dDelta_dTau()));
1011 // dh/drhomolar|T
1012 drho = T * R / rho * (tau * delta * AS.d2alphar_dDelta_dTau() + delta * AS.dalphar_dDelta() + pow(delta, 2) * AS.d2alphar_dDelta2());
1013 if (index == iHmass) {
1014 // dhmolar/drhomolar|T * dhmass/dhmolar where dhmass/dhmolar = 1/mole mass
1015 drho /= AS.molar_mass();
1016 dT /= AS.molar_mass();
1017 }
1018 break;
1019 }
1020 case iSmass:
1021 case iSmolar: {
1022 // ds/dT|rho
1023 dT = R / T * (-pow(tau, 2) * (AS.d2alpha0_dTau2() + AS.d2alphar_dTau2()));
1024 // ds/drho|T
1025 drho = R / rho * (-(1 + delta * AS.dalphar_dDelta() - tau * delta * AS.d2alphar_dDelta_dTau()));
1026 if (index == iSmass) {
1027 // ds/drho|T / drhomass/drhomolar where drhomass/drhomolar = mole mass
1028 drho /= AS.molar_mass();
1029 dT /= AS.molar_mass();
1030 }
1031 break;
1032 }
1033 case iUmass:
1034 case iUmolar: {
1035 // du/dT|rho
1036 dT = R * (-pow(tau, 2) * (AS.d2alpha0_dTau2() + AS.d2alphar_dTau2()));
1037 // du/drho|T
1038 drho = AS.T() * R / rho * (tau * delta * AS.d2alphar_dDelta_dTau());
1039 if (index == iUmass) {
1040 // du/drho|T / drhomass/drhomolar where drhomass/drhomolar = mole mass
1041 drho /= AS.molar_mass();
1042 dT /= AS.molar_mass();
1043 }
1044 break;
1045 }
1046 case iGmass:
1047 case iGmolar: {
1048 // dg/dT|rho
1049 const double dTau_dT = 1 / dT_dtau;
1050 dT = R * AS.T() * (AS.dalpha0_dTau() + AS.dalphar_dTau() + AS.delta() * AS.d2alphar_dDelta_dTau()) * dTau_dT
1051 + R * (1 + AS.alpha0() + AS.alphar() + AS.delta() * AS.dalphar_dDelta());
1052 // dg/drho|T
1053 const double dDelta_drho = 1 / rhor;
1054 drho = AS.T() * R * (AS.dalpha0_dDelta() + AS.dalphar_dDelta() + AS.delta() * AS.d2alphar_dDelta2() + AS.dalphar_dDelta()) * dDelta_drho;
1055 if (index == iGmass) {
1056 // dg/drho|T / drhomass/drhomolar where drhomass/drhomolar = mole mass
1057 drho /= AS.molar_mass();
1058 dT /= AS.molar_mass();
1059 }
1060 break;
1061 }
1062 case iTau:
1063 dT = 1 / dT_dtau;
1064 drho = 0;
1065 break;
1066 case iDelta:
1067 dT = 0;
1068 drho = 1 / rhor;
1069 break;
1070 case iCvmolar:
1071 case iCvmass: {
1072 // use the second order derivative of internal energy
1073 // make it cleaner by using the function get_dT_drho_second_derivatives directly?
1074 // dcvdT|rho = d2u/dT2|rho
1075 dT = R / T * pow(tau, 2) * (tau * (AS.d3alpha0_dTau3() + AS.d3alphar_dTau3()) + 2 * (AS.d2alpha0_dTau2() + AS.d2alphar_dTau2()));
1076 // dcvdrho|T = d2u/dT/drho
1077 drho = R / rho * (-pow(tau, 2) * delta * AS.d3alphar_dDelta_dTau2());
1078 if (index == iCvmass) {
1079 drho /= AS.molar_mass();
1080 dT /= AS.molar_mass();
1081 }
1082 break;
1083 }
1084 case iCpmolar:
1085 case iCpmass: {
1086 // dcp/dT|rho = d2h/dT2 + dh/drho * dP/dT * d2P/drhodT / ( dp/drho )^2 - ( d2h/dTdrho * dP/dT + dh/drho * d2P/dT2 ) / ( dP/drho )
1087 dT = R / T * pow(tau, 2)
1088 * (tau * (AS.d3alpha0_dTau3() + AS.d3alphar_dTau3()) + 2 * (AS.d2alpha0_dTau2() + AS.d2alphar_dTau2())
1089 + delta * AS.d3alphar_dDelta_dTau2());
1090 dT += (T * R / rho * (tau * delta * AS.d2alphar_dDelta_dTau() + delta * AS.dalphar_dDelta() + pow(delta, 2) * AS.d2alphar_dDelta2()))
1091 * (rho * R * (1 + delta * AS.dalphar_dDelta() - tau * delta * AS.d2alphar_dDelta_dTau()))
1092 * (R
1093 * (1 + 2 * delta * AS.dalphar_dDelta() + pow(delta, 2) * AS.d2alphar_dDelta2() - 2 * delta * tau * AS.d2alphar_dDelta_dTau()
1094 - tau * pow(delta, 2) * AS.d3alphar_dDelta2_dTau()))
1095 / pow(R * T * (1 + 2 * delta * AS.dalphar_dDelta() + pow(delta, 2) * AS.d2alphar_dDelta2()), 2);
1096 dT -= ((R / rho * delta
1097 * (delta * AS.d2alphar_dDelta2() - pow(tau, 2) * AS.d3alphar_dDelta_dTau2() + AS.dalphar_dDelta()
1098 - tau * delta * AS.d3alphar_dDelta2_dTau() - tau * AS.d2alphar_dDelta_dTau()))
1099 * (rho * R * (1 + delta * AS.dalphar_dDelta() - tau * delta * AS.d2alphar_dDelta_dTau()))
1100 + (T * R / rho * (tau * delta * AS.d2alphar_dDelta_dTau() + delta * AS.dalphar_dDelta() + pow(delta, 2) * AS.d2alphar_dDelta2()))
1101 * (rho * R / T * (pow(tau, 2) * delta * AS.d3alphar_dDelta_dTau2())))
1102 / (R * T * (1 + 2 * delta * AS.dalphar_dDelta() + pow(delta, 2) * AS.d2alphar_dDelta2()));
1103 // dcpdrho|T = d2h/dTdrho + dh/drho * dP/dT * d2P/drho2 / ( dp/drho )^2 - ( d2h/drho2 * dP/dT + dh/drho * d2P/dTdrho ) / ( dP/drho )
1104 drho = R / rho * delta
1105 * (delta * AS.d2alphar_dDelta2() - pow(tau, 2) * AS.d3alphar_dDelta_dTau2() + AS.dalphar_dDelta()
1106 - tau * delta * AS.d3alphar_dDelta2_dTau() - tau * AS.d2alphar_dDelta_dTau()); //d2h/dTdrho
1107 drho +=
1108 (T * R / rho * (tau * delta * AS.d2alphar_dDelta_dTau() + delta * AS.dalphar_dDelta() + pow(delta, 2) * AS.d2alphar_dDelta2()))
1109 * (rho * R * (1 + delta * AS.dalphar_dDelta() - tau * delta * AS.d2alphar_dDelta_dTau()))
1110 * (T * R / rho * (2 * delta * AS.dalphar_dDelta() + 4 * pow(delta, 2) * AS.d2alphar_dDelta2() + pow(delta, 3) * AS.d3alphar_dDelta3()))
1111 / pow(R * T * (1 + 2 * delta * AS.dalphar_dDelta() + pow(delta, 2) * AS.d2alphar_dDelta2()), 2);
1112 drho -= ((R * T * pow(delta / rho, 2) * (tau * AS.d3alphar_dDelta2_dTau() + 2 * AS.d2alphar_dDelta2() + delta * AS.d3alphar_dDelta3()))
1113 * (rho * R * (1 + delta * AS.dalphar_dDelta() - tau * delta * AS.d2alphar_dDelta_dTau()))
1114 + (T * R / rho * (tau * delta * AS.d2alphar_dDelta_dTau() + delta * AS.dalphar_dDelta() + pow(delta, 2) * AS.d2alphar_dDelta2()))
1115 * (R
1116 * (1 + 2 * delta * AS.dalphar_dDelta() + pow(delta, 2) * AS.d2alphar_dDelta2()
1117 - 2 * delta * tau * AS.d2alphar_dDelta_dTau() - tau * pow(delta, 2) * AS.d3alphar_dDelta2_dTau())))
1118 / (R * T * (1 + 2 * delta * AS.dalphar_dDelta() + pow(delta, 2) * AS.d2alphar_dDelta2()));
1119 if (index == iCpmass) {
1120 drho /= AS.molar_mass();
1121 dT /= AS.molar_mass();
1122 }
1123 break;
1124 }
1125 case ispeed_sound: {
1126 //dwdT
1127 const double aa = 1.0 + delta * AS.dalphar_dDelta() - delta * tau * AS.d2alphar_dDelta_dTau();
1128 const double bb = pow(tau, 2) * (AS.d2alpha0_dTau2() + AS.d2alphar_dTau2());
1129 const double daa_dTau = -delta * tau * AS.d3alphar_dDelta_dTau2();
1130 const double dbb_dTau =
1131 pow(tau, 2) * (AS.d3alpha0_dTau3() + AS.d3alphar_dTau3()) + 2.0 * tau * (AS.d2alpha0_dTau2() + AS.d2alphar_dTau2());
1132 const double w = AS.speed_sound();
1133 dT = 1.0 / 2.0 / w / T
1134 * (pow(w, 2)
1135 - R * Tr / AS.molar_mass()
1136 * (2.0 * delta * AS.d2alphar_dDelta_dTau() + pow(delta, 2) * AS.d3alphar_dDelta2_dTau()
1137 - (2 * aa / bb * daa_dTau - pow(aa / bb, 2) * dbb_dTau)));
1138 //dwdrho
1139 const double daa_dDelta =
1140 AS.dalphar_dDelta() + delta * AS.d2alphar_dDelta2() - tau * (AS.d2alphar_dDelta_dTau() + delta * AS.d3alphar_dDelta2_dTau());
1141 const double dbb_dDelta = pow(tau, 2) * (AS.d3alpha0_dDelta_dTau2() + AS.d3alphar_dDelta_dTau2());
1142 drho = R * T / 2.0 / AS.molar_mass() / w / rhor
1143 * (2.0 * (AS.dalphar_dDelta() + delta * AS.d2alphar_dDelta2())
1144 + (2.0 * delta * AS.d2alphar_dDelta2() + pow(delta, 2) * AS.d3alphar_dDelta3())
1145 - (2 * aa / bb * daa_dDelta - pow(aa / bb, 2) * dbb_dDelta));
1146 break;
1147 }
1148 default:
1149 throw ValueError(format("input to get_dT_drho[%s] is invalid", get_parameter_information(index, "short").c_str()));
1150 }
1151}
1153 const CoolPropDbl T = AS.T(), rho = AS.rhomolar(), rhor = AS.rhomolar_reducing(), Tr = AS.T_reducing(), R = AS.gas_constant(), delta = rho / rhor,
1154 tau = Tr / T;
1155
1156 // Here we use T and rho as independent variables since derivations are already done by Thorade, 2013,
1157 // Partial derivatives of thermodynamic state propertiesfor dynamic simulation, DOI 10.1007/s12665-013-2394-z
1158
1159 switch (index) {
1160 case iT:
1161 case iDmass:
1162 case iDmolar:
1163 dT2 = 0; // d2rhomolar_dtau2
1164 drho2 = 0;
1165 drho_dT = 0;
1166 break;
1167 case iTau:
1168 dT2 = 2 * Tr / pow(T, 3);
1169 drho_dT = 0;
1170 drho2 = 0;
1171 break;
1172 case iDelta:
1173 dT2 = 0;
1174 drho_dT = 0;
1175 drho2 = 0;
1176 break;
1177 case iP: {
1178 drho2 =
1179 T * R / rho * (2 * delta * AS.dalphar_dDelta() + 4 * pow(delta, 2) * AS.d2alphar_dDelta2() + pow(delta, 3) * AS.d3alphar_dDelta3());
1180 dT2 = rho * R / T * (pow(tau, 2) * delta * AS.d3alphar_dDelta_dTau2());
1181 drho_dT = R
1182 * (1 + 2 * delta * AS.dalphar_dDelta() + pow(delta, 2) * AS.d2alphar_dDelta2() - 2 * delta * tau * AS.d2alphar_dDelta_dTau()
1183 - tau * pow(delta, 2) * AS.d3alphar_dDelta2_dTau());
1184 break;
1185 }
1186 case iHmass:
1187 case iHmolar: {
1188 // d2h/drho2|T
1189 drho2 = R * T * pow(delta / rho, 2) * (tau * AS.d3alphar_dDelta2_dTau() + 2 * AS.d2alphar_dDelta2() + delta * AS.d3alphar_dDelta3());
1190 // d2h/dT2|rho
1191 dT2 = R / T * pow(tau, 2)
1192 * (tau * (AS.d3alpha0_dTau3() + AS.d3alphar_dTau3()) + 2 * (AS.d2alpha0_dTau2() + AS.d2alphar_dTau2())
1193 + delta * AS.d3alphar_dDelta_dTau2());
1194 // d2h/drho/dT
1195 drho_dT = R / rho * delta
1196 * (delta * AS.d2alphar_dDelta2() - pow(tau, 2) * AS.d3alphar_dDelta_dTau2() + AS.dalphar_dDelta()
1197 - tau * delta * AS.d3alphar_dDelta2_dTau() - tau * AS.d2alphar_dDelta_dTau());
1198 if (index == iHmass) {
1199 drho2 /= AS.molar_mass();
1200 drho_dT /= AS.molar_mass();
1201 dT2 /= AS.molar_mass();
1202 }
1203 break;
1204 }
1205 case iSmass:
1206 case iSmolar: {
1207 // d2s/rho2|T
1208 drho2 = R / pow(rho, 2) * (1 - pow(delta, 2) * AS.d2alphar_dDelta2() + tau * pow(delta, 2) * AS.d3alphar_dDelta2_dTau());
1209 // d2s/dT2|rho
1210 dT2 = R * pow(tau / T, 2) * (tau * (AS.d3alpha0_dTau3() + AS.d3alphar_dTau3()) + 3 * (AS.d2alpha0_dTau2() + AS.d2alphar_dTau2()));
1211 // d2s/drho/dT
1212 drho_dT = R / (T * rho) * (-pow(tau, 2) * delta * AS.d3alphar_dDelta_dTau2());
1213 if (index == iSmass) {
1214 drho2 /= AS.molar_mass();
1215 drho_dT /= AS.molar_mass();
1216 dT2 /= AS.molar_mass();
1217 }
1218 break;
1219 }
1220 case iUmass:
1221 case iUmolar: {
1222 // d2u/rho2|T
1223 drho2 = R * T * tau * pow(delta / rho, 2) * AS.d3alphar_dDelta2_dTau();
1224 // d2u/dT2|rho
1225 dT2 = R / T * pow(tau, 2) * (tau * (AS.d3alpha0_dTau3() + AS.d3alphar_dTau3()) + 2 * (AS.d2alpha0_dTau2() + AS.d2alphar_dTau2()));
1226 // d2u/drho/dT
1227 drho_dT = R / rho * (-pow(tau, 2) * delta * AS.d3alphar_dDelta_dTau2());
1228 if (index == iUmass) {
1229 drho2 /= AS.molar_mass();
1230 drho_dT /= AS.molar_mass();
1231 dT2 /= AS.molar_mass();
1232 }
1233 break;
1234 }
1235 default:
1236 throw ValueError(format("input to get_dT_drho_second_derivatives[%s] is invalid", get_parameter_information(index, "short").c_str()));
1237 }
1238}
1240 CoolPropDbl dOf_dT = NAN, dOf_drho = NAN, dWrt_dT = NAN, dWrt_drho = NAN, dConstant_dT = NAN, dConstant_drho = NAN;
1241
1242 get_dT_drho(*this, Of, dOf_dT, dOf_drho);
1243 get_dT_drho(*this, Wrt, dWrt_dT, dWrt_drho);
1244 get_dT_drho(*this, Constant, dConstant_dT, dConstant_drho);
1245
1246 return (dOf_dT * dConstant_drho - dOf_drho * dConstant_dT) / (dWrt_dT * dConstant_drho - dWrt_drho * dConstant_dT);
1247}
1249 CoolPropDbl dOf1_dT = NAN, dOf1_drho = NAN, dWrt1_dT = NAN, dWrt1_drho = NAN, dConstant1_dT = NAN, dConstant1_drho = NAN, d2Of1_dT2 = NAN,
1250 d2Of1_drhodT = NAN, d2Of1_drho2 = NAN, d2Wrt1_dT2 = NAN, d2Wrt1_drhodT = NAN, d2Wrt1_drho2 = NAN, d2Constant1_dT2 = NAN,
1251 d2Constant1_drhodT = NAN, d2Constant1_drho2 = NAN, dWrt2_dT = NAN, dWrt2_drho = NAN, dConstant2_dT = NAN, dConstant2_drho = NAN,
1252 N = NAN, D = NAN, dNdrho__T = NAN, dDdrho__T = NAN, dNdT__rho = NAN, dDdT__rho = NAN, dderiv1_drho = NAN, dderiv1_dT = NAN,
1253 second = NAN;
1254
1255 // First and second partials needed for terms involved in first derivative
1256 get_dT_drho(*this, Of1, dOf1_dT, dOf1_drho);
1257 get_dT_drho(*this, Wrt1, dWrt1_dT, dWrt1_drho);
1258 get_dT_drho(*this, Constant1, dConstant1_dT, dConstant1_drho);
1259 get_dT_drho_second_derivatives(*this, Of1, d2Of1_dT2, d2Of1_drhodT, d2Of1_drho2);
1260 get_dT_drho_second_derivatives(*this, Wrt1, d2Wrt1_dT2, d2Wrt1_drhodT, d2Wrt1_drho2);
1261 get_dT_drho_second_derivatives(*this, Constant1, d2Constant1_dT2, d2Constant1_drhodT, d2Constant1_drho2);
1262
1263 // First derivatives of terms involved in the second derivative
1264 get_dT_drho(*this, Wrt2, dWrt2_dT, dWrt2_drho);
1265 get_dT_drho(*this, Constant2, dConstant2_dT, dConstant2_drho);
1266
1267 // Numerator and denominator of first partial derivative term
1268 N = dOf1_dT * dConstant1_drho - dOf1_drho * dConstant1_dT;
1269 D = dWrt1_dT * dConstant1_drho - dWrt1_drho * dConstant1_dT;
1270
1271 // Derivatives of the numerator and denominator of the first partial derivative term with respect to rho, T held constant
1272 // They are of similar form, with Of1 and Wrt1 swapped
1273 dNdrho__T = dOf1_dT * d2Constant1_drho2 + d2Of1_drhodT * dConstant1_drho - dOf1_drho * d2Constant1_drhodT - d2Of1_drho2 * dConstant1_dT;
1274 dDdrho__T = dWrt1_dT * d2Constant1_drho2 + d2Wrt1_drhodT * dConstant1_drho - dWrt1_drho * d2Constant1_drhodT - d2Wrt1_drho2 * dConstant1_dT;
1275
1276 // Derivatives of the numerator and denominator of the first partial derivative term with respect to T, rho held constant
1277 // They are of similar form, with Of1 and Wrt1 swapped
1278 dNdT__rho = dOf1_dT * d2Constant1_drhodT + d2Of1_dT2 * dConstant1_drho - dOf1_drho * d2Constant1_dT2 - d2Of1_drhodT * dConstant1_dT;
1279 dDdT__rho = dWrt1_dT * d2Constant1_drhodT + d2Wrt1_dT2 * dConstant1_drho - dWrt1_drho * d2Constant1_dT2 - d2Wrt1_drhodT * dConstant1_dT;
1280
1281 // First partial of first derivative term with respect to T
1282 dderiv1_drho = (D * dNdrho__T - N * dDdrho__T) / pow(D, 2);
1283
1284 // First partial of first derivative term with respect to rho
1285 dderiv1_dT = (D * dNdT__rho - N * dDdT__rho) / pow(D, 2);
1286
1287 // Complete second derivative
1288 second = (dderiv1_dT * dConstant2_drho - dderiv1_drho * dConstant2_dT) / (dWrt2_dT * dConstant2_drho - dWrt2_drho * dConstant2_dT);
1289
1290 return second;
1291}
1292// // ----------------------------------------
1293// // Smoothing functions for density
1294// // ----------------------------------------
1295// /// A smoothed version of the derivative using a spline curve in the region of x=0 to x=xend
1296// virtual double AbstractState::drhodh_constp_smoothed(double xend);
1297// /// A smoothed version of the derivative using a spline curve in the region of x=0 to x=xend
1298// virtual double AbstractState::drhodp_consth_smoothed(double xend);
1299// /// Density corresponding to the smoothed derivatives in the region of x=0 to x=xend
1300// virtual void AbstractState::rho_smoothed(double xend, double *rho_spline, double *dsplinedh, double *dsplinedp);
1301
1302} /* namespace CoolProp */
1303
1304#ifdef ENABLE_CATCH
1305
1306#include <catch2/catch_all.hpp>
1307
1308TEST_CASE("Check AbstractState", "[AbstractState]") {
1309 SECTION("bad backend") {
1310 CHECK_THROWS(shared_ptr<CoolProp::AbstractState>(CoolProp::AbstractState::factory("DEFINITELY_A_BAD_BACKEND", "Water")));
1311 }
1312 SECTION("good backend - bad fluid") {
1313 CHECK_THROWS(shared_ptr<CoolProp::AbstractState>(CoolProp::AbstractState::factory("HEOS", "DEFINITELY_A_BAD_FLUID")));
1314 }
1315 SECTION("good backend - helmholtz") {
1316 CHECK_NOTHROW(shared_ptr<CoolProp::AbstractState>(CoolProp::AbstractState::factory("HEOS", "Water")));
1317 }
1318 SECTION("good backend - incomp") {
1319 CHECK_NOTHROW(shared_ptr<CoolProp::AbstractState>(CoolProp::AbstractState::factory("INCOMP", "DEB")));
1320 }
1321 // SECTION("good backend - REFPROP") {
1322 // CHECK_NOTHROW(shared_ptr<CoolProp::AbstractState>(CoolProp::AbstractState::factory("REFPROP", "Water")));
1323 // }
1324 // Code below lets the test suite continue with REFPROP isn't present while still reporting a visile warning from CATCH2
1325 SECTION("good backend - REFPROP") {
1326 try {
1327 auto s = shared_ptr<CoolProp::AbstractState>(CoolProp::AbstractState::factory("REFPROP", "Water"));
1328 CHECK(s); // assert we got a non-null pointer when REFPROP is present
1329 } catch (const std::exception& e) {
1330 WARN(std::string("REFPROP backend unavailable. All tests requiring REFPROP will be skipped."));
1331 }
1332 }
1333}
1334
1335TEST_CASE("Check derivatives in first_partial_deriv", "[derivs_in_first_partial_deriv]") {
1336 shared_ptr<CoolProp::AbstractState> Water(CoolProp::AbstractState::factory("HEOS", "Water"));
1337 shared_ptr<CoolProp::AbstractState> WaterplusT(CoolProp::AbstractState::factory("HEOS", "Water"));
1338 shared_ptr<CoolProp::AbstractState> WaterminusT(CoolProp::AbstractState::factory("HEOS", "Water"));
1339 shared_ptr<CoolProp::AbstractState> Waterplusrho(CoolProp::AbstractState::factory("HEOS", "Water"));
1340 shared_ptr<CoolProp::AbstractState> Waterminusrho(CoolProp::AbstractState::factory("HEOS", "Water"));
1341
1342 double dT = 1e-3, drho = 1;
1343 Water->update(CoolProp::PT_INPUTS, 101325, 300);
1344 WaterplusT->update(CoolProp::DmolarT_INPUTS, Water->rhomolar(), 300 + dT);
1345 WaterminusT->update(CoolProp::DmolarT_INPUTS, Water->rhomolar(), 300 - dT);
1346 Waterplusrho->update(CoolProp::DmolarT_INPUTS, Water->rhomolar() + drho, 300);
1347 Waterminusrho->update(CoolProp::DmolarT_INPUTS, Water->rhomolar() - drho, 300);
1348
1349 // Numerical derivatives
1350 CoolPropDbl dP_dT_num = (WaterplusT->p() - WaterminusT->p()) / (2 * dT);
1351 CoolPropDbl dP_drho_num = (Waterplusrho->p() - Waterminusrho->p()) / (2 * drho);
1352
1353 CoolPropDbl dHmolar_dT_num = (WaterplusT->hmolar() - WaterminusT->hmolar()) / (2 * dT);
1354 CoolPropDbl dHmolar_drho_num = (Waterplusrho->hmolar() - Waterminusrho->hmolar()) / (2 * drho);
1355 CoolPropDbl dHmass_dT_num = (WaterplusT->hmass() - WaterminusT->hmass()) / (2 * dT);
1356 CoolPropDbl dHmass_drho_num = (Waterplusrho->hmass() - Waterminusrho->hmass()) / (2 * drho);
1357
1358 CoolPropDbl dSmolar_dT_num = (WaterplusT->smolar() - WaterminusT->smolar()) / (2 * dT);
1359 CoolPropDbl dSmolar_drho_num = (Waterplusrho->smolar() - Waterminusrho->smolar()) / (2 * drho);
1360 CoolPropDbl dSmass_dT_num = (WaterplusT->smass() - WaterminusT->smass()) / (2 * dT);
1361 CoolPropDbl dSmass_drho_num = (Waterplusrho->smass() - Waterminusrho->smass()) / (2 * drho);
1362
1363 CoolPropDbl dUmolar_dT_num = (WaterplusT->umolar() - WaterminusT->umolar()) / (2 * dT);
1364 CoolPropDbl dUmolar_drho_num = (Waterplusrho->umolar() - Waterminusrho->umolar()) / (2 * drho);
1365 CoolPropDbl dUmass_dT_num = (WaterplusT->umass() - WaterminusT->umass()) / (2 * dT);
1366 CoolPropDbl dUmass_drho_num = (Waterplusrho->umass() - Waterminusrho->umass()) / (2 * drho);
1367
1368 CoolPropDbl dGmolar_dT_num = (WaterplusT->gibbsmolar() - WaterminusT->gibbsmolar()) / (2 * dT);
1369 CoolPropDbl dGmolar_drho_num = (Waterplusrho->gibbsmolar() - Waterminusrho->gibbsmolar()) / (2 * drho);
1370 CoolPropDbl dGmass_dT_num = (WaterplusT->gibbsmass() - WaterminusT->gibbsmass()) / (2 * dT);
1371 CoolPropDbl dGmass_drho_num = (Waterplusrho->gibbsmass() - Waterminusrho->gibbsmass()) / (2 * drho);
1372
1373 CoolPropDbl dCvmolar_dT_num = (WaterplusT->cvmolar() - WaterminusT->cvmolar()) / (2 * dT);
1374 CoolPropDbl dCvmolar_drho_num = (Waterplusrho->cvmolar() - Waterminusrho->cvmolar()) / (2 * drho);
1375 CoolPropDbl dCvmass_dT_num = (WaterplusT->cvmass() - WaterminusT->cvmass()) / (2 * dT);
1376 CoolPropDbl dCvmass_drho_num = (Waterplusrho->cvmass() - Waterminusrho->cvmass()) / (2 * drho);
1377
1378 CoolPropDbl dCpmolar_dT_num = (WaterplusT->cpmolar() - WaterminusT->cpmolar()) / (2 * dT);
1379 CoolPropDbl dCpmolar_drho_num = (Waterplusrho->cpmolar() - Waterminusrho->cpmolar()) / (2 * drho);
1380 CoolPropDbl dCpmass_dT_num = (WaterplusT->cpmass() - WaterminusT->cpmass()) / (2 * dT);
1381 CoolPropDbl dCpmass_drho_num = (Waterplusrho->cpmass() - Waterminusrho->cpmass()) / (2 * drho);
1382
1383 CoolPropDbl dspeed_sound_dT_num = (WaterplusT->speed_sound() - WaterminusT->speed_sound()) / (2 * dT);
1384 CoolPropDbl dspeed_sound_drho_num = (Waterplusrho->speed_sound() - Waterminusrho->speed_sound()) / (2 * drho);
1385
1386 // Pressure
1387 CoolPropDbl dP_dT_analyt, dP_drho_analyt;
1388 CoolProp::get_dT_drho(*Water, CoolProp::iP, dP_dT_analyt, dP_drho_analyt);
1389 // Enthalpy
1390 CoolPropDbl dHmolar_dT_analyt, dHmolar_drho_analyt;
1391 CoolProp::get_dT_drho(*Water, CoolProp::iHmolar, dHmolar_dT_analyt, dHmolar_drho_analyt);
1392 CoolPropDbl dHmass_dT_analyt, dHmass_drho_analyt;
1393 CoolProp::get_dT_drho(*Water, CoolProp::iHmass, dHmass_dT_analyt, dHmass_drho_analyt);
1394 // Entropy
1395 CoolPropDbl dSmolar_dT_analyt, dSmolar_drho_analyt;
1396 CoolProp::get_dT_drho(*Water, CoolProp::iSmolar, dSmolar_dT_analyt, dSmolar_drho_analyt);
1397 CoolPropDbl dSmass_dT_analyt, dSmass_drho_analyt;
1398 CoolProp::get_dT_drho(*Water, CoolProp::iSmass, dSmass_dT_analyt, dSmass_drho_analyt);
1399 // Internal energy
1400 CoolPropDbl dUmolar_dT_analyt, dUmolar_drho_analyt;
1401 CoolProp::get_dT_drho(*Water, CoolProp::iUmolar, dUmolar_dT_analyt, dUmolar_drho_analyt);
1402 CoolPropDbl dUmass_dT_analyt, dUmass_drho_analyt;
1403 CoolProp::get_dT_drho(*Water, CoolProp::iUmass, dUmass_dT_analyt, dUmass_drho_analyt);
1404 // Gibbs
1405 CoolPropDbl dGmolar_dT_analyt, dGmolar_drho_analyt;
1406 CoolProp::get_dT_drho(*Water, CoolProp::iGmolar, dGmolar_dT_analyt, dGmolar_drho_analyt);
1407 CoolPropDbl dGmass_dT_analyt, dGmass_drho_analyt;
1408 CoolProp::get_dT_drho(*Water, CoolProp::iGmass, dGmass_dT_analyt, dGmass_drho_analyt);
1409 // Isochoric heat capacity
1410 CoolPropDbl dCvmolar_dT_analyt, dCvmolar_drho_analyt;
1411 CoolProp::get_dT_drho(*Water, CoolProp::iCvmolar, dCvmolar_dT_analyt, dCvmolar_drho_analyt);
1412 CoolPropDbl dCvmass_dT_analyt, dCvmass_drho_analyt;
1413 CoolProp::get_dT_drho(*Water, CoolProp::iCvmass, dCvmass_dT_analyt, dCvmass_drho_analyt);
1414 // Isobaric heat capacity
1415 CoolPropDbl dCpmolar_dT_analyt, dCpmolar_drho_analyt;
1416 CoolProp::get_dT_drho(*Water, CoolProp::iCpmolar, dCpmolar_dT_analyt, dCpmolar_drho_analyt);
1417 CoolPropDbl dCpmass_dT_analyt, dCpmass_drho_analyt;
1418 CoolProp::get_dT_drho(*Water, CoolProp::iCpmass, dCpmass_dT_analyt, dCpmass_drho_analyt);
1419 // Speed of sound
1420 CoolPropDbl dspeed_sound_dT_analyt, dspeed_sound_drho_analyt;
1421 CoolProp::get_dT_drho(*Water, CoolProp::ispeed_sound, dspeed_sound_dT_analyt, dspeed_sound_drho_analyt);
1422
1423 double eps = 1e-3;
1424
1425 CHECK(std::abs(dP_dT_analyt / dP_dT_num - 1) < eps);
1426 CHECK(std::abs(dP_drho_analyt / dP_drho_num - 1) < eps);
1427
1428 CHECK(std::abs(dHmolar_dT_analyt / dHmolar_dT_num - 1) < eps);
1429 CHECK(std::abs(dHmolar_drho_analyt / dHmolar_drho_num - 1) < eps);
1430 CHECK(std::abs(dHmass_dT_analyt / dHmass_dT_num - 1) < eps);
1431 CHECK(std::abs(dHmass_drho_analyt / dHmass_drho_num - 1) < eps);
1432
1433 CHECK(std::abs(dSmolar_dT_analyt / dSmolar_dT_num - 1) < eps);
1434 CHECK(std::abs(dSmolar_drho_analyt / dSmolar_drho_num - 1) < eps);
1435 CHECK(std::abs(dSmass_dT_analyt / dSmass_dT_num - 1) < eps);
1436 CHECK(std::abs(dSmass_drho_analyt / dSmass_drho_num - 1) < eps);
1437
1438 CHECK(std::abs(dUmolar_dT_analyt / dUmolar_dT_num - 1) < eps);
1439 CHECK(std::abs(dUmolar_drho_analyt / dUmolar_drho_num - 1) < eps);
1440 CHECK(std::abs(dUmass_dT_analyt / dUmass_dT_num - 1) < eps);
1441 CHECK(std::abs(dUmass_drho_analyt / dUmass_drho_num - 1) < eps);
1442
1443 CHECK(std::abs(dGmolar_dT_analyt / dGmolar_dT_num - 1) < eps);
1444 CHECK(std::abs(dGmolar_drho_analyt / dGmolar_drho_num - 1) < eps);
1445 CHECK(std::abs(dGmass_dT_analyt / dGmass_dT_num - 1) < eps);
1446 CHECK(std::abs(dGmass_drho_analyt / dGmass_drho_num - 1) < eps);
1447
1448 CHECK(std::abs(dCvmolar_dT_analyt / dCvmolar_dT_num - 1) < eps);
1449 CHECK(std::abs(dCvmolar_drho_analyt / dCvmolar_drho_num - 1) < eps);
1450 CHECK(std::abs(dCvmass_dT_analyt / dCvmass_dT_num - 1) < eps);
1451 CHECK(std::abs(dCvmass_drho_analyt / dCvmass_drho_num - 1) < eps);
1452
1453 CHECK(std::abs(dCpmolar_dT_analyt / dCpmolar_dT_num - 1) < eps);
1454 CHECK(std::abs(dCpmolar_drho_analyt / dCpmolar_drho_num - 1) < eps);
1455 CHECK(std::abs(dCpmass_dT_analyt / dCpmass_dT_num - 1) < eps);
1456 CHECK(std::abs(dCpmass_drho_analyt / dCpmass_drho_num - 1) < eps);
1457
1458 CHECK(std::abs(dspeed_sound_dT_analyt / dspeed_sound_dT_num - 1) < eps);
1459 CHECK(std::abs(dspeed_sound_drho_analyt / dspeed_sound_drho_num - 1) < eps);
1460}
1461
1462#endif