Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Sectors] Non-abelian fusion #1363

Open
wants to merge 57 commits into
base: main
Choose a base branch
from
Open

Conversation

ogauthe
Copy link
Contributor

@ogauthe ogauthe commented Mar 25, 2024

This is a work in progress for a full implementation of non-abelian fusion rules in Sectors. It also contains several patches on other components of Sectors. A core aspect is that the dependency order between GradedAxes and Sectors has been reversed: Sectors now depends on GradedAxes.

To do:

  • implement fusion rules for CategoryProduct
  • implement SymmetryStyle trait with values AbelianGroup, NonAbelianGroup and NonGroupSymmetry

To be decided:

  • type return for c1 ⊗ c2: either always a GradedUnitRange or a GradedUnitRange or a Category depending on SymmetryStyle
  • whether to keep SU2 and SU{2}.
  • whether to implement SU(N) fusion rules or add dependency / fork SUNRepresentations.jl

Decisions:

  • c1 ⊗ c2: return type depends on SymmetryStyle
  • work on replacing SU2 with SU{2} and try to have a half-integer interface specific to SU{2}

Long term: how to handle braiding for fermions and anyons.

rebase on latest GradedAxes
set GradedAxes as a Sector dependency
define fusion rules for simple categories
pass test for simple categories
Comment on lines 31 to 32
# name conflict with LabelledNumber.label. TBD is that an issue?
label(c::AbstractCategory) = error("method `label` not defined for type $(typeof(c))")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either we can separate Sectors.label and LabelledNumber.label functions, or come up with a different name for the label of a category. We could rename this one to category_label to be more specific.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's too bad, because label is pretty much the standard term and it is short and sweet. (E.g. I checked the von Delft paper on SU(N) and they frequently use the term "label".) The only other names I've seen are in the category / topology / stat mech literature where they call the labels variously "simple objects", "anyon types", or "anyon sectors" and names like that. But of course those names are never used in the group theory literature.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

category_label is a sensible compromise. But I was liking how we weren't using the term category too much to intentionally be ambiguous about whether something might be a regular group.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could have both group_label and category_label, or not overload label from LabelledNumbers and have a private Sectors.label function.

Having groups define group_label and categories define category_label seems ok to me, since I doubt that there will be some generic code that accepts either a group or a category and makes use of the label.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rather avoid having both group_label and category_label, as they would play the same role and lead to code dupplicate. The name should stay internal and not exposed, so I think cagerory_label is the best solution.

@codecov-commenter
Copy link

codecov-commenter commented Mar 25, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 57.49%. Comparing base (ceb26a7) to head (1fa1f7b).

❗ Current head 1fa1f7b differs from pull request most recent head de95045. Consider uploading reports for the commit de95045 to get more accurate results

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1363      +/-   ##
==========================================
+ Coverage   49.23%   57.49%   +8.26%     
==========================================
  Files         110      114       +4     
  Lines        8320     8867     +547     
==========================================
+ Hits         4096     5098    +1002     
+ Misses       4224     3769     -455     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Comment on lines 83 to 118
function GradedAxes.tensor_product(
c::C, g::GradedAxes.GradedUnitRange{Vector{LabelledNumbers.LabelledInteger{V,C}}}
) where {V,C<:AbstractCategory}
return c ⊗ g
end

function GradedAxes.tensor_product(
g1::GradedAxes.GradedUnitRange{Vector{LabelledNumbers.LabelledInteger{V,C}}},
g2::GradedAxes.GradedUnitRange{Vector{LabelledNumbers.LabelledInteger{V,C}}},
) where {V,C<:AbstractCategory}
return g1 ⊗ g2
end

GradedAxes.fuse_labels(c1::AbstractCategory, c2::AbstractCategory) = c1 ⊗ c2

# =============== sum rules ====================
⊕(c1::C, c2::C) where {C<:AbstractCategory} = GradedAxes.gradedrange([c1 => 1, c2 => 1])

function ⊕(
c::C, g::GradedAxes.GradedUnitRange{Vector{LabelledNumbers.LabelledInteger{V,C}}}
) where {V<:Integer,C<:AbstractCategory}
return GradedAxes.gradedrange([c => 1]) ⊕ g
end

function ⊕(
g::GradedAxes.GradedUnitRange{Vector{LabelledNumbers.LabelledInteger{V,C}}}, c::C
) where {V<:Integer,C<:AbstractCategory}
return g ⊕ GradedAxes.gradedrange([c => 1])
end

function ⊕(
g1::GradedAxes.GradedUnitRange{Vector{LabelledNumbers.LabelledInteger{V,C}}},
g2::GradedAxes.GradedUnitRange{Vector{LabelledNumbers.LabelledInteger{V,C}}},
) where {V<:Integer,C<:AbstractCategory}
return GradedAxes.chain(g1, g2)
end
Copy link
Member

@mtfishman mtfishman Mar 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think all of these type signatures are too restrictive, and we can just use AbstractUnitRange instead of GradedUnitRange.

It may not make sense to direct sum certain AbstractUnitRange objects with an AbstractCategory, but I think we can think about a different way to catch that kind of issue, for example perhaps that is caught in chain/axis_cat, or we have an orthogonal concept of axis promotion, for example:

