diff --git a/Project.toml b/Project.toml index 5b3c9be..a4a85d5 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "ITensorBase" uuid = "4795dd04-0d67-49bb-8f44-b89c448a1dc7" -version = "0.8.3" +version = "0.8.4" authors = ["ITensor developers and contributors"] [workspace] diff --git a/docs/make.jl b/docs/make.jl index cb5759f..45334c0 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,7 +1,11 @@ using Documenter: Documenter, DocMeta, deploydocs, makedocs -using ITensorBase: ITensorBase +using ITensorBase using ITensorFormatter: ITensorFormatter +# `using ITensorBase` (rather than `using ITensorBase: ITensorBase`) binds the exported +# names in `Main`. The tensor `show` qualifies the element type relative to the active +# module, so without it the doctests and `@example` blocks render `ITensorBase.ITensor` +# instead of `ITensor`. DocMeta.setdocmeta!(ITensorBase, :DocTestSetup, :(using ITensorBase); recursive = true) ITensorFormatter.make_index!(pkgdir(ITensorBase)) @@ -15,7 +19,12 @@ makedocs(; edit_link = "main", assets = ["assets/favicon.ico", "assets/extras.css"] ), - pages = ["Home" => "index.md", "Reference" => "reference.md"] + pages = [ + "Home" => "index.md", + "User Interface" => "user_interface.md", + "Developer Interface" => "dev_interface.md", + "Reference" => "reference.md", + ] ) deploydocs(; diff --git a/docs/src/dev_interface.md b/docs/src/dev_interface.md new file mode 100644 index 0000000..2c672e3 --- /dev/null +++ b/docs/src/dev_interface.md @@ -0,0 +1,61 @@ +# Developer Interface + +```@meta +CurrentModule = ITensorBase +``` + +This page covers the named-array model that indices and tensors are built on, the interface +for implementing a new tensor type, and experimental features that are not yet part of the +stable user-facing API. For the stable user-facing API, see the [User Interface](@ref). + +## Named array types + +A concrete tensor type subtypes [`AbstractITensor`](@ref). [`NamedUnitRange`](@ref) is the +named-range type that a tensor's dimensions are ([`Index`](@ref) is the flavor keyed by an +index name). + +```@docs; canonical=false +AbstractITensor +NamedUnitRange +``` + +## Named array operations + +Construct named objects with [`named`](@ref) and [`nameddims`](@ref), recover their parts +with [`name`](@ref), [`unnamed`](@ref), and [`dimnames`](@ref), and query their types with +[`dimnametype`](@ref), [`nametype`](@ref), and [`unnamedtype`](@ref). [`setname`](@ref) and +[`replacedimnames`](@ref) rename, and [`aligndims`](@ref) and [`aligneddims`](@ref) reorder a +tensor's dimensions by name (a copy and a view, respectively). + +```@docs; canonical=false +named +nameddims +name +unnamed +dimnames +dimnametype +nametype +unnamedtype +setname +replacedimnames +aligndims +aligneddims +``` + +## Experimental + +These features support building and applying operators, where an operator is a tensor whose +dimension names are split into a codomain (output) set and a domain (input) set. The API is +still being refined and is subject to change. Build an operator with [`operator`](@ref) or +allocate one with [`similar_operator`](@ref), apply it to a tensor with [`apply`](@ref), and +recover its underlying tensor and name sets with [`state`](@ref), [`codomainnames`](@ref), +and [`domainnames`](@ref). + +```@docs; canonical=false +operator +similar_operator +apply +state +codomainnames +domainnames +``` diff --git a/docs/src/user_interface.md b/docs/src/user_interface.md new file mode 100644 index 0000000..1aa846b --- /dev/null +++ b/docs/src/user_interface.md @@ -0,0 +1,90 @@ +# User Interface + +```@meta +CurrentModule = ITensorBase +``` + +This page covers the stable, user-facing API. For the lower-level named-array model, tensor +construction internals and accessors, and experimental features, see the +[Developer Interface](@ref). For a complete alphabetical listing of every documented name, +see the [Reference](@ref). + +## Indices and tensors + +An [`ITensor`](@ref) labels its dimensions by name, and an [`Index`](@ref) is a named +dimension. Get a tensor's indices with [`inds`](@ref), make distinct copies of an index with +[`prime`](@ref) and [`noprime`](@ref), and mint a fresh unique name with [`uniquename`](@ref). + +```@docs; canonical=false +Index +ITensor +inds +prime +noprime +uniquename +``` + +## Constructors + +Build a tensor by calling a `Base` array constructor on indices instead of sizes. `randn`, +`rand`, `zeros`, `ones`, and `fill` all accept indices and return an `ITensor` carrying them. +These are `Base` functions extended to accept indices, so they are shown here by example +rather than in the [Reference](@ref). + +```@example userinterface +using ITensorBase: Index +i, j = Index(2), Index(3) +randn(i, j) +``` + +```@example userinterface +zeros(i, j) +``` + +## Algebra + +ITensors support the standard arithmetic. `*` contracts over shared indices, leaving the +unshared ones. + +```@example userinterface +k = Index(2) +a, b = randn(i, j), randn(j, k) +a * b +``` + +`+` and `-` add and subtract tensors. They are matched up by index, so the operands need not +list their indices in the same order. + +```@example userinterface +c = randn(j, i) +a + c +``` + +Multiplying by a scalar scales the tensor. + +```@example userinterface +2 * a +``` + +## Broadcasting + +Broadcasting works elementwise and matches operands by name, so they need not share index +order. Its advantage over the plain arithmetic above is fusion: a dotted expression is +evaluated in a single pass over the elements, without allocating the intermediate tensors the +undotted form does. + +For example, the plain expression below allocates `2 * a` and `3 * c` before adding them: + +```@example userinterface +2 * a + 3 * c +``` + +Adding dots fuses the whole expression into one pass, giving the same result with no +intermediates: + +```@example userinterface +2 .* a .+ 3 .* c +``` + +Non-linear broadcasting (functions of one or more tensors, such as `sin.(a)` or `a .^ 2`) is +experimental and incompletely supported, and is subject to change. diff --git a/src/ITensorBase.jl b/src/ITensorBase.jl index 37433e8..278bfa1 100644 --- a/src/ITensorBase.jl +++ b/src/ITensorBase.jl @@ -1,10 +1,11 @@ module ITensorBase -export ITensor, Index, aligndims, dimnametype, named, nameddims, - operator, similar_operator +export AbstractITensor, ITensor, Index, NamedUnitRange, aligndims, aligneddims, + apply, codomainnames, dimnames, dimnametype, domainnames, inds, named, + nameddims, noprime, operator, prime, similar_operator, state, uniquename using Compat: @compat -@compat public to_inds @compat public @names +@compat public name, nametype, replacedimnames, setname, unnamed, unnamedtype # Named-array machinery (relocated from NamedDimsArrays.jl). include("isnamed.jl") diff --git a/src/abstractitensor.jl b/src/abstractitensor.jl index d3e5f1a..5b9e8d1 100644 --- a/src/abstractitensor.jl +++ b/src/abstractitensor.jl @@ -8,6 +8,17 @@ using TensorAlgebra: TensorAlgebra, permuteddims, zero! # https://github.com/mcabbott/NamedPlus.jl # https://pytorch.org/docs/stable/named_tensor.html +""" + AbstractITensor{DimName} + +Supertype for tensors whose dimensions are labeled by names of type `DimName` rather +than ordered by position. Subtypes such as [`ITensor`](@ref) line their dimensions up +by name under contraction, addition, and indexing. Unlike an `AbstractArray`, the rank +and element type live in the data rather than the type, so `ndims` and `eltype` are not +fixed at the type level. + +See also [`ITensor`](@ref), [`dimnames`](@ref), [`inds`](@ref). +""" abstract type AbstractITensor{DimName} end # Rank and element type live in the data, not the type, so the type-level `ndims` @@ -16,6 +27,30 @@ abstract type AbstractITensor{DimName} end # supplied directly below rather than inherited. Base.ndims(::Type{<:AbstractITensor}) = Any +""" + dimnames(a::AbstractITensor) + dimnames(a::AbstractITensor, dim::Int) + +The dimension names of `a`, as a collection in dimension order. The second form returns +the name of dimension `dim`. + +# Examples + +```jldoctest +julia> a = nameddims(zeros(2, 3), (:i, :j)); + +julia> dimnames(a) +2-element Vector{Symbol}: + :i + :j + +julia> dimnames(a, 2) +:j +``` + +See also [`inds`](@ref), [`nameddims`](@ref). +""" +function dimnames end dimnames(a::AbstractITensor) = throw(MethodError(dimnames, a)) function dimnames(a::AbstractITensor, dim::Int) return dimnames(a)[dim] @@ -52,6 +87,29 @@ unnamed(a::AbstractITensor) = throw(MethodError(unnamed, a)) unnamed(a::AbstractITensor, inds) = unnamed(aligneddims(a, inds)) unname(a::AbstractITensor, inds) = unnamed(aligndims(a, inds)) +""" + inds(a::AbstractITensor) + inds(a::AbstractITensor, dim::Int) + +The named axes (indices) of `a`, as a `Tuple` with one entry per dimension. Each entry +pairs a dimension's axis with its name. The second form returns the index of dimension +`dim`. Compare with [`dimnames`](@ref), which returns just the names without the axes. + +# Examples + +```jldoctest +julia> a = nameddims(zeros(2, 3), (:i, :j)); + +julia> inds(a) +(named(Base.OneTo(2), :i), named(Base.OneTo(3), :j)) + +julia> inds(a, 1) +named(Base.OneTo(2), :i) +``` + +See also [`dimnames`](@ref), [`nameddims`](@ref). +""" +function inds end # Output the named axes/indices of the named dims array, as a `Tuple` (even though # the dimension names are stored as a `Vector`). inds(a::AbstractITensor) = named.(axes(unnamed(a)), Tuple(dimnames(a))) @@ -92,6 +150,18 @@ end nameddims(a::AbstractArray, inds) Construct a named dimensions array from an unnamed array `a` and named dimensions `inds`. + +# Examples + +```jldoctest +julia> nameddims(zeros(2, 3), (:i, :j)) +named(Base.OneTo(2), :i)×named(Base.OneTo(3), :j) ITensor{Symbol}: +2×3 Matrix{Float64}: + 0.0 0.0 0.0 + 0.0 0.0 0.0 +``` + +See also [`ITensor`](@ref), [`named`](@ref). """ function nameddims(a::AbstractArray, inds) return ITensor(a, inds) @@ -337,6 +407,30 @@ function setdimnames(a::AbstractITensor, dimnames) return nameddims(unnamed(a), dimnames) end +""" + replacedimnames(a::AbstractITensor, replacements::Pair...) + replacedimnames(f, a::AbstractITensor) + +Return a tensor with the same data as `a` but with its dimension names replaced. The +first form takes `old => new` pairs, replacing matching names and leaving the rest +unchanged. The second form replaces each name with `f(name)`. + +# Examples + +```jldoctest +julia> using ITensorBase: replacedimnames + +julia> a = nameddims(zeros(2, 3), (:i, :j)); + +julia> dimnames(replacedimnames(a, :i => :k)) +2-element Vector{Symbol}: + :k + :j +``` + +See also [`dimnames`](@ref). +""" +function replacedimnames end function replacedimnames(a::AbstractITensor, replacements::Pair...) new_dimnames = replace(dimnames(a), replacements...) return nameddims(unnamed(a), new_dimnames) @@ -770,6 +864,26 @@ end # Permute/align dimensions +""" + aligndims(a::AbstractITensor, dims) + +Reorder the dimensions of `a` into the order given by `dims`, matched by name. Returns a +tensor with the same data and dimension names as `a` but with the dimensions permuted, and +throws a `NameMismatch` if `dims` is not a permutation of `a`'s dimension names. + +# Examples + +```jldoctest +julia> a = nameddims(zeros(2, 3), (:i, :j)); + +julia> aligndims(a, (:j, :i)) +named(Base.OneTo(3), :j)×named(Base.OneTo(2), :i) ITensor{Symbol}: +3×2 Matrix{Float64}: + 0.0 0.0 + 0.0 0.0 + 0.0 0.0 +``` +""" function aligndims(a::AbstractITensor, dims) new_dimnames = name.(dims) perm = Tuple(getperm(dimnames(a), new_dimnames)) @@ -781,6 +895,26 @@ function aligndims(a::AbstractITensor, dims) return nameddims(permutedims(unnamed(a), perm), new_dimnames) end +""" + aligneddims(a::AbstractITensor, dims) + +Like [`aligndims`](@ref), but returns a lazily-permuted view that shares data with `a` +instead of copying. Reorders the dimensions of `a` into the order given by `dims`, matched by +name, and throws a `NameMismatch` if `dims` is not a permutation of `a`'s dimension names. + +# Examples + +```jldoctest +julia> a = nameddims(reshape(1:6, 2, 3), (:i, :j)); + +julia> dimnames(aligneddims(a, (:j, :i))) +2-element Vector{Symbol}: + :j + :i +``` + +See also [`aligndims`](@ref). +""" function aligneddims(a::AbstractITensor, dims) new_dimnames = name.(dims) perm = getperm(dimnames(a), new_dimnames) diff --git a/src/index.jl b/src/index.jl index 17a9e0e..b3ac746 100644 --- a/src/index.jl +++ b/src/index.jl @@ -68,6 +68,49 @@ function unsettag(n::IndexName, tagname::String) return settags(n, newtags) end +""" + prime(i) + +Increment the prime level of an index or index name by one, returning a new index that +is distinct from `i`. Priming is the usual way to make a second copy of an index that +carries the same tags but is not contracted against the original. The inverse is +[`noprime`](@ref), which resets the prime level to zero. + +# Examples + +```jldoctest +julia> i = Index(2); + +julia> prime(i) == i +false + +julia> noprime(prime(i)) == i +true +``` + +See also [`noprime`](@ref), [`Index`](@ref). +""" +function prime end + +""" + noprime(i) + +Reset the prime level of an index or index name to zero, returning a new index. This +undoes any number of [`prime`](@ref) calls. + +# Examples + +```jldoctest +julia> i = Index(2); + +julia> noprime(prime(i)) == i +true +``` + +See also [`prime`](@ref), [`Index`](@ref). +""" +function noprime end + prime(n::IndexName) = setplev(n, plev(n) + 1) noprime(n::IndexName) = setplev(n, 0) @@ -86,11 +129,25 @@ function Base.show(io::IO, i::IndexName) return nothing end -# An `Index` is a named unit range whose name is an `IndexName` (carrying the id, -# tags, and prime level), not a distinct type. Construction (`Index(3)`, -# `Index(1:3)`) goes through the generic `NamedUnitRange{Name}` constructors, which -# mint a fresh `IndexName` via `uniquename`. Attributes (tags, prime level) are set -# after construction with the modifier functions below. +""" + Index(length) + Index(range) + +An index of an `ITensor`: a named unit range whose name is a freshly minted, unique +identifier carrying tags and a prime level. `Index(2)` makes an index of length `2` over +`Base.OneTo(2)`, and `Index(1:3)` makes one over an explicit range. Each call mints a new +name, so two indices built the same way are still distinct, and tensors share a dimension +only when they share the same `Index`. + +# Examples + +```jldoctest +julia> i = Index(2); + +julia> length(i) +2 +``` +""" const Index = NamedUnitRange{IndexName} # TODO: Define for `NamedViewIndex`. diff --git a/src/itensor.jl b/src/itensor.jl index 725b288..3d18c3a 100644 --- a/src/itensor.jl +++ b/src/itensor.jl @@ -1,3 +1,21 @@ +""" + ITensor(array::AbstractArray, dims) + +A dense tensor whose dimensions are labeled by names instead of ordered by position. It pairs +an underlying `array` with one name per dimension (`dims`), so contraction, addition, and +indexing line dimensions up by name. An `ITensor` is usually built by calling `randn`, `zeros`, +and the like on indices, or through [`nameddims`](@ref), rather than constructed directly. + +# Examples + +```jldoctest +julia> ITensor(zeros(2, 3), (:i, :j)) +named(Base.OneTo(2), :i)×named(Base.OneTo(3), :j) ITensor{Symbol}: +2×3 Matrix{Float64}: + 0.0 0.0 0.0 + 0.0 0.0 0.0 +``` +""" struct ITensor{DimName} <: AbstractITensor{DimName} unnamed::AbstractArray dimnames::Vector{DimName} diff --git a/src/itensoroperator.jl b/src/itensoroperator.jl index cdcd338..7d624fb 100644 --- a/src/itensoroperator.jl +++ b/src/itensoroperator.jl @@ -6,13 +6,70 @@ using Random: Random # Choi state representation of the named operator. # https://en.wikipedia.org/wiki/Choi%E2%80%93Jamio%C5%82kowski_isomorphism +""" + state(a) + +The underlying tensor of a named operator, with its codomain/domain structure +forgotten. An operator carries a tensor together with a pairing of its codomain and +domain dimension names (its Choi, or state, representation). `state` returns that tensor +on its own. For a plain tensor that is not an operator, `state` returns it unchanged. + +# Examples + +```jldoctest +julia> a = nameddims(zeros(2), (:i,)); + +julia> state(a) == a +true +``` + +See also [`operator`](@ref), [`codomainnames`](@ref), [`domainnames`](@ref). +""" state(a) = throw(MethodError(state, (a,))) # Operator representation of the named state given pairs of named codomain and domain indices. operator(a, codomain, domain) = throw(MethodError(operator, (a, codomain, domain))) # Get the codomain dimension names of the operator. +""" + codomainnames(a) + +The codomain (output) dimension names of an operator `a`. An operator pairs each of its +codomain names with a domain name. Applying the operator contracts over the domain and +leaves the codomain. + +# Examples + +```jldoctest +julia> op = operator(zeros(2, 2), ("i",), ("j",)); + +julia> collect(codomainnames(op)) +1-element Vector{String}: + "i" +``` + +See also [`domainnames`](@ref), [`operator`](@ref), [`apply`](@ref). +""" codomainnames(a) = throw(MethodError(codomainnames, (a,))) + # Get the domain dimension names of the operator. +""" + domainnames(a) + +The domain (input) dimension names of an operator `a`. These are the names contracted +over when the operator is applied to a tensor. + +# Examples + +```jldoctest +julia> op = operator(zeros(2, 2), ("i",), ("j",)); + +julia> collect(domainnames(op)) +1-element Vector{String}: + "j" +``` + +See also [`codomainnames`](@ref), [`operator`](@ref), [`apply`](@ref). +""" domainnames(a) = throw(MethodError(domainnames, (a,))) # Given a domain dimension name, return the corresponding codomain dimension name. @@ -22,6 +79,28 @@ get_codomain_name(a, i) = throw(MethodError(get_codomain_name, (a, i))) # If it doesn't exist, return the index itself. get_domain_name(a, i) = throw(MethodError(get_domain_name, (a, i))) +""" + apply(x::AbstractITensor, y::AbstractITensor) + +Apply the operator `x` to `y`. This contracts the state tensors of `x` and `y` over +their shared names, then renames each surviving codomain name of `x` back to its paired +domain name, so the result carries the same names `y` would map to. Applying the +identity operator leaves `y` unchanged. + +# Examples + +```jldoctest +julia> op = operator(reshape(Float64[1, 0, 0, 1], 2, 2), ("i",), ("j",)); + +julia> v = nameddims([3.0, 4.0], ("j",)); + +julia> apply(op, v) == v +true +``` + +See also [`operator`](@ref), [`state`](@ref), [`codomainnames`](@ref), +[`domainnames`](@ref). +""" function apply(x::AbstractITensor, y::AbstractITensor) xy = state(x) * state(y) return mapdimnames(xy) do i @@ -131,6 +210,36 @@ function get_domain_name(a::ITensorOperator, i) return get(inverse(a.dimnames_bijection), i, i) end +""" + operator(a, codomain, domain) + +Build a named operator from a tensor (or plain array) `a` by partitioning its dimension +names into a `codomain` (output) set and a `domain` (input) set. The operator pairs each +codomain name with a domain name, so it can be applied to a tensor with +[`apply`](@ref), contracting over the domain. `codomain` and `domain` may be given as +dimension names or as named ranges such as `Index`es. Recover the underlying tensor +with [`state`](@ref) and the name sets with [`codomainnames`](@ref) and +[`domainnames`](@ref). + +# Examples + +```jldoctest +julia> op = operator(zeros(2, 2), ("i",), ("j",)); + +julia> collect(codomainnames(op)) +1-element Vector{String}: + "i" + +julia> collect(domainnames(op)) +1-element Vector{String}: + "j" +``` + +See also [`state`](@ref), [`codomainnames`](@ref), [`domainnames`](@ref), +[`apply`](@ref), [`similar_operator`](@ref). +""" +function operator end + # `codomain` and `domain` may be given as dimension names or as named ranges # (such as `Index`es); `name` maps the latter to their names and leaves names as-is. # TODO: Unify these two functions. @@ -383,6 +492,18 @@ Element type defaults to `eltype(prototype)`. Codomain names default to fresh explicit names, the second takes already-named axes and reuses their names as the domain. Storage layout (including the bra/ket flip on the domain side for graded axes) is delegated to `TensorAlgebra.similar_map`. + +# Examples + +```jldoctest +julia> op = similar_operator(zeros(2, 2), (Base.OneTo(2),), (:i,), (:j,)); + +julia> collect(domainnames(op)) +1-element Vector{Symbol}: + :j +``` + +See also [`operator`](@ref), [`uniquename`](@ref). """ function similar_operator( prototype, ::Type{T}, unnamed_domain_axes, codomain_names, domain_names diff --git a/src/name.jl b/src/name.jl index 12ad229..b2a5fbb 100644 --- a/src/name.jl +++ b/src/name.jl @@ -18,11 +18,15 @@ end @names x[1:3] y[1:3, 2:4] ... Short-hand notation for constructing "named symbols", i.e. objects that can be used as names. -In other words, the following expressions are equivalent: +`@names x y z` is equivalent to `Name.((:x, :y, :z))`, returning one name per symbol. + +# Examples + +```jldoctest +julia> using ITensorBase: @names + +julia> x, y, z = @names x y z; -```julia -x, y, z = @names x y z -x, y, z = Name.((:x, :y, :z)) ``` """ macro names(exs...) diff --git a/src/named.jl b/src/named.jl index 9d4593c..79dd9ae 100644 --- a/src/named.jl +++ b/src/named.jl @@ -14,12 +14,120 @@ end # without being a separate type. const NamedInteger{Name, Unnamed <: Integer} = Named{Name, Unnamed} +""" + name(a) + +The name attached to a named object `a`, such as a `Named` scalar, a named array, or a +named unit range. This is the inverse of the name component of [`named`](@ref): `name` +recovers the name, [`unnamed`](@ref) recovers the value. + +# Examples + +```jldoctest +julia> using ITensorBase: name + +julia> name(named(2, :i)) +:i +``` + +See also [`named`](@ref), [`unnamed`](@ref), [`setname`](@ref). +""" +function name end + +""" + unnamed(a) + +The underlying value of a named object `a`, with its name stripped off. This is the +inverse of the value component of [`named`](@ref): [`name`](@ref) recovers the name, +`unnamed` recovers the value. On an [`AbstractITensor`](@ref) it returns the underlying +unnamed array. + +# Examples + +```jldoctest +julia> using ITensorBase: unnamed + +julia> unnamed(named(2, :i)) +2 +``` + +See also [`named`](@ref), [`name`](@ref). +""" +function unnamed end + +""" + setname(a, name) + +Return a copy of the named object `a` with its name replaced by `name`, keeping the +underlying value unchanged. + +# Examples + +```jldoctest +julia> using ITensorBase: setname + +julia> setname(named(2, :i), :j) +named(2, :j) +``` + +See also [`named`](@ref), [`name`](@ref). +""" +function setname end + +""" + nametype(type::Type) + +The type of the name carried by a named type, such as a `Named` scalar type, a named +array type, or a named unit range type. + +# Examples + +```jldoctest +julia> using ITensorBase: nametype + +julia> nametype(typeof(named(2, :i))) +Symbol +``` + +See also [`name`](@ref), [`unnamedtype`](@ref). +""" +function nametype end + +""" + unnamedtype(type::Type) + +The type of the underlying (unnamed) value carried by a named type. + +# Examples + +```jldoctest +julia> using ITensorBase: unnamedtype + +julia> unnamedtype(typeof(named(2, :i))) +Int64 +``` + +See also [`unnamed`](@ref), [`nametype`](@ref). +""" +function unnamedtype end + # Minimal interface. unnamed(i::Named) = i.value name(i::Named) = i.name -# Shorthand. Attaching a name to a scalar produces a `Named`; arrays and unit -# ranges have their own more specific `named` methods. +""" + named(value, name) + +Attach `name` to `value`, pairing them into a single named object. On a scalar this produces +a `Named`. Arrays and unit ranges have their own more specific methods. + +# Examples + +```jldoctest +julia> named(2, :i) +named(2, :i) +``` +""" named(value, name) = Named(value, name) # Derived interface. diff --git a/src/namedunitrange.jl b/src/namedunitrange.jl index 64c993c..34211d4 100644 --- a/src/namedunitrange.jl +++ b/src/namedunitrange.jl @@ -1,3 +1,20 @@ +""" + NamedUnitRange{Name} + +A unit range with a name attached, used as a named dimension (axis) of a tensor. It +pairs an underlying integer unit range with a name of type `Name`. [`Index`](@ref) is +the `NamedUnitRange` flavor whose name is an `IndexName`. Build one by calling +[`named`](@ref) on a range, or use `Index` to mint a fresh unique name. + +# Examples + +```jldoctest +julia> named(1:3, :i) +named(1:3, :i) +``` + +See also [`Index`](@ref), [`named`](@ref). +""" struct NamedUnitRange{Name, UnnamedT <: Integer, Unnamed <: AbstractUnitRange{UnnamedT}} <: AbstractNamedVector{Name, UnnamedT} value::Unnamed diff --git a/src/uniquename.jl b/src/uniquename.jl index 4954c22..0a2f2f6 100644 --- a/src/uniquename.jl +++ b/src/uniquename.jl @@ -1,6 +1,28 @@ using Random: AbstractRNG, RandomDevice -# Generate a new unique name, for example in matrix factorizations. +""" + uniquename([rng,] name) + uniquename([rng,] type::Type) + +Mint a fresh, unique name. Given an existing `name` (or a name `type`), produce a new +name of the same flavor that is distinct from any other, for example to label a freshly +generated dimension in a matrix factorization. Randomness defaults to OS entropy +(`Random.RandomDevice`) so that minting a name neither perturbs nor is perturbed by the +numerical RNG. Pass an explicit `rng` for a reproducible name. + +# Examples + +```jldoctest +julia> i = Index(2); + +julia> uniquename(i) != i +true +``` + +See also [`named`](@ref). +""" +function uniquename end + # The randomness defaults to `Random.RandomDevice()` (OS entropy) rather than # `Random.default_rng()`, so minting a name neither perturbs nor is perturbed by # the numerical RNG (`Random.seed!` does not make ids reproducible), mirroring diff --git a/test/test_exports.jl b/test/test_exports.jl index 8ed8ef5..b90b5c7 100644 --- a/test/test_exports.jl +++ b/test/test_exports.jl @@ -2,10 +2,16 @@ using ITensorBase: ITensorBase using Test: @test, @testset @testset "Test exports" begin exports = [ - :ITensorBase, :ITensor, :Index, :aligndims, :dimnametype, - :named, :nameddims, :operator, :similar_operator, + :ITensorBase, :AbstractITensor, :ITensor, :Index, :NamedUnitRange, + :aligndims, :aligneddims, :apply, :codomainnames, :dimnames, :dimnametype, + :domainnames, + :inds, :named, :nameddims, :noprime, :operator, :prime, :similar_operator, + :state, :uniquename, + ] + publics = [ + :name, :nametype, :replacedimnames, :setname, :unnamed, :unnamedtype, + Symbol("@names"), ] - publics = [:to_inds, Symbol("@names")] if VERSION ≥ v"1.11-" exports = [exports; publics] end