Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make AbstractQuantity and AbstractDimensions #24

Merged
merged 31 commits into from
Jun 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
1befe74
Make `AbstractQuantity` and `AbstractDimensions`
MilesCranmer Jun 12, 2023
a09c512
Fix docstrings for abstract types
MilesCranmer Jun 12, 2023
645726c
Implement new `quantity` function
MilesCranmer Jun 12, 2023
26355cc
Further extend abstract interface
MilesCranmer Jun 12, 2023
de11481
Remove redundancy in abstract type interface
MilesCranmer Jun 12, 2023
88da738
Make generic constructors for Dimension
MilesCranmer Jun 12, 2023
d3b1081
Merge branch 'additional-utils' into abstract-types-2
MilesCranmer Jun 13, 2023
f38db8e
Merge tag 'v0.4.0' into abstract-types-2
MilesCranmer Jun 14, 2023
41f6257
Change functions user needs to define
MilesCranmer Jun 14, 2023
68941b8
Back to generated version
MilesCranmer Jun 14, 2023
6cf61a6
`getproperty` instead of `getfield` for abstraction
MilesCranmer Jun 15, 2023
7a50e43
Default for `dimension_name` is to return key name
MilesCranmer Jun 15, 2023
12ecee2
Add missing converter to integers
MilesCranmer Jun 15, 2023
c04adc7
Get abstract quantities working with constructors
MilesCranmer Jun 15, 2023
ff844d1
Can now use abstract types without overloading functions
MilesCranmer Jun 15, 2023
6548c65
Be explicit that user needs to define their own `quantity_constructor`
MilesCranmer Jun 15, 2023
3060585
Add docstrings on `quantity_constructor`
MilesCranmer Jun 15, 2023
1e1cb56
Abstract versions of zero, one, oneunit
MilesCranmer Jun 15, 2023
9271cde
Clean up container type
MilesCranmer Jun 15, 2023
0970d71
Clean up container type
MilesCranmer Jun 16, 2023
255ecf8
Disable math directly on `AbstractDimensions`
MilesCranmer Jun 24, 2023
6f00d47
Refactor quantities to parametrize on dimensions
MilesCranmer Jun 24, 2023
f321205
Fix constructor ambiguity issues
MilesCranmer Jun 24, 2023
895c69e
Simplify abstract constructors
MilesCranmer Jun 24, 2023
b583e46
Clean up docs
MilesCranmer Jun 24, 2023
5c7f069
Type conversion tests
MilesCranmer Jun 24, 2023
53a9a33
Print non-reals with parentheses
MilesCranmer Jun 25, 2023
12308de
Fix promotion rules for complex
MilesCranmer Jun 25, 2023
6b93f97
Fix upreferred validation
MilesCranmer Jun 25, 2023
e176d81
Merge branch 'main' into abstract-types-2
MilesCranmer Jun 25, 2023
a365da0
Test custom unit raises error
MilesCranmer Jun 26, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ jobs:
- uses: julia-actions/cache@v1
- uses: julia-actions/julia-buildpkg@v1
- name: "Run tests"
shell: bash
run: |
julia --color=yes --project=. -e 'import Pkg; Pkg.add("Coverage")'
julia --color=yes --threads=auto --check-bounds=yes --depwarn=yes --code-coverage=user --project=. -e 'import Pkg; Pkg.test(coverage=true)'
DQ_TEST_UPREFERRED=true julia --color=yes --threads=auto --check-bounds=yes --depwarn=yes --code-coverage=user --project=. -e 'import Pkg; Pkg.test(coverage=true)'
julia --color=yes --project=. coverage.jl
- name: "Coveralls"
uses: coverallsapp/github-action@v2
Expand Down
3 changes: 2 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ version = "0.4.0"

[deps]
Requires = "ae029012-a4dd-5104-9daa-d747884805df"
Tricks = "410a4b4d-49e4-4fbc-ab6d-cb71b17b3775"

[weakdeps]
Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"
Expand All @@ -19,8 +20,8 @@ julia = "1.6"

[extras]
Ratios = "c84ed2f1-dad5-54f0-aa8e-dbefe2724439"
SaferIntegers = "88634af6-177f-5301-88b8-7819386cfa38"
SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f"
SaferIntegers = "88634af6-177f-5301-88b8-7819386cfa38"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"

Expand Down
22 changes: 16 additions & 6 deletions ext/DynamicQuantitiesUnitfulExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,28 @@ else
import ..Unitful: @u_str
end

# This lets the user override the preferred units:
function unitful_equivalences()
si_units = (length=u"m", mass=u"kg", time=u"s", current=u"A", temperature=u"K", luminosity=u"cd", amount=u"mol")
function get_si_units()
return (length=u"m", mass=u"kg", time=u"s", current=u"A", temperature=u"K", luminosity=u"cd", amount=u"mol")
end

function validate_upreferred()
si_units = get_si_units()
for k in keys(si_units)
if Unitful.upreferred(si_units[k]) !== si_units[k]
error("Found custom `Unitful.preferunits`. This is not supported when interfacing Unitful and DynamicQuantities: you must leave the default `Unitful.upreferred`, which is the SI base units.")
end
end
return true
end

function unitful_equivalences()
si_units = get_si_units()
return NamedTuple((k => si_units[k] for k in keys(si_units)))
end

Base.convert(::Type{Unitful.Quantity}, x::DynamicQuantities.Quantity) =
let
validate_upreferred()
cumulator = DynamicQuantities.ustrip(x)
dims = DynamicQuantities.dimension(x)
equiv = unitful_equivalences()
Expand All @@ -35,16 +44,17 @@ Base.convert(::Type{Unitful.Quantity}, x::DynamicQuantities.Quantity) =
end

Base.convert(::Type{DynamicQuantities.Quantity}, x::Unitful.Quantity{T}) where {T} = convert(DynamicQuantities.Quantity{T,DynamicQuantities.DEFAULT_DIM_TYPE}, x)
Base.convert(::Type{DynamicQuantities.Quantity{T,R}}, x::Unitful.Quantity) where {T,R} =
Base.convert(::Type{DynamicQuantities.Quantity{T,D}}, x::Unitful.Quantity) where {T,R,D<:DynamicQuantities.AbstractDimensions{R}} =
let
value = Unitful.ustrip(Unitful.upreferred(x))
dimension = convert(DynamicQuantities.Dimensions{R}, Unitful.dimension(x))
dimension = convert(D, Unitful.dimension(x))
return DynamicQuantities.Quantity(convert(T, value), dimension)
end

Base.convert(::Type{DynamicQuantities.Dimensions}, d::Unitful.Dimensions) = convert(DynamicQuantities.Dimensions{DynamicQuantities.DEFAULT_DIM_TYPE}, d)
Base.convert(::Type{DynamicQuantities.Dimensions}, d::Unitful.Dimensions) = convert(DynamicQuantities.DEFAULT_DIM_TYPE, d)
Base.convert(::Type{DynamicQuantities.Dimensions{R}}, d::Unitful.Dimensions{D}) where {R,D} =
let
validate_upreferred()
cumulator = DynamicQuantities.Dimensions{R}()
for dim in D
dim_symbol = _map_dim_name_to_dynamic_units(typeof(dim))
Expand Down
1 change: 1 addition & 0 deletions src/DynamicQuantities.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module DynamicQuantities

