Skip to content

Commit 2799e49

Browse files
committed
Merge branch 'new_axes'
2 parents 732a7a0 + 1f373cf commit 2799e49

File tree

12 files changed

+192
-196
lines changed

12 files changed

+192
-196
lines changed

Project.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "ComponentArrays"
22
uuid = "b0b7db55-cfe3-40fc-9ded-d10e2dbeff66"
33
authors = ["Jonnie Diegelman <47193959+jonniedie@users.noreply.github.com>"]
4-
version = "0.10.7"
4+
version = "0.11.0"
55

66
[deps]
77
ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9"
@@ -11,7 +11,6 @@ Requires = "ae029012-a4dd-5104-9daa-d747884805df"
1111

1212
[compat]
1313
ArrayInterface = "2.13, 3"
14-
ChainRulesCore = "0.8, 0.9, 0.10"
1514
Requires = "1.0.1"
1615
julia = "1"
1716

src/ComponentArrays.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ include("lazyarray.jl")
1818
include("axis.jl")
1919
export AbstractAxis, Axis, PartitionedAxis, ShapedAxis, ViewAxis, FlatAxis
2020

21-
include("componentindex.jl")
22-
export KeepIndex
23-
2421
include("componentarray.jl")
2522
export ComponentArray, ComponentVector, ComponentMatrix, getaxes, getdata, valkeys
2623

24+
include("componentindex.jl")
25+
export KeepIndex
26+
2727
include("array_interface.jl")
2828
# Base methods: parent, size, elsize, axes, reinterpret, hcat, vcat, permutedims, IndexStyle, to_indices, to_index, getindex, setindex!, view, pointer, unsafe_convert, strides, stride
2929
# ArrayInterface methods: strides, size, lu_instance, parent_type

src/array_interface.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ ArrayInterface.size(A::ComponentArray) = ArrayInterface.size(parent(A))
55

66
Base.elsize(x::Type{<:ComponentArray{T,N,A,Axes}}) where {T,N,A,Axes} = Base.elsize(A)
77

8-
Base.axes(x::ComponentArray) = axes(getdata(x))
8+
# Base.axes(x::ComponentArray) = axes(getdata(x))
9+
Base.axes(x::ComponentArray) = CombinedAxis.(getaxes(x), axes(getdata(x)))
910

1011
Base.reinterpret(::Type{T}, x::ComponentArray, args...) where T = ComponentArray(reinterpret(T, getdata(x), args...), getaxes(x))
1112

src/axis.jl

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ const NotShapedOrPartitionedAxis = Union{Axis{IdxMap}, FlatAxis, NullAxis} where
140140

141141
Base.merge(axs::Axis...) = Axis(merge(indexmap.(axs)...))
142142

143+
Base.firstindex(ax::AbstractAxis) = first(viewindex(first(indexmap(ax))))
143144
Base.lastindex(ax::AbstractAxis) = last(viewindex(last(indexmap(ax))))
144145

