DynamicQuantities.jl icon indicating copy to clipboard operation
DynamicQuantities.jl copied to clipboard

Basic Makie.jl support

Open icweaver opened this issue 11 months ago • 6 comments

Closes #164

Demo

using DynamicQuantities, GLMakie

fig = Figure()

y = 60:10:100

scatter(fig[1, 1], y * u"m")
scatter(fig[1, 2], QuantityArray(y, u"m"))
scatter(fig[2, 1], y * u"kg", y * u"m")
scatter(fig[2, 2], y * us"m/s/Hz")

fig

example

See docs for more examples using Makie's dimension conversion machinery

TODO

Future PR(s)?

  • Affine units?
  • Heatmap and Image support. Maybe with a recipe
  • Rich strings (Unitful example)

icweaver avatar Mar 09 '25 21:03 icweaver

Benchmark Results

main ba44320fa7dfd6... main / ba44320fa7dfd6...
Quantity/creation/Quantity(x) 3.11 ± 0.01 ns 3.1 ± 0.01 ns 1 ± 0.0046
Quantity/creation/Quantity(x, length=y) 3.74 ± 0.01 ns 3.73 ± 0.01 ns 1 ± 0.0038
Quantity/with_numbers/*real 2.79 ± 0.01 ns 3.11 ± 0.01 ns 0.9 ± 0.0043
Quantity/with_numbers/^int 8.37 ± 1.6 ns 8.68 ± 1.6 ns 0.964 ± 0.25
Quantity/with_numbers/^int * real 8.99 ± 1.6 ns 8.98 ± 1.6 ns 1 ± 0.25
Quantity/with_quantity/+y 4.35 ± 0.01 ns 4.35 ± 0.001 ns 1 ± 0.0023
Quantity/with_quantity//y 3.42 ± 0.001 ns 3.42 ± 0.011 ns 1 ± 0.0032
Quantity/with_self/dimension 4.03 ± 0.011 ns 2.79 ± 0 ns 1.44 ± 0.0039
Quantity/with_self/inv 3.12 ± 0.92 ns 3.11 ± 0.01 ns 1 ± 0.3
Quantity/with_self/ustrip 3.11 ± 0.01 ns 2.79 ± 0.01 ns 1.11 ± 0.0053
QuantityArray/broadcasting/multi_array_of_quantities 0.0872 ± 0.00033 ms 0.0875 ± 0.00034 ms 0.997 ± 0.0054
QuantityArray/broadcasting/multi_normal_array 0.0496 ± 0.0019 ms 0.0477 ± 0.0023 ms 1.04 ± 0.064
QuantityArray/broadcasting/multi_quantity_array 0.053 ± 0.00025 ms 0.0529 ± 0.0028 ms 1 ± 0.054
QuantityArray/broadcasting/x^2_array_of_quantities 12.9 ± 1.7 μs 13.5 ± 1.7 μs 0.958 ± 0.18
QuantityArray/broadcasting/x^2_normal_array 1.99 ± 0.37 μs 2 ± 0.33 μs 0.995 ± 0.25
QuantityArray/broadcasting/x^2_quantity_array 3.45 ± 0.12 μs 3.46 ± 0.091 μs 0.997 ± 0.044
QuantityArray/broadcasting/x^4_array_of_quantities 0.0811 ± 0.00029 ms 0.0813 ± 0.00031 ms 0.997 ± 0.0052
QuantityArray/broadcasting/x^4_normal_array 0.0466 ± 0.00012 ms 0.0466 ± 0.00011 ms 1 ± 0.0035
QuantityArray/broadcasting/x^4_quantity_array 0.0467 ± 0.00013 ms 0.0468 ± 0.0031 ms 0.998 ± 0.067
time_to_load 0.195 ± 0.0027 s 0.196 ± 0.00024 s 0.993 ± 0.014

Benchmark Plots

A plot of the benchmark results have been uploaded as an artifact to the workflow run for this PR. Go to "Actions"->"Benchmark a pull request"->[the most recent run]->"Artifacts" (at the bottom).

github-actions[bot] avatar Mar 09 '25 21:03 github-actions[bot]

Just saw this really cool update from Julius on the AoG side of things!

https://github.com/MakieOrg/AlgebraOfGraphics.jl/pull/619

https://aog.makie.org/v0.9.6/examples/scales/units#units

This also looks to address https://github.com/MakieOrg/Makie.jl/issues/3890 in one fell swoop

icweaver avatar Mar 27 '25 17:03 icweaver

Cool!

Let me know (by tagging me) when you're ready for review

MilesCranmer avatar Mar 27 '25 18:03 MilesCranmer

Hi @MilesCranmer, I think this should be just about ready for a first pass of reviews now.

The main additions are:

I'm not quite sure yet how I feel about Makie's choice to automatically change non-compound units for folks out from under them, so I kept that bit of machinery out for now. I do see the convenience of it though, it just seems to add a fair bit of complexity that we maybe don't need right now. Instead, this current design favors explicit over implicit unit handling, e.g.,

Unitful example (taken from ReferenceTests)

# Don't swallow units past the first
f, a, p = scatter((1:10) .* u"J/s")
# Don't simplify (assume the user knows better)
scatter(f[1, 2], (1:10) .* u"K", exp.(1:10) .* u"mm/m^2")
# Only change prefixes of simple units, not compound units
scatter(f[2, 1], 10 .^ (1:6) .* u"W/m^2", (1:6) .* 1000 .* u"nm")
# Only change units/prefixes for simple units when adding more plots
scatter(f[2, 2], (0:10) .* u"W/m^2", (0:10) .* u"g")
scatter!((0:10) .* u"kW/m^2", (0:10) .* u"kg")
f

unitful_example

Here the nanometer plot in the bottom left is auto-converted to microns without the user specifying that unit. In contrast:

This PR

using DynamicQuantities, Makie
const DQConversion = Base.get_extension(DynamicQuantities, :DynamicQuantitiesMakieExt).DQConversion

fig = Figure()

ax1 = Axis(fig[1, 1]; dim2_conversion=DQConversion(us"J/s"))
ax2 = Axis(fig[1, 2]; dim2_conversion=DQConversion(us"mm/m^2"))
ax3 = Axis(fig[2, 1]; dim1_conversion=DQConversion(us"W/m^2"), dim2_conversion=DQConversion(us"μm"))
ax4 = Axis(fig[2, 2]; dim1_conversion=DQConversion(us"W/m^2"))

scatter!(ax1, (1:10) .* u"J/s")
scatter!(ax2, (1:10) .* u"K", exp.(1:10) .* u"mm/m^2")
scatter!(ax3, 10 .^ (1:6) .* u"W/m^2", (1:6) .* 1000 .* u"nm")
scatter!(ax4, (0:10) .* u"W/m^2", (0:10) .* u"g")
scatter!(ax4, (0:10) .* u"kW/m^2", (0:10) .* u"kg")

fig

or with the appropriate use of SymbolicDimensions:

fig = Figure()

scatter(fig[1, 1], (1:10) .* us"J/s")
scatter(fig[1, 2], (1:10) .* u"K", exp.(1:10) .* us"mm/m^2")
scatter(fig[2, 1], 10 .^ (1:6) .* us"W/m^2", (1:6) .* 1000 .* u"nm"; axis=(; dim2_conversion=DQConversion(us"μm")))
scatter(fig[2, 2], (0:10) .* u"W/m^2", (0:10) .* u"g"; axis=(; dim1_conversion=DQConversion(us"W/m^2")))
scatter!(fig[2, 2], (0:10) .* u"kW/m^2", (0:10) .* us"kg")

fig

dq_example

Happy to save that kind of discussion for a separate PR though if it's starting to get too in the weeds for this basic functionality PR. Thanks again for taking a look!

icweaver avatar Mar 31 '25 00:03 icweaver

This would be great for a project I'm working on right now. Can it be pushed?

bertini97 avatar Apr 29 '25 07:04 bertini97

Sure let me review it; sorry i missed the earlier notification. I think I unsubscribed from the thread as it was a draft

MilesCranmer avatar Apr 29 '25 10:04 MilesCranmer

Hi @MilesCranmer, haha, I think I missed a notification too, sorry for the delay! Thanks for the comments, I think I have responded to all of them now for your review

icweaver avatar May 13 '25 02:05 icweaver

One other question: @jkrumbiegel, do you think this functionality should live directly in Makie.jl, or is it better kept as an extension here?

I’m a bit concerned about maintainability if we keep it here, since it relies on internal, non-public APIs of Makie.jl (though it uses the public API of DQ). Typically, in scenarios where an interface primarily relies on the internal details of package A (Makie.jl), but the public API of package B (DQ.jl), it feels safer to have it reside directly within package A as it lives closer to the people who might edit such internals. However, if there are strong objections, I’m fine with keeping it here.

MilesCranmer avatar May 13 '25 15:05 MilesCranmer

do you think this functionality should live directly in Makie.jl, or is it better kept as an extension here?

Good question, Makie depends directly on Unitful so I'm not sure if we shouldn't add DynamicQuantities as an extension. But I didn't implement unit support so I'll ping @simondanisch here

jkrumbiegel avatar May 14 '25 10:05 jkrumbiegel

We're pretty tight on maintenance, so I'd prefer this to stay here. I'm not sure what exactly you mean by non public interface, since on a quick look it seems to overload the interface as documented. To be fair, it may change a bit in the next breaking release, but that may always happen, and we'll try to document & make it as non breaking as possible.

SimonDanisch avatar May 14 '25 11:05 SimonDanisch

@SimonDanisch If @icweaver is happy to maintain the extension, could we move it into Makie? My main concern is that changes are far more likely on the Makie side than in DQ, so leaving it here risks silent breakage, whereas Makie’s CI would flag issues before release. It would remain an extension – no hard dependency – and we could add @icweaver to CODEOWNERS, meaning no extra maintenance for core Makie. @icweaver – would you be comfortable taking this on?

MilesCranmer avatar May 14 '25 13:05 MilesCranmer

I'm not sure what you mean by no extra maintenance if our CIs start failing and we can only move on once someone else fixed the failure ;)

SimonDanisch avatar May 14 '25 14:05 SimonDanisch

To be fair, if DQ is 100% stable, our CI should only fail if we actually change the interface. Still, I'd rather not have more extensions in Makie.jl. There's also the problem, that extensions trigger compilation if any indirect dependency brings in DQ, making Makie compilation take much longer for everyone. So, I think DQ is the right place. We do try to make sure we really only break APIs on a breaking release, so silent breakage shouldn't be as much of an issue.

SimonDanisch avatar May 14 '25 14:05 SimonDanisch

Hi folks, I just wanted to start by thanking Miles for making, imo, a nice alternative to Unitful, and to the Makie team for putting together such a slick system for pluggable dim conversions

I'm happy to go wherever the code is and to help maintain things to the best of my ability. Thanks all again for your time =]

icweaver avatar May 14 '25 16:05 icweaver

@SimonDanisch Thanks for clarifying; those are fair concerns. Quickly addressing each:

  1. Compile time: Definitely important, but here there's no impact. The extension precompiles whenever Makie and DQ coexist, regardless of where the source lives, so moving it wouldn't affect users either way.
  2. CI complexity: Good point. To avoid overhead, we could easily run these DQ tests separately (e.g., non-blocking CI job or allow_failure), set up to alert @icweaver directly rather than core Makie.

The reason I'm suggesting Makie is mostly semantic: the extension depends primarily on Makie symbols rather than DQ's API. It feels more natural to keep it alongside those internals (similar to Unitful). That way, repo-wide edits (like renaming M.needs_tick_update_observable) would automatically update this extension, rather than us both discovering such issues after users report them.

If that approach seems reasonable, we can quickly set up a minimal-overhead solution. But I'm happy to defer if you'd still prefer keeping it in DQ; I just wanted to clearly lay out the rationale first.

MilesCranmer avatar May 14 '25 17:05 MilesCranmer

If neither side currently wants to take on this glue code, one could also just make a package that implements the integration. It would not be as slick because you wouldn't get the benefits automatically with installing Makie and DynamicQuantities, but it's also much lower stakes. It could still be integrated into either DQ or Makie later.

jkrumbiegel avatar May 15 '25 21:05 jkrumbiegel

Benchmark Results (Julia v1.10)

Time benchmarks
main 6534a36155302d... main / 6534a36155302d...
Quantity/creation/Quantity(x) 2.79 ± 0.01 ns 3.42 ± 0.01 ns 0.818 ± 0.0038
Quantity/creation/Quantity(x, length=y) 3.41 ± 0.01 ns 3.11 ± 0.01 ns 1.1 ± 0.0048
Quantity/with_numbers/*real 3.1 ± 0.01 ns 3.11 ± 0.01 ns 1 ± 0.0046
Quantity/with_numbers/^int 8.05 ± 2.2 ns 8.05 ± 2.2 ns 1 ± 0.38
Quantity/with_numbers/^int * real 8.37 ± 1.9 ns 8.67 ± 2.5 ns 0.965 ± 0.35
Quantity/with_quantity/+y 4.57 ± 0.09 ns 4.04 ± 0.001 ns 1.13 ± 0.022
Quantity/with_quantity//y 3.58 ± 0.07 ns 3.42 ± 0.01 ns 1.05 ± 0.021
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.001 ns 3.11 ± 0.01 ns 1 ± 0.0032
Quantity/with_self/ustrip 2.79 ± 0.92 ns 2.79 ± 0.01 ns 1 ± 0.33
QuantityArray/broadcasting/multi_array_of_quantities 0.14 ± 0.0031 ms 0.144 ± 0.0029 ms 0.974 ± 0.029
QuantityArray/broadcasting/multi_normal_array 0.0528 ± 0.00094 ms 0.0532 ± 0.0031 ms 0.992 ± 0.06
QuantityArray/broadcasting/multi_quantity_array 0.157 ± 0.00098 ms 0.155 ± 0.0025 ms 1.01 ± 0.018
QuantityArray/broadcasting/x^2_array_of_quantities 22.6 ± 1.5 μs 22.5 ± 1.3 μs 1 ± 0.09
QuantityArray/broadcasting/x^2_normal_array 4.23 ± 1.1 μs 4.18 ± 0.99 μs 1.01 ± 0.36
QuantityArray/broadcasting/x^2_quantity_array 6.91 ± 0.21 μs 6.91 ± 0.26 μs 1 ± 0.048
QuantityArray/broadcasting/x^4_array_of_quantities 0.0815 ± 0.00062 ms 0.0815 ± 0.00053 ms 1 ± 0.01
QuantityArray/broadcasting/x^4_normal_array 0.0497 ± 0.00018 ms 0.0498 ± 0.00017 ms 1 ± 0.0049
QuantityArray/broadcasting/x^4_quantity_array 0.053 ± 0.00018 ms 0.0499 ± 0.00017 ms 1.06 ± 0.0051
time_to_load 0.184 ± 0.0002 s 0.185 ± 0.00055 s 0.999 ± 0.0031
Memory benchmarks
main 6534a36155302d... main / 6534a36155302d...
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

github-actions[bot] avatar May 27 '25 21:05 github-actions[bot]

Benchmark Results (Julia v1)

Time benchmarks
main 6534a36155302d... main / 6534a36155302d...
Quantity/creation/Quantity(x) 3.1 ± 0.01 ns 3.11 ± 0.92 ns 0.997 ± 0.3
Quantity/creation/Quantity(x, length=y) 3.73 ± 0.01 ns 3.73 ± 0.01 ns 1 ± 0.0038
Quantity/with_numbers/*real 3.11 ± 0.01 ns 2.79 ± 0.001 ns 1.11 ± 0.0036
Quantity/with_numbers/^int 8.98 ± 2.2 ns 8.68 ± 2.2 ns 1.03 ± 0.36
Quantity/with_numbers/^int * real 9.29 ± 2.2 ns 9.29 ± 2.2 ns 1 ± 0.33
Quantity/with_quantity/+y 4.35 ± 0.01 ns 4.35 ± 0.009 ns 1 ± 0.0031
Quantity/with_quantity//y 3.42 ± 0.01 ns 3.41 ± 0.01 ns 1 ± 0.0042
Quantity/with_self/dimension 3.71 ± 0.92 ns 3.1 ± 0.01 ns 1.19 ± 0.3
Quantity/with_self/inv 3.1 ± 0.01 ns 3.11 ± 0.01 ns 1 ± 0.0046
Quantity/with_self/ustrip 2.79 ± 0.91 ns 3.1 ± 0.01 ns 0.903 ± 0.29
QuantityArray/broadcasting/multi_array_of_quantities 0.0906 ± 0.00099 ms 0.0907 ± 0.00078 ms 1 ± 0.014
QuantityArray/broadcasting/multi_normal_array 0.0498 ± 0.00022 ms 0.0498 ± 0.00024 ms 1 ± 0.0065
QuantityArray/broadcasting/multi_quantity_array 0.0539 ± 0.0062 ms 0.0622 ± 0.0091 ms 0.866 ± 0.16
QuantityArray/broadcasting/x^2_array_of_quantities 13.3 ± 2.2 μs 12.6 ± 1.7 μs 1.05 ± 0.22
QuantityArray/broadcasting/x^2_normal_array 2.09 ± 1.8 μs 2.48 ± 2.4 μs 0.843 ± 1.1
QuantityArray/broadcasting/x^2_quantity_array 3.86 ± 0.78 μs 3.54 ± 0.29 μs 1.09 ± 0.24
QuantityArray/broadcasting/x^4_array_of_quantities 0.0845 ± 0.0017 ms 0.0845 ± 0.00061 ms 1 ± 0.022
QuantityArray/broadcasting/x^4_normal_array 0.0466 ± 0.00016 ms 0.0497 ± 0.00022 ms 0.938 ± 0.0053
QuantityArray/broadcasting/x^4_quantity_array 0.0499 ± 0.00017 ms 0.0498 ± 0.003 ms 1 ± 0.061
time_to_load 0.202 ± 0.0026 s 0.198 ± 0.0017 s 1.02 ± 0.016
Memory benchmarks
main 6534a36155302d... main / 6534a36155302d...
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

github-actions[bot] avatar May 27 '25 21:05 github-actions[bot]

Hi @MilesCranmer, here's the latest with Makie's new ComputePipeline now integrated in this PR for your review. Lots of cool stuff going on in their new release!

icweaver avatar Jun 27 '25 02:06 icweaver

@SimonDanisch @jkrumbiegel just to raise this again: I would really really prefer if Makie had this as an extension rather than DQ. As you can see from the latest commit on this PR, there are already edits going in from Makie’s latest version release. Whereas DQ is rock solid. I don’t want the releases of DQ to simply be re-releasing a patch whenever Makie makes a change. Once this part of the API is guaranteed to be stable, I am happy to have it here, but until then, I think it doesn’t make sense to have me maintaining a list of symbols for an unrelated package. Makie already stores Unitful’s extension and they are quite a very similar if not identical API (in fact there have been discussions of having a lightweight AbstractUnits package to formalise this) so I don’t think it will be that bad to include DQ there too. We all have a huge maintenance load; I would say that this is about reducing the combined load on us, which I believe it would.

And thanks @icweaver for your efforts, its much appreciated. Would it be alright if you make this as a PR to Makie? Hopefully the above reasoning makes sense to you - I really appreciate the effort, I just think the location isn’t optimal.

MilesCranmer avatar Jun 27 '25 06:06 MilesCranmer

Hi Miles, totally understand. Happy to get a PR + docs going on Makie's side if their team is open to it. I think this would also help make using our DQConversion a bit more ergonomic for the user

https://github.com/SymbolicML/DynamicQuantities.jl/blob/2f7086eac70ec9c45de6258eb21f35d8d1dc877c/docs/src/examples.md?plain=1#L533-L534

icweaver avatar Jun 27 '25 18:06 icweaver

I guess we can do that;)

SimonDanisch avatar Jun 27 '25 18:06 SimonDanisch

haha, cool, just opened https://github.com/MakieOrg/Makie.jl/pull/5137

icweaver avatar Jun 30 '25 08:06 icweaver

Ok, just updated things here so that it is a companion docs PR to the functionality PR in Makie now

icweaver avatar Jul 12 '25 06:07 icweaver

Codecov Report

:white_check_mark: All modified and coverable lines are covered by tests. :warning: Please upload report for BASE (main@0d7c092). Learn more about missing BASE report. :warning: Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #165   +/-   ##
=======================================
  Coverage        ?   99.21%           
=======================================
  Files           ?       21           
  Lines           ?     1273           
  Branches        ?        0           
=======================================
  Hits            ?     1263           
  Misses          ?       10           
  Partials        ?        0           

:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.

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

codecov[bot] avatar Aug 05 '25 21:08 codecov[bot]

Thanks; could you make a new PR for that though? This thread is kinda long and unrelated

MilesCranmer avatar Aug 06 '25 00:08 MilesCranmer

haha, for sure #183

icweaver avatar Aug 06 '25 03:08 icweaver