From 786fcc23ed55ada59fc4f3c94bd4bb3965daea6c Mon Sep 17 00:00:00 2001 From: Hiroshi Shinaoka Date: Fri, 13 Feb 2026 06:53:13 +0900 Subject: [PATCH 1/2] Fix segfault and infinite loop in affine_transform with negative b Three bugs in affine_transform_core/tensors when offset vector b has negative entries or entries exceeding 2^R (fixes #44): 1. BoundsError (hidden as segfault by @inbounds): activebit=false path computed y freely via z & 1, producing y_index=2 on a length-1 array. Fix: skip carry paths where y != 0 when bits are inactive. 2. Infinite loop (caused ~50GB memory): arithmetic right shift on negative b never reaches 0 (-1 >> 1 = -1). Fix: work with abs(b) and track signs separately so shifting always terminates. 3. Sign of b lost: copysign(b_, abs(b_)) always returned abs(b_), so negative offsets were treated as positive. Fix: pass signed bit contributions (bsign .* (b .& 1)) to the core function. Co-Authored-By: Claude Opus 4.6 --- src/affine.jl | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/affine.jl b/src/affine.jl index c47f728..95b50cb 100644 --- a/src/affine.jl +++ b/src/affine.jl @@ -112,11 +112,16 @@ function affine_transform_tensors( # dimensions (links) vary tensors = Vector{Array{Bool,4}}(undef, R) + # Track sign separately and work with absolute value so that + # right-shifting always terminates (fixes negative b handling). + bsign = sign.(b) + b = abs.(b) + # The initial carry is zero carry = [zero(SVector{M,Int})] for r in R:-1:1 # Figure out the current bit to add from the shift term and shift - bcurr = SVector{M,Int}((copysign(b_, abs(b_)) & 1 for b_ in b)) + bcurr = (b .& 1) .* bsign # Get tensor. new_carry, data = affine_transform_core(A, bcurr, s, carry) @@ -138,12 +143,12 @@ function affine_transform_tensors( b = @. b >> 1 end - if boundary == OpenBoundaryConditions() && maximum(abs, b) > 0 + if boundary == OpenBoundaryConditions() && maximum(b) > 0 # Extend the tensors to the left until we have no more nonzero bits in b # This is equivalent to a larger domain. tensors_ext = Array{Bool,4}[] - while maximum(abs, b) > 0 - bcurr = SVector{M,Int}((copysign(b_, abs(b_)) & 1 for b_ in b)) + while maximum(b) > 0 + bcurr = (b .& 1) .* bsign new_carry, data = affine_transform_core(A, bcurr, s, carry; activebit=false) pushfirst!(tensors_ext, data) @@ -215,6 +220,10 @@ function affine_transform_core( # if s is odd, then there is a unique y which solves satisfies # above condition (simply the lowest bit) y = @. Bool(z & 1) + if !activebit && any(y) + # y must be zero when bits are inactive; skip dead-end carry + continue + end y_index = digits_to_number(y) + 1 # Correct z and compute carry From 4fc400c7a8be329cafbea74393997122d79fd004 Mon Sep 17 00:00:00 2001 From: Hiroshi Shinaoka Date: Fri, 13 Feb 2026 06:54:49 +0900 Subject: [PATCH 2/2] Require TensorCrossInterpolation >= 0.9.18 for TCIITensorConversion extension The MPO(::TensorTrain; sites=...) and MPS(::TensorTrain; sites=...) constructors used in fouriertransform.jl were moved from the standalone TCIITensorConversion.jl package (dropped in 9cf8d28) to a weak-dep extension in TensorCrossInterpolation.jl v0.9.18. Co-Authored-By: Claude Opus 4.6 --- Project.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Project.toml b/Project.toml index 648685e..849e3ca 100644 --- a/Project.toml +++ b/Project.toml @@ -29,6 +29,7 @@ PartitionedMPSs = "0.5.2, 0.6" QuanticsTCI = "0.7" SparseIR = "^0.96, 0.97, 1" StaticArrays = "1" +TensorCrossInterpolation = "0.9.18" julia = "1" [extras]