diff --git a/src/sage/manifolds/scalarfield.py b/src/sage/manifolds/scalarfield.py index 5f38403936a..1111b51f53b 100644 --- a/src/sage/manifolds/scalarfield.py +++ b/src/sage/manifolds/scalarfield.py @@ -40,12 +40,13 @@ # https://www.gnu.org/licenses/ # ***************************************************************************** -from sage.structure.element import CommutativeAlgebraElement +from sage.structure.element import (CommutativeAlgebraElement, + ModuleElementWithMutability) from sage.symbolic.expression import Expression from sage.manifolds.chart_func import ChartFunction +from sage.misc.cachefunc import cached_method - -class ScalarField(CommutativeAlgebraElement): +class ScalarField(CommutativeAlgebraElement, ModuleElementWithMutability): r""" Scalar field on a topological manifold. @@ -277,6 +278,48 @@ class ScalarField(CommutativeAlgebraElement): sage: zer is M.scalar_field_algebra().zero() True + The constant scalar fields zero and one are immutable, and therefore + their expressions cannot be changed:: + + sage: zer.is_immutable() + True + sage: zer.set_expr(x) + Traceback (most recent call last): + ... + AssertionError: the expressions of an immutable element cannot be + changed + sage: one = M.one_scalar_field() + sage: one.is_immutable() + True + sage: one.set_expr(x) + Traceback (most recent call last): + ... + AssertionError: the expressions of an immutable element cannot be + changed + + Other scalar fields can be declared immutable, too:: + + sage: c.is_immutable() + False + sage: c.set_immutable() + sage: c.is_immutable() + True + sage: c.set_expr(y^2) + Traceback (most recent call last): + ... + AssertionError: the expressions of an immutable element cannot be + changed + sage: c.set_name('b') + Traceback (most recent call last): + ... + AssertionError: the name of an immutable element cannot be changed + + Immutable elements are hashable and can therefore be used as keys for + dictionaries:: + + sage: {c: 1}[c] + 1 + By definition, a scalar field acts on the manifold's points, sending them to elements of the manifold's base field (real numbers in the present case):: @@ -1077,7 +1120,7 @@ def __init__(self, parent, coord_expression=None, chart=None, name=None, sage: TestSuite(f).run() """ - CommutativeAlgebraElement.__init__(self, parent) + super().__init__(parent) # both super classes have same signature domain = parent._domain self._domain = domain self._manifold = domain.manifold() @@ -1442,6 +1485,9 @@ def set_name(self, name=None, latex_name=None): \Phi """ + if self.is_immutable(): + raise AssertionError("the name of an immutable element " + "cannot be changed") if name is not None: self._name = name if latex_name is None: @@ -1723,17 +1769,19 @@ def set_expr(self, coord_expression, chart=None): sage: z.set_expr(3*y) Traceback (most recent call last): ... - AssertionError: the expressions of the element zero cannot be changed + AssertionError: the expressions of an immutable element cannot be + changed sage: one = M.one_scalar_field() sage: one.set_expr(3*y) Traceback (most recent call last): ... - AssertionError: the expressions of the element 1 cannot be changed + AssertionError: the expressions of an immutable element cannot be + changed """ - if self is self.parent().one() or self is self.parent().zero(): - raise AssertionError("the expressions of the element " - "{} cannot be changed".format(self._name)) + if self.is_immutable(): + raise AssertionError("the expressions of an immutable element " + "cannot be changed") if chart is None: chart = self._domain._def_chart self._express.clear() @@ -1785,17 +1833,19 @@ def add_expr(self, coord_expression, chart=None): sage: z.add_expr(cos(u)-sin(v), c_uv) Traceback (most recent call last): ... - AssertionError: the expressions of the element zero cannot be changed + AssertionError: the expressions of an immutable element cannot be + changed sage: one = M.one_scalar_field() sage: one.add_expr(cos(u)-sin(v), c_uv) Traceback (most recent call last): ... - AssertionError: the expressions of the element 1 cannot be changed + AssertionError: the expressions of an immutable element cannot be + changed """ - if self is self.parent().one() or self is self.parent().zero(): - raise AssertionError("the expressions of the element " - "{} cannot be changed".format(self._name)) + if self.is_immutable(): + raise AssertionError("the expressions of an immutable element " + "cannot be changed") if chart is None: chart = self._domain._def_chart self._express[chart] = chart.function(coord_expression) @@ -1863,6 +1913,9 @@ def add_expr_by_continuation(self, chart, subdomain): on V: (u, v) |--> arctan(1/(u^2 + v^2)) """ + if self.is_immutable(): + raise AssertionError("the expressions of an immutable element " + "cannot be changed") if not chart._domain.is_subset(self._domain): raise ValueError("the chart is not defined on a subset of " + "the scalar field domain") @@ -1898,6 +1951,9 @@ def set_restriction(self, rst): True """ + if self.is_immutable(): + raise AssertionError("the expressions of an immutable element " + "cannot be changed") if not isinstance(rst, ScalarField): raise TypeError("the argument must be a scalar field") if not rst._domain.is_subset(self._domain): @@ -3459,3 +3515,28 @@ def set_calc_order(self, symbol, order, truncate=False): if truncate: expr.simplify() self._del_derived() + + @cached_method + def __hash__(self): + r""" + Hash function. + + TESTS:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: f = M.scalar_field(x^2, name='f') + sage: f.set_immutable() + sage: hash(f) == f.__hash__() + True + + Let us check that ``f`` can be used as a dictionary key:: + + sage: {f: 1}[f] + 1 + + """ + if self.is_mutable(): + raise ValueError('element must be immutable in order to be ' + 'hashable') + return hash((self._domain, repr(self))) diff --git a/src/sage/manifolds/scalarfield_algebra.py b/src/sage/manifolds/scalarfield_algebra.py index e5eed57d2fd..3ee4b383f9d 100644 --- a/src/sage/manifolds/scalarfield_algebra.py +++ b/src/sage/manifolds/scalarfield_algebra.py @@ -596,6 +596,7 @@ def zero(self): coord_expression=coord_express, name='zero', latex_name='0') zero._is_zero = True + zero.set_immutable() return zero @cached_method @@ -625,6 +626,7 @@ def one(self): """ coord_express = {chart: chart.one_function() for chart in self._domain.atlas()} - return self.element_class(self, - coord_expression=coord_express, - name='1', latex_name='1') + one = self.element_class(self, coord_expression=coord_express, + name='1', latex_name='1') + one.set_immutable() + return one