Opened 2 years ago

Closed 2 years ago

#27633 closed enhancement (fixed)

Redesign of the constructor for Hyperelliptic curves

Reported by: annasomoza Owned by:
Priority: major Milestone: sage-8.8
Component: number theory Keywords: days98, hyperelliptic curves
Cc: sijsling, mstreng, vdelecroix, slelievre Merged in:
Authors: Anna Somoza Reviewers: Vincent Delecroix
Report Upstream: N/A Work issues:
Branch: fe002eb (Commits, GitHub, GitLab) Commit: fe002eba9502615124b4d17467a8dce423bb2adf
Dependencies: Stopgaps:

Status badges

Description (last modified by slelievre)

As discussed in #22173, the current the design of the hyperelliptic curves classes was not good.

This changes create the (genus, ring) subclasses dynamically, removing the need for a class/file per case.

Change History (28)

comment:1 Changed 2 years ago by annasomoza

  • Branch set to u/annasomoza/redesign_of_the_constructor_for_hyperelliptic_curves

comment:2 Changed 2 years ago by annasomoza

  • Authors set to Anna Somoza
  • Cc sijsling mstreng vdelecroix added
  • Commit set to e4d5db4ac23d3369e0a4838f0d8a49bd646f088a
  • Component changed from PLEASE CHANGE to number theory
  • Description modified (diff)
  • Keywords days98 hyperelliptic curves added
  • Status changed from new to needs_review
  • Type changed from PLEASE CHANGE to enhancement

comment:3 Changed 2 years ago by vdelecroix

  • Reviewers set to Vincent Delecroix
  • Status changed from needs_review to needs_work

This is a good move.

The logic in the following is weird

fld = map(lambda fnc : fnc(R), [is_FiniteField, is_RationalField, is_pAdicField])
fld_type = fld.index(True)

You would rather do something like

fields = [
   ("FiniteField", is_FiniteField, HyperellipticCurve_finite_field),
   ("RationalField", is_RationalField, HyperellipticCurve_rational_field),
   ("pAdicField", is_pAdicField, HyperellipticCurve_padic_field)]
for name, test, cls in fields:
    if test(R):
        ....    # treat the class
        break   # the tests are exclusive, no need to run through all cases
Last edited 2 years ago by vdelecroix (previous) (diff)

comment:4 Changed 2 years ago by git

  • Commit changed from e4d5db4ac23d3369e0a4838f0d8a49bd646f088a to 1c35da7d8f2230f64fd5eae83629daa0d21bbc4c

Branch pushed to git repo; I updated commit sha1. New commits:

1c35da7Modify field-related data treatment

comment:5 Changed 2 years ago by annasomoza

  • Status changed from needs_work to needs_review

Agreed, with your approach the code is easier to understand.

comment:6 Changed 2 years ago by git

  • Commit changed from 1c35da7d8f2230f64fd5eae83629daa0d21bbc4c to 2863afabd251b2b2d57e70295c3457c9532325c0

Branch pushed to git repo; I updated commit sha1. New commits:

2863afaDoc now builds

comment:7 Changed 2 years ago by vdelecroix

Similarly, why do you construct a dictionary in the following

+    genus_class = {2:HyperellipticCurve_g2}
+    if g in genus_class.keys():
+        supercls.append(genus_class[g])

This is simpler as

if g == 2:
    supercls.append(HyperellipticCurve_g2)
Last edited 2 years ago by vdelecroix (previous) (diff)

comment:8 Changed 2 years ago by annasomoza

This I did with #22173 in mind, where I will add the class for genus 3 to the dictionary (I am working on it right now). In general, if more genus are added, then using the dictionary leads to a cleaner code than a list of ifs.

comment:9 Changed 2 years ago by vdelecroix

I see. What about?

if g == 2:
    supercls.append(HyperellipticCurve_g2)
# elif g == 3:
#     supercls.append(HyperellipticCurve_g3)

comment:10 Changed 2 years ago by vdelecroix

What do you think about removing the inheritance from HyperellipticCurve_generic in HyperellipticCurve_rational_field, HyperellipticCurve_padic_field, etc? All the inheritance is handled by this new function anyway.

In this case, the function would be

cls = [HyperellipticCurve_generic]

...

if len(cls) > 1:   # contains more than just HyperellipticCurve_generic
    cls = type("HyperellipticCurve_" + "_".join(cls_name), tuple(cls), {})

return cls(PP, f, h, names=names, genus=g)

comment:11 follow-up: Changed 2 years ago by vdelecroix

And last but not the least, you definitely don't to create twice the same class. The first reason for this is that if you construct twice a hyperelliptic curves over rational you want the type to be the same. But the type function will recreate a new class each time (even if it has the same name).

Also, you don't want to create classes that are not necessary. Creating a class for each genus is a waste of time.

comment:12 Changed 2 years ago by git

  • Commit changed from 2863afabd251b2b2d57e70295c3457c9532325c0 to 5b5c570959a5704f168b30faec3132f115482df5

Branch pushed to git repo; I updated commit sha1. New commits:

5b5c570Modify genus-related data treatment. Store already-created classes for future use.

comment:13 in reply to: ↑ 11 ; follow-up: Changed 2 years ago by annasomoza

Replying to vdelecroix:

I see. What about?

if g == 2:
    supercls.append(HyperellipticCurve_g2)
# elif g == 3:
#     supercls.append(HyperellipticCurve_g3)

How about this new version? There I just use the information of the classes that are imported in the module.

Replying to vdelecroix:

What do you think about removing the inheritance from HyperellipticCurve_generic in HyperellipticCurve_rational_field, HyperellipticCurve_padic_field, etc? All the inheritance is handled by this new function anyway.

In this case, the function would be

cls = [HyperellipticCurve_generic]

...

if len(cls) > 1:   # contains more than just HyperellipticCurve_generic
    cls = type("HyperellipticCurve_" + "_".join(cls_name), tuple(cls), {})

return cls(PP, f, h, names=names, genus=g)

The methods in HyperellipticCurve_rational_field and others use methods from HyperellipticCurve_generic, so I don't think that it is a good idea.

Replying to vdelecroix:

And last but not the least, you definitely don't to create twice the same class. The first reason for this is that if you construct twice a hyperelliptic curves over rational you want the type to be the same. But the type function will recreate a new class each time (even if it has the same name).

Also, you don't want to create classes that are not necessary. Creating a class for each genus is a waste of time.

Agreed, I included the functionality and added a test for it.

comment:14 in reply to: ↑ 13 Changed 2 years ago by vdelecroix

Replying to annasomoza:

Replying to vdelecroix:

I see. What about?

if g == 2:
    supercls.append(HyperellipticCurve_g2)
# elif g == 3:
#     supercls.append(HyperellipticCurve_g3)

How about this new version? There I just use the information of the classes that are imported in the module.

This is terrible. Explicit code is to my mind much better. With the current version, a curious person looking at the code would have no clue about how it works.

comment:15 Changed 2 years ago by vdelecroix

Otherwise, the caching with the dictionary is ok.

comment:16 follow-up: Changed 2 years ago by VivianePons

The new version is good as it forces new contributions to follow the same name pattern and only requires to add the new class without changing the source code of the main function.

So I don't think "terrible" is an appropriate adjective! (Unless you have an other way to catch the existing classes?) A list of ifs might just get very long and unpractical.

I do agree that it is not very reader-friendly as it is. This could easily be fixed with more comments/documentation.

comment:17 in reply to: ↑ 16 Changed 2 years ago by vdelecroix

Replying to VivianePons:

The new version is good as it forces new contributions to follow the same name pattern and only requires to add the new class without changing the source code of the main function.

So I don't think "terrible" is an appropriate adjective! (Unless you have an other way to catch the existing classes?) A list of ifs might just get very long and unpractical.

The list has length one and after #22173 it will have length two. I don't understand this objection.

I do agree that it is not very reader-friendly as it is. This could easily be fixed with more comments/documentation.

"Terrible design" is appropriate.

  1. As I said it is unreadable.
  1. The code is sensitive to code injection
    sage: import sage.schemes.hyperelliptic_curves.constructor as constructor
    sage: constructor.HyperellipticCurve_g3 = int
    

You might consider this as a super feature, but it is very much error prone.

  1. The way it is written goes through several dictionary accesses and a try/except each time a hyperelliptic curve is created. This is a waste of time as you want constructors to be as light as possible.
Last edited 2 years ago by slelievre (previous) (diff)

comment:18 Changed 2 years ago by VivianePons