145146
Base.keys(ax::AbstractAxis) = keys(indexmap(ax))
@@ -154,3 +155,39 @@ reindex(ax::ViewAxis, offset) = ViewAxis(viewindex(ax) .+ offset, indexmap(ax))
154155
@inline Base.getindex(ax::AbstractAxis, ::Colon) = ComponentIndex(:, ax)
155156
@inline Base.getindex(::AbstractAxis{IdxMap}, s::Symbol) where IdxMap =
156157
ComponentIndex(getproperty(IdxMap, s))
158+
159+
Base.iterate(ax::AbstractAxis, state=1) = state > lastindex(ax) ? nothing : (ax[state], state+1)
160+
161+
Base.length(ax::AbstractAxis) = lastindex(ax) - firstindex(ax) + 1
162+
163+
Base.UnitRange(ax::AbstractAxis) = firstindex(ax):lastindex(ax)
164+
Base.UnitRange{T}(ax::AbstractAxis) where {T} = T(firstindex(ax)):T(lastindex(ax))
165+
166+
167+
struct CombinedAxis{C,A} <: AbstractUnitRange{Int}
168+
component_axis::C
169+
array_axis::A
170+
end
171+
172+
const CombinedOrRegularAxis = Union{Integer, AbstractUnitRange, CombinedAxis}
173+
174+
_component_axis(ax::CombinedAxis) = ax.component_axis
175+
_component_axis(ax) = FlatAxis()
176+
177+
_array_axis(ax::CombinedAxis) = ax.array_axis
178+
_array_axis(ax) = ax
179+
180+
Base.first(ax::CombinedAxis) = first(_array_axis(ax))
181+
182+
Base.last(ax::CombinedAxis) = last(_array_axis(ax))
183+
184+
Base.firstindex(ax::CombinedAxis) = firstindex(_array_axis(ax))
185+
186+
Base.lastindex(ax::CombinedAxis) = lastindex(_array_axis(ax))
187+
188+
Base.getindex(ax::CombinedAxis, i::Integer) = _array_axis(ax)[i]
189+
Base.getindex(ax::CombinedAxis, i::AbstractArray) = _array_axis(ax)[i]
190+
191+
Base.length(ax::CombinedAxis) = lastindex(ax) - firstindex(ax) + 1
192+
193+
Base.CartesianIndices(ax::Tuple{Vararg{CombinedAxis}}) = CartesianIndices(_array_axis.(ax))

src/broadcasting.jl

Lines changed: 13 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,119 +1,30 @@
11
const BC = Base.Broadcast
22

3+
Base.BroadcastStyle(::Type{<:ComponentArray{T, N, A, Axes}}) where {T, N, A, Axes} = BC.BroadcastStyle(A)
34

4-
struct CAStyle{InnerStyle<:BC.BroadcastStyle, Axes, N} <: BC.AbstractArrayStyle{N} end
5-
CAStyle(::InnerStyle, ::Axes, N) where {InnerStyle, Axes} = CAStyle{InnerStyle, Axes, N}()
6-
CAStyle(::InnerStyle, ::Type{<:Axes}, N) where {InnerStyle, Axes} = CAStyle{InnerStyle, Axes, N}()
5+
Base.getindex(bc::BC.Broadcasted, inds::ComponentIndex...) = bc[value.(inds)...]
76

