Julia throws MethodError: no method matching, with CoolProp using NonlinearSystem

Hi all,

I’m trying to get Julia’s NonlinearSolve to play well with CoolProp. I am very new at Julia, coming from many years of Python, so I am still learning the ropes.

This is the code that is giving me trouble:

using ModelingToolkit, NonlinearSolve, CoolProp


@variables dm ρ1
@parameters a1 T1 P1

# Define a nonlinear system
eqs = [
    ρ1 ~ PropsSI("D","T",T1,"P",P1,"AIR"),
    k1 ~ PropsSI("ISENTROPIC_EXPANSION_COEFFICIENT","T",T1,"P",P1,"AIR"),
    dm ~ a1 * sqrt(k1*ρ1*P1*(2/(k1+1))^((k1+1)/(k1-1)))
    ]
@mtkbuild ns = NonlinearSystem(eqs, [dm, ρ1], [a1, T1, P1])


d1 = 0.15e-3

guess = [
    dm => 0.5e-3,
    ρ1 => 20 
    ]

ps = [
        P1 => 20e5,
        a1 => (d1/2)^2 * pi,
        T1 => 293.0
    ]

prob = NonlinearProblem(ns, guess, ps)
sol = solve(prob, NewtonRaphson())

And it returns this error:

MethodError: no method matching Float64(::Num)

Closest candidates are:
  (::Type{T})(::Real, ::RoundingMode) where T<:AbstractFloat
   @ Base rounding.jl:207
  (::Type{T})(::T) where T<:Number
   @ Core boot.jl:792
  Float64(::IrrationalConstants.Logtwo)
   @ IrrationalConstants ~/.julia/packages/IrrationalConstants/vp5v4/src/macro.jl:112
  ...


Stacktrace:
 [1] convert(::Type{Float64}, x::Num)
   @ Base ./number.jl:7
 [2] cconvert(T::Type, x::Num)
   @ Base ./essentials.jl:543
 [3] PropsSI(output::String, name1::String, value1::Num, name2::String, value2::Num, fluid::String)
   @ CoolProp ~/.julia/packages/CoolProp/RDEcq/src/CoolProp.jl:81
 [4] top-level scope
   @ In[81]:8

I would expect this to just work but clearly there’s something here that I misunderstand. I also know that I could solve this system analytically, but this is a baby step before building a bigger system.

I think I understand that it is the wrong type being passed to the PropsSI function, but I don’t understand how to troubleshoot this any further, and methoderror seems quite common for newbies with Julia. Can I simply convert the types?

I have based my example on the documentation for NonlinearSolve, and it seems clear to me that it’s CoolProp giving the issue.

I can pass variables to CoolProp and get meaningful returns, as I would in Python, just fine, but I don’t understand why it breaks when I try to use in in a system of equations here.

The solution was found in @register_symbolic

The code below works as I want it to.

using ModelingToolkit, NonlinearSolve, CoolProp

air_density_tp(t,p) = PropsSI("D", "T", t, "P", p, "AIR")
air_isentropic_coef_tp(t,p) = PropsSI("ISENTROPIC_EXPANSION_COEFFICIENT", "T", t, "P", p, "AIR")

@variables dm r1 k1
@parameters a1 T1 P1
@register_symbolic air_density_tp(t,p) 
@register_symbolic air_isentropic_coef_tp(t,p)

# Define a nonlinear system
eqs = [
    r1 ~ air_density_tp(T1,P1),
    k1 ~ air_isentropic_coef_tp(T1,P1),
    dm ~ a1 * sqrt(k1*r1*P1*(2/(k1+1))^((k1+1)/(k1-1)))
    ]
@mtkbuild ns = NonlinearSystem(eqs, [dm, r1, k1], [a1, T1, P1])


d1 = 0.15e-3

guess = [
    dm => 0.5e-3,
    r1 => 20,
    k1 => 1.4
    ]

ps = [
    a1 => (d1/2)^2 * pi,
    T1 => 293.0,
    P1 => 20e5
    ]

prob = NonlinearProblem(ns, guess, ps)
sol = solve(prob, NewtonRaphson())

If you use the symbolic ModelingToolkit route, as you’re doing here, then you need to make sure your functions are symbolically-representable. CoolProp.jl’s functions are not registered for symbolic operations and so that’s what you’re hitting. I would either suggest just doing the normal purely numeric route (that would probably be more familiar, that’s usage more similar to SciPy), i.e. the first tutorial of NonlinearSolve.jl

Or if you stick the symbolic route (more similar to Modelica, CASADI, SymPy), you register your symbolic functions. The latter is done by:

using Symbolics
@register_symbolic CoolProp.PropsSI(x::String,y::String,z,a::String,b,c::String)

The full code looks like:

using ModelingToolkit, NonlinearSolve, CoolProp

using Symbolics
@register_symbolic CoolProp.PropsSI(x::String,y::String,z,a::String,b,c::String)


@variables dm ρ1 k1
@parameters a1 T1 P1

# Define a nonlinear system
eqs = [
    ρ1 ~ PropsSI("D","T",T1,"P",P1,"AIR"),
    k1 ~ PropsSI("ISENTROPIC_EXPANSION_COEFFICIENT","T",T1,"P",P1,"AIR"),
    dm ~ a1 * sqrt(k1*ρ1*P1*(2/(k1+1))^((k1+1)/(k1-1)))
    ]
@mtkbuild ns = NonlinearSystem(eqs, [dm, ρ1, k1], [a1, T1, P1])

d1 = 0.15e-3

guess = [
    dm => 0.5e-3,
    ρ1 => 20 
    ]

ps = [
        P1 => 20e5,
        a1 => (d1/2)^2 * pi,
        T1 => 293.0
    ]

prob = NonlinearProblem(ns, guess, ps)
sol = solve(prob, NewtonRaphson())
sol[dm]
sol[ρ1]
sol[k1]

In this case, all of the equations symbolically simplify away and it builds the equations for the analytical solution. But that shouldn’t be surprising because the equations are all written as explicit computations, so I assume the real case must be more complex.

2 Likes

And note we can add these by default to the symbolic library to continue to grow what’s automatically supported, but this is just showing how you can make use of it before we add it.