I understand the objection about security and efficiency. The argument that the list will be of length 2 is not very convincing though: the very purpose of this ticket is to write an architecture which allows for easy extensions.

I would suggest we go back to the initial solution that was proposed: use a dictionary associating the class name to the actual class. I find it much more readable than the list of ifs and easier to extend.

comment:19 Changed 2 years ago by slelievre

  • Cc slelievre added
  • Description modified (diff)

comment:20 Changed 2 years ago by git

  • Commit changed from 5b5c570959a5704f168b30faec3132f115482df5 to 92953194a0bcd415a3c4a3eee55b4e76ed9bd207

Branch pushed to git repo; I updated commit sha1. New commits:

9295319Back to using a dictionary to deal with genus-related classes

comment:21 Changed 2 years ago by annasomoza

Following Viviane's comment, I went back to using a dictionary.

I am not sure of the meaning of the error that the bot gives, I think it comes from the fact that some files where removed and is otherwise green.

comment:22 Changed 2 years ago by vdelecroix

You might want to test that inheritance is actually correct in the doctests

sage: R.<t> = PolynomialRing(GF(next_prime(10^9)))
sage: C = HyperellipticCurve(t^5 + t + 1)
sage: import inspect
sage: inspect.getmro(type(C))
(<class 'sage.schemes.hyperelliptic_curves.constructor.HyperellipticCurve_g2_FiniteField_with_category'>,
 <class 'sage.schemes.hyperelliptic_curves.constructor.HyperellipticCurve_g2_FiniteField'>,
 <class 'sage.schemes.hyperelliptic_curves.hyperelliptic_g2.HyperellipticCurve_g2'>,
 <class 'sage.schemes.hyperelliptic_curves.hyperelliptic_finite_field.HyperellipticCurve_finite_field'>,
 <class 'sage.schemes.hyperelliptic_curves.hyperelliptic_generic.HyperellipticCurve_generic'>,
 ...)

comment:23 Changed 2 years ago by annasomoza

Is it better to add a test for every case or with the one you gave it's enough?

Also, while looking into this I realized that I am duplicating the classes with only one superclass.

sage: R.<t> = PolynomialRing(GF(next_prime(10^9)))
sage: C = HyperellipticCurve(t^7 + t + 1)
sage: import inspect
sage: inspect.getmro(type(C))
(<class 'sage.schemes.hyperelliptic_curves.constructor.HyperellipticCurve_FiniteField_with_category'>,
 <class 'sage.schemes.hyperelliptic_curves.constructor.HyperellipticCurve_FiniteField'>,
 <class 'sage.schemes.hyperelliptic_curves.hyperelliptic_finite_field.HyperellipticCurve_finite_field'>,
 <class 'sage.schemes.hyperelliptic_curves.hyperelliptic_generic.HyperellipticCurve_generic'>,
...)

Would it be preferable to initiate the created_classes dictionary with the classes HyperellipticCurve_g2, HyperellipticCurve_FiniteField, etc to avoid that? That could also simplify the last part of the code if HyperellipticCurve_generic was added too.

comment:24 Changed 2 years ago by git

  • Commit changed from 92953194a0bcd415a3c4a3eee55b4e76ed9bd207 to 12069c782ba3b2f9dde21b1740fed8df7b45d7d7

Branch pushed to git repo; I updated commit sha1. New commits:

12069c7Test added. Dynamic creation of classes is now done with `dynamic_class` function.

comment:25 Changed 2 years ago by vdelecroix

Even nicer.

One last little thing. In the documentation, you need a linebreak after ::.

comment:26 Changed 2 years ago by git

  • Commit changed from 12069c782ba3b2f9dde21b1740fed8df7b45d7d7 to fe002eba9502615124b4d17467a8dce423bb2adf

Branch pushed to git repo; I updated commit sha1. New commits:

fe002ebAdd missing endline in documentation

comment:27 Changed 2 years ago by vdelecroix

  • Status changed from needs_review to positive_review

Perfect! Thanks for your patience.

comment:28 Changed 2 years ago by vbraun

  • Branch changed from u/annasomoza/redesign_of_the_constructor_for_hyperelliptic_curves to fe002eba9502615124b4d17467a8dce423bb2adf
  • Resolution set to fixed
  • Status changed from positive_review to closed
Note: See TracTickets for help on using tickets.