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

Cache computed properties for toric varieties #839

Merged
merged 23 commits into from
Dec 29, 2021

Conversation

HereAround
Copy link
Member

@HereAround HereAround commented Nov 29, 2021

  • Add two dicts properties and attributes to struct NormalToricVarieties, AffineNormalToricVarieties, ToricDivisors.
  • Use these dicts to cache computed properties.
  • Before computation, check if the result is already known/set in these dicts.
  • Initialize properties for famous toric varieties (projective space, hirzebruch surface and del Pezzo) upon construction.
  • Improve show methods.
  • A couple of minor adjustments.

Please note that this PR contains the changes of #838. In this sense it is a draft and must be rebased once this other PR has been merged.

@thofma
Copy link
Collaborator

thofma commented Nov 29, 2021

There is already a mechanism for this using @declare_other and set_special/get_special, see for example

function factored_order(K::FinField)
l = get_special(K, :factored_order)
if l === nothing
l = factor(size(K)-1)
set_special(K, :factored_order => l)
end
return l
end

For this the structure needs just one additonal declaration, e.g.
mutable struct PermGroup <: GAPGroup
X::GapObj
deg::Int64 # G < Sym(deg)
AbstractAlgebra.@declare_other

@fieker rolled this out, so maybe he has an idea on whether we should try to use this everywhere in Oscar.

@fieker
Copy link
Contributor

fieker commented Nov 29, 2021 via email

@fingolfin
Copy link
Member

fingolfin commented Nov 30, 2021

Yeah, we ought to (a) rename @declare_other and set_special/get_special to something more consistent and sensible, (b) document it and then (c) advertise it to all our devs and beyond

@@ -74,7 +74,9 @@ P2 = toric_projective_space(2)
@test length(irrelevant_ideal(P2).gens) == 3
end

H5 = hirzebruch_surface(5)
rays = [1 0; 0 1; -1 5; 0 -1]
Copy link
Member

Choose a reason for hiding this comment

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

ERROR: LoadError: cannot assign a value to variable Oscar.rays from module Main


# if no Betti numbers have been computed
betti_numbers = Vector{fmpz}
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
betti_numbers = Vector{fmpz}

Copy link
Member Author

Choose a reason for hiding this comment

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

This line was intended for type stability, as I wanted to save these numbers always as fmpz (rather than Int, Int64). If removed, I might have to change the following line 19 to betti_numbers = fill(fmpz(-1),2*dim(v)+1), i.e. explicitly invoking the cast of -1 to fmpz(-1) there. (Which is of course ok.)

A related question. As there are several Betti numbers for one variety, it seems reasonable to cache them in a vector. Currently, I fill this vector with -1 at each position. As Betti numbers are always non-negative, a -1 tells that the corresponding Betti number has not (yet) been computed.

But maybe there is a better way than filling with -1s. E.g. to create a vector of suitable length by no entries set and then perform a check if an entry has been set? Suggestions very much appreciated.

This question/issue/challenge repeats for line bundle cohomologies (which I hope to turn to very soon).

Copy link
Member

Choose a reason for hiding this comment

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

This line does not do anything useful: in particular that value is immediately discarded in the following two lines. If you want type stability for this variable, you need to adjust each assignment to it to ensure the right type is assigned, eg via a type assertion and/or an explicit conversion

Copy link
Member Author

Choose a reason for hiding this comment

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

Thank you. Adjusted in 0113ddb.

if !has_attribute(cqs, :continued_fraction_hirzebruch_jung)
set_attribute!(cqs, :continued_fraction_hirzebruch_jung, Vector{fmpz}(pm_object(cqs).CONTINUED_FRACTION))
end
return get_attribute(cqs, :continued_fraction_hirzebruch_jung)
Copy link
Member

Choose a reason for hiding this comment

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

This and several similar functions could be simplified by using

    return get_attribute!(cqs, :continued_fraction_hirzebruch_jung) do
        return Vector{fmpz}(pm_object(cqs).CONTINUED_FRACTION)
    end

Copy link
Member

Choose a reason for hiding this comment

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

In general I am thinking about a system to make this stuff even nicer, inspired by GAP but my brain is still mushy

Copy link
Member Author

Choose a reason for hiding this comment

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

Thank you. That is a good suggestion. Indeed, this change can be done in a ton of places. I will do this in a new commit rather than rebasing everything (unless I hear differently).

Copy link
Member Author

Choose a reason for hiding this comment

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

Adjusted in d9efae2

end
end

# torusfactor?
Copy link
Member

Choose a reason for hiding this comment

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

?? Doesn't seem to fit the code?

Copy link
Member Author

Choose a reason for hiding this comment

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

Agreed - I will update this comment (which was intended to say "check what we know about the torusfactor").

NVM: This comment is entirely out of place (copied from the corresponding output string from toric varieties)...

I will adjust this.

Copy link
Member Author

Choose a reason for hiding this comment

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

Adjusted in 36f51bf.


# torusfactor?
if last(out_string) == ','
out_string = chop(out_string)
Copy link
Member

Choose a reason for hiding this comment

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

Personally I'd write this kind of function differently: first create a Vector{String} and push! all those adjectives into it. Then use join to concatenate cleverly.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point. I will adjust accordingly.

Copy link
Member Author

Choose a reason for hiding this comment

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

36f51bf, a1368a2 have been adjusted accordingly. Let me know what you think.

function Base.show(io::IO, td::ToricDivisor)
print(io, "A torus invariant divisor on a normal toric variety")
print(io, output_string(td))
Copy link
Member

Choose a reason for hiding this comment

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

why this way? Why not instead print into a string when you really need one?

Copy link
Member Author

Choose a reason for hiding this comment

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

My thoughts were no deeper than extending the existing code. As always, I am happy to change to a more robust/preferred/quicker alternative. Are saying that print(output_string(td)) is better?

Copy link
Member

Choose a reason for hiding this comment

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

No, I am saying that instead of shoving data into a string and then printing it, it is better to directly print it. And if you sometimes really want a string, you can simply print into a string.

Notw that even join takes an io argument and can thus print instead of creating a (temporary) string

Copy link
Member Author

Choose a reason for hiding this comment

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

36f51bf, a1368a2 have been adjusted accordingly. Let me know what you think.

Comment on lines +12 to +14
return get_attribute!(v, :dim) do
return pm_object(v).FAN_DIM::Int
end
Copy link
Member

Choose a reason for hiding this comment

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