function (g::AbstractUnitRange c::AbstractCategory)
  return (gradedaxis_promote(g, c)...)
end

function gradedaxis_promote(g::GradedUnitRange, c::AbstractCategory)
  return (g, gradedrange([c => 1]))
end

function gradedaxis_promote(g::AbstractUnitRange, c::AbstractCategory)
  return error("No promotion defined.")
end

That kind of promotion system is how Base Julia handles algebra between various number types. We probably want a lower level concept of a gradedaxis_promote_rule that handles both:

gradedaxis_promote(g::GradedUnitRange, c::AbstractCategory)
gradedaxis_promote(c::AbstractCategory, g::GradedUnitRange)

in a single gradedaxis_promote_rule, like how Julia has promote_type which calls promote_rule internally.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does make me wonder how often we need to direct sum a category onto a graded axis, so perhaps there is another way of thinking about this concept. I guess this is used in generic fusion code, where results of fusions need to be direct summed with each other to make the full space, but fusions can output either a category or a graded unit range?

If that is the case, it seems like one way to handle this would be to not define ⊕(g::GradedUnitRange c::AbstractCategory) at all (which feels like a strange thing to define, from a generic coding perspective) and instead, in the fusion code, eagerly promote to a GradedUnitRange before things get input into .

So let's think about the code flow and the context for where these definitions are being used.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I updated previous code by @emstoudenmire . We do not really need to direct sum spaces, at least not for the symmetric tensor network implementation I have in mind. However I think we can be slightly more ambitious and implement group theory in a generic way.
Anyway promoting looks like the way to go as it also be used for fusion.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, thanks for the clarification. Agreed, many of the promotion rules should be the same between this code and fusion. I would lean toward removing from this package for now. For the time being we can use axis_cat as needed, and add back later on as a nicer interface on top of axis_cat.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finally promotion is mostly avoided by specializing according to SymmetryStyle. The few cases left are deal with to_graded_axis.

@emstoudenmire
Copy link
Collaborator

Regarding the question of the SU(2) types, I believe we had settled on the following plan

  • remove the specialized SU(2) type and just have a single type for all SU(N)
  • add a type parameter which sets the convention of using integer versus half-integer labels (though does this concept generalize to SU(N) well?)
  • specialize certain methods to the SU{2} case whenever performance is a concern. Though at first maybe don't do this at all and just check later by profiling if this is an issue.

@ogauthe ogauthe marked this pull request as ready for review April 8, 2024 23:59
@ogauthe
Copy link
Contributor Author

ogauthe commented Apr 9, 2024

I made the requested changes: all explicit type comparison are replaced by dispatch. I propose conventions for tensor_product, fusion_product and dual. I leave the unification of SU2 and SU{2} for future work.

@ogauthe
Copy link
Contributor Author

ogauthe commented Apr 9, 2024

I had a look at the failures in CI. They all come from julia 1.6, all tests pass with julia 1.10. Failures boil down to two cases: first a return type is not correctly inferred in one case, this is not a big deal. Second getindex(::NamedTuple, ::Tuple{Symbol})is not supported. I think writing a dictionary-like implementation of CategoryProduct without using this function would be a lot of work. As order product implementation is already fine for julia 1.6, maybe the best would be to require julia >= 1.7 to use the dictionary-like implementation and keep the current code?

@mtfishman
Copy link
Member

I had a look at the failures in CI. They all come from julia 1.6, all tests pass with julia 1.10. Failures boil down to two cases: first a return type is not correctly inferred in one case, this is not a big deal. Second getindex(::NamedTuple, ::Tuple{Symbol})is not supported. I think writing a dictionary-like implementation of CategoryProduct without using this function would be a lot of work. As order product implementation is already fine for julia 1.6, maybe the best would be to require julia >= 1.7 to use the dictionary-like implementation and keep the current code?

It's more verbose, but we could use getindices from Indexing.jl:

julia> using Indexing

julia> getindices((; a=2, b=3, c=4), (; a=:a, b=:b))
(a = 2, b = 3)

which works across Julia versions. We will likely drop support for Julia 1.6 at some point, but I would rather not do that here.

@mtfishman
Copy link
Member

mtfishman commented Apr 10, 2024

We could wrap that functionality up into a nicer interface, maybe:

subkeys(collection::NamedTuple, keys) = getindices(collection, (; (keys .=> keys)...))
subkeys(collection::Dict, keys) = getindices(collection, Dict(keys .=> keys))

to be used like:

julia> subkeys((; a=2, b=3, c=4), (:a, :b))
(a = 2, b = 3)

julia> subkeys(Dict([:a => 2, :b => 3, :c => 4]), [:a, :b])
Dict{Symbol, Int64} with 2 entries:
  :a => 2
  :b => 3

which has the advantage over getindex that the API generalizes to other collections, like Dict.

Named Category implementation calls Ordered Products, better to
define and test Ordered Product first.
@ogauthe
Copy link
Contributor Author

ogauthe commented Apr 11, 2024

Thank you for the clues, I ended up using another way. I also removed a few @inferred from the tests and now they all pass with julia 1.6. I understand jenkins failure to be unrelated to this PR.

@mtfishman mtfishman added the NDTensors Requires changes to the NDTensors.jl library. label May 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
NDTensors Requires changes to the NDTensors.jl library.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants