This package is in development
UntifulDual implements a dual number that can hold physical quantities (i.e. numbers with physical dimensions and units) building upon Unitful. An UnitDual number is created from physical quantities and the partial derivatives with respect to every other UnitDual number in the computation (in the same order for every UnitDual number):
julia> using UnitfulDual
julia> using Unitful
julia> r₁ = 1.0u"mol/L"
1.0 mol L^-1
julia> r₂ = 1.0u"mol/L"
1.0 mol L^-1
julia> k = 0.1u"L/mol/s"
0.1 L mol^-1 s^-1
julia> dr₁ = UnitDual(r₁, 1.0, zero(r₁/r₂), zero(r₁/k))
1.0 mol L^-1 {1.0, 0.0, 0.0 mol^2 s L^-2}
julia> dr₂ = UnitDual(r₂, zero(r₂/r₁), 1.0, zero(r₂/k))
1.0 mol L^-1 {0.0, 1.0, 0.0 mol^2 s L^-2}
julia> dk = UnitDual(k, zero(k/r₁), zero(k/r₂), 1.0)
0.1 L mol^-1 s^-1 {0.0 L^2 mol^-2 s^-1, 0.0 L^2 mol^-2 s^-1, 1.0}Note that, even though the partial derivative of r₁ with respect to k is just 0, we need to assign it the correct physical dimensions associated to the expression
julia> rate = dr₁*dr₂*dk
0.1 mol L^-1 s^-1 {0.1 s^-1, 0.1 s^-1, 1.0 mol^2 L^-2}You can also assign names to the partial derivatives and use different order for different dual numbers:
julia> dr₁ = UnitDual(r₁, r₁ = 1.0, r₂ = zero(r₁/r₂), k = zero(r₁/k))
1.0 mol L^-1 {r₁ = 1.0, r₂ = 0.0, k = 0.0 mol^2 s L^-2}
julia> dr₂ = UnitDual(r₂, r₂ = 1.0, r₁ = zero(r₂/r₁), k = zero(r₂/k))
1.0 mol L^-1 {r₂ = 1.0, r₁ = 0.0, k = 0.0 mol^2 s L^-2}
julia> dk = UnitDual(k, k = 1.0, r₁ = zero(k/r₁), r₂ = zero(k/r₂))
0.1 L mol^-1 s^-1 {k = 1.0, r₁ = 0.0 L^2 mol^-2 s^-1, r₂ = 0.0 L^2 mol^-2 s^-1}
julia> rate = dr₁*dr₂*dk
0.1 mol L^-1 s^-1 {r₁ = 0.1 s^-1, r₂ = 0.1 s^-1, k = 1.0 mol^2 L^-2}
julia> partials(rate).k
1.0 mol^2 L^-2UnitfulDual is strongly inspired by ForwardDiff. The main difference is that the tuple of partial derivatives in an UnitDual number is heterogeneous, as different physical dimensions result in different types. Thus, the implementation is simply:
struct UnitPartials{N, TT <: Union{Tuple, NamedTuple}}
partials::TT
end
struct UnitDual{DT <: Number, PT <: UnitPartials}
value::DT
partials::PT
endAs can be seen, the heterogenity of partial derivatives propagates to the UnitDual object. This means that a homogeneous container of UnitDual numbers will cause type instability (see example below) so it is better to store them in tuples or using an SOA approach:
julia> @inbounds get_first(v) = v[1]
get_first (generic function with 1 method)
@code_warntype get_first([rate, dk])
Body::UnitDual
1 ─ %1 = (Base.arrayref)(true, v, 1)::UnitDual
└── return %1Many features such as tagging are not implemented for UnitDual numbers yet. This package is being developed primarily to support calculation of sensitivities in scientific models that already make use of Unitful. Application to automatic differentiation to compute gradients, Jacobians, etc will probably require removing physical units when extracting partials to avoid type instability issues.