Engineering Thermodynamics infrastructure in Julia.


The EngThermBase.jl package provides a common platform for:

  • Engineering thermodynamics packages, and
  • Case calculations.

Engineering thermodynamics quantity tagging:

julia> using EngThermBase

julia> pars = [ T_(500u"°C"), P_(1u"MPa"), q_(800u"kJ/kg") ]
3-element Vector{AMOUNTS{Float64, EX}}:
 T₆₄: 773.15 K
 P₆₄: 1000.0 kPa
 q₆₄: 800.00 kJ/kg

In the above example, by respectivly using the T_, P_, and q_ constructors (from EngThermBase.jl), the respective argument quantities of 500u"°C", 1u"MPa", and 800u"kJ/kg" were tagged (or labeled) as a temperature, a pressure, and a heat interaction.

Once tagged, the quantity is stored and shown in default units for each quantity type, meaning in the T_(500u"°C") constructor call, there was a unit conversion from 500 °C into 773.15 K, in the P_(1u"MPa") constructor call, there was a unit conversion from 1 MPa into 1000 kPa, and no unit conversion in the q_(800u"kJ/kg") constructor call.

Moreover, the quantities also pretty-print with a pre-settable amount of significant digits, and optional floating point precision, as in the T₆₄: 773.15 K output, the T indicates the quantity type---a temperature; the ₆₄ the underlying floating point precision (of 64 bits), while 773.15 K is the amount, powered by Unitful.jl.

julia> typeof(pars[1])
T_amt{Float64, EX}

julia> typeof(pars[3])
q_amt{Float64, EX, MA}

It is worth noting that amounts are parameterized. Amounts such as temperature and pressure are (1) floating-point precision-, and (2) exactness- parameterized, for instance, in T_amt{Float64, EX}, the precision is Float64, and the exactness is EX, meaning "exact".

Due to EngThermBase.jl tagging and type tree, we can then make some theory queries, such as "asking" whether or not quantities are (i) properties or (ii) interactions, as well as their base:

julia> property_pars = [ p for p in pars if p isa Property ]
2-element Vector{WProperty{Float64, EX}}:
 T₆₄: 773.15 K
 P₆₄: 1000.0 kPa

julia> interaction_pars = [ p for p in pars if p isa Interact ]
1-element Vector{q_amt{Float64, EX, MA}}:
 q₆₄: 800.00 kJ/kg

julia> precof(pars[3]), exacof(pars[3]), baseof(pars[3])
(Float64, EX, MA)

Therefore, pars[1] and pars[2], respectively tagged as a temperature and a pressure, are listed as properties, while pars[3], tagged as a specific heat transfer, is listed as an interaction. Moreover, its precision, exactness, and base are recovered in the last example, with the precof, exacof, and baseof functions.

Quantity untagging (and optional unit conversion):

In EngThermBase.jl, amounts are functors, meaning they can be called as a function. The default behavior is to untag itself, returning unaltered it's val member. If, however, a unit is passed as an argument to the functor, a unit conversion will be attempted. All unit operations on EngThermBase.jl are powered by Unitful.jl.

julia> x, y = T_(512), P_(1024)
(T₆₄: 512.00 K, P₆₄: 1024.0 kPa)

julia> x(), y()
(512.0 K, 1024.0 kPa)

julia> x(u"°C")
238.85000000000002 °C

The example illustrates that constructors apply default units to unitless arguments, so that the default temperature unit, K, was applied by T_ in the T_(512) call. An analogous behavior is illusrtated with the P_(1024) call.

Untagging happens when the x and y objects are called (as functors), with x(), and y(), in which case we see the plain underlying values of 512.0 K and 1024.0 kPa returned as a 2-tuple. Nota that there's no mor pretty-printing because the values are not EngThermBase.jl amounts.

In the last example, a unit conversion is performed when a unit is passed to the functor call x(u"°C"), that returns 238.85 °C. Note again, the lack of pretty-printing.

Other untagging functions are: amt, bare, and pod; which, respectively return the (i) underlying amount (with units, just like the functor), (ii) the "bare" numerical value without units, and (iii) a "plain-old data", which also strips from bare numerical values any possible uncertainty.

julia> x = T_(300 ± 0.1)
T₆₄: (300.00 ± 0.10 K)

julia> [ F(x) for F in (amt, bare, pod) ]
3-element Vector{Number}:
 300.0 ± 0.1 K
       300.0 ± 0.1

In this case, typeof(x) returns T_amt{Float64, MM}. The MM exactness parameter indicates a measurement, powered by Measurements.jl.

Note that by applying the amt(), bare(), and pod() functions on x, returned the illustrated values, with all operations untagging x, returning a (i) united measurement, a (ii) unitless measurement, and a (iii) simple numeric value, or a plain-old data.

Automatic re-tagging

Certain "known" operations with tagged operands yield quantities of other, however known, tags:

julia> u_(300) + P_(100) * v_(0.1)
h₆₄: 310.00 kJ/kg

julia> u_(400) - T_(300) * s_(1.0)
a₆₄: 100.00 kJ/kg

