From dc41a58d06ae7cc5a0ac9c42c5e12d3e8bb0d7d5 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Tue, 28 Oct 2025 15:12:37 -0400 Subject: [PATCH 1/9] Reintroduce `AbstractNetworkIterator` and `AbstractProblem` interface --- src/ITensorNetworksNext.jl | 2 + src/abstract_problem.jl | 1 + src/iterators.jl | 173 +++++++++++++++++++++++++++++++++++++ test/test_iterators.jl | 161 ++++++++++++++++++++++++++++++++++ 4 files changed, 337 insertions(+) create mode 100644 src/abstract_problem.jl create mode 100644 src/iterators.jl create mode 100644 test/test_iterators.jl diff --git a/src/ITensorNetworksNext.jl b/src/ITensorNetworksNext.jl index b59c3bd..e1cded3 100644 --- a/src/ITensorNetworksNext.jl +++ b/src/ITensorNetworksNext.jl @@ -4,5 +4,7 @@ include("lazynameddimsarrays.jl") include("abstracttensornetwork.jl") include("tensornetwork.jl") include("contract_network.jl") +include("abstract_problem.jl") +include("iterators.jl") end diff --git a/src/abstract_problem.jl b/src/abstract_problem.jl new file mode 100644 index 0000000..5a65e0a --- /dev/null +++ b/src/abstract_problem.jl @@ -0,0 +1 @@ +abstract type AbstractProblem end diff --git a/src/iterators.jl b/src/iterators.jl new file mode 100644 index 0000000..1fe4844 --- /dev/null +++ b/src/iterators.jl @@ -0,0 +1,173 @@ +""" + abstract type AbstractNetworkIterator + +A stateful iterator with two states: `increment!` and `compute!`. Each iteration begins +with a call to `increment!` before executing `compute!`, however the initial call to +`iterate` skips the `increment!` call as it is assumed the iterator is initalized such that +this call is implict. Termination of the iterator is controlled by the function `done`. +""" +abstract type AbstractNetworkIterator end + +# We use greater than or equals here as we increment the state at the start of the iteration +islaststep(iterator::AbstractNetworkIterator) = state(iterator) >= length(iterator) + +function Base.iterate(iterator::AbstractNetworkIterator, init = true) + # 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. + init || increment!(iterator) + rv = compute!(iterator) + return rv, false +end + +function increment! end +compute!(iterator::AbstractNetworkIterator) = iterator + +step!(iterator::AbstractNetworkIterator) = step!(identity, iterator) +function step!(f, iterator::AbstractNetworkIterator) + compute!(iterator) + f(iterator) + increment!(iterator) + return iterator +end + +# +# RegionIterator +# +""" + struct RegionIterator{Problem, RegionPlan} <: AbstractNetworkIterator +""" +mutable struct RegionIterator{Problem, RegionPlan} <: AbstractNetworkIterator + problem::Problem + region_plan::RegionPlan + 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 + +function RegionIterator(problem; sweep, sweep_kwargs...) + plan = region_plan(problem; sweep_kwargs...) + return RegionIterator(problem, plan, sweep) +end + +function new_region_iterator(iterator::RegionIterator; sweep_kwargs...) + return RegionIterator(iterator.problem; sweep_kwargs...) +end + +state(region_iter::RegionIterator) = region_iter.which_region +Base.length(region_iter::RegionIterator) = length(region_iter.region_plan) + +problem(region_iter::RegionIterator) = region_iter.problem + +function current_region_plan(region_iter::RegionIterator) + return region_iter.region_plan[region_iter.which_region] +end + +function current_region(region_iter::RegionIterator) + region, _ = current_region_plan(region_iter) + return region +end + +function region_kwargs(region_iter::RegionIterator) + _, kwargs = current_region_plan(region_iter) + return kwargs +end +function region_kwargs(f::Function, iter::RegionIterator) + return get(region_kwargs(iter), Symbol(f, :_kwargs), (;)) +end + +function prev_region(region_iter::RegionIterator) + state(region_iter) <= 1 && return nothing + prev, _ = region_iter.region_plan[region_iter.which_region - 1] + return prev +end + +function next_region(region_iter::RegionIterator) + islaststep(region_iter) && return nothing + next, _ = region_iter.region_plan[region_iter.which_region + 1] + return next +end + +# +# Functions associated with RegionIterator +# +function increment!(region_iter::RegionIterator) + region_iter.which_region += 1 + return region_iter +end + +function compute!(iter::RegionIterator) + _, local_state = extract!(iter; region_kwargs(extract!, iter)...) + _, local_state = update!(iter, local_state; region_kwargs(update!, iter)...) + insert!(iter, local_state; region_kwargs(insert!, iter)...) + + return iter +end + +region_plan(problem; sweep_kwargs...) = euler_sweep(state(problem); sweep_kwargs...) + +# +# SweepIterator +# + +mutable struct SweepIterator{Problem, Iter} <: AbstractNetworkIterator + region_iter::RegionIterator{Problem} + sweep_kwargs::Iterators.Stateful{Iter} + which_sweep::Int + function SweepIterator(problem::Prob, sweep_kwargs::Iter) where {Prob, Iter} + stateful_sweep_kwargs = Iterators.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 + +islaststep(sweep_iter::SweepIterator) = isnothing(peek(sweep_iter.sweep_kwargs)) + +region_iterator(sweep_iter::SweepIterator) = sweep_iter.region_iter +problem(sweep_iter::SweepIterator) = problem(region_iterator(sweep_iter)) + +state(sweep_iter::SweepIterator) = sweep_iter.which_sweep +Base.length(sweep_iter::SweepIterator) = length(sweep_iter.sweep_kwargs) +function increment!(sweep_iter::SweepIterator) + sweep_iter.which_sweep += 1 + sweep_kwargs, _ = Iterators.peel(sweep_iter.sweep_kwargs) + update_region_iterator!(sweep_iter; sweep_kwargs...) + return sweep_iter +end + +function update_region_iterator!(iterator::SweepIterator; kwargs...) + sweep = state(iterator) + iterator.region_iter = new_region_iterator(iterator.region_iter; sweep, kwargs...) + return iterator +end + +function compute!(sweep_iter::SweepIterator) + for _ in sweep_iter.region_iter + # TODO: Is it sensible to execute the default region callback function? + end + return +end + +# More basic constructor where sweep_kwargs are constant throughout sweeps +function SweepIterator(problem, nsweeps::Int; sweep_kwargs...) + # Initialize this to an empty RegionIterator + sweep_kwargs_iter = Iterators.repeated(sweep_kwargs, nsweeps) + return SweepIterator(problem, sweep_kwargs_iter) +end diff --git a/test/test_iterators.jl b/test/test_iterators.jl new file mode 100644 index 0000000..456ebcf --- /dev/null +++ b/test/test_iterators.jl @@ -0,0 +1,161 @@ +using Test: @test, @testset, @test_throws +import ITensorNetworksNext as ITensorNetworks +using .ITensorNetworks: SweepIterator, RegionIterator, islaststep, state, increment!, compute!, eachregion + +module TestIteratorUtils + + import ITensorNetworksNext as ITensorNetworks + using .ITensorNetworks + + struct TestProblem <: ITensorNetworks.AbstractProblem + data::Vector{Int} + end + ITensorNetworks.region_plan(::TestProblem) = [:a => (; val = 1), :b => (; val = 2)] + function ITensorNetworks.compute!(iter::ITensorNetworks.RegionIterator{<:TestProblem}) + kwargs = ITensorNetworks.region_kwargs(iter) + push!(ITensorNetworks.problem(iter).data, kwargs.val) + return iter + end + + + mutable struct TestIterator <: ITensorNetworks.AbstractNetworkIterator + state::Int + max::Int + output::Vector{Int} + end + + ITensorNetworks.increment!(TI::TestIterator) = TI.state += 1 + Base.length(TI::TestIterator) = TI.max + ITensorNetworks.state(TI::TestIterator) = TI.state + function ITensorNetworks.compute!(TI::TestIterator) + push!(TI.output, ITensorNetworks.state(TI)) + return TI + end + + mutable struct SquareAdapter <: ITensorNetworks.AbstractNetworkIterator + parent::TestIterator + end + + Base.length(SA::SquareAdapter) = length(SA.parent) + ITensorNetworks.increment!(SA::SquareAdapter) = ITensorNetworks.increment!(SA.parent) + ITensorNetworks.state(SA::SquareAdapter) = ITensorNetworks.state(SA.parent) + function ITensorNetworks.compute!(SA::SquareAdapter) + ITensorNetworks.compute!(SA.parent) + return last(SA.parent.output)^2 + end + +end + +@testset "Iterators" begin + + 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 + + prob = TestIteratorUtils.TestProblem([]) + @test_throws BoundsError SweepIterator(prob, 0) + @test_throws BoundsError RegionIterator(prob, [], 1) + end + + TI = TestIteratorUtils.TestIterator(1, 4, []) + + @test !islaststep((TI)) + + # First iterator should compute only + rv, st = iterate(TI) + @test !islaststep((TI)) + @test !st + @test rv === TI + @test length(TI.output) == 1 + @test only(TI.output) == 1 + @test state(TI) == 1 + @test !st + + rv, st = iterate(TI, st) + @test !islaststep((TI)) + @test !st + @test length(TI.output) == 2 + @test state(TI) == 2 + @test TI.output == [1, 2] + + increment!(TI) + @test !islaststep((TI)) + @test state(TI) == 3 + @test length(TI.output) == 2 + @test TI.output == [1, 2] + + compute!(TI) + @test !islaststep((TI)) + @test state(TI) == 3 + @test length(TI.output) == 3 + @test TI.output == [1, 2, 3] + + # Final Step + iterate(TI, false) + @test islaststep((TI)) + @test state(TI) == 4 + @test length(TI.output) == 4 + @test TI.output == [1, 2, 3, 4] + + @test iterate(TI, false) === nothing + + TI = TestIteratorUtils.TestIterator(1, 5, []) + + cb = [] + + for _ in TI + @test length(cb) == length(TI.output) - 1 + @test cb == (TI.output)[1:(end - 1)] + push!(cb, state(TI)) + @test cb == TI.output + end + + @test islaststep((TI)) + @test length(TI.output) == 5 + @test length(cb) == 5 + @test cb == TI.output + + + TI = TestIteratorUtils.TestIterator(1, 5, []) + end + + @testset "Adapters" begin + TI = TestIteratorUtils.TestIterator(1, 5, []) + SA = TestIteratorUtils.SquareAdapter(TI) + + @testset "Generic" begin + + i = 0 + for rv in SA + i += 1 + @test rv isa Int + @test rv == i^2 + @test state(SA) == i + end + + @test islaststep((SA)) + + TI = TestIteratorUtils.TestIterator(1, 5, []) + SA = TestIteratorUtils.SquareAdapter(TI) + + SA_c = collect(SA) + + @test SA_c isa Vector + @test length(SA_c) == 5 + @test SA_c == [1, 4, 9, 16, 25] + + end + end +end From d739a6e2af3911af831baa2a9fe49ef81253321c Mon Sep 17 00:00:00 2001 From: Jack Dunham <72548217+jack-dunham@users.noreply.github.com> Date: Mon, 3 Nov 2025 10:13:57 -0500 Subject: [PATCH 2/9] Alphabetise `using` statements. Co-authored-by: Matt Fishman --- test/test_iterators.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_iterators.jl b/test/test_iterators.jl index 456ebcf..8b788e6 100644 --- a/test/test_iterators.jl +++ b/test/test_iterators.jl @@ -1,6 +1,6 @@ using Test: @test, @testset, @test_throws import ITensorNetworksNext as ITensorNetworks -using .ITensorNetworks: SweepIterator, RegionIterator, islaststep, state, increment!, compute!, eachregion +using .ITensorNetworks: RegionIterator, SweepIterator, compute!, increment!, islaststep, state module TestIteratorUtils From 481089ae070f9dd4aca42fc80c7a16fe1046df18 Mon Sep 17 00:00:00 2001 From: Jack Dunham <72548217+jack-dunham@users.noreply.github.com> Date: Mon, 3 Nov 2025 10:16:28 -0500 Subject: [PATCH 3/9] Include type signature in `increment!` definition. Co-authored-by: Matt Fishman --- src/iterators.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iterators.jl b/src/iterators.jl index 1fe4844..a51580b 100644 --- a/src/iterators.jl +++ b/src/iterators.jl @@ -24,7 +24,7 @@ function Base.iterate(iterator::AbstractNetworkIterator, init = true) return rv, false end -function increment! end +increment!(iterator::AbstractNetworkIterator) = throw(MethodError(increment!, Tuple{typeof(iterator)})) compute!(iterator::AbstractNetworkIterator) = iterator step!(iterator::AbstractNetworkIterator) = step!(identity, iterator) From b9386f21cdf13f80711c5cfeb191dab5dd8dc0ac Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Mon, 3 Nov 2025 11:30:18 -0500 Subject: [PATCH 4/9] Improve error handling of empty iterators --- src/iterators.jl | 12 +++++++++--- test/test_iterators.jl | 4 ++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/iterators.jl b/src/iterators.jl index a51580b..aa73f45 100644 --- a/src/iterators.jl +++ b/src/iterators.jl @@ -47,8 +47,13 @@ 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.")) + try + first(region_plan) + catch e + if e isa BoundsError + throw(ArgumentError("Cannot construct a region iterator with 0 elements.")) + end + rethrow() end return new{P, R}(problem, region_plan, 1, sweep) end @@ -125,10 +130,11 @@ 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_state = Iterators.peel(stateful_sweep_kwargs) if isnothing(first_state) - throw(BoundsError("Cannot construct a sweep iterator with 0 elements.")) + throw(ArgumentError("Cannot construct a sweep iterator with 0 elements.")) end first_kwargs, _ = first_state diff --git a/test/test_iterators.jl b/test/test_iterators.jl index 8b788e6..2775e8f 100644 --- a/test/test_iterators.jl +++ b/test/test_iterators.jl @@ -65,8 +65,8 @@ end @test only(cb) == 1 prob = TestIteratorUtils.TestProblem([]) - @test_throws BoundsError SweepIterator(prob, 0) - @test_throws BoundsError RegionIterator(prob, [], 1) + @test_throws ArgumentError SweepIterator(prob, 0) + @test_throws ArgumentError RegionIterator(prob, [], 1) end TI = TestIteratorUtils.TestIterator(1, 4, []) From cf4728249dfb7e3ba935bbe338ba66fec7af4e51 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Mon, 3 Nov 2025 12:22:12 -0500 Subject: [PATCH 5/9] Remove try catch statement in `RegionIterator` construction for empty `region_plan` Now just throws an `ArgumentError` --- src/iterators.jl | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/iterators.jl b/src/iterators.jl index aa73f45..acba2b2 100644 --- a/src/iterators.jl +++ b/src/iterators.jl @@ -47,13 +47,8 @@ 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} - try - first(region_plan) - catch e - if e isa BoundsError - throw(ArgumentError("Cannot construct a region iterator with 0 elements.")) - end - rethrow() + if isempty(region_plan) + throw(ArgumentError("Cannot construct a region iterator with 0 elements.")) end return new{P, R}(problem, region_plan, 1, sweep) end From 5207a3af0a08317cedc3a1c9d2dd97236a5c02fe Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Mon, 3 Nov 2025 12:24:19 -0500 Subject: [PATCH 6/9] Interface now assumes `local_state` is stored in the `AbstractProblem` when required --- src/iterators.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/iterators.jl b/src/iterators.jl index acba2b2..3e0b5c7 100644 --- a/src/iterators.jl +++ b/src/iterators.jl @@ -106,9 +106,9 @@ function increment!(region_iter::RegionIterator) end function compute!(iter::RegionIterator) - _, local_state = extract!(iter; region_kwargs(extract!, iter)...) - _, local_state = update!(iter, local_state; region_kwargs(update!, iter)...) - insert!(iter, local_state; region_kwargs(insert!, iter)...) + extract!(iter; region_kwargs(extract!, iter)...) + update!(iter; region_kwargs(update!, iter)...) + insert!(iter; region_kwargs(insert!, iter)...) return iter end From 790fd68d27b7c63224f9c8c9576a054f4fb17746 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Mon, 3 Nov 2025 12:26:30 -0500 Subject: [PATCH 7/9] Remove `new_region_iterator` function. --- src/iterators.jl | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/iterators.jl b/src/iterators.jl index 3e0b5c7..62d5b21 100644 --- a/src/iterators.jl +++ b/src/iterators.jl @@ -59,10 +59,6 @@ function RegionIterator(problem; sweep, sweep_kwargs...) return RegionIterator(problem, plan, sweep) end -function new_region_iterator(iterator::RegionIterator; sweep_kwargs...) - return RegionIterator(iterator.problem; sweep_kwargs...) -end - state(region_iter::RegionIterator) = region_iter.which_region Base.length(region_iter::RegionIterator) = length(region_iter.region_plan) @@ -155,7 +151,7 @@ end function update_region_iterator!(iterator::SweepIterator; kwargs...) sweep = state(iterator) - iterator.region_iter = new_region_iterator(iterator.region_iter; sweep, kwargs...) + iterator.region_iter = RegionIterator(problem(iterator); sweep, kwargs...) return iterator end From 7e9ba0f27b463b6bc81e8b0ffd685cc8df853c47 Mon Sep 17 00:00:00 2001 From: Jack Dunham Date: Mon, 3 Nov 2025 14:21:48 -0500 Subject: [PATCH 8/9] Add `adapters.jl` code --- src/ITensorNetworksNext.jl | 1 + src/adapters.jl | 45 +++++++++++++++++++++++++++ test/test_iterators.jl | 62 +++++++++++++++++++++++++++++++++++++- 3 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 src/adapters.jl diff --git a/src/ITensorNetworksNext.jl b/src/ITensorNetworksNext.jl index e1cded3..6e2a466 100644 --- a/src/ITensorNetworksNext.jl +++ b/src/ITensorNetworksNext.jl @@ -6,5 +6,6 @@ include("tensornetwork.jl") include("contract_network.jl") include("abstract_problem.jl") include("iterators.jl") +include("adapters.jl") end diff --git a/src/adapters.jl b/src/adapters.jl new file mode 100644 index 0000000..28318fb --- /dev/null +++ b/src/adapters.jl @@ -0,0 +1,45 @@ +""" + struct IncrementOnly{S<:AbstractNetworkIterator} <: AbstractNetworkIterator + +Iterator wrapper whos `compute!` function simply returns itself, doing nothing in the +process. This allows one to manually call a custom `compute!` or insert their own code it in +the loop body in place of `compute!`. +""" +struct IncrementOnly{S <: AbstractNetworkIterator} <: AbstractNetworkIterator + parent::S +end + +islaststep(adapter::IncrementOnly) = islaststep(adapter.parent) +increment!(adapter::IncrementOnly) = increment!(adapter.parent) +compute!(adapter::IncrementOnly) = adapter + +IncrementOnly(adapter::IncrementOnly) = adapter + +""" + struct EachRegion{SweepIterator} <: AbstractNetworkIterator + +Adapter that flattens each region iterator in the parent sweep iterator into a single +iterator. +""" +struct EachRegion{SI <: SweepIterator} <: AbstractNetworkIterator + parent::SI +end + +# In keeping with Julia convention. +eachregion(iter::SweepIterator) = EachRegion(iter) + +# Essential definitions +function islaststep(adapter::EachRegion) + region_iter = region_iterator(adapter.parent) + return islaststep(adapter.parent) && islaststep(region_iter) +end +function increment!(adapter::EachRegion) + region_iter = region_iterator(adapter.parent) + islaststep(region_iter) ? increment!(adapter.parent) : increment!(region_iter) + return adapter +end +function compute!(adapter::EachRegion) + region_iter = region_iterator(adapter.parent) + compute!(region_iter) + return adapter +end diff --git a/test/test_iterators.jl b/test/test_iterators.jl index 2775e8f..a17c7be 100644 --- a/test/test_iterators.jl +++ b/test/test_iterators.jl @@ -1,6 +1,6 @@ using Test: @test, @testset, @test_throws import ITensorNetworksNext as ITensorNetworks -using .ITensorNetworks: RegionIterator, SweepIterator, compute!, increment!, islaststep, state +using .ITensorNetworks: RegionIterator, SweepIterator, IncrementOnly, compute!, increment!, islaststep, state, eachregion module TestIteratorUtils @@ -157,5 +157,65 @@ end @test SA_c == [1, 4, 9, 16, 25] end + + @testset "IncrementOnly" begin + TI = TestIteratorUtils.TestIterator(1, 5, []) + NI = IncrementOnly(TI) + + NI_c = [] + + for _ in IncrementOnly(TI) + push!(NI_c, state(TI)) + end + + @test length(NI_c) == 5 + @test isempty(TI.output) + end + + @testset "EachRegion" begin + prob = TestIteratorUtils.TestProblem([]) + prob_region = TestIteratorUtils.TestProblem([]) + + SI = SweepIterator(prob, 5) + SI_region = SweepIterator(prob_region, 5) + + callback = [] + callback_region = [] + + let i = 1 + for _ in SI + push!(callback, i) + i += 1 + end + end + + @test length(callback) == 5 + + let i = 1 + for _ in eachregion(SI_region) + push!(callback_region, i) + i += 1 + end + end + + @test length(callback_region) == 10 + + @test prob.data == prob_region.data + + @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 From 715946cbea37eafd8c4262abd27ce855510bf6f7 Mon Sep 17 00:00:00 2001 From: Matt Fishman Date: Mon, 3 Nov 2025 14:57:02 -0500 Subject: [PATCH 9/9] Bump version from 0.1.11 to 0.1.12 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 42df730..4eb513e 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "ITensorNetworksNext" uuid = "302f2e75-49f0-4526-aef7-d8ba550cb06c" authors = ["ITensor developers and contributors"] -version = "0.1.11" +version = "0.1.12" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"