From 548654eb5f10f95813bd265e1d297744d23143ca Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Tue, 28 Oct 2025 10:37:57 -0400 Subject: [PATCH 1/3] Fix bug with AbstractNetworkIterator types of length 1 not executing their single `compute!` call --- src/solvers/iterators.jl | 5 ++++- test/solvers/test_iterators.jl | 13 +++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/solvers/iterators.jl b/src/solvers/iterators.jl index 990d86d2..b11a86c1 100644 --- a/src/solvers/iterators.jl +++ b/src/solvers/iterators.jl @@ -12,7 +12,10 @@ abstract type AbstractNetworkIterator end islaststep(iterator::AbstractNetworkIterator) = state(iterator) >= length(iterator) function Base.iterate(iterator::AbstractNetworkIterator, init = true) - islaststep(iterator) && return nothing + # The assumption is that first "increment!" is implicit, therefore we must skip the + # the termination check for the first iteration, i.e. `AbstractNetworkIterator` is not + # defined when length < 1, + init || islaststep(iterator) && return nothing # We seperate increment! from step! and demand that any AbstractNetworkIterator *must* # define a method for increment! This way we avoid cases where one may wish to nest # calls to different step! methods accidentaly incrementing multiple times. diff --git a/test/solvers/test_iterators.jl b/test/solvers/test_iterators.jl index 730eee93..7f616d68 100644 --- a/test/solvers/test_iterators.jl +++ b/test/solvers/test_iterators.jl @@ -49,6 +49,19 @@ end import .TestIteratorUtils @testset "`AbstractNetworkIterator` Interface" begin + + @testset "Edge cases" begin + TI = TestIteratorUtils.TestIterator(1, 1, []) + cb = [] + @test islaststep(TI) + for _ in TI + @test islaststep(TI) + push!(cb, state(TI)) + end + @test length(cb) == 1 + @test length(TI.output) == 1 + @test only(cb) == 1 + end TI = TestIteratorUtils.TestIterator(1, 4, []) @test !islaststep((TI)) From 89c787ba5118c5b65ba88bf8dcf96db272efff16 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Tue, 28 Oct 2025 10:39:06 -0400 Subject: [PATCH 2/3] Constructing a `SweepIterator` and `RegionIterator` of length 0 now gives `BoundsError`. Constructing `RegionIterator` of length 0 is now undefined. Adjust region iterator `BoundsError` message to align with `SweepIterator`. --- src/solvers/iterators.jl | 12 +++++++++++- test/solvers/test_iterators.jl | 9 +++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/solvers/iterators.jl b/src/solvers/iterators.jl index b11a86c1..1fe48449 100644 --- a/src/solvers/iterators.jl +++ b/src/solvers/iterators.jl @@ -47,6 +47,9 @@ mutable struct RegionIterator{Problem, RegionPlan} <: AbstractNetworkIterator which_region::Int const which_sweep::Int function RegionIterator(problem::P, region_plan::R, sweep::Int) where {P, R} + if length(region_plan) == 0 + throw(BoundsError("Cannot construct a region iterator with 0 elements.")) + end return new{P, R}(problem, region_plan, 1, sweep) end end @@ -122,8 +125,15 @@ mutable struct SweepIterator{Problem, Iter} <: AbstractNetworkIterator which_sweep::Int function SweepIterator(problem::Prob, sweep_kwargs::Iter) where {Prob, Iter} stateful_sweep_kwargs = Iterators.Stateful(sweep_kwargs) - first_kwargs, _ = Iterators.peel(stateful_sweep_kwargs) + first_state = Iterators.peel(stateful_sweep_kwargs) + + if isnothing(first_state) + throw(BoundsError("Cannot construct a sweep iterator with 0 elements.")) + end + + first_kwargs, _ = first_state region_iter = RegionIterator(problem; sweep = 1, first_kwargs...) + return new{Prob, Iter}(region_iter, stateful_sweep_kwargs, 1) end end diff --git a/test/solvers/test_iterators.jl b/test/solvers/test_iterators.jl index 7f616d68..b88b1202 100644 --- a/test/solvers/test_iterators.jl +++ b/test/solvers/test_iterators.jl @@ -1,5 +1,5 @@ -using Test: @test, @testset -using ITensorNetworks: SweepIterator, islaststep, state, increment!, compute!, eachregion +using Test: @test, @testset, @test_throws +using ITensorNetworks: SweepIterator, RegionIterator, islaststep, state, increment!, compute!, eachregion module TestIteratorUtils @@ -61,7 +61,12 @@ end @test length(cb) == 1 @test length(TI.output) == 1 @test only(cb) == 1 + + prob = TestIteratorUtils.TestProblem([]) + @test_throws BoundsError SweepIterator(prob, 0) + @test_throws BoundsError RegionIterator(prob, [], 1) end + TI = TestIteratorUtils.TestIterator(1, 4, []) @test !islaststep((TI)) From 9d05d683d8606813f841ff27b2b73ca7aeedc585 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Tue, 28 Oct 2025 12:50:02 -0400 Subject: [PATCH 3/3] Add test for single element `EachRegion` types --- test/solvers/test_iterators.jl | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/solvers/test_iterators.jl b/test/solvers/test_iterators.jl index b88b1202..b73e2189 100644 --- a/test/solvers/test_iterators.jl +++ b/test/solvers/test_iterators.jl @@ -189,6 +189,17 @@ end @test prob.data[1:2:end] == fill(1, 5) @test prob.data[2:2:end] == fill(2, 5) + + let i = 1, prob = TestIteratorUtils.TestProblem([]) + SI = SweepIterator(prob, 1) + cb = [] + for _ in eachregion(SI) + push!(cb, i) + i += 1 + end + @test length(cb) == 2 + end + end end end