julia> (P_(100) * v_(0.1)) / (R_(0.2) * T_(500))
Z₆₄: 0.10000julia> ve(1500u"km/hr") / cs(1200u"km/hr")
Ma₆₄: 1.2500julia> cp(5) / cv(4)
γ₆₄: 1.2500

In this example, the operation u + P * v resulted in an enthalpy, h. Moreover, by definition, u - T * s --> a, and (P * v) / (R * T) --> Z. Moreover, a Mach number Ma of 1.25 is obtained by dividing the velocity of 1500 km/h by the sound speed of 1200 km/h, and the classic ratio cp / cv --> γ.

Automatic re-basing

EngThermBase.jl encodes the following

  • Thermodynamic bases
    • MA (mass),
    • MO (molar),
    • SY (system, or extensive), and
    • DT (rate);

According to theory, the first two are intensive, while the others, extensive. Here's examples of what can be done computationally:

julia> sp_int_energy = u_(300)
u₆₄: 300.00 kJ/kg

julia> syst_mass = m_(3.0u"kg")
m₆₄: 3.0000 kg

julia> syst_energy = sp_int_energy * syst_mass
U₆₄: 900.00 kJ

julia> pars = [ syst_mass, sp_int_energy, syst_energy ]
3-element Vector{BProperty{Float64, EX}}:
 m₆₄: 3.0000 kg
 u₆₄: 300.00 kJ/kg
 U₆₄: 900.00 kJ

julia> intensive_pars = [ p for p in pars if baseof(p) <: IntBase ]
1-element Vector{u_amt{Float64, EX, MA}}:
 u₆₄: 300.00 kJ/kg

julia> extensive_pars = [ p for p in pars if baseof(p) <: ExtBase ]
2-element Vector{BProperty{Float64, EX, SY}}:
 m₆₄: 3.0000 kg
 U₆₄: 900.00 kJ

It is worth noting that an automatic change of base took place in the product that defined the syst_energy, when a specific internal energy amount was multiplied by a system mass amount, EngThermBase.jl returned a system (based) internal energy amount, in kJ:

julia> typeof(syst_energy)
u_amt{Float64, EX, SY}

Abstract Type Hierarchy

EngThermBase.jl conceptual abstract types have 4 (four) branches placed under the top-most type AbstractTherm:

julia> using TypeTree

julia> subtypes(AbstractTherm)
4-element Vector{Any}:

The AMOUNTS are the tagged quantities and are already introduced above. The other branches expand like the following:

julia> print.(TypeTree.tt(BASES));
 ├─ ExactBase
    ├─ EX
    └─ MM
 └─ ThermBase
     ├─ ExtBase
        ├─ DT
        └─ SY
     └─ IntBase
         ├─ MA
         └─ MO

julia> print.(TypeTree.tt(COMBOS));
 ├─ PropPair{𝗽, 𝘅} where [...]
    ├─ ChFPair{𝗽, 𝘅}
    └─ EoSPair{𝗽, 𝘅}
        ├─ PvPair{𝕡, 𝕩}
        ├─ TPPair{𝕡, 𝕩}
        └─ TvPair{𝕡, 𝕩}
 ├─ PropQuad{𝗽, 𝘅}
 └─ PropTrio{𝗽, 𝘅}
     └─ TPxTrio{𝕡, 𝕩}

julia> print.(TypeTree.tt(MODELS));
 ├─ Heat{𝗽, 𝘅} where [...]
    ├─ BivarHeat{𝗽, 𝘅, 𝗯}
    ├─ ConstHeat{𝗽, 𝘅, 𝗯}
    ├─ GenerHeat{𝗽, 𝘅, 𝗯}
    └─ UnvarHeat{𝗽, 𝘅, 𝗯}
 ├─ Medium{𝗽, 𝘅}
    └─ Substance{𝗽, 𝘅}
 └─ System{𝗽, 𝘅}
     └─ Scope{𝗽, 𝘅}
         ├─ Mixtures{𝗽, 𝘅}
            ├─ Reactiv{𝗽, 𝘅}
            └─ Unreact{𝗽, 𝘅}
         └─ PureSubs{𝗽, 𝘅}

The abstract type hyerarchy provides hooks for thermodynamic models of heat capacity, pure substance (by equation of state, or EoS), mixtures, etc... such as the IdealGasLib.jl.

For additional information and examples, please refer to the package's documentation.


Prof. C. Naaktgeboren, PhD. Lattes.

Federal University of Technology, Paraná (site), Guarapuava Campus.

NaaktgeborenC <dot!> PhD {at!} gmail [dot!] com


This project is licensed under the MIT license.


How to cite this project:

  author       = {C. Naaktgeboren},
  title        = {{EngThermBase.jl} -- Engineering Thermodynamics infrastructure in Julia},
  howpublished = {Online},
  month        = {August},
  year         = {2023},
  journal      = {GitHub repository},
  publisher    = {GitHub},
  url          = {https://github.com/JEngTherm/EngThermBase.jl},
  note         = {release 0.4.4 of 24-03-11},