i was going to publish a benchmark inmediately after that comment, but i found my IAPWS95 could be optimized. after the rewrite i accidentally changed one sign of one constant, and it was a pain to find (IAPWS95 comes with some tests for implementations, really a life saver). after that those are the results, using the benchmark on volume calculation:
Pythermo (i dont know what eos uses here)
julia> @time [ρ_TP(thermo_H2O, T, P) for T in LinRange(280, 330, 100), P in LinRange(8e4, 12e5, 100)];
#4.271689 seconds (2.22 M allocations: 130.980 MiB, 1.10% gc time, 33.34% compilation time
julia> @benchmark [ρ_TP(thermo_H2O, T, P) for T in LinRange(280, 330, 100), P in LinRange(8e4, 12e5, 100)];
BenchmarkTools.Trial:
memory estimate: 14.73 MiB
allocs estimate: 320005
--------------
minimum time: 2.903 s (0.00% GC)
median time: 2.994 s (0.00% GC)
mean time: 2.994 s (0.00% GC)
maximum time: 3.085 s (0.00% GC)
--------------
samples: 2
evals/sample: 1
Coolprop (IAPWS95)
julia> @time [ρ_TP(CP_H2O, T, P) for T in LinRange(280, 330, 100), P in LinRange(8e4, 12e5, 100)];
0.538447 seconds (115.41 k allocations: 4.366 MiB, 6.34% compilation time)
julia> @benchmark [ρ_TP(CP_H2O, T, P) for T in LinRange(280, 330, 100), P in LinRange(8e4, 12e5, 100)]
BenchmarkTools.Trial:
memory estimate: 859.66 KiB
allocs estimate: 50005
--------------
minimum time: 696.510 ms (0.00% GC)
median time: 700.885 ms (0.00% GC)
mean time: 701.333 ms (0.00% GC)
maximum time: 708.771 ms (0.00% GC)
--------------
samples: 8
evals/sample: 1
OpenSAFT (IAPWS95):
using OpenSAFT #development branch
function ρ_TP(species::TT, T, P) where TT<:OpenSAFT.EoSModel
v = OpenSAFT.volume(species,P,T)
return 1/v
end
OpenSAFT_EOS = IAPWS95()
julia> @time [ρ_TP(w, T, P) for T in LinRange(280, 330, 100), P in LinRange(8e4, 12e5, 100)];
0.830912 seconds (451.73 k allocations: 25.016 MiB, 1.16% gc time, 7.03% compilation time)
julia> @benchmark [ρ_TP(w, T, P) for T in LinRange(280, 330, 100), P in LinRange(8e4, 12e5, 100)]
BenchmarkTools.Trial:
memory estimate: 20.48 MiB
allocs estimate: 368673
--------------
minimum time: 765.410 ms (0.00% GC)
median time: 777.859 ms (0.00% GC)
mean time: 779.380 ms (0.21% GC)
maximum time: 796.346 ms (0.00% GC)
--------------
samples: 7
evals/sample: 1
same equation, same results
julia> OpenSAFT.sat_pure(w,330)
(17213.151668037026, 1.8294254890028448e-5, 0.15862466665274236)
so, near CoolProp. i think that, on one component, they just use a fitted vapor pressure curve and look if the pressure-temperature pair is above or below said line. whereas here there are 2 aditional helmholtz evaluations to select the correct phase. a good thing choosing a functional api, is that you can parallelize (from my laptop, 4 threads):
julia> @time Threads.@threads for T in LinRange(280, 330, 100) for P in LinRange(8e4, 12e5, 100)
ρ_TP(w, T, P)
end
end
0.312199 seconds (433.66 k allocations: 24.927 MiB, 16.72% gc time, 27.31% compilation time)
if we only need evaluations on multiple points, we can just throw a cluster and evaluate points simultaneously. if you know your compounds, you can use Cubics:
julia> cubic = SRK(["water"])
SRK{BasicIdeal} with 1 component:
"water"
Contains parameters: a, b, acentricfactor, Tc, Pc, Mw
julia> @benchmark [ρ_TP(cubic, T, P) for T in LinRange(280, 330, 100), P in LinRange(8e4, 12e5, 100)]
BenchmarkTools.Trial:
memory estimate: 18.08 MiB
allocs estimate: 300005
--------------
minimum time: 19.961 ms (0.00% GC)
median time: 21.105 ms (0.00% GC)
mean time: 23.406 ms (11.20% GC)
maximum time: 33.276 ms (22.45% GC)
--------------
samples: 214
evals/sample: 1
(obviusly there is a speed improvement here, as volume roots of cubics have an analytical solution)