Danger Will Robinson: Inherited LinearCode classes do not automatically become Modules

Issue #60 resolved
Johan Rosenkilde created an issue

It is clearly a running assumption in LinearCode that it inherits from module.Module. In LinearCode.__init__ there are the lines:

cat = Modules(base_ring).FiniteDimensional().WithBasis().Finite()
facade_for = generator_matrix.row(0).parent()
self.Element = type(generator_matrix.row(0)) # for when we make this a non-facade parent
Parent.__init__(self, base=base_ring, facade=facade_for, category=cat)

This initialises LinearCode as a valid member of the Sage category framework (a "category" is a matemathical term pertaining to the theory of how sets/rings/etc. operate wrt. each other. A sensible computer algebra system must be build around this theory at least to a certain extend. Sage was majorly reformed a few years ago to do this.).

This code must be run by any sub-class of LinearCode - otherwise, various functionality in LinearCode just won't work, and codes will interact badly with the rest of Sage (in, admittedly, probably relatively obscure uses.).

A way to test if a code has been properly initialised in the category framework is to ask for its parent:

sage: print LinearCode(random_matrix(GF(2), 2, 10)).parent()
<class 'sage.coding.linear_code.LinearCode_with_category'>

Notice the prefixed _with_category on the class name! If you want to know more, there is a long thread with some pedagogy on the trac ticket which fixed the problem originally in LinearCode (found using git-blame on the line in question.): trac #16644.

Some, but not all, category-related functions fail on the current GRS code, e.g. cardinality(). One thing that should have turned on an alarm bell in your brain is the following:

sage: print GeneralizedReedSolomonCode(GF(29), range(0, 10), 3).base_ring()
None

which you must have noticed, since your LinearCode.field() is written with the strange if-statement in case self.base_ring() indeed returns None.

In relation to the programmatic solution to this, this issue is related to #55. The category initialisation code could be put in the same __init__linear_code method.

Comments (6)

  1. Johan Rosenkilde reporter

    Ok, I now see cardinality fails just because base_ring does, so it was not a very good foundational example.

  2. David Lucas repo owner

    I read the trac thread and the documentation, and I understand the issue here, there is definitely something that must be fixed in my constructions.

    I have a question though : in the code you quote above, there is the following lines :

    facade_for = generator_matrix.row(0).parent() 
    self.Element = type(generator_matrix.row(0)) # for when we make this a non-facade parent
    

    I understand these lines works well for the actual LinearCode class, as it takes GeneratorMatrix as a parameter. But these cannot be ran for subclasses construction, as generator_matrix is not built at the time the code initializes. And I can't figure out something general enough to cover all the "families" we have now, and the one that will follow. Well, I thought about something like that : facade_for = VectorSpace(self.base_field, self._length) but I'm not sure it is correct...

    Am I missing something?

  3. Johan Rosenkilde reporter

    Hmm, I think you've got it write. What you're writing is one possibility, but it requires that a sub class sets its base_field before calling this super-initialiser. For instance, we could have the following method in LinearCode:

    def __init_linear_code(self):
        F = self._base_field
        cat = Modules(F).FiniteDimensional().WithBasis().Finite()
        facade_for = VectorSpace(F, self._length)
        self.Element = type(facade_for.an_element()) # for when we make this a non-facade parent
        Parent.__init__(self, base=F, facade=facade_for, category=cat)
    
        <initialise the cache dictionaries>
    

    And in e.g. GRS code constructor, we would write something like

    def __init__(self, eval_points, dimension):
        F = eval_points[0].parent()
        self._base_field = F
        self._length = len(eval_points)
        self.__init_linear_code()
    

    Alternatively, LinearCode.__init_linear_code should take the field and the length as arguments and then also set those two fields itself. That might save a few lines in most sub-classes. We could then write for the GRS code

    def __init__(self, eval_points, dimension):
        self.__init_linear_code(eval_points[0].parent(), len(eval_points))
        # self._base_field/base_ring and self._length are now all set
    
  4. David Lucas repo owner

    What you're writing is one possibility, but it requires that a sub class sets its base_field before calling this super-initialiser.

    That was my concern too... So I take your proposal which sets field and length as arguments for the super-initialiser.

  5. Log in to comment