Skip to content

oneITensor to rule them all #618

Open
@mtfishman

Description

@mtfishman

A common code pattern is:

A = ITensor(1)
A *= B
A *= C

which is useful when initializing a loop, for example. However, it has bothered me that it makes an unnecessary copy of B. We decided, I think correctly, that ITensor(1) * B should make a copy of B, since it could lead to very subtle bugs if ITensor(1) * B didn't copy but ITensor(1.000001) * B did copy (say you were doing floating point operations to compute a constant a, and then did ITensor(a) * B, and then one time a happened to be equal to 1 but you were relying on the copying behavior).

My proposal for this would be to introduce yet another storage type as well as a special number type called One. The proposal would be the following:

using ITensors
using ITensors.NDTensors

# Multiplicative identity
struct One <: Number
end

struct Scalar{ElT} <: TensorStorage{ElT}
  data::Base.RefValue{ElT}
end
Scalar(n::Number) = Scalar(Ref(n))

oneITensor() = itensor(Scalar(One()), IndexSet())

The Base.RefValue storage allows the value to be modified in-place, i.e. would allow T[] = 2.0. That's not totally necessary but I think is helpful for generic code, since a lot of code in NDTensors assumes the tensors are mutable.

Then, we can define contraction such that (oneITensor() * B) = B (without copying). Scalar(One()) storage could be interpreted as a universal multiplicative identity. Note that if someone did 1.0 * oneITensor(), it would create a tensor with storage Scalar(1.0), which would then act as ITensor(1.0) with copying behavior with multiplication. This allows the nice property that (1.0 * oneITensor()) * B and oneITensor() * (1.0 * B) both return copies of B.

Another note that the Scalar storage isn't strictly necessary here, and we could use a length 1 Dense storage with element type One, but I've been meaning to add a Scalar type anyway since I think it will help clean up some code logic with scalar ITensor contractions.

A bonus from this is getting a behavior that I've wanted for delta tensors, which is that A * delta(i, j) would not copy A if the contraction is equivalent to an index replacement. This faced a similar issue to trying to make ITensor(1) * A non-copying, which is that A * (1 * delta(i, j)) would not copy but (A * 1) * delta(i, j) would copy (and similarly, if 1 was replaced by a general floating point constant a, the behavior would subtly change of a happened to be equal to 1).

With the new One type, a delta tensor could have storage with a value of One instead of 1, which would be clearly documented. Then, if you did 1 * delta(i, j), it would return a tensor with storage of 1.0 and have the regular copying behavior, which would have the property that A * (1 * delta(i, j)) and (A * 1) * delta(i, j) always return a copy of A.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or requestperformanceIssues related to performance

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions