I’ve just put together a couple of simple packages for dealing with model parameters, as I deal with a lot of them… but I find myself using them all the time now, so maybe they are generally useful. But Julia lets you do basically anything, and I’m not sure yet if they are totally sane. So feedback would be great before I use them everywhere.
This is a working example of both - a declaration of three interchangable types with parameters for the three modes of an arrhenius type temperature function:
using Parameters, Unitful, Mixers, MetaParameters
@metaparam describe ""
@metaparam paramrange [300.0u"K", 400.0u"K"]
abstract type AbstractTempCorr end
@mix tbase{T} begin
reftemp::T = 310.0u"K" | [273.0u"K", 325.0u"K"] | "Reference temperature for all rate parameters"
arrtemp::T = 2000.0u"K" | [200.0u"K", 4000.0u"K"] | "Arrhenius temperature"
end
@mix tlow{T} begin
lowerbound::T = 280.0u"K" | [273.0u"K", 325.0u"K"] | "Lower boundary of tolerance range"
arrlower::T = 20000.0u"K" | [2000.0u"K", 40000.0u"K"] | "Arrhenius temperature for lower boundary"
end
@mix tup{T} begin
upperbound::T = 315.0u"K" | [273.0u"K", 325.0u"K"] | "Upper boundary of tolerance range"
arrupper::T = 70000.0u"K" | [7000.0u"K", 140000.0u"K"] | "Arrhenius temperature for upper boundary"
end
@tbase @describe @paramrange @with_kw mutable struct TempCorr{} <: AbstractTempCorr end
@tbase @tlow @describe @paramrange @with_kw mutable struct TempCorrLower{} <: AbstractTempCorr end
@tbase @tlow @tup @describe @paramrange @with_kw mutable struct TempCorrLowerUpper{} <: AbstractTempCorr end
julia> fieldnames(TempCorrLower)
4-element Array{Symbol,1}:
:reftemp
:arrtemp
:lowerbound
:arrlower
julia> fieldnames(TempCorrLowerUpper)
6-element Array{Symbol,1}:
:reftemp
:arrtemp
:lowerbound
:arrlower
:upperbound
:arrupper
julia> describe(TempCorrLowerUpper(), :arrupper)
"Arrhenius temperature for upper boundary"
julia> paramrange(TempCorrLowerUpper(), :arrupper)
2-element Array{Unitful.Quantity{Float64,Unitful.Dimensions{(Unitful.Dimension{:Temperature}(1//1),)},Unitful.FreeUnits{(Unitful.Unit{:Kelvin,Unitful.Dimensions{(Unitful.Dimension{:Temperature}(1//1),)}}(0, 1//1),),Unitful.Dimensions{(Unitful.Dimension{:Temperature}(1//1),)}}},1}:
7000.0 K
140000.0 K
It looks crazy with that many macros. But everything has a range, description and default value on the same line without duplication or much boilerplate besides the macros. Mixers.jl could accept macros and take the union as it does parametric types, and even those would be DRY! But maybe that is even crazier than using that many macros in the first place.
Mixers.jl provides the composable mixins. They include both parametric types and fields so you can just chain them together arbitrarily to add fields to a struct.
Mostly this is intended to reduce code duplication in type composition and provide shared data layout decoupled from the type hierarchy. Including parametric types in the mixin makes it much more succinct and general than just a @def macro.
MetaParameters.jl adds the “metaparameters” to struct fields that are retrieved with a function of the same name as the custom macro. I used to build big structs to pass these kind of things around but they are only occasionally needed and are immutable, so why clutter my models with them? It’s not registered yet, I’m not sure if it should be…