8-
function CAStyle(::InnerStyle, ax::Axes, ::Val{N}) where {InnerStyle, Axes, N}
9-
return CAStyle(InnerStyle(), ax, N)
10-
end
11-
12-
13-
function Base.BroadcastStyle(::Type{<:ComponentArray{T, N, A, Axes}}) where {T, A, N, Axes}
14-
return CAStyle(Base.BroadcastStyle(A), getaxes(Axes), ndims(A))
15-
end
16-
function Base.BroadcastStyle(AA::Type{<:Adjoint{T, <:ComponentArray{T, N, A, Axes}}}) where {T, N, A, Axes}
17-
return CAStyle(Base.BroadcastStyle(Adjoint{T,A}), getaxes(AA), ndims(AA))
18-
end
19-
function Base.BroadcastStyle(AA::Type{<:Transpose{T, <:ComponentArray{T, N, A, Axes}}}) where {T, N, A, Axes}
20-
return CAStyle(Base.BroadcastStyle(Transpose{T,A}), getaxes(AA), ndims(AA))
21-
end
22-
23-
function Base.BroadcastStyle(::CAStyle{InnerStyle, Axes, N}, bc::BC.Broadcasted) where {InnerStyle, Axes, N}
24-
return CAStyle(Base.BroadcastStyle(InnerStyle(), bc), Axes, N)
25-
end
26-
27-
28-
function BC.BroadcastStyle(::CAStyle{<:In1, <:Ax1, <:N1}, ::CAStyle{<:In2, <:Ax2, <:N2}) where {In1, Ax1, N1, In2, Ax2, N2}
29-
ax, N = fill_flat(Ax1, Ax2, N1, N2)
30-
inner_style = BC.BroadcastStyle(In1(), In2())
31-
if inner_style isa BC.Unknown
32-
inner_style = BC.DefaultArrayStyle{N}()
33-
end
34-
return CAStyle(inner_style, ax, N)
35-
end
36-
function BC.BroadcastStyle(::CAStyle{In, Ax, N1}, ::Style) where Style<:BC.DefaultArrayStyle{N2} where {In, Ax, N1, N2}
37-
N = max(N1, N2)
38-
ax = fill_flat(Ax, max(N1, N2))
39-
inner_style = BC.BroadcastStyle(In(), Style())
40-
return CAStyle(inner_style, ax, N)
41-
end
42-
function BC.BroadcastStyle(CAS::CAStyle{In, Ax, N1}, ::BC.DefaultArrayStyle{0}) where {In, Ax, N1}
43-
return CAS
44-
end
45-
function BC.BroadcastStyle(CAS::CAStyle{In, Ax, N}, ::BC.DefaultArrayStyle{N}) where {In, Ax, N}
46-
return CAS
47-
end
48-
function BC.BroadcastStyle(::CAStyle{In, Ax, N1}, ::Style) where Style<:BC.AbstractArrayStyle{N2} where {In, Ax, N1, N2}
49-
N = max(N1, N2)
50-
ax = fill_flat(Ax, max(N1, N2))
51-
inner_style = BC.BroadcastStyle(In(), Style())
52-
return CAStyle(inner_style, ax, N)
53-
end
54-
55-
56-
Base.convert(::Type{<:BC.Broadcasted{Nothing}}, bc::BC.Broadcasted{<:CAStyle,Axes,F,Args}) where {Axes,F,Args} = getdata(bc)
57-
58-
getdata(bc::BC.Broadcasted{<:CAStyle}) = BC.broadcasted(bc.f, map(getdata, bc.args)...)
59-
60-
61-
function Base.similar(bc::BC.Broadcasted{<:CAStyle{InnerStyle, Axes, N}}, args...) where {InnerStyle, Axes, N}
62-
return ComponentArray{Axes}(similar(BC.Broadcasted{InnerStyle}(bc.f, bc.args, bc.axes), args...))
63-
end
64-
function Base.similar(bc::BC.Broadcasted{<:CAStyle{InnerStyle, Axes, N}}, T::Type) where {InnerStyle, Axes, N}
65-
return ComponentArray{Axes}(similar(BC.Broadcasted{InnerStyle}(bc.f, bc.args, bc.axes), T))
66-
end
67-
function Base.similar(bc::BC.Broadcasted{<:CAStyle{<:BC.Unknown, Axes, N}}, T::Type) where {InnerStyle, Axes, N}
68-
return ComponentArray{Axes}(similar(BC.Broadcasted{BC.DefaultArrayStyle{N}}(bc.f, bc.args, bc.axes), T))
69-
end
7+
# Need special case here for adjoint vectors in order to avoid type instability in axistype
8+
BC.combine_axes(a::ComponentArray, b::AdjOrTransComponentVector) = (axes(a)[1], axes(b)[2])
9+
BC.combine_axes(a::AdjOrTransComponentVector, b::ComponentArray) = (axes(b)[2], axes(a)[1])
7010

11+
BC.axistype(a::CombinedAxis, b::AbstractUnitRange) = a
12+
BC.axistype(a::AbstractUnitRange, b::CombinedAxis) = b
13+
BC.axistype(a::CombinedAxis, b::CombinedAxis) = CombinedAxis(FlatAxis(), Base.Broadcast.axistype(_array_axis(a), _array_axis(b)))
14+
BC.axistype(a::T, b::T) where {T<:CombinedAxis} = a
7115

72-
# BC.broadcasted(f, x::ComponentArray) = ComponentArray(map(f, getdata(x)), getaxes(x))
16+
Base.promote_shape(a::Tuple{Vararg{CombinedAxis}}, b::NTuple{N,AbstractUnitRange}) where N = Base.promote_shape(_array_axis.(a), b)
17+
Base.promote_shape(a::NTuple{N,AbstractUnitRange}, b::Tuple{Vararg{CombinedAxis}}) where N = Base.promote_shape(a, _array_axis.(b))
18+
Base.promote_shape(a::Tuple{Vararg{CombinedAxis}}, b::Tuple{Vararg{CombinedAxis}}) = Base.promote_shape(_array_axis.(a), _array_axis.(b))
19+
Base.promote_shape(a::T, b::T) where {T<:Tuple{Vararg{CombinedAxis}}} = a
7320

7421
# Need a special case here because `map` doesn't follow same rules as normal broadcasting. To be safe and avoid ambiguities,
7522
# we'll just handle the case where everything is a ComponentArray. Else it falls back to a plain Array output.
7623
function Base.map(f, xs::ComponentArray{<:Any, <:Any, <:Any, Axes}...) where Axes
7724
return ComponentArray(map(f, getdata.(xs)...), getaxes(Axes))
7825
end
7926

80-
# function Base.copy(bc::BC.Broadcasted{<:CAStyle{InnerStyle, Axes, N}}) where {InnerStyle, Axes, N}
81-
# return ComponentArray{Axes}(Base.copy(BC.broadcasted(bc.f, map(getdata, bc.args)...)))
82-
# end
83-
# function Base.copy(bc::BC.Broadcasted{<:CAStyle{InnerStyle, Axes, N}}) where {InnerStyle, Axes, N}
84-
# return ComponentArray{Axes}(Base.copy(BC.Broadcasted(InnerStyle())))
85-
# end
8627

8728
# From https://github.com/JuliaArrays/OffsetArrays.jl/blob/master/src/OffsetArrays.jl
8829
Base.dataids(A::ComponentArray) = Base.dataids(parent(A))
8930
Broadcast.broadcast_unalias(dest::ComponentArray, src) = getdata(dest) === getdata(src) ? src : Broadcast.unalias(dest, src)
90-
91-
92-
93-
# Helper for extruding axes
94-
function fill_flat(Ax1, Ax2, N1, N2)
95-
if N1<N2
96-
N = N2
97-
ax1 = fill_flat(Ax1,N)
98-
ax2 = Ax2
99-
elseif N1>N2
100-
N = N1
101-
ax1 = Ax1
102-
ax2 = fill_flat(Ax2,N)
103-
else
104-
N = N1
105-
ax1, ax2 = Ax1, Ax2
106-
end
107-
# Ax = Base.promote_typeof(getaxes(ax1), getaxes(ax2))
108-
Ax = broadcast_promote_typeof(getaxes(ax1), getaxes(ax2))
109-
return Ax, N
110-
end
111-
fill_flat(Ax::Type{<:VarAxes}, N) = fill_flat(getaxes(Ax), N) |> typeof
112-
function fill_flat(Ax::VarAxes, N)
113-
axs = Ax
114-
n = length(axs)
115-
if N>n
116-
axs = (axs..., ntuple(x -> FlatAxis(), N-n)...)
117-
end
118-
return axs
119-
end

src/compat/staticarrays.jl

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,5 @@
11
ComponentArray{A}(::UndefInitializer, ax::Axes) where {A<:StaticArrays.StaticArray,Axes<:Tuple} =
22
ComponentArray(similar(A), ax...)
33

4-
# Sometimes you just need to get things done.
5-
function Base.Broadcast.BroadcastStyle(::Type{s66}) where s66<:Transpose{T,s19} where s19<:ComponentArray{s62,s63,s64,s65} where s65<:Tuple{Vararg{AbstractAxis,N}} where s64<:StaticArrays.StaticArray{S,s62,N} where S<:Tuple where s63<:N where s62<:T where {T, N}
6-
return CAStyle(StaticArrays.StaticArrayStyle{2}(), getaxes(s19), 2)
7-
end
8-
function Base.Broadcast.BroadcastStyle(::Type{s66}) where s66<:Adjoint{T,s19} where s19<:ComponentArray{s62,s63,s64,s65} where s65<:Tuple{Vararg{AbstractAxis,N}} where s64<:StaticArrays.StaticArray{S,s62,N} where S<:Tuple where s63<:N where s62<:T where {T, N}
9-
return CAStyle(StaticArrays.StaticArrayStyle{2}(), getaxes(s19), 2)
10-
end
11-
12-
# This is pretty unideal. It gets the answer right, but it allocates in the process. Need to
13-
# figure this out because what's the point of StaticArrays if they are going to allocate?
14-
function Base.copy(bc::Base.Broadcast.Broadcasted{Style,<:Ax,<:F,s19}) where {s19<:Tuple, Axes, Ax, F, Style<:CAStyle{s18,<:s17,<:s12}} where {s12, s17, s18<:StaticArrays.StaticArrayStyle}
15-
args = map(getdata, bc.args)
16-
# # style = BC.combine_styles(BC.BroadcastStyle.(typeof.(args))...)
17-
return ComponentArray{s17}(Base.copy(BC.broadcasted(bc.f, args...)))
18-
end
194

205

src/componentarray.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ const CMatrix = ComponentMatrix
121121

122122
const AdjOrTrans{T, A} = Union{Adjoint{T, A}, Transpose{T, A}}
123123
const AdjOrTransComponentArray{T, A} = Union{Adjoint{T, A}, Transpose{T, A}} where A<:ComponentArray
124+
const AdjOrTransComponentVector{T} = Union{Adjoint{T, A}, Transpose{T, A}} where A<:ComponentVector
124125

125126
const ComponentVecOrMat = Union{ComponentVector, ComponentMatrix}
126127
const AdjOrTransComponentVecOrMat = AdjOrTrans{T, <:ComponentVecOrMat} where T

src/componentindex.jl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,24 @@ ComponentIndex(idx::Int) = ComponentIndex(idx, NullAxis())
66
ComponentIndex(idx::Union{FlatIdx, Colon}) = ComponentIndex(idx, FlatAxis())
77
ComponentIndex(vax::ViewAxis{Inds,IdxMap,Ax}) where {Inds,IdxMap,Ax} = ComponentIndex(Inds, vax.ax)
88

9+
value(idx::ComponentIndex) = idx.idx
10+
value(idx) = idx
11+
912
const FlatComponentIndex{Idx} = ComponentIndex{Idx, FlatAxis}
1013
const NullComponentIndex{Idx} = ComponentIndex{Idx, NullAxis}
1114

1215
function Base.getindex(A::AbstractArray, ind::ComponentIndex, inds::ComponentIndex...)
1316
inds = (ind, inds...)
1417
return ComponentArray(A[(i.idx for i in inds)...], Tuple(i.ax for i in inds))
1518
end
19+
Base.getindex(A::ComponentArray, ind::ComponentIndex, inds::ComponentIndex...) = getindex(A, ind.idx, (i.idx for i in inds)...)
20+
Base.getindex(bc::Base.Broadcast.Broadcasted{Nothing}, idx::ComponentIndex) = bc[CartesianIndex(idx)]
21+
22+
# Do we still need this?
23+
Base.getindex(ax::AbstractAxis, ind::ComponentIndex) = ax[ind.idx]
24+
25+
Base.CartesianIndex(idx::Union{ComponentIndex, Integer, CartesianIndex}...) = CartesianIndex(value.(idx)...)
26+
1627

1728
"""
1829
KeepIndex(idx)

src/similar_convert_copy.jl

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,39 @@
22
# Similar
33
Base.similar(x::ComponentArray) = ComponentArray(similar(getdata(x)), getaxes(x)...)
44
Base.similar(x::ComponentArray, ::Type{T}) where T = ComponentArray(similar(getdata(x), T), getaxes(x)...)
5-
# Base.similar(x::ComponentArray, ::Type{T}, ax::Tuple{Vararg{Int64,N}}) where {T,N} = similar(x, T, ax...)
6-
# function Base.similar(x::ComponentArray, ::Type{T}, ax::Union{Integer, Base.OneTo}...) where T
7-
# A = similar(getdata(x), T, ax...)
8-
# if size(getdata(x)) == size(A)
9-
# return ComponentArray(A, getaxes(x))
10-
# else
11-
# return A
12-
# end
13-
# end
14-
function Base.similar(x::ComponentArray{T1,N,A,Ax}, ::Type{T}, dims::NTuple{N,Int}) where {T,T1,N,A,Ax}
15-
arr = similar(getdata(x), T, dims)
16-
return ComponentArray(arr, getaxes(x))
17-
end
18-
function Base.similar(x::ComponentArray{T1,N1,A,Ax}, ::Type{T}, dims::NTuple{N2,Int}) where {T,T1,N1,N2,A,Ax}
19-
return similar(getdata(x), T, dims)
20-
end
21-
22-
## TODO: write length method for AbstractAxis so we can do this?
23-
# function Base.similar(::Type{CA}) where CA<:ComponentArray{T,N,A,Axes} where {T,N,A,Axes}
24-
# axs = getaxes(CA)
25-
# return ComponentArray(similar(A, length.(axs)...), axs...)
26-
# end
5+
function Base.similar(x::AbstractArray, dims::Tuple{<:CombinedAxis, Vararg{<:CombinedOrRegularAxis}})
6+
arr = similar(getdata(x), length.(_array_axis.(dims)))
7+
return ComponentArray(arr, _component_axis.(dims)...)
8+
end
9+
function Base.similar(x::AbstractArray, dims::Tuple{<:Union{Integer,AbstractUnitRange}, <:CombinedAxis, Vararg{<:CombinedOrRegularAxis}})
10+
arr = similar(getdata(x), length.(_array_axis.(dims)))
11+
return ComponentArray(arr, _component_axis.(dims)...)
12+
end
13+
function Base.similar(x::AbstractArray, dims::Tuple{<:CombinedAxis, <:CombinedAxis, Vararg{<:CombinedOrRegularAxis}})
14+
arr = similar(getdata(x), length.(_array_axis.(dims)))
15+
return ComponentArray(arr, _component_axis.(dims)...)
16+
end
17+
function Base.similar(x::AbstractArray, ::Type{T}, dims::Tuple{<:CombinedAxis, Vararg{<:CombinedOrRegularAxis}}) where {T}
18+
arr = similar(getdata(x), T, length.(_array_axis.(dims)))
19+
return ComponentArray(arr, _component_axis.(dims)...)
20+
end
21+
function Base.similar(x::AbstractArray, ::Type{T}, dims::Tuple{<:Union{Integer,AbstractUnitRange}, <:CombinedAxis, Vararg{<:CombinedOrRegularAxis}}) where {T}
22+
arr = similar(getdata(x), T, length.(_array_axis.(dims)))
23+
return ComponentArray(arr, _component_axis.(dims)...)
24+
end
25+
function Base.similar(x::Type{<:AbstractArray}, dims::Tuple{<:CombinedAxis, Vararg{<:CombinedOrRegularAxis}})
26+
arr = similar(x, length.(_array_axis.(dims)))
27+
return ComponentArray(arr, _component_axis.(dims)...)
28+
end
29+
function Base.similar(x::Type{<:AbstractArray}, dims::Tuple{<:CombinedOrRegularAxis, <:CombinedAxis, Vararg{<:CombinedOrRegularAxis}})
30+
arr = similar(x, length.(_array_axis.(dims)))
31+
return ComponentArray(arr, _component_axis.(dims)...)
32+
end
33+
function Base.similar(x::Type{<:AbstractArray}, dims::Tuple{<:CombinedAxis, <:CombinedAxis, Vararg{<:CombinedOrRegularAxis}})
34+
arr = similar(x, length.(_array_axis.(dims)))
35+
return ComponentArray(arr, _component_axis.(dims)...)
36+
end
37+
2738

2839
Base.zero(x::ComponentArray) = zero.(x)
2940

test/diffeq_test/Project.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[deps]
2+
DifferentialEquations = "0c46a032-eb83-5123-abaf-570d42b7fbaa"
3+
FastBroadcast = "7034ab61-46d4-4ed7-9d0f-46aef9175898"
4+
LabelledArrays = "2ee39098-c373-598a-b85f-a56591580800"
5+
Sundials = "c3572dad-4567-51f8-b174-8c6c989267f4"
6+
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
7+
Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"

0 commit comments

Comments
 (0)