Integration with Unitful.jl
GalaxyProfiles.jl provides integration with Unitful.jl and UnitfulAstro.jl through Requires.jl. When both Unitful and UnitfulAstro are imported, GalaxyProfiles.jl/src/units.jl will be included, which will overload many of the default methods and constructors to work with Unitful quantities.
Type Constructors
Constructors for our defined types will accept Unitful quantities with correct dimensionality. So as to not explicitly depend on Unitful and UnitfulAstro, units are never stored in types. Instead, they are converted to default units and stored in the types as Reals.
julia> import Unitful as ujulia> import UnitfulAstro as uajulia> using GalaxyProfiles[ Info: Correct dimensions, converted and stored internally as Float64.julia> d = GeneralIsothermal(1.0 * ua.Msun / ua.kpc^3, 1.0 * ua.kpc, 2.5)GeneralIsothermal{Float64}(1.0, 1.0, 2.5)[ Info: Incorrect dimensions on first argument, will error.julia> d = GeneralIsothermal(1.0 * ua.Msun / ua.kpc^2, 1.0 * ua.kpc, 2.5)ERROR: MethodError: no method matching GeneralIsothermal(::Unitful.Quantity{Float64, 𝐌 𝐋^-2, Unitful.FreeUnits{(kpc^-2, M⊙), 𝐌 𝐋^-2, nothing}}, ::Unitful.Quantity{Float64, 𝐋, Unitful.FreeUnits{(kpc,), 𝐋, nothing}}, ::Float64) The type `GeneralIsothermal` exists, but no method is defined for this combination of argument types when trying to construct it. Closest candidates are: GeneralIsothermal(::Union{Unitful.Quantity{T, 𝐌 𝐋^-3, U}, Unitful.Level{L, S, Unitful.Quantity{T, 𝐌 𝐋^-3, U}} where {L, S}} where {T, U}, ::Union{Unitful.Quantity{T, 𝐋, U}, Unitful.Level{L, S, Unitful.Quantity{T, 𝐋, U}} where {L, S}} where {T, U}, ::Real) @ GalaxyProfilesUnitfulExt ~/work/GalaxyProfiles.jl/GalaxyProfiles.jl/ext/GalaxyProfilesUnitfulExt.jl:83 GeneralIsothermal(::T, ::T, ::T) where T<:Real @ GalaxyProfiles ~/work/GalaxyProfiles.jl/GalaxyProfiles.jl/src/densities/general_isothermal.jl:25 GeneralIsothermal(::Real, ::Real, ::Real) @ GalaxyProfiles ~/work/GalaxyProfiles.jl/GalaxyProfiles.jl/src/densities/general_isothermal.jl:29 ...
Default Units
The default units used by type constructors and methods are defined in the submodule GalaxyProfiles.defaultunits (found at the top of src/units.jl) and are generally named after the quantity that they represent. These include
- mass:
UnitfulAstro.Msun - ∇mass:
UnitfulAstro.Msun / UnitfulAstro.kpc - density:
UnitfulAstro.Msun / UnitfulAstro.kpc^3 - surfacedensity:
UnitfulAstro.Msun / UnitfulAstro.kpc^2 - length:
UnitfulAstro.kpc - velocity:
Unitful.km / Unitful.s - time:
Unitful.yr - Φunit:
Unitful.km^2 / Unitful.s^2 - ∇Φunit:
Unitful.km^2 / Unitful.s^2 / UnitfulAstro.kpc - ∇∇Φunit:
Unitful.km^2 / Unitful.s^2 / UnitfulAstro.kpc^2
Accompanying dimensions for proper dispatch are also defined in src/units.jl, including SurfaceDensity, ∇ρdimension, Φdimension, ∇∇Φdimension, ∇mdimension. These function like so:
[ Info: Get Unitful package extension, if on version of Julia that supports itjulia> if isdefined(Base, :get_extension) ext = Base.get_extension(GalaxyProfiles, :GalaxyProfilesUnitfulExt) else ext = GalaxyProfiles.GalaxyProfilesUnitfulExt endGalaxyProfilesUnitfulExtjulia> 1 * ua.Msun / ua.pc^2 isa ext.SurfaceDensitytruejulia> ua.Msun / ua.pc^2 isa ext.SurfaceDensityUnitstrue
See also the documentation for Unitful.@derived_dimension.
Methods
Methods defined on Real inputs are not overloaded to return Unitful quantities. This is to ensure identical behaviors of these methods regardless of whether Unitful integration is active or not.
julia> ρ(GeneralIsothermal(1.0 * ua.Msun / ua.kpc^3, 1.0 * ua.kpc, 2.5), 1.0)1.0
It is therefore safest when constructing types with Unitful inputs to also call methods with Unitful inputs. Otherwise, you can accidentally end up mixing units in ways that are hard to reason about. See the section on units warning.
Methods that only take a GalaxyProfiles.AbstractMassProfile instance as input generally have one additional method allowing for a conversion of that quantity. For example,
julia> d = ExponentialDisk(1 * ua.Msun / ua.pc^2, 100 * ua.pc)ExponentialDisk{Rational{Int64}}(1000000//1, 1//10)julia> Mtot(d) # default method62831.853071795864julia> Mtot(ua.Msun, d)62831.853071795864 M⊙julia> Mtot(u.kg, d)1.2493547684220855e35 kg
Methods that take a GalaxyProfiles.AbstractMassProfile and a Real will have three additional methods allowing for Unitful integrations. For example,
julia> d = GeneralIsothermal(1.0 * ua.Msun / ua.kpc^3, 1.0 * ua.kpc, 2.5)GeneralIsothermal{Float64}(1.0, 1.0, 2.5)julia> ρ(d, 1.0) # default method1.0julia> ρ(ua.Msun/ua.pc^3, d, 1.0) # for real argument and a unit conversion of result1.0e-9 M⊙ pc^-3julia> ρ(d, 1.0*ua.kpc) # for a Unitful argument1.0 M⊙ kpc^-3julia> ρ(ua.Msun/ua.pc^3, d, 1.0*ua.kpc) # for Unitful argument and result conversion1.0e-9 M⊙ pc^-3
Units Warning
For example, say that I define the scale radius in parsecs when constructing a type. This will be converted and stored in the type as a plain float type with an implicit unit of kpc (see above section on default units). If I then call a method with a plain float radius argument that I expect to be in the same units as the scale radius I provided, the result will be in the wrong units. After constructing a type with Unitful quantities, all methods called on that type with plain float inputs must be in the default units that are expected. When calling methods with basic numeric inputs, you are responsible for managing the units properly. A solution is to provide units on inputs when calling methods to ensure all units are properly converted. Calls like this will return Unitful quantities.
julia> @info "Unitful arguments are converted to basic numeric types."[ Info: Unitful arguments are converted to basic numeric types.julia> d = GeneralIsothermal(1.0 * ua.Msun / ua.kpc^3, 1.0 * ua.pc, 2.5)GeneralIsothermal{Float64}(1.0, 0.001, 2.5)julia> rs = 1.0 # I might expect this to be in parsecs, like the scale radius I provided ...1.0julia> ρ(d, rs) # But this assumes that rs is in the default length unit, which is kpc.3.162277660168379e-8julia> rs = 1.0 * ua.pc # The safer way to do this is provide rs with units.1.0 pcjulia> ρ(d, rs)1.0 M⊙ kpc^-3