-
Notifications
You must be signed in to change notification settings - Fork 41
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
WIP: redesign tensormap structure #140
base: master
Are you sure you want to change the base?
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #140 +/- ##
==========================================
- Coverage 79.72% 69.96% -9.76%
==========================================
Files 42 40 -2
Lines 4961 4905 -56
==========================================
- Hits 3955 3432 -523
- Misses 1006 1473 +467 ☔ View full report in Codecov by Sentry. |
I've been continuing with this PR and will soon push an update. One set of questions that comes up is what we do with indexing, in particular to
The question is whether we want to keep all of those. Without a type alias, they would start with |
Another question is what the interface needs to be to get the data vector of a |
I think my first reaction is to indeed just get rid of This being said, I would definitely like to keep the indexing behavior for non-symmetric tensors, both with scalar getindex and setindex methods, and even to consider allowing slices/view. It is still not too straightforward to fill up a symmetric tensor, so I don't think we want to get rid of this. I don't have a strong opinion on having an interface for accessing the data. From what I understand, the fields of a type are never really considered public, and for TensorMap this has also never really been the case, the exact way the data is stored is kept hidden as much as possible. Given that it is mostly VectorInterface that can use direct access to the field to speed up the implementation, I'm okay with that being "don't try this at home direct field access" 😁 . I also don't think there is a lot of benefit to have this as part of the interface, and would really consider it an implementation detail. |
I am all in favor of getting rid of the For the nonsymmetric / trivially symmetric tensors, the identity
implies that maybe the left one is not strictly necessary, as it is still accessible (in an even more flexible way that also allows slicing etc) as long als The reason that I would consider providing an interface to at least get the data, is that unfortunately there are many external libraries that only work on
where function f(data)
# reconstruct tensor
t = TensorMap(data, space)
# act
tnew = # act on t ...
# return data in case output is also a tensor (e.g. ODE right hand side)
return tnew.data
end We could even go one step further and make So I think this requires some careful deliberation weighing the pros and cons of different approaches. |
I think for the question of I am not a big fan of As a sidenote, we already have some functionality for the type of functions you describe through t0 = input_tensor
v0, from_vec = to_vec(to)
function my_wrapper_fun(v::Vector)
t = from_vec(v)
[...]
return to_vec(result)[1]
end I also just realised that this allows a little more fine-grained control over the fact that our data does not have the inner product you would expect whenever non-abelian symmetries are involved, i.e. |
There are all good points; the Regarding the inner product; in principle we could consider absorbing a factor of |
cdf19c4
to
da6e3c5
Compare
src/spaces/homspace.jl
Outdated
d₂ = dim(dom, s₂) | ||
r₂ = (inner_offset₂ + 1):(inner_offset₂ + d₂) | ||
inner_offset₂ = last(r₂) | ||
# TODO: # now we run the code below for every f₂; should we do this separately |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is definitely an optimization I did in TensorTrack, it might be worth it to separate that out (although if it is cached it should also not matter too much)
src/tensors/tensor.jl
Outdated
size(datac) == size(b) || throw(DimensionMismatch("wrong size of block for sector $c")) | ||
copy!(b, datac) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we instead have setindex!(t::AbstractTensorMap, c::Sector)
we could make the size check and haskey check automatic
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, that's part of the indexing discussion then. So you would prefer to also do block access via getindex
and setindex!
? I was a bit hesitant in channeling all these different functionalities through the same interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I would indeed prefer this, mostly for the setindex!
part, I like that we can avoid handing the user a view to then use in-place, and the ability to re-use some of the boundschecking functionality is also convenient. Additionally, I feel like it really is quite intuitive and a proper use of getindex
/setindex
, so I cannot really think of a reason not to do this, as we also do it for the fusiontrees
src/tensors/tensor.jl
Outdated
T = promote_type(scalartype(TT₁), scalartype(TT₂)) | ||
A = promote_type(storagetype(TT₁), storagetype(TT₂)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure if this can ever pose problems, but we could ensure compatibility between T and A by:
T = promote_type(scalartype(TT₁), scalartype(TT₂)) | |
A = promote_type(storagetype(TT₁), storagetype(TT₂)) | |
A = promote_type(storagetype(TT₁), storagetype(TT₂)) | |
T = scalartype(A) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Neither of the two are probably a good idea:
julia> promote_type(Vector{Float64}, Vector{ComplexF32})
Vector (alias for Array{T, 1} where T)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://discourse.julialang.org/t/type-promototion-of-vectors/30227/6
https://github.com/JuliaLang/julia/blob/e52a46c5c192bfe16853ae0b63ac33b280fba063/base/range.jl#L1282
It seems like this is not really something that the Base promotion system is designed to resolve. From
what I can find, the current behaviour is to only promote if at least one of the eltypes would not change, which is probably not what we want.
I would maybe suggest inserting our own promote_storagetype
, which defaults to Vector{promote_scalartype}
and we can then further specialise however we want? It seems like this is something that would be resolved with the new Memory
types too, as we can then just hardcode that and only have to deal with the different addrspace
implementations for GPU/CPU...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see no logic in the response of StefanKarpinski to Matt's question. Maybe we can just use VectorInterface.promote_add(A1, A2)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That indeed seems to work, I think that should be fine. It does rely on type inference, but I guess we can see if it causes any issues and still change it if we have to?
I wouldn't mind absorbing the normalization in the data, but I think in that case we should also consider changing the normalization of the fusiontrees, etc, to the isotopic normalization (I think that would be equivalent?) which is definitely not something I look forward to having to do. On the other hand, it does seem like the isotopic normalization convention appears somewhat more often in our use-cases (string-nets etc), so maybe it might be worth it... |
Another question that comes up as I continue with this PR is that all the linear algebra factorisations and things like applying functions to 'square' TensorMaps, current implementation works as:
With the new approach, you cannot construct the tensormap from the blocks in a data sharing way, so you allocate new data and copy the blocks into this. This causes (a potentially great deal of) extra allocations. Maybe this can be resolved by writing mutating versions of these operations in |
For the MatrixAlgebra, indeed, I cannot really think of a different solution. However, considering that LinearAlgebra does not expose this for these functions, it might be that the overhead of the extra copy is really just negligible for those operations? In any case, I think the code looks a bit nicer too if we don't have to first create all the block data and only then construct the output tensor, which also separates out the allocation and implementation cleanly. for c in blocksectors(A)
eig!((block(V, c), block(D, c)), block(A, c))
end |
Plan:
HomSpace
and experiment with different strategies for caching this informationMemory
object in Julia 1.11 and beyond.