export AbstractQuantity, AbstractDimensions
export Quantity, Dimensions, DimensionError, ustrip, dimension, valid
export ulength, umass, utime, ucurrent, utemperature, uluminosity, uamount
export uparse, @u_str
Expand Down
1 change: 1 addition & 0 deletions src/fixed_rational.jl
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Base.convert(::Type{F}, x::Rational) where {F<:FixedRational} = F(x)
Base.convert(::Type{Rational{R}}, x::F) where {R,F<:FixedRational} = Rational{R}(x.num, denom(F))
Base.convert(::Type{Rational}, x::F) where {F<:FixedRational} = Rational{eltype(F)}(x.num, denom(F))
Base.convert(::Type{AF}, x::F) where {AF<:AbstractFloat,F<:FixedRational} = convert(AF, x.num) / convert(AF, denom(F))
Base.convert(::Type{I}, x::F) where {I<:Integer,F<:FixedRational} = convert(I, convert(Rational, x))
Base.round(::Type{T}, x::F) where {T,F<:FixedRational} = div(convert(T, x.num), convert(T, denom(F)), RoundNearest)
Base.promote(x::Integer, y::F) where {F<:FixedRational} = (F(x), y)
Base.promote(x::F, y::Integer) where {F<:FixedRational} = reverse(promote(y, x))
Expand Down
76 changes: 39 additions & 37 deletions src/math.jl
Original file line number Diff line number Diff line change
@@ -1,44 +1,46 @@
Base.:*(l::Dimensions, r::Dimensions) = @map_dimensions(+, l, r)
Base.:*(l::Quantity, r::Quantity) = Quantity(l.value * r.value, l.dimensions * r.dimensions)
Base.:*(l::Quantity, r::Dimensions) = Quantity(l.value, l.dimensions * r)
Base.:*(l::Dimensions, r::Quantity) = Quantity(r.value, l * r.dimensions)
Base.:*(l::Quantity, r) = Quantity(l.value * r, l.dimensions)
Base.:*(l, r::Quantity) = Quantity(l * r.value, r.dimensions)
Base.:*(l::Dimensions, r) = Quantity(r, l)
Base.:*(l, r::Dimensions) = Quantity(l, r)
Base.:*(l::AbstractDimensions, r::AbstractDimensions) = map_dimensions(+, l, r)
Base.:*(l::AbstractQuantity, r::AbstractQuantity) = new_quantity(typeof(l), ustrip(l) * ustrip(r), dimension(l) * dimension(r))
Base.:*(l::AbstractQuantity, r::AbstractDimensions) = new_quantity(typeof(l), ustrip(l), dimension(l) * r)
Base.:*(l::AbstractDimensions, r::AbstractQuantity) = new_quantity(typeof(r), ustrip(r), l * dimension(r))
Base.:*(l::AbstractQuantity, r) = new_quantity(typeof(l), ustrip(l) * r, dimension(l))
Base.:*(l, r::AbstractQuantity) = new_quantity(typeof(r), l * ustrip(r), dimension(r))
Base.:*(l::AbstractDimensions, r) = error("Please use an `AbstractQuantity` for multiplication. You used multiplication on types: $(typeof(l)) and $(typeof(r)).")
Base.:*(l, r::AbstractDimensions) = error("Please use an `AbstractQuantity` for multiplication. You used multiplication on types: $(typeof(l)) and $(typeof(r)).")

Base.:/(l::Dimensions, r::Dimensions) = @map_dimensions(-, l, r)
Base.:/(l::Quantity, r::Quantity) = Quantity(l.value / r.value, l.dimensions / r.dimensions)
Base.:/(l::Quantity, r::Dimensions) = Quantity(l.value, l.dimensions / r)
Base.:/(l::Dimensions, r::Quantity) = Quantity(inv(r.value), l / r.dimensions)
Base.:/(l::Quantity, r) = Quantity(l.value / r, l.dimensions)
Base.:/(l, r::Quantity) = l * inv(r)
Base.:/(l::Dimensions, r) = Quantity(inv(r), l)
Base.:/(l, r::Dimensions) = Quantity(l, inv(r))
Base.:/(l::AbstractDimensions, r::AbstractDimensions) = map_dimensions(-, l, r)
Base.:/(l::AbstractQuantity, r::AbstractQuantity) = new_quantity(typeof(l), ustrip(l) / ustrip(r), dimension(l) / dimension(r))
Base.:/(l::AbstractQuantity, r::AbstractDimensions) = new_quantity(typeof(l), ustrip(l), dimension(l) / r)
Base.:/(l::AbstractDimensions, r::AbstractQuantity) = new_quantity(typeof(r), inv(ustrip(r)), l / dimension(r))
Base.:/(l::AbstractQuantity, r) = new_quantity(typeof(l), ustrip(l) / r, dimension(l))
Base.:/(l, r::AbstractQuantity) = l * inv(r)
Base.:/(l::AbstractDimensions, r) = error("Please use an `AbstractQuantity` for division. You used division on types: $(typeof(l)) and $(typeof(r)).")
Base.:/(l, r::AbstractDimensions) = error("Please use an `AbstractQuantity` for division. You used division on types: $(typeof(l)) and $(typeof(r)).")

Base.:+(l::Quantity, r::Quantity) = dimension(l) == dimension(r) ? Quantity(l.value + r.value, l.dimensions) : throw(DimensionError(l, r))
Base.:-(l::Quantity) = Quantity(-l.value, l.dimensions)
Base.:-(l::Quantity, r::Quantity) = l + (-r)
Base.:+(l::AbstractQuantity, r::AbstractQuantity) = dimension(l) == dimension(r) ? new_quantity(typeof(l), ustrip(l) + ustrip(r), dimension(l)) : throw(DimensionError(l, r))
Base.:-(l::AbstractQuantity) = new_quantity(typeof(l), -ustrip(l), dimension(l))
Base.:-(l::AbstractQuantity, r::AbstractQuantity) = l + (-r)

Base.:+(l::Quantity, r) = dimension(l) == dimension(r) ? Quantity(l.value + r, l.dimensions) : throw(DimensionError(l, r))
Base.:+(l, r::Quantity) = dimension(l) == dimension(r) ? Quantity(l + r.value, r.dimensions) : throw(DimensionError(l, r))
Base.:-(l::Quantity, r) = l + (-r)
Base.:-(l, r::Quantity) = l + (-r)
Base.:+(l::AbstractQuantity, r) = iszero(dimension(l)) ? new_quantity(typeof(l), ustrip(l) + r, dimension(l)) : throw(DimensionError(l, r))
Base.:+(l, r::AbstractQuantity) = iszero(dimension(r)) ? new_quantity(typeof(r), l + ustrip(r), dimension(r)) : throw(DimensionError(l, r))
Base.:-(l::AbstractQuantity, r) = l + (-r)
Base.:-(l, r::AbstractQuantity) = l + (-r)

_pow(l::Dimensions, r) = @map_dimensions(Base.Fix1(*, r), l)
_pow(l::Quantity{T}, r) where {T} = Quantity(l.value^r, _pow(l.dimensions, r))
_pow_as_T(l::Quantity{T}, r) where {T} = Quantity(l.value^convert(T, r), _pow(l.dimensions, r))
Base.:^(l::Dimensions{R}, r::Integer) where {R} = _pow(l, r)
Base.:^(l::Dimensions{R}, r::Number) where {R} = _pow(l, tryrationalize(R, r))
Base.:^(l::Quantity{T,R}, r::Integer) where {T,R} = _pow(l, r)
Base.:^(l::Quantity{T,R}, r::Number) where {T,R} = _pow_as_T(l, tryrationalize(R, r))
# We don't promote on the dimension types:
_pow(l::AbstractDimensions{R}, r::R) where {R} = map_dimensions(Base.Fix1(*, r), l)
Base.:^(l::AbstractDimensions{R}, r::Number) where {R} = _pow(l, tryrationalize(R, r))
Base.:^(l::AbstractQuantity{T,D}, r::Integer) where {T,R,D<:AbstractDimensions{R}} = new_quantity(typeof(l), ustrip(l)^r, dimension(l)^r)
Base.:^(l::AbstractQuantity{T,D}, r::Number) where {T,R,D<:AbstractDimensions{R}} =
let dim_pow = tryrationalize(R, r), val_pow = convert(T, dim_pow)
# Need to ensure we take the numerical power by the rationalized quantity:
return new_quantity(typeof(l), ustrip(l)^val_pow, dimension(l)^dim_pow)
end

Base.inv(d::Dimensions) = @map_dimensions(-, d)
Base.inv(q::Quantity) = Quantity(inv(q.value), inv(q.dimensions))
Base.inv(d::AbstractDimensions) = map_dimensions(-, d)
Base.inv(q::AbstractQuantity) = new_quantity(typeof(q), inv(ustrip(q)), inv(dimension(q)))

Base.sqrt(d::Dimensions{R}) where {R} = d^inv(convert(R, 2))
Base.sqrt(q::Quantity) = Quantity(sqrt(q.value), sqrt(q.dimensions))
Base.cbrt(d::Dimensions{R}) where {R} = d^inv(convert(R, 3))
Base.cbrt(q::Quantity) = Quantity(cbrt(q.value), cbrt(q.dimensions))
Base.sqrt(d::AbstractDimensions{R}) where {R} = d^inv(convert(R, 2))
Base.sqrt(q::AbstractQuantity) = new_quantity(typeof(q), sqrt(ustrip(q)), sqrt(dimension(q)))
Base.cbrt(d::AbstractDimensions{R}) where {R} = d^inv(convert(R, 3))
Base.cbrt(q::AbstractQuantity) = new_quantity(typeof(q), cbrt(ustrip(q)), cbrt(dimension(q)))

Base.abs(q::Quantity) = Quantity(abs(q.value), q.dimensions)
Base.abs(q::AbstractQuantity) = new_quantity(typeof(q), abs(ustrip(q)), dimension(q))
91 changes: 43 additions & 48 deletions src/types.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
const DEFAULT_DIM_TYPE = FixedRational{Int32, 2^4 * 3^2 * 5^2 * 7}
import Tricks: static_fieldnames, static_fieldtypes

const DEFAULT_DIM_BASE_TYPE = FixedRational{Int32,2^4 * 3^2 * 5^2 * 7}
const DEFAULT_VALUE_TYPE = Float64

abstract type AbstractQuantity{T,D} end
abstract type AbstractDimensions{R} end

"""
Dimensions{R}

Expand All @@ -22,54 +27,33 @@ which is by default a rational number.

# Constructors

- `Dimensions(args...)`: Pass all the dimensions as arguments. `R` is set to `DEFAULT_DIM_TYPE`.
- `Dimensions(; kws...)`: Pass a subset of dimensions as keyword arguments. `R` is set to `DEFAULT_DIM_TYPE`.
- `Dimensions(args...)`: Pass all the dimensions as arguments. `R` is set to `DEFAULT_DIM_BASE_TYPE`.
- `Dimensions(; kws...)`: Pass a subset of dimensions as keyword arguments. `R` is set to `DEFAULT_DIM_BASE_TYPE`.
- `Dimensions(::Type{R}; kws...)` or `Dimensions{R}(; kws...)`: Pass a subset of dimensions as keyword arguments, with the output type set to `Dimensions{R}`.
- `Dimensions{R}(args...)`: Pass all the dimensions as arguments, with the output type set to `Dimensions{R}`.
- `Dimensions{R}()`: Create a dimensionless object typed as `Dimensions{R}`.
- `Dimensions{R}(d::Dimensions)`: Copy the dimensions from another `Dimensions` object, with the output type set to `Dimensions{R}`.

"""
struct Dimensions{R <: Real}
struct Dimensions{R<:Real} <: AbstractDimensions{R}
length::R
mass::R
time::R
current::R
temperature::R
luminosity::R
amount::R

function Dimensions(length::_R,
mass::_R,
time::_R,
current::_R,
temperature::_R,
luminosity::_R,
amount::_R) where {_R<:Real}
new{_R}(length, mass, time, current, temperature, luminosity, amount)
end
Dimensions(; kws...) = Dimensions(DEFAULT_DIM_TYPE; kws...)
Dimensions(::Type{_R}; kws...) where {_R} = Dimensions(
tryrationalize(_R, get(kws, :length, zero(_R))),
tryrationalize(_R, get(kws, :mass, zero(_R))),
tryrationalize(_R, get(kws, :time, zero(_R))),
tryrationalize(_R, get(kws, :current, zero(_R))),
tryrationalize(_R, get(kws, :temperature, zero(_R))),
tryrationalize(_R, get(kws, :luminosity, zero(_R))),
tryrationalize(_R, get(kws, :amount, zero(_R))),
)
Dimensions{_R}(; kws...) where {_R} = Dimensions(_R; kws...)
Dimensions{_R}(args...) where {_R} = Dimensions(Base.Fix1(convert, _R).(args)...)
Dimensions{_R}(d::Dimensions) where {_R} = Dimensions{_R}(d.length, d.mass, d.time, d.current, d.temperature, d.luminosity, d.amount)
end

const DIMENSION_NAMES = Base.fieldnames(Dimensions)
const DIMENSION_SYNONYMS = (:m, :kg, :s, :A, :K, :cd, :mol)
const SYNONYM_MAPPING = NamedTuple(DIMENSION_NAMES .=> DIMENSION_SYNONYMS)
(::Type{D})(::Type{R}; kws...) where {R,D<:AbstractDimensions} = constructor_of(D){R}((tryrationalize(R, get(kws, k, zero(R))) for k in static_fieldnames(D))...)
(::Type{D})(; kws...) where {R,D<:AbstractDimensions{R}} = constructor_of(D)(R; kws...)
(::Type{D})(; kws...) where {D<:AbstractDimensions} = D(DEFAULT_DIM_BASE_TYPE; kws...)
(::Type{D})(d::AbstractDimensions) where {R,D<:AbstractDimensions{R}} = D((getproperty(d, k) for k in static_fieldnames(D))...)

const DEFAULT_DIM_TYPE = Dimensions{DEFAULT_DIM_BASE_TYPE}

"""
Quantity{T,R}
Quantity{T,D}

Physical quantity with value `value` of type `T` and dimensions `dimensions` of type `Dimensions{R}`.
Physical quantity with value `value` of type `T` and dimensions `dimensions` of type `D`.
For example, the velocity of an object with mass 1 kg and velocity
2 m/s is `Quantity(2, mass=1, length=1, time=-1)`.
You should access these fields with `ustrip(q)`, and `dimensions(q)`.
Expand All @@ -83,26 +67,37 @@ dimensions according to the operation.
# Fields

- `value::T`: value of the quantity of some type `T`. Access with `ustrip(::Quantity)`
- `dimensions::Dimensions{R}`: dimensions of the quantity with dimension type `R`. Access with `dimension(::Quantity)`
- `dimensions::D`: dimensions of the quantity. Access with `dimension(::Quantity)`

# Constructors

- `Quantity(x; kws...)`: Construct a quantity with value `x` and dimensions given by the keyword arguments. The value type is inferred from `x`. `R` is set to `DEFAULT_DIM_TYPE`.
- `Quantity(x, ::Type{R}; kws...)`: Construct a quantity with value `x`. The dimensions parametric type is set to `R`.
- `Quantity(x, d::Dimensions{R})`: Construct a quantity with value `x` and dimensions `d`.
- `Quantity{T}(q::Quantity)`: Construct a quantity with value `q.value` and dimensions `q.dimensions`, but with value type converted to `T`.
- `Quantity{T,R}(q::Quantity)`: Construct a quantity with value `q.value` and dimensions `q.dimensions`, but with value type converted to `T` and dimensions parametric type set to `R`.
- `Quantity(x; kws...)`: Construct a quantity with value `x` and dimensions given by the keyword arguments. The value
type is inferred from `x`. `R` is set to `DEFAULT_DIM_TYPE`.
- `Quantity(x, ::Type{D}; kws...)`: Construct a quantity with value `x` with dimensions given by the keyword arguments,
and the dimensions type set to `D`.
- `Quantity(x, d::D)`: Construct a quantity with value `x` and dimensions `d` of type `D`.
- `Quantity{T}(...)`: As above, but converting the value to type `T`. You may also pass a `Quantity` as input.
- `Quantity{T,D}(...)`: As above, but converting the value to type `T` and dimensions to `D`. You may also pass a
`Quantity` as input.
"""
struct Quantity{T, R}
struct Quantity{T,D<:AbstractDimensions} <: AbstractQuantity{T,D}
value::T
dimensions::Dimensions{R}

Quantity(x; kws...) = new{typeof(x), DEFAULT_DIM_TYPE}(x, Dimensions(; kws...))
Quantity(x, ::Type{_R}; kws...) where {_R} = new{typeof(x), _R}(x, Dimensions(_R; kws...))
Quantity(x, d::Dimensions{_R}) where {_R} = new{typeof(x), _R}(x, d)
Quantity{T}(q::Quantity) where {T} = Quantity(convert(T, q.value), dimension(q))
Quantity{T,R}(q::Quantity) where {T,R} = Quantity(convert(T, q.value), Dimensions{R}(dimension(q)))
dimensions::D
end
(::Type{Q})(x::T, ::Type{D}; kws...) where {D<:AbstractDimensions,T,T2,Q<:AbstractQuantity{T2}} = constructor_of(Q)(convert(T2, x), D(; kws...))
(::Type{Q})(x, ::Type{D}; kws...) where {D<:AbstractDimensions,Q<:AbstractQuantity} = constructor_of(Q)(x, D(; kws...))
(::Type{Q})(x::T; kws...) where {T,T2,Q<:AbstractQuantity{T2}} = constructor_of(Q)(convert(T2, x), dim_type(Q)(; kws...))
(::Type{Q})(x; kws...) where {Q<:AbstractQuantity} = constructor_of(Q)(x, dim_type(Q)(; kws...))

(::Type{Q})(q::AbstractQuantity) where {T,D<:AbstractDimensions,Q<:AbstractQuantity{T,D}} = constructor_of(Q)(convert(T, ustrip(q)), convert(D, dimension(q)))
(::Type{Q})(q::AbstractQuantity) where {T,Q<:AbstractQuantity{T}} = constructor_of(Q)(convert(T, ustrip(q)), dimension(q))

new_dimensions(::Type{D}, dims...) where {D<:AbstractDimensions} = constructor_of(D)(dims...)
new_quantity(::Type{Q}, l, r) where {Q<:AbstractQuantity} = constructor_of(Q)(l, r)

dim_type(::Type{Q}) where {T,D<:AbstractDimensions,Q<:AbstractQuantity{T,D}} = D
dim_type(::Type{<:AbstractQuantity}) = DEFAULT_DIM_TYPE
constructor_of(::Type{T}) where {T} = Base.typename(T).wrapper

struct DimensionError{Q1,Q2} <: Exception
q1::Q1
Expand Down
Loading