diff --git a/src/sage/combinat/root_system/type_relabel.py b/src/sage/combinat/root_system/type_relabel.py index 350e290b32a..712937d08ce 100644 --- a/src/sage/combinat/root_system/type_relabel.py +++ b/src/sage/combinat/root_system/type_relabel.py @@ -10,7 +10,7 @@ from sage.misc.cachefunc import cached_method from sage.misc.lazy_attribute import lazy_attribute -from sage.sets.family import FiniteFamily +from sage.sets.family import Family, FiniteFamily from sage.combinat.root_system import cartan_type from sage.combinat.root_system import ambient_space from sage.combinat.root_system.root_lattice_realizations import RootLatticeRealizations @@ -173,10 +173,10 @@ def __init__(self, type, relabelling): sage: rI5 = CartanType(['I',5]).relabel({1:0,2:1}) sage: rI5.root_system().ambient_space() """ - assert isinstance(relabelling, FiniteFamily) cartan_type.CartanType_decorator.__init__(self, type) - self._relabelling = relabelling._dictionary - self._relabelling_inverse = relabelling.inverse_family()._dictionary + relabelling = Family(relabelling) + self._relabelling = dict(relabelling.items()) + self._relabelling_inverse = dict(relabelling.inverse_family().items()) self._index_set = tuple(sorted(relabelling[i] for i in type.index_set())) # TODO: design an appropriate infrastructure to handle this # automatically? Maybe using categories and axioms? diff --git a/src/sage/numerical/mip.pxd b/src/sage/numerical/mip.pxd index 612324a5424..e73cc408d09 100644 --- a/src/sage/numerical/mip.pxd +++ b/src/sage/numerical/mip.pxd @@ -3,8 +3,11 @@ cdef extern from *: cdef int REAL = -1 cdef int INTEGER = 0 +from sage.sets.family cimport FiniteFamily from sage.structure.sage_object cimport SageObject from sage.numerical.backends.generic_backend cimport GenericBackend + + cdef class MIPVariable @@ -26,10 +29,8 @@ cdef class MixedIntegerLinearProgram(SageObject): cpdef sum(self, L) noexcept -cdef class MIPVariable(SageObject): +cdef class MIPVariable(FiniteFamily): cdef MixedIntegerLinearProgram _p - cdef dict _dict - cdef bint _dynamic_indices cdef int _vtype cdef str _name cdef object _lower_bound diff --git a/src/sage/numerical/mip.pyx b/src/sage/numerical/mip.pyx index 1e6e6caa7bd..803724bf29b 100644 --- a/src/sage/numerical/mip.pyx +++ b/src/sage/numerical/mip.pyx @@ -3237,7 +3237,7 @@ class MIPSolverException(RuntimeError): pass -cdef class MIPVariable(SageObject): +cdef class MIPVariable(FiniteFamily): r""" ``MIPVariable`` is a variable used by the class ``MixedIntegerLinearProgram``. @@ -3286,17 +3286,16 @@ cdef class MIPVariable(SageObject): MIPVariable with 0 real components, >= 0 """ - self._dict = {} + super().__init__({}) self._p = mip self._vtype = vtype self._lower_bound = lower_bound self._upper_bound = upper_bound self._name = name - self._dynamic_indices = True if indices is not None: for i in indices: self[i] # creates component - self._dynamic_indices = False + self._keys = indices def __copy__(self): r""" @@ -3398,9 +3397,9 @@ cdef class MIPVariable(SageObject): """ cdef int j - if i in self._dict: - return self._dict[i] - if not self._dynamic_indices: + if i in self._dictionary: + return self._dictionary[i] + if self._keys is not None: raise IndexError("{} does not index a component of {}".format(i, self)) zero = self._p._backend.zero() name = self._name + "[" + str(i) + "]" if self._name else None @@ -3415,7 +3414,7 @@ cdef class MIPVariable(SageObject): name=name) v = self._p.linear_functions_parent()({j : 1}) self._p._variables[v] = j - self._dict[i] = v + self._dictionary[i] = v return v def copy_for_mip(self, mip): @@ -3461,8 +3460,8 @@ cdef class MIPVariable(SageObject): """ cdef MIPVariable cp = type(self)(mip, self._vtype, self._name, self._lower_bound, self._upper_bound) - cp._dict = copy(self._dict) - cp._dynamic_indices = self._dynamic_indices + cp._dictionary = copy(self._dictionary) + cp._keys = self._keys return cp def set_min(self, min): @@ -3501,7 +3500,7 @@ cdef class MIPVariable(SageObject): """ self._lower_bound = min - for v in self._dict.values(): + for v in self._dictionary.values(): self._p.set_min(v,min) def set_max(self, max): @@ -3537,7 +3536,7 @@ cdef class MIPVariable(SageObject): True """ self._upper_bound = max - for v in self._dict.values(): + for v in self._dictionary.values(): self._p.set_max(v,max) def _repr_(self): @@ -3570,9 +3569,9 @@ cdef class MIPVariable(SageObject): """ s = 'MIPVariable{0} with {1} {2} component{3}'.format( " " + self._name if self._name else "", - len(self._dict), + len(self._dictionary), {0:"binary", -1:"real", 1:"integer"}[self._vtype], - "s" if len(self._dict) != 1 else "") + "s" if len(self._dictionary) != 1 else "") if (self._vtype != 0) and (self._lower_bound is not None): s += ', >= {0}'.format(self._lower_bound) if (self._vtype != 0) and (self._upper_bound is not None): @@ -3591,11 +3590,11 @@ cdef class MIPVariable(SageObject): sage: sorted(v.keys()) [0, 1] """ - return self._dict.keys() + return self._dictionary.keys() def items(self): r""" - Return the pairs (keys,value) contained in the dictionary. + Return the pairs (keys, value) contained in the dictionary. EXAMPLES:: @@ -3605,7 +3604,7 @@ cdef class MIPVariable(SageObject): sage: sorted(v.items()) [(0, x_0), (1, x_1)] """ - return self._dict.items() + return self._dictionary.items() def values(self): r""" @@ -3619,7 +3618,7 @@ cdef class MIPVariable(SageObject): sage: sorted(v.values(), key=str) [x_0, x_1] """ - return self._dict.values() + return self._dictionary.values() def mip(self): r""" diff --git a/src/sage/sets/family.pxd b/src/sage/sets/family.pxd new file mode 100644 index 00000000000..f5d8f755ecc --- /dev/null +++ b/src/sage/sets/family.pxd @@ -0,0 +1,11 @@ +from sage.structure.parent cimport Parent + + +cdef class AbstractFamily(Parent): + cdef public __custom_name + cdef dict __dict__ # enables Python attributes as needed for EnumeratedSets() + + +cdef class FiniteFamily(AbstractFamily): + cdef public dict _dictionary + cdef public object _keys diff --git a/src/sage/sets/family.py b/src/sage/sets/family.pyx similarity index 96% rename from src/sage/sets/family.py rename to src/sage/sets/family.pyx index f3c3d0a7556..d1c102261a8 100644 --- a/src/sage/sets/family.py +++ b/src/sage/sets/family.pyx @@ -22,10 +22,16 @@ Category of finite enumerated sets """ -# **************************************************************************** -# Copyright (C) 2008 Nicolas Thiery , -# Mike Hansen , -# Florent Hivert +# ***************************************************************************** +# Copyright (C) 2008-2017 Nicolas Thiery +# 2008-2009 Mike Hansen +# 2008-2010 Florent Hivert +# 2013-2021 Travis Scrimshaw +# 2014 Nathann Cohen +# 2017 Erik M. Bray +# 2018 Frédéric Chapoton +# 2019 Markus Wageringel +# 2022-2023 Matthias Koeppe # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -38,19 +44,18 @@ from pprint import pformat, saferepr from collections.abc import Iterable -from sage.misc.abstract_method import abstract_method -from sage.misc.cachefunc import cached_method -from sage.structure.parent import Parent from sage.categories.enumerated_sets import EnumeratedSets from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets from sage.categories.infinite_enumerated_sets import InfiniteEnumeratedSets -from sage.sets.finite_enumerated_set import FiniteEnumeratedSet -from sage.misc.lazy_import import lazy_import -from sage.rings.integer import Integer +from sage.misc.cachefunc import cached_method from sage.misc.call import AttrCallObject -from sage.sets.non_negative_integers import NonNegativeIntegers +from sage.misc.lazy_import import LazyImport from sage.rings.infinity import Infinity -lazy_import('sage.combinat.combinat', 'CombinatorialClass') +from sage.rings.integer import Integer +from sage.sets.finite_enumerated_set import FiniteEnumeratedSet +from sage.sets.non_negative_integers import NonNegativeIntegers + +CombinatorialClass = LazyImport('sage.combinat.combinat', 'CombinatorialClass') def Family(indices, function=None, hidden_keys=[], hidden_function=None, lazy=False, name=None): @@ -396,8 +401,7 @@ def Family(indices, function=None, hidden_keys=[], hidden_function=None, lazy=Fa return TrivialFamily(indices) if isinstance(indices, (FiniteFamily, LazyFamily, TrivialFamily)): return indices - if (indices in EnumeratedSets() - or isinstance(indices, CombinatorialClass)): + if indices in EnumeratedSets(): return EnumeratedFamily(indices) if isinstance(indices, Iterable): return TrivialFamily(indices) @@ -418,7 +422,7 @@ def Family(indices, function=None, hidden_keys=[], hidden_function=None, lazy=Fa keys=indices) -class AbstractFamily(Parent): +cdef class AbstractFamily(Parent): """ The abstract class for family @@ -436,7 +440,6 @@ def hidden_keys(self): """ return [] - @abstract_method def keys(self): """ Return the keys of the family. @@ -447,8 +450,8 @@ def keys(self): sage: sorted(f.keys()) [3, 4, 7] """ + raise NotImplementedError - @abstract_method(optional=True) def values(self): """ Return the elements (values) of this family. @@ -459,6 +462,7 @@ def values(self): sage: sorted(f.values()) ['aa', 'bb', 'cc'] """ + raise NotImplementedError def items(self): """ @@ -535,7 +539,8 @@ def inverse_family(self): return Family({self[k]: k for k in self.keys()}) -class FiniteFamily(AbstractFamily): + +cdef class FiniteFamily(AbstractFamily): r""" A :class:`FiniteFamily` is an associative container which models a finite family `(f_i)_{i \in I}`. Its elements `f_i` are therefore its @@ -712,8 +717,8 @@ def __eq__(self, other): False """ return (isinstance(other, self.__class__) and - self._keys == other._keys and - self._dictionary == other._dictionary) + self._keys == ( other)._keys and + self._dictionary == ( other)._dictionary) def _repr_(self): """ @@ -869,15 +874,17 @@ def __getitem__(self, i): ... KeyError """ - if i in self._dictionary: - return self._dictionary[i] - - if i not in self.hidden_dictionary: - if i not in self._hidden_keys: - raise KeyError - self.hidden_dictionary[i] = self.hidden_function(i) - - return self.hidden_dictionary[i] + try: + return FiniteFamily.__getitem__(self, i) + except KeyError: + try: + return self.hidden_dictionary[i] + except KeyError: + if i not in self._hidden_keys: + raise KeyError + v = self.hidden_function(i) + self.hidden_dictionary[i] = v + return v def hidden_keys(self): """ @@ -902,11 +909,11 @@ def __getstate__(self): """ from sage.misc.fpickle import pickle_function f = pickle_function(self.hidden_function) - return {'dictionary': self._dictionary, - 'hidden_keys': self._hidden_keys, - 'hidden_dictionary': self.hidden_dictionary, - 'hidden_function': f, - 'keys': self._keys} + state = super().__getstate__() + state.update({'hidden_keys': self._hidden_keys, + 'hidden_dictionary': self.hidden_dictionary, + 'hidden_function': f}) + return state def __setstate__(self, d): """ @@ -922,7 +929,7 @@ def __setstate__(self, d): 6 """ hidden_function = d['hidden_function'] - if isinstance(hidden_function, str): + if isinstance(hidden_function, (str, bytes)): # Let's assume that hidden_function is an unpickled function. from sage.misc.fpickle import unpickle_function hidden_function = unpickle_function(hidden_function) @@ -1074,7 +1081,7 @@ def _repr_(self): """ if self.function_name is not None: name = self.function_name + "(i)" - elif isinstance(self.function, type(lambda x: 1)): + elif isinstance(self.function, types.LambdaType): name = self.function.__name__ name = name + "(i)" else: