Skip to content

Additional QuantityArray constructions #178

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

Open
wants to merge 22 commits into
base: main
Choose a base branch
from

Conversation

icweaver
Copy link
Contributor

@icweaver icweaver commented May 31, 2025

Closes #166

To-do

@icweaver icweaver changed the title Additional QuantityArray constructions Additional QuantityArray constructions May 31, 2025
@icweaver icweaver marked this pull request as draft May 31, 2025 00:08
Copy link
Contributor

github-actions bot commented May 31, 2025

Benchmark Results (Julia v1.10)

Time benchmarks
main 227ece9... main / 227ece9...
Quantity/creation/Quantity(x) 3.41 ± 0.001 ns 3.42 ± 0.01 ns 0.997 ± 0.0029
Quantity/creation/Quantity(x, length=y) 3.11 ± 0.01 ns 3.11 ± 0.01 ns 1 ± 0.0046
Quantity/with_numbers/*real 3.1 ± 0.01 ns 3.11 ± 0.01 ns 0.997 ± 0.0045
Quantity/with_numbers/^int 8.05 ± 2.2 ns 8.05 ± 2.2 ns 1 ± 0.38
Quantity/with_numbers/^int * real 8.67 ± 2.5 ns 8.67 ± 2.5 ns 1 ± 0.4
Quantity/with_quantity/+y 4.04 ± 0.001 ns 4.04 ± 0.001 ns 1 ± 0.00035
Quantity/with_quantity//y 3.42 ± 0.011 ns 3.42 ± 0.011 ns 1 ± 0.0046
Quantity/with_self/dimension 3.1 ± 0.01 ns 3.11 ± 0.01 ns 0.997 ± 0.0045
Quantity/with_self/inv 3.11 ± 0.01 ns 3.11 ± 0.01 ns 1 ± 0.0046
Quantity/with_self/ustrip 2.79 ± 0.01 ns 2.79 ± 0.01 ns 1 ± 0.0051
QuantityArray/broadcasting/multi_array_of_quantities 0.144 ± 0.003 ms 0.144 ± 0.0031 ms 0.998 ± 0.03
QuantityArray/broadcasting/multi_normal_array 0.0558 ± 0.0031 ms 0.0544 ± 0.0031 ms 1.03 ± 0.081
QuantityArray/broadcasting/multi_quantity_array 0.155 ± 0.0037 ms 0.155 ± 0.0018 ms 1 ± 0.027
QuantityArray/broadcasting/x^2_array_of_quantities 22.7 ± 1.3 μs 22.6 ± 1.4 μs 1 ± 0.085
QuantityArray/broadcasting/x^2_normal_array 4.08 ± 0.79 μs 4.25 ± 0.85 μs 0.96 ± 0.27
QuantityArray/broadcasting/x^2_quantity_array 6.89 ± 0.19 μs 6.9 ± 0.31 μs 0.999 ± 0.053
QuantityArray/broadcasting/x^4_array_of_quantities 0.0814 ± 0.00049 ms 0.0814 ± 0.00057 ms 1 ± 0.0092
QuantityArray/broadcasting/x^4_normal_array 0.0498 ± 0.00017 ms 0.0498 ± 0.00017 ms 1 ± 0.0048
QuantityArray/broadcasting/x^4_quantity_array 0.0529 ± 0.00015 ms 0.0529 ± 0.00019 ms 1 ± 0.0046
time_to_load 0.184 ± 0.00077 s 0.194 ± 0.0023 s 0.95 ± 0.012
Memory benchmarks
main 227ece9... main / 227ece9...
Quantity/creation/Quantity(x) 0 allocs: 0 B 0 allocs: 0 B
Quantity/creation/Quantity(x, length=y) 0 allocs: 0 B 0 allocs: 0 B
Quantity/with_numbers/*real 0 allocs: 0 B 0 allocs: 0 B
Quantity/with_numbers/^int 0 allocs: 0 B 0 allocs: 0 B
Quantity/with_numbers/^int * real 0 allocs: 0 B 0 allocs: 0 B
Quantity/with_quantity/+y 0 allocs: 0 B 0 allocs: 0 B
Quantity/with_quantity//y 0 allocs: 0 B 0 allocs: 0 B
Quantity/with_self/dimension 0 allocs: 0 B 0 allocs: 0 B
Quantity/with_self/inv 0 allocs: 0 B 0 allocs: 0 B
Quantity/with_self/ustrip 0 allocs: 0 B 0 allocs: 0 B
QuantityArray/broadcasting/multi_array_of_quantities 2 allocs: 0.382 MB 2 allocs: 0.382 MB 1
QuantityArray/broadcasting/multi_normal_array 2 allocs: 0.0763 MB 2 allocs: 0.0763 MB 1
QuantityArray/broadcasting/multi_quantity_array 2 allocs: 0.0763 MB 2 allocs: 0.0763 MB 1
QuantityArray/broadcasting/x^2_array_of_quantities 2 allocs: 0.382 MB 2 allocs: 0.382 MB 1
QuantityArray/broadcasting/x^2_normal_array 2 allocs: 0.0763 MB 2 allocs: 0.0763 MB 1
QuantityArray/broadcasting/x^2_quantity_array 2 allocs: 0.0763 MB 2 allocs: 0.0763 MB 1
QuantityArray/broadcasting/x^4_array_of_quantities 2 allocs: 0.382 MB 2 allocs: 0.382 MB 1
QuantityArray/broadcasting/x^4_normal_array 2 allocs: 0.0763 MB 2 allocs: 0.0763 MB 1
QuantityArray/broadcasting/x^4_quantity_array 2 allocs: 0.0763 MB 2 allocs: 0.0763 MB 1
time_to_load 0.153 k allocs: 14.5 kB 0.153 k allocs: 14.5 kB 1

Copy link
Contributor

github-actions bot commented May 31, 2025

Benchmark Results (Julia v1)

Time benchmarks
main 227ece9... main / 227ece9...
Quantity/creation/Quantity(x) 3.72 ± 0.01 ns 3.1 ± 0.01 ns 1.2 ± 0.005
Quantity/creation/Quantity(x, length=y) 3.43 ± 0.011 ns 3.73 ± 0.01 ns 0.919 ± 0.0038
Quantity/with_numbers/*real 3.41 ± 0.01 ns 3.1 ± 0.01 ns 1.1 ± 0.0048
Quantity/with_numbers/^int 8.98 ± 2.2 ns 8.98 ± 2.2 ns 1 ± 0.34
Quantity/with_numbers/^int * real 9.29 ± 2.2 ns 9.29 ± 2.2 ns 1 ± 0.33
Quantity/with_quantity/+y 4.04 ± 0.01 ns 4.35 ± 0.009 ns 0.929 ± 0.003
Quantity/with_quantity//y 3.11 ± 0 ns 3.41 ± 0.01 ns 0.912 ± 0.0027
Quantity/with_self/dimension 3.1 ± 0.01 ns 2.79 ± 0 ns 1.11 ± 0.0036
Quantity/with_self/inv 3.41 ± 0.01 ns 3.11 ± 0.01 ns 1.1 ± 0.0048
Quantity/with_self/ustrip 2.79 ± 0.01 ns 2.79 ± 0.01 ns 1 ± 0.0051
QuantityArray/broadcasting/multi_array_of_quantities 0.0904 ± 0.00077 ms 0.0907 ± 0.0034 ms 0.997 ± 0.039
QuantityArray/broadcasting/multi_normal_array 0.0498 ± 0.00024 ms 0.0498 ± 0.00021 ms 1 ± 0.0064
QuantityArray/broadcasting/multi_quantity_array 0.053 ± 0.00027 ms 0.0591 ± 0.00018 ms 0.897 ± 0.0053
QuantityArray/broadcasting/x^2_array_of_quantities 16.4 ± 6 μs 19.4 ± 5.1 μs 0.842 ± 0.38
QuantityArray/broadcasting/x^2_normal_array 2.91 ± 3.3 μs 3.55 ± 1.7 μs 0.82 ± 1
QuantityArray/broadcasting/x^2_quantity_array 3.5 ± 0.2 μs 6.48 ± 0.079 μs 0.539 ± 0.032
QuantityArray/broadcasting/x^4_array_of_quantities 0.0873 ± 0.00089 ms 0.0844 ± 0.00079 ms 1.03 ± 0.014
QuantityArray/broadcasting/x^4_normal_array 0.0497 ± 0.00022 ms 0.0466 ± 0.00013 ms 1.07 ± 0.0056
QuantityArray/broadcasting/x^4_quantity_array 0.0499 ± 0.00019 ms 0.0498 ± 0.00015 ms 1 ± 0.0049
time_to_load 0.212 ± 0.0023 s 0.225 ± 0.0006 s 0.945 ± 0.011
Memory benchmarks
main 227ece9... main / 227ece9...
Quantity/creation/Quantity(x) 0 allocs: 0 B 0 allocs: 0 B
Quantity/creation/Quantity(x, length=y) 0 allocs: 0 B 0 allocs: 0 B
Quantity/with_numbers/*real 0 allocs: 0 B 0 allocs: 0 B
Quantity/with_numbers/^int 0 allocs: 0 B 0 allocs: 0 B
Quantity/with_numbers/^int * real 0 allocs: 0 B 0 allocs: 0 B
Quantity/with_quantity/+y 0 allocs: 0 B 0 allocs: 0 B
Quantity/with_quantity//y 0 allocs: 0 B 0 allocs: 0 B
Quantity/with_self/dimension 0 allocs: 0 B 0 allocs: 0 B
Quantity/with_self/inv 0 allocs: 0 B 0 allocs: 0 B
Quantity/with_self/ustrip 0 allocs: 0 B 0 allocs: 0 B
QuantityArray/broadcasting/multi_array_of_quantities 3 allocs: 0.382 MB 3 allocs: 0.382 MB 1
QuantityArray/broadcasting/multi_normal_array 3 allocs: 0.0764 MB 3 allocs: 0.0764 MB 1
QuantityArray/broadcasting/multi_quantity_array 3 allocs: 0.0764 MB 3 allocs: 0.0764 MB 1
QuantityArray/broadcasting/x^2_array_of_quantities 3 allocs: 0.382 MB 3 allocs: 0.382 MB 1
QuantityArray/broadcasting/x^2_normal_array 3 allocs: 0.0764 MB 3 allocs: 0.0764 MB 1
QuantityArray/broadcasting/x^2_quantity_array 3 allocs: 0.0764 MB 3 allocs: 0.0764 MB 1
QuantityArray/broadcasting/x^4_array_of_quantities 3 allocs: 0.382 MB 3 allocs: 0.382 MB 1
QuantityArray/broadcasting/x^4_normal_array 3 allocs: 0.0764 MB 3 allocs: 0.0764 MB 1
QuantityArray/broadcasting/x^4_quantity_array 3 allocs: 0.0764 MB 3 allocs: 0.0764 MB 1
time_to_load 0.159 k allocs: 11.2 kB 0.159 k allocs: 11.2 kB 1

@icweaver icweaver marked this pull request as ready for review June 3, 2025 20:36
@icweaver
Copy link
Contributor Author

icweaver commented Jun 3, 2025

Hi @MilesCranmer, this PR should be ready for your review now. Two follow-up questions I had were:

  1. Do we also want to add methods for creating QuantityArrays from generators? I just went with the types defined in ABSTRACT_QUANTITY_TYPES for now https://github.com/SymbolicML/DynamicQuantities.jl/blob/fe78d12e69e98a1d9e7a0b2eeb060558afc62cad/src/types.jl#L212-L216
  2. Did you also want the assorted examples docs for QuantityArray updated, or would you prefer just keeping the explicit QuantityArray(<blah>) constructions there as-is?

@icweaver
Copy link
Contributor Author

icweaver commented Jul 10, 2025

Sweet, looks like this is working nicely with the new Makie PR:

julia> using CairoMakie, DynamicQuantities

julia> x = [6, 7, 8]us"cm"
3-element QuantityArray(::Vector{Float64}, ::Quantity{Float64, SymbolicDimensions{FixedRational{Int32, 25200}}}):
 6.0 cm
 7.0 cm
 8.0 cm

julia> y = (4:6)u"kg"
3-element QuantityArray(::StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}, ::Quantity{Float64, Dimensions{FixedRational{Int32, 25200}}}):
 4.0 kg
 5.0 kg
 6.0 kg

julia> scatter(x, y)
display

@icweaver
Copy link
Contributor Author

  1. Did you also want the assorted examples docs for QuantityArray updated, or would you prefer just keeping the explicit QuantityArray() constructions there as-is?

Actually, think I found a good balance, just pushed. Does this look alright to you?

Copy link
Member

@MilesCranmer MilesCranmer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Thanks for this and sorry for the delay. I only had a couple requests

@MilesCranmer
Copy link
Member

Also re: your question, I think creating from generators is probably too much for now.

@icweaver
Copy link
Contributor Author

Thanks Miles! Sorry for the mix-up with the README and generated index file; I think things have been straightened out now.

In the process, I think I noticed that the HTML table of contents does not work as intended on my end at least, I think due to the case-sensitivity of the section headers. I'd be happy to open up a separate PR for this and maybe exploring the preprocessing step you mentioned for README code blocks if you think that would be useful

@MilesCranmer
Copy link
Member

Sure! Sounds great, thanks

Copy link

codecov bot commented Jul 15, 2025

Codecov Report

Attention: Patch coverage is 66.66667% with 1 line in your changes missing coverage. Please review.

Project coverage is 95.62%. Comparing base (b209b7c) to head (12b20d2).

Files with missing lines Patch % Lines
src/math.jl 66.66% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #178      +/-   ##
==========================================
- Coverage   99.21%   95.62%   -3.60%     
==========================================
  Files          21       20       -1     
  Lines        1273     1211      -62     
==========================================
- Hits         1263     1158     -105     
- Misses         10       53      +43     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@icweaver
Copy link
Contributor Author

icweaver commented Jul 15, 2025

Added a to-do for the method ambiguities picked up by Aqua

Note to self, related reading:

@icweaver
Copy link
Contributor Author

Hi @MilesCranmer, definitely not the most elegant, but I think the remaining method ambiguities have been addressed now and that this PR is ready for another review

@icweaver icweaver requested a review from MilesCranmer August 2, 2025 18:46
@MilesCranmer
Copy link
Member

Sorry is the Dates extension new? I don’t think I noticed that before. Could you put the Dates.jl extension in a separate PR please? Sorry for not mentioning earlier, I didn’t notice

Copy link

codecov bot commented Aug 2, 2025

Codecov Report

❌ Patch coverage is 63.63636% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 98.83%. Comparing base (3e4e19d) to head (227ece9).

Files with missing lines Patch % Lines
ext/DynamicQuantitiesLinearAlgebraExt.jl 0.00% 3 Missing ⚠️
src/arrays.jl 66.66% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #178      +/-   ##
==========================================
- Coverage   99.21%   98.83%   -0.39%     
==========================================
  Files          21       21              
  Lines        1273     1284      +11     
==========================================
+ Hits         1263     1269       +6     
- Misses         10       15       +5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Documenter = "1"

[sources]
DynamicQuantities = {path = ".."}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DynamicQuantities doesn’t need to be specified here. (Also ideally this would go in a separate PR if possible, since it’s unrelated to the QuantityArray stuff)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yep, that snuck in by mistake, my bad. Looking forward to it being included in Abhro's PR =]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MilesCranmer
Copy link
Member

Hm I think the test coverage CI should fail, I think the repo change means codecov isn’t aware of the current coverage

@icweaver
Copy link
Contributor Author

icweaver commented Aug 2, 2025

Sorry is the Dates extension new? I don’t think I noticed that before. Could you put the Dates.jl extension in a separate PR please? Sorry for not mentioning earlier, I didn’t notice

Yep, I introduced it in 096ca12 while playing method ambiguity whac-a-mole with Aqua. sg, will split into its own PR

Updated: Started #181

@icweaver
Copy link
Contributor Author

icweaver commented Aug 4, 2025

Hi @MilesCranmer, since the Dates.jl stuff covered some method ambiguities introduced by this PR, tests are understandably failing here now. Is this just an unavoidable consequence of relaxing the multiplication rules so that <Abstract array> * <Quantity> can produce a QuantityArray in this PR, or was I being too loose?

@icweaver icweaver mentioned this pull request Aug 4, 2025
@MilesCranmer
Copy link
Member

For such a large number of method ambiguities I think it could be because the new methods introduced are too generic. If we are going so far as to need package extensions just to fix ambiguities, my gut feeling is that we are only fixing the symptoms, but not the cause (which could very well be my fault)

@icweaver
Copy link
Contributor Author

icweaver commented Aug 5, 2025

Oh sorry, I should have tried out tighter subtyping first instead of just blindly patching things with Aqua. Restricting to abstract arrays of Numbers looks to do the trick (046b8d4), sorry for the noise!

I'll go ahead and close the superfluous Dates.jl draft PR

The handful of patches here are still needed to cover the remaining method ambiguities, but I hope that's not a deal breaker

# DynamicQuantities.jl/src/math.jl
Base.:*(A::StepRangeLen{<:Real, <:Base.TwicePrecision}, q::AbstractRealQuantity) = QuantityArray(A, q)
Base.:*(q::AbstractRealQuantity, A::StepRangeLen{<:Real, <:Base.TwicePrecision}) = A * q
Base.:/(A::BitArray, q::AbstractRealQuantity) = A * inv(q)
Base.:/(A::BitArray, q::AbstractQuantity) = A * inv(q)
Base.:/(A::StepRangeLen{<:Real, <:Base.TwicePrecision}, q::AbstractRealQuantity) = A * inv(q)

@icweaver icweaver requested a review from MilesCranmer August 5, 2025 21:45
Copy link
Member

@MilesCranmer MilesCranmer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gave some general comments. Also need to have explicit tests of all the operators.

src/arrays.jl Outdated
@@ -14,6 +14,14 @@ and so can be used in most places where a normal array would be used, including
# Constructors
The most convenient way to create a `QuantityArray` is by multiplying your array-like object by the desired dimension(s), e.g.,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be "desired units"

using TestItems: @testitem

DQ.is_ext_loaded(::Val{:LinearAlgebra}) = true
DQ.norm(u) = LA.norm(u)
LA.norm(q::UnionAbstractQuantity, p::Real=2) = new_quantity(typeof(q), LA.norm(ustrip(q), p), dimension(q))

const ARRAY_TYPES_CONCRETE = (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at this now, I guess it should just be inlined, rather than having a constant for it.. to be consistent with the other for loop.

src/math.jl Outdated
@@ -49,9 +49,21 @@ for (type, base_type, _) in ABSTRACT_QUANTITY_TYPES
function Base.:/(l::AbstractDimensions, r::$type)
new_quantity(typeof(r), inv(ustrip(r)), l / dimension(r))
end

# Defining here instead of outside loop with UnionAbstractQuantity to avoid method ambiguities
Base.:*(A::AbstractArray{T}, q::$type) where {T<:Number} = QuantityArray(A, q)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be in the arrays file; this file is just scalar math

src/math.jl Outdated
end
end

# Deal with ambiguous QuantityArray operations:
Base.:*(A::StepRangeLen{<:Real, <:Base.TwicePrecision}, q::AbstractRealQuantity) = QuantityArray(A, q)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar comment - should be in the arrays file near the related operators

@icweaver
Copy link
Contributor Author

icweaver commented Aug 6, 2025

Sick, thanks Miles! Your comments should be in now. Happy to add more tests, but just wanted to make sure I was adding them to the right places first 👍🏾

src/arrays.jl Outdated
@@ -441,6 +459,13 @@ for op in (:(Base.:*), :(Base.:/), :(Base.:\))
end
end

# Cover method ambiguities from, e.g., op(::Array, ::Quantity)::QuantityArray`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disambiguities should go in src/disambiguities

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be more specific: the new “ Eagerly construct a QuantityArray from the following:” part can stay here. But specific disambiguities should go in the disambiguities file

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Roger! Oh nice, there's even an Aqua section 😄

src/arrays.jl Outdated
@@ -60,6 +68,16 @@ for (type, base_type, default_type) in ABSTRACT_QUANTITY_TYPES
if type in (AbstractQuantity, AbstractGenericQuantity)
@eval QuantityArray(v::AbstractArray{<:$base_type}, d::AbstractDimensions) = QuantityArray(v, d, $default_type)
end

# Eagerly construct a `QuantityArray` from the following:
# ∘ array * unit
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments like this aren’t needed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Roger!

@icweaver
Copy link
Contributor Author

icweaver commented Aug 6, 2025

Okie doke, next round should be up in the latest commit 227ece9

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

QuantityArray convenience
2 participants