Skip to content

Commit d6dfe6f

Browse files
committed
Merge #103 from branch clarify-supports-inplace
2 parents e6ead20 + 356d684 commit d6dfe6f

10 files changed

Lines changed: 86 additions & 42 deletions

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ authors = ["Michael Goerz <mail@michaelgoerz.net>"]
44
version = "0.8.5+dev"
55

66
[deps]
7+
ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9"
78
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
89
OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
910
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
@@ -25,6 +26,7 @@ QuantumPropagatorsRecursiveArrayToolsExt = "RecursiveArrayTools"
2526
QuantumPropagatorsStaticArraysExt = "StaticArrays"
2627

2728
[compat]
29+
ArrayInterface = "7.0"
2830
OffsetArrays = "1"
2931
OrdinaryDiffEq = "6.59"
3032
ProgressMeter = "1"

docs/Project.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
[deps]
2+
ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9"
23
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
34
DisplayAs = "0b91fe84-8a4c-11e9-3e1d-67c38462b6d6"
45
DocInventories = "43dc2714-ed3b-44b5-b226-857eda1aa7de"

docs/make.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ links = InterLinks(
4545
"StaticArrays" => "https://juliaarrays.github.io/StaticArrays.jl/stable/",
4646
"ComponentArrays" => "https://sciml.github.io/ComponentArrays.jl/stable/",
4747
"RecursiveArrayTools" => "https://docs.sciml.ai/RecursiveArrayTools/stable/",
48+
"ArrayInterface" => "https://docs.sciml.ai/ArrayInterface/stable/",
4849
"qutip" => "https://qutip.readthedocs.io/en/qutip-5.0.x/",
4950
)
5051

src/interfaces/operator.jl

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Test
22

33
using LinearAlgebra
4+
import ArrayInterface
45
using ..Controls: get_controls, evaluate
56

67

@@ -52,14 +53,18 @@ for two-dimensional arrays:
5253
* `length(op)` must equal `prod(size(op))`
5354
* `iterate(op)` must be defined
5455
* `similar(op)` must be defined and return a mutable array with the same shape
55-
and element type
56+
and element type. "Mutability" is determined by
57+
[`ArrayInterface.ismutable`](@extref).
5658
* `similar(op, ::Type{S})` must return a mutable array with the same shape and
5759
element type `S`
5860
* `similar(op, dims::Dims)` must return a mutable array with the same element
5961
type and the given dimensions
6062
* `similar(op, ::Type{S}, dims::Dims)` must return a mutable array with the
6163
given element type and dimensions
6264
65+
The read-write method `setindex!` is not a requirement for
66+
`supports_matrix_interface`.
67+
6368
The function returns `true` for a valid operator and `false` for an invalid
6469
operator. Unless `quiet=true`, it will log an error to indicate which of the
6570
conditions failed.
@@ -328,9 +333,9 @@ function check_operator(
328333

329334
try
330335
op2 = similar(op)
331-
if !supports_inplace(op2)
336+
if !ArrayInterface.ismutable(op2)
332337
quiet ||
333-
@error "$(px)`similar(op)` must return a mutable array (`supports_inplace` must be `true`), got $(typeof(op2))"
338+
@error "$(px)`similar(op)` must return a mutable array (`ArrayInterface.ismutable` must be `true`), got $(typeof(op2))"
334339
success = false
335340
end
336341
if size(op2) != size(op)
@@ -354,9 +359,9 @@ function check_operator(
354359
try
355360
S = (eltype(op) == ComplexF64) ? ComplexF32 : ComplexF64
356361
op2 = similar(op, S)
357-
if !supports_inplace(op2)
362+
if !ArrayInterface.ismutable(op2)
358363
quiet ||
359-
@error "$(px)`similar(op, $S)` must return a mutable array (`supports_inplace` must be `true`), got $(typeof(op2))"
364+
@error "$(px)`similar(op, $S)` must return a mutable array (`ArrayInterface.ismutable` must be `true`), got $(typeof(op2))"
360365
success = false
361366
end
362367
if size(op2) != size(op)
@@ -380,9 +385,9 @@ function check_operator(
380385
try
381386
dims = size(op)
382387
op2 = similar(op, dims)
383-
if !supports_inplace(op2)
388+
if !ArrayInterface.ismutable(op2)
384389
quiet ||
385-
@error "$(px)`similar(op, dims)` must return a mutable array (`supports_inplace` must be `true`), got $(typeof(op2))"
390+
@error "$(px)`similar(op, dims)` must return a mutable array (`ArrayInterface.ismutable` must be `true`), got $(typeof(op2))"
386391
success = false
387392
end
388393
if size(op2) != dims
@@ -407,9 +412,9 @@ function check_operator(
407412
S = (eltype(op) == ComplexF64) ? ComplexF32 : ComplexF64
408413
dims = size(op)
409414
op2 = similar(op, S, dims)
410-
if !supports_inplace(op2)
415+
if !ArrayInterface.ismutable(op2)
411416
quiet ||
412-
@error "$(px)`similar(op, $S, dims)` must return a mutable array (`supports_inplace` must be `true`), got $(typeof(op2))"
417+
@error "$(px)`similar(op, $S, dims)` must return a mutable array (`ArrayInterface.ismutable` must be `true`), got $(typeof(op2))"
413418
success = false
414419
end
415420
if size(op2) != dims

src/interfaces/state.jl

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Test
22

3+
import ArrayInterface
34
using LinearAlgebra
45

56

@@ -36,8 +37,8 @@ Any `state` must support the following not-in-place operations:
3637
If `supports_inplace(state)` is `true`, the `state` must also support the
3738
following:
3839
39-
* `similar(state)` must be defined and return a valid state of the same type a
40-
`state`
40+
* `similar(state)` must be defined and return a valid state of the same type as
41+
the original `state`
4142
* `copyto!(other, state)` must be defined
4243
* `fill!(state, c)` must be defined
4344
* `LinearAlgebra.lmul!(c, state)` for a scalar `c` must be defined
@@ -60,7 +61,8 @@ for one-dimensional arrays:
6061
* `length(state)` must equal `prod(size(state))`
6162
* `iterate(state)` must be defined
6263
* `similar(state)` must be defined and return a mutable vector with the same
63-
length and element type.
64+
length and element type. "Mutability" is determined by
65+
[`ArrayInterface.ismutable`](@extref).
6466
* `similar(state, ::Type{S})` must return a mutable vector with the same length
6567
and element type `S`
6668
* `similar(state, dims::Dims)` must return a mutable array with the same
@@ -454,9 +456,9 @@ function check_state(
454456

455457
try
456458
st2 = similar(state)
457-
if !supports_inplace(st2)
459+
if !ArrayInterface.ismutable(st2)
458460
quiet ||
459-
@error "$(px)`similar(state)` must return a mutable vector (`supports_inplace` must be `true`), got $(typeof(st2))"
461+
@error "$(px)`similar(state)` must return a mutable vector (`ArrayInterface.ismutable` must be `true`), got $(typeof(st2))"
460462
success = false
461463
end
462464
if size(st2) != size(state)
@@ -480,9 +482,9 @@ function check_state(
480482
try
481483
S = (eltype(state) == ComplexF64) ? ComplexF32 : ComplexF64
482484
st2 = similar(state, S)
483-
if !supports_inplace(st2)
485+
if !ArrayInterface.ismutable(st2)
484486
quiet ||
485-
@error "$(px)`similar(state, $S)` must return a mutable vector (`supports_inplace` must be `true`), got $(typeof(st2))"
487+
@error "$(px)`similar(state, $S)` must return a mutable vector (`ArrayInterface.ismutable` must be `true`), got $(typeof(st2))"
486488
success = false
487489
end
488490
if size(st2) != size(state)
@@ -506,9 +508,9 @@ function check_state(
506508
try
507509
dims = size(state)
508510
st2 = similar(state, dims)
509-
if !supports_inplace(st2)
511+
if !ArrayInterface.ismutable(st2)
510512
quiet ||
511-
@error "$(px)`similar(state, dims)` must return a mutable array (`supports_inplace` must be `true`), got $(typeof(st2))"
513+
@error "$(px)`similar(state, dims)` must return a mutable array (`ArrayInterface.ismutable` must be `true`), got $(typeof(st2))"
512514
success = false
513515
end
514516
if size(st2) != dims
@@ -533,9 +535,9 @@ function check_state(
533535
S = (eltype(state) == ComplexF64) ? ComplexF32 : ComplexF64
534536
dims = size(state)
535537
st2 = similar(state, S, dims)
536-
if !supports_inplace(st2)
538+
if !ArrayInterface.ismutable(st2)
537539
quiet ||
538-
@error "$(px)`similar(state, $S, dims)` must return a mutable array (`supports_inplace` must be `true`), got $(typeof(st2))"
540+
@error "$(px)`similar(state, $S, dims)` must return a mutable array (`ArrayInterface.ismutable` must be `true`), got $(typeof(st2))"
539541
success = false
540542
end
541543
if size(st2) != dims

src/interfaces/supports_inplace.jl

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import ..Operator
22
import ..ScaledOperator
33
import LinearAlgebra
44
import SparseArrays: SparseMatrixCSC
5+
import ArrayInterface
56

67
"""Indicate whether a type supports in-place operations.
78
@@ -22,27 +23,33 @@ see [`QuantumPropagators.Interfaces.check_operator`](@ref).
2223
2324
For operators, a `true` result indicates that the operator can be evaluated
2425
in-place with [`evaluate!`](@ref), see
25-
[`QuantumPropagators.Interfaces.check_generator`](@ref).
26-
27-
Note that `supports_inplace` is not quite the same as
28-
[`Base.ismutabletype`](@extref) and/or [`Base.ismutable`](@extref): When using
29-
[custom structs](@extref Julia :label:`Mutable-Composite-Types`) for states or
26+
[`QuantumPropagators.Interfaces.check_generator`](@ref). Again, this is
27+
intended only as an indicator for what assumptions can be made in the
28+
implementation of a particular propagator: `supports_inplace` is semantically
29+
separate from [`Base.ismutabletype`](@extref), [`Base.ismutable`](@extref), or
30+
similar "traits": When using [custom structs](@extref Julia
31+
:label:`Mutable-Composite-Types`) for states or
3032
operators, even if those structs are not defined as `mutable`, they may still
3133
define the in-place interface (typically because their *components* are
32-
mutable).
34+
mutable). Conversely, even types that are "mutable" may want to opt out
35+
of `evaluate!` for performance reasons.
36+
37+
Mutable abstract arrays ([`ArrayInterface.ismutable`](@extref)) without
38+
considerable performance issues
39+
([`ArrayInterface.fast_scalar_indexing`](@extref))
40+
should support in-place operations.
3341
"""
3442
supports_inplace(::Type{<:Vector{ComplexF64}}) = true
35-
supports_inplace(::Type{T}) where {T<:AbstractVector} = ismutabletype(T) # fallback
36-
# The fallback doesn't actually guarantee that the required interface implied
37-
# by `supports_inplace` is fulfilled, but it's a reasonable expectation to
38-
# have, and the `check_state` function will test it.
3943

4044
supports_inplace(::Type{<:Matrix}) = true
4145
supports_inplace(::Type{<:Operator}) = true
4246
supports_inplace(::Type{<:LinearAlgebra.Diagonal}) = true
43-
supports_inplace(::Type{<:SparseMatrixCSC}) = true
47+
supports_inplace(::Type{<:SparseMatrixCSC}) = true # XXX is this a good idea?
4448
supports_inplace(::Type{<:ScaledOperator{<:Any,OT}}) where {OT} = supports_inplace(OT)
45-
supports_inplace(::Type{T}) where {T<:AbstractMatrix} = ismutabletype(T) # fallback
49+
50+
# Fallback (both for operators and states)
51+
supports_inplace(::Type{T}) where {T<:AbstractArray} =
52+
ArrayInterface.ismutable(T) && ArrayInterface.fast_scalar_indexing(T)
4653

4754
# Generic catch-all for types without a specific method (prevents StackOverflow
4855
# from the value→type fallback below)

src/interfaces/supports_matrix_interface.jl

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@ returns `true` if `T` implements the
99
for two-dimensional arrays. This is `true` for all subtypes of
1010
`AbstractMatrix`, but may also be `true` for types that implement an array
1111
interface (`size`, `getindex`, etc.) without declaring themselves subtypes
12-
of `AbstractMatrix`. Calling `supports_matrix_interface` on an instance
12+
of `AbstractMatrix`.
13+
14+
A `setindex!` method is not a requirement for `supports_matrix_interface`. That
15+
is, only the read-interface for matrices is enforced.
16+
17+
Calling `supports_matrix_interface` on an instance
1318
`x` also works via a convenience fallback that forwards to
1419
`supports_matrix_interface(typeof(x))`.
1520

test/Project.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
[deps]
2+
ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9"
23
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
34
ComponentArrays = "b0b7db55-cfe3-40fc-9ded-d10e2dbeff66"
45
Coverage = "a2441757-f6aa-5fb2-8edb-039e3f45d037"

test/test_invalid_interfaces.jl

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -179,10 +179,7 @@ end
179179
end
180180
@test captured.value false
181181
# similar(op): wrong mutability, shape, and eltype
182-
@test contains(
183-
captured.output,
184-
"`similar(op)` must return a mutable array (`supports_inplace` must be `true`)"
185-
)
182+
@test contains(captured.output, "`similar(op)` must return a mutable array")
186183
@test contains(
187184
captured.output,
188185
"`similar(op)` must return an array with the same shape"
@@ -627,10 +624,7 @@ end
627624
end
628625
@test captured.value false
629626
# similar(state): wrong mutability, shape, and eltype
630-
@test contains(
631-
captured.output,
632-
"`similar(state)` must return a mutable vector (`supports_inplace` must be `true`)"
633-
)
627+
@test contains(captured.output, "`similar(state)` must return a mutable vector")
634628
@test contains(
635629
captured.output,
636630
"`similar(state)` must return a vector with the same shape"

test/test_operator_linalg.jl

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
using Test
22
using LinearAlgebra
33
using QuantumControlTestUtils.RandomObjects: random_matrix, random_state_vector
4-
using QuantumPropagators.Interfaces: check_operator, supports_matrix_interface
4+
using QuantumPropagators.Interfaces:
5+
check_operator, check_generator, supports_matrix_interface
56
import QuantumPropagators.Interfaces: supports_inplace
67
import QuantumPropagators.Controls: get_controls, evaluate
8+
import ArrayInterface
79
using StaticArrays: SMatrix, SVector
810

911
using QuantumPropagators: Generator, Operator, ScaledOperator
@@ -332,3 +334,27 @@ end
332334
@test check_operator(SFreeOp; state = Ψ)
333335

334336
end
337+
338+
339+
@testset "Hermitian matrix supports in-place operations" begin
340+
341+
# Test the resolution of
342+
# https://github.com/JuliaQuantumControl/QuantumPropagators.jl/issues/102
343+
344+
Ψ0 = ComplexF64[1, 0]
345+
= ComplexF64[
346+
0 0.5
347+
0.5 0
348+
]
349+
tlist = collect(range(0.0, 1.0, length = 101))
350+
generator = (Hermitian(Ĥ),)
351+
op = evaluate(generator, tlist, 1)
352+
T = Hermitian{ComplexF64,Matrix{ComplexF64}}
353+
@test op isa T
354+
@test supports_inplace(T)
355+
op2 = similar(op)
356+
@test op2 isa T
357+
@test ArrayInterface.ismutable(T)
358+
@test check_generator(generator; state = Ψ0, tlist)
359+
360+
end

0 commit comments

Comments
 (0)