I think some care is needed to retain type stability; the type assertion needs to be moved to the outer return. I think this might work (but didn't test):

Suggested change
return get_attribute!(v, :dim) do
return pm_object(v).FAN_DIM::Int
end
return get_attribute!(v, :dim)::Int do
return pm_object(v).FAN_DIM
end

You can verify if it worked by (a) calling it (to makes sure I got the syntax right, I am not sure), and (b) using @code_warntype on it to verify the return type is inferred correctly.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thank you for the suggestion @fingolfin . For me, this change leads to the following:

ERROR: LoadError: LoadError: LoadError: syntax: "function" at Oscar.jl/src/ToricVarieties/NormalToricVarieties/attributes.jl:11 expected "end", got "do"

Copy link
Member Author

Choose a reason for hiding this comment

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

For all it matters, here is a MWE via @code_warntype, which supports your suspicion that type stability requires more care:

v = toric_projective_space(2)
A normal, non-affine, smooth, projective, gorenstein, q-gorenstein, fano, 2-dimensional toric variety without torusfactor

julia> @code_warntype dim(v)
Variables
#self#::Core.Const(AbstractAlgebra.Generic.dim)
v::NormalToricVariety
#1418::Oscar.var"#1418#1419"{NormalToricVariety}

Body::Any
1 ─ %1 = Oscar.:(var"#1418#1419")::Core.Const(Oscar.var"#1418#1419")
│ %2 = Core.typeof(v)::Core.Const(NormalToricVariety)
│ %3 = Core.apply_type(%1, %2)::Core.Const(Oscar.var"#1418#1419"{NormalToricVariety})
│ (#1418 = %new(%3, v))
│ %5 = #1418::Oscar.var"#1418#1419"{NormalToricVariety}
│ %6 = Oscar.get_attribute!(%5, v, :dim)::Any
└── return %6

Hence, if I read this information correctly, Body::Any show that the return type is Any rather than Int.

Copy link
Member Author

Choose a reason for hiding this comment

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

How about turning function dim(v::AbstractNormalToricVariety) into dim(v::AbstractNormalToricVariety)::Int? This would seem to work and - based on @code_warntype - enusure that the return value is always Int64.

Copy link
Member Author

Choose a reason for hiding this comment

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

I am uncertain if the failures in the runs of the nightly build (ERROR: LoadError: Failed to precompile CxxWrap) are correlated to the changes in this PR.

Copy link
Member

Choose a reason for hiding this comment

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

writing dim(v::...)::Int has different meaning, though: it converts the result to Int, by inserting a call to convert(Int, ...); while a type assertion does just that, it checks that the value is an Int (if not, an exception is thrown).

Anyway, let's not worry about this for the moment. I describe a potential future solution for this in another comment.

Copy link
Member

@fingolfin fingolfin left a comment

Choose a reason for hiding this comment

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

Looks good to me.

From my POV we could merge now; the things I mention are truly minor. I'll do it tomorrow or the day after if I hear no complaints

Comment on lines 42 to 47
function euler_characteristic(v::AbstractNormalToricVariety)
f_vector = Vector{Int}(pm_object(v).F_VECTOR)
return f_vector[dim(v)]
return get_attribute!(v, :euler_characteristic) do
f_vector = Vector{Int}(pm_object(v).F_VECTOR)
return f_vector[dim(v)]
end
end
Copy link
Member

Choose a reason for hiding this comment

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

Clearly this is a recurring pattern, and one that I'd expect to see more in the future. It would be good to get this closer to what e.g. GAP (and probably also Polymake and other systems) can do in terms of ease of use and capabilities. E.g. having a general way to test if a value is already known. In GAP parlance, for every attribute Attr there is a "tests" HasAttr to detect if the attribute value is known. We could do the same and have methods like has_euler_characteristic (or haseuler_characteristic but I like it more with the underscore?); or perhaps it should be knows_euler_characteristic?

Anyway, a potential macro syntax

@attribute function euler_characteristic(v::AbstractNormalToricVariety)
    f_vector = Vector{Int}(pm_object(v).F_VECTOR)
    return f_vector[dim(v)]
end

So really the old code just with @attribute prefixed. Also available for inline functions, and it should be engineered to also transfer information about the return type, whether from type assertions or by regular type inference (by using Base.return_types)

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes that would be great.

Comment on lines +12 to +14
return get_attribute!(v, :dim) do
return pm_object(v).FAN_DIM::Int
end
Copy link
Member

Choose a reason for hiding this comment

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

writing dim(v::...)::Int has different meaning, though: it converts the result to Int, by inserting a call to convert(Int, ...); while a type assertion does just that, it checks that the value is an Int (if not, an exception is thrown).

Anyway, let's not worry about this for the moment. I describe a potential future solution for this in another comment.

src/ToricVarieties/NormalToricVarieties/methods.jl Outdated Show resolved Hide resolved
Return the toric variety of a torus-invariant Weil divisor.
"""
function toricvariety(td::ToricDivisor)
return get_attribute(td, :toricvariety)
Copy link
Member

Choose a reason for hiding this comment

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

This will return nothing if toricvariety is not set. Is this intentional? Or can it never happen? But if it can never happen, then why is toricvariety even an attribute instead of a regular struct field?

If it really should stay an attribute, at least add a type assertion?

Suggested change
return get_attribute(td, :toricvariety)
return get_attribute(td, :toricvariety)::AbstractNormalToricVariety

Copy link
Member Author

Choose a reason for hiding this comment

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

You are right - let me turn this into a struct field (it will be set upon construction of every toric divisor td).

@HereAround
Copy link
Member Author

HereAround commented Dec 29, 2021

Looks good to me.

From my POV we could merge now; the things I mention are truly minor. I'll do it tomorrow or the day after if I hear no complaints

Thank you @fingolfin.

Co-authored-by: Max Horn <max@quendi.de>
@fingolfin fingolfin merged commit 5fa3a5c into oscar-system:master Dec 29, 2021
@HereAround HereAround deleted the Cache branch March 7, 2022 19:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants