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

Affine Units Functionality #159

Open
wants to merge 28 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3f37480
Basic arithmatic between AffineUnits and other units
Deduction42 Jan 29, 2025
93d065c
Successful exponentiation
Deduction42 Jan 29, 2025
24f7966
Better constructors
Deduction42 Jan 29, 2025
be23440
added affine units parsing
Deduction42 Jan 29, 2025
ebf0fd5
Modified settings
Deduction42 Jan 30, 2025
b611801
Updated plan for unit display
RGonTheNoble Jan 30, 2025
8613bc1
@register_unit now populates AffineUnits
RGonTheNoble Jan 30, 2025
4d07529
Completed unit registration strategy
RGonTheNoble Jan 30, 2025
e811c1c
Better macro documentation
RGonTheNoble Jan 30, 2025
e2eb10f
Fixed ambiguity issues, all unit tests pass
Deduction42 Jan 31, 2025
bcf26df
More robust constructors
Deduction42 Jan 31, 2025
d3c0f6c
More informative dimension mismatch errors
Deduction42 Jan 31, 2025
270ce0e
Fixed some unit conversion bugs
Deduction42 Jan 31, 2025
1565b51
Overloaded approx and equals for AffineQuantities
Deduction42 Jan 31, 2025
0e8b6d8
First ound of unit tests
Deduction42 Feb 3, 2025
0eab0f0
Very strong affine_dimensions.jl coverage
Deduction42 Feb 4, 2025
70cff63
100% coverage of affine_dimensions.jl
Deduction42 Feb 4, 2025
c94ffc9
Renamed AffineUnitsParse to AffineUnits (for convention)
Deduction42 Feb 4, 2025
24bfebd
register_units now 100% covered
Deduction42 Feb 4, 2025
bb6a70f
Added documentation
Deduction42 Feb 4, 2025
39ac03a
Added docu for @register_affine_unit
Deduction42 Feb 4, 2025
782c1bf
Fixed indentation issue
Deduction42 Feb 4, 2025
e8197bd
Fixed documenter typo
Deduction42 Feb 4, 2025
fa1709b
Delete lcov.info
Deduction42 Feb 4, 2025
c1c4045
Added "psig" to affine unit tests, added warning tests
Deduction42 Feb 4, 2025
b9c1328
Fixed a counting issue in tests
Deduction42 Feb 4, 2025
be7df25
Registered degC and degF, tested them
Deduction42 Feb 5, 2025
ed6e0ab
AffineDimension equality ignores symbolic name
Deduction42 Feb 10, 2025
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
50 changes: 49 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,6 @@ Note that `SymbolicUnits` and `SymbolicConstants` are exported,
so you can simply access these as `SymbolicUnits.cm` and `SymbolicConstants.h`,
respectively.


#### Custom Units

You can create custom units with the `@register_unit` macro:
Expand All @@ -284,6 +283,55 @@ julia> 3us"V" |> us"OneFiveV"
2.0 OneFiveV
```

### Affine units
Units that have an offset (such as °C = K + 273.15) are an unfortunate fact of life: they are used extensively but often result in ambiguous mathematical operations (many other packages, such as Unitful.jl only support limited operations for affine dimensions). `AffineDimensions` seeks to extend DynamicQuantities.jl to reduce dependence on Unitful.jl, and enable handling/converting such units in a flexible, type-stable manner.

`AffineDimensions` are a generalization of `Dimensions` and `SymbolicDimensions`. While SymbolicDimensions essentially add a scale to Dimensions, AffineDimensions will add both a scale and an offset. Verious constructors can be used to construct `AffineDimensions` from other dimensions.
```
kelvin = AffineDimensions(basedim=u"K") #Assumes a scale of 1 and offset 0
rankine = AffineDimensions(scale=5/9, offset=0.0, basedim=dimension(u"K")) #Rankine is a scaled version of Kelvin, offset is assumed to be of units 'basedim'
fahrenheit = AffineDimensions(scale=1.0, offset=Quantity(459.67, rankine), basedim=rankine) #Its best to make offset a `Quantity` to be explicit
celsius = AffineDimensions(scale=9/5, offset=Quantity(32.0, rankine), basedim=fahrenheit) #When AffineDimensiosn are used, offset starts with basedim's offset
```
#### Custom affine units
To access units from the affine unit registry, the string macro `ua"..."` can be used. This macro will always return quantities with AffineDimensions, even if a non-affine unit is called (it will simply have an offset of 0). Because AffineDimensions are a generalization of SymbolicDimensions, the affine unit registry will mirror the symbolic unit registry.
```
@register_unit psi 6.89476us"kPa"
u"psi"
>> 6894.76 m⁻¹ kg s⁻²
us"psi"
>> 1.0 psi
ua"psi"
>> 1.0 psi
```
However, strictly affine units cannot belong to the symbolic registry, so a different macro must be used on an AffineDimension (or quantity thereof)
```
@register_affine_unit psig AffineDimensions(offset=u"Constants.atm", basedim=u"psi") #Gauge pressure implies atmospheric offset
ua"psig"
>> 1.0 psig
us"psig"
>> ERROR: LoadError: ArgumentError: Symbol psig not found in `Units` or `Constants`.
```
Affine unit parsing can also be done outside of a macro using `aff_uparse(str::AbstractString)`
```
aff_uparse("°C")
>> 1.0 °C
```
#### Operations on affine quantities
In Unitful.jl, multiplication of affine quantities is not supported for affine dimensions:
```
using Unitful
u"R"*0u"°C"
>> ERROR: AffineError: an invalid operation was attempted with affine units: °C
```
This behaviour is mimicked in DynamicQuantities:
```
using DynamicQuantities
u"Constants.R"*(0ua"°C")
>> AssertionError: AffineDimensions °C has a non-zero offset, implicit conversion is not allowed due to ambiguity. Use uexpand(x) to explicitly convert
```
In general, it's best to treat quantities with AffineDimensions as placeholders and use `uexpand(q)` or `uconvert(units, q)` as soon as possible. The main objective of AffineDimesnions is to provide you with convenient, type-stable tools to do this conversion before applying mathematical operations.


### Arrays

Expand Down
1 change: 1 addition & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ makedocs(;
"Units" => "units.md",
"Constants" => "constants.md",
"Symbolic Units" => "symbolic_units.md",
"Affine Units" => "affine_units.md",
"Types" => "types.md",
],
warnonly = [:missing_docs]
Expand Down
53 changes: 53 additions & 0 deletions docs/src/affine_units.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Affine Dimensions
Units that have an offset (such as °C = K + 273.15) are an unfortunate fact of life: they are used extensively but often result in ambiguous mathematical operations (many other packages, such as Unitful.jl only support limited operations for affine dimensions). `AffineDimensions` seeks to extend DynamicQuantities.jl to reduce dependence on Unitful.jl, and enable handling/converting such units in a flexible, type-stable manner.

`AffineDimensions` are a generalization of `Dimensions` and `SymbolicDimensions`. While SymbolicDimensions essentially add a scale to Dimensions, AffineDimensions will add both a scale and an offset. Verious constructors can be used to construct `AffineDimensions` from other dimensions.
```
kelvin = AffineDimensions(basedim=u"K") #Assumes a scale of 1 and offset 0
rankine = AffineDimensions(scale=5/9, offset=0.0, basedim=dimension(u"K")) #Rankine is a scaled version of Kelvin, offset is assumed to be of units 'basedim'
fahrenheit = AffineDimensions(scale=1.0, offset=Quantity(459.67, rankine), basedim=rankine) #Its best to make offset a `Quantity` to be explicit
celsius = AffineDimensions(scale=9/5, offset=Quantity(32.0, rankine), basedim=fahrenheit) #When AffineDimensiosn are used, offset starts with basedim's offset
```
## Registration and parsing
To access units from the affine unit registry, the string macro `ua"..."` can be used. This macro will always return quantities with AffineDimensions, even if a non-affine unit is called (it will simply have an offset of 0). Because AffineDimensions are a generalization of SymbolicDimensions, the affine unit registry will mirror the symbolic unit registry.
```
@register_unit psi 6.89476us"kPa"
u"psi"
>> 6894.76 m⁻¹ kg s⁻²
us"psi"
>> 1.0 psi
ua"psi"
>> 1.0 psi
```
However, strictly affine units cannot belong to the symbolic registry, so a different macro must be used on an AffineDimension (or quantity thereof)
```
@register_affine_unit psig AffineDimensions(offset=u"Constants.atm", basedim=u"psi") #Gauge pressure implies atmospheric offset
ua"psig"
>> 1.0 psig
us"psig"
>> ERROR: LoadError: ArgumentError: Symbol psig not found in `Units` or `Constants`.
```
Affine unit parsing can also be done outside of a macro using `aff_uparse(str::AbstractString)`
```
aff_uparse("°C")
>> 1.0 °C
```
```@docs
@ua_str
@register_affine_unit
aff_uparse
```
## Operations
In Unitful.jl, multiplication of affine quantities is not supported for affine dimensions:
```
using Unitful
u"R"*0u"°C"
>> ERROR: AffineError: an invalid operation was attempted with affine units: °C
```
This behaviour is mimicked in DynamicQuantities:
```
using DynamicQuantities
u"Constants.R"*(0ua"°C")
>> AssertionError: AffineDimensions °C has a non-zero offset, implicit conversion is not allowed due to ambiguity. Use uexpand(x) to explicitly convert
```
In general, it's best to treat quantities with AffineDimensions as placeholders and use `uexpand(q)` or `uconvert(units, q)` as soon as possible. The main objective of AffineDimesnions is to provide you with convenient, type-stable tools to do this conversion before applying mathematical operations.
4 changes: 3 additions & 1 deletion src/DynamicQuantities.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ export Quantity, GenericQuantity, RealQuantity
export FixedRational
export AbstractDimensions, Dimensions, NoDims
export AbstractSymbolicDimensions, SymbolicDimensions, SymbolicDimensionsSingleton
export AbstractAffineDimensions, AffineDimensions
export QuantityArray
export DimensionError
export ustrip, dimension
export ulength, umass, utime, ucurrent, utemperature, uluminosity, uamount
export uparse, @u_str, sym_uparse, @us_str, uexpand, uconvert, @register_unit
export uparse, @u_str, sym_uparse, @us_str, aff_uparse, @ua_str, uexpand, uconvert, @register_unit, @register_affine_unit

# Deprecated:
export expand_units
Expand All @@ -32,6 +33,7 @@ using DispatchDoctor: @stable
include("complex.jl")
include("register_units.jl")
include("disambiguities.jl")
include("affine_dimensions.jl")

include("deprecated.jl")
end
Expand Down
Loading
Loading