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

Chart: Implement pickling via __getstate__/__setstate__ #31901

Open
mkoeppe opened this issue Jun 3, 2021 · 54 comments
Open

Chart: Implement pickling via __getstate__/__setstate__ #31901

mkoeppe opened this issue Jun 3, 2021 · 54 comments

Comments

@mkoeppe
Copy link
Contributor

mkoeppe commented Jun 3, 2021

In this ticket, we implement the pickling protocol using __getstate__ and __setstate__

Depends on #31894

CC: @egourgoulhon @tscrim @mjungmath

Component: manifolds

Work Issues: redo on top of #31894

Branch/Commit: u/mkoeppe/chart__no_longer_use_uniquerepresentation__implement___getstate_____setstate__ @ 30a5d0b

Issue created by migration from https://trac.sagemath.org/ticket/31901

@mkoeppe mkoeppe added this to the sage-9.4 milestone Jun 3, 2021
@mkoeppe

This comment has been minimized.

@mkoeppe mkoeppe changed the title Chart: UniqueRepresentation fixes Chart: No longer use UniqueRepresentation; implement __getstate__/__setstate__ Jun 20, 2021
@egourgoulhon
Copy link
Member

Replying to @mkoeppe:

In this ticket,

  • we make it possible to pass the codomain restrictions already when initializing the chart

This would be nice! In the early stages of the manifold project, I did not manage to do it simply, i.e. without changing Sage's preparser, since

sage: D = Manifold(2, 'D')                                                                          
sage: X.<x,y> = D.chart(restrictions=x^2 + y^2 < 1)                                                 

would generate NameError: name 'y' is not defined. Of course, one could replace x^2 + y^2 < 1 by the string "x^2 + y^2 < 1", but this is not very user-friendly. Do you already have some solution in mind?

@egourgoulhon
Copy link
Member

comment:3

Some thought: if UniqueRepresentation is abandoned for charts, it may be desirable to maintain WithEqualityById (one of the two mother classes of UniqueRepresentation) to have a fast equality operator. Indeed charts are heavily used as dictionary keys: for coordinates of manifold points, for coordinate expressions of scalar fields (the latter being used, among other things, to store the components of tensor fields), for coordinate expressions of continuous maps between manifolds (the keys being pairs of charts in that case), etc. Certainly WithEqualityById is the fastest equality operator and thus provides a fast access to the above dictionaries.

@mkoeppe
Copy link
Contributor Author

mkoeppe commented Jun 24, 2021

comment:4

Replying to @egourgoulhon:

Replying to @mkoeppe:

sage: D = Manifold(2, 'D')                                                                          
sage: X.<x,y> = D.chart(restrictions=x^2 + y^2 < 1)                                                 

would generate NameError: name 'y' is not defined. Of course, one could replace x^2 + y^2 < 1 by the string "x^2 + y^2 < 1", but this is not very user-friendly. Do you already have some solution in mind?

Ah! That's of course a problem, I didn't realize this

@mkoeppe

This comment has been minimized.

@mkoeppe
Copy link
Contributor Author

mkoeppe commented Jun 24, 2021

comment:6

Maybe a chart should become immutable "upon first use"?

@mkoeppe
Copy link
Contributor Author

mkoeppe commented Jun 24, 2021

@mkoeppe
Copy link
Contributor Author

mkoeppe commented Jun 24, 2021

New commits:

5215e93Chart: WIP WithEqualityById instead of UniqueRepresentation; disambiguate _restrictions -> _coordinate_restrictions

@mkoeppe

This comment has been minimized.

@mkoeppe
Copy link
Contributor Author

mkoeppe commented Jun 24, 2021

Commit: 5215e93

@sagetrac-git
Copy link
Mannequin

sagetrac-git mannequin commented Jun 30, 2021

Branch pushed to git repo; I updated commit sha1. Last 10 new commits:

facad97ContinuousMap.preimage: Handle identity_map specially
c2ecf3eScalarField.preimage: Handle the case of the zero scalar field
3b1c428ManifoldSubsetPullback: Make codomain_subset required 2nd init arg; fix pycodestyle/pyflakes warnings
d321b93ContinuousMap: Return domain if the map's codomain is contained in the given subset
07aba9eManifoldSubsetPullback.is_closed: Preimage of finite sets is closed
653c651src/sage/manifolds/subsets/pullback.py: Fix docstring markup
0f2e018Merge #31688
e3e38caChart.__init__: Move code that attaches self to the domain to TopologicalManifold.chart
3581c75Chart.function_ring: Cache via @cached_method, not UniqueRepresentation
869599dChart: WIP equality/immutability/pickling

@sagetrac-git
Copy link
Mannequin

sagetrac-git mannequin commented Jun 30, 2021

Changed commit from 5215e93 to 869599d

@mkoeppe
Copy link
Contributor Author

mkoeppe commented Jun 30, 2021

Dependencies: #31688

@sagetrac-git
Copy link
Mannequin

sagetrac-git mannequin commented Jun 30, 2021

Changed commit from 869599d to d071d56

@sagetrac-git
Copy link
Mannequin

sagetrac-git mannequin commented Jun 30, 2021

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

d071d56TopologicalSubmanifold: Adjust to renamed chart attribute _coord_restrictions

@mkoeppe
Copy link
Contributor Author

mkoeppe commented Jun 30, 2021

comment:12

The last obstacle is this code which tries to hash a chart when it is still mutable.

        # The null and one functions of the coordinates:
        # Expression in self of the zero and one scalar fields of open sets
        # containing the domain of self:
        for dom in self.open_supersets():
            dom._zero_scalar_field._express[chart] = chart.function_ring().zero()
            dom._one_scalar_field._express[chart] = chart.function_ring().one()

@sagetrac-git
Copy link
Mannequin

sagetrac-git mannequin commented Jun 30, 2021

Changed commit from d071d56 to 7b822b4

@sagetrac-git
Copy link
Mannequin

sagetrac-git mannequin commented Jun 30, 2021

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

7b822b4Fixup

@mkoeppe
Copy link
Contributor Author

mkoeppe commented Jun 30, 2021

comment:14

Replying to @mkoeppe:

Maybe a chart should become immutable "upon first use"?

This is the approach that I have been trying out in this branch.
But it does not work in examples like this one:

            sage: M = Manifold(2, 'M', structure='topological')
            sage: X.<x,y> = M.chart()
            sage: U = M.open_subset('U', coord_def={X: [x>1, y<pi]})

because X in coord_def is unhashable, leading to an error.

So perhaps an API change is needed, after all...

@egourgoulhon
Copy link
Member

comment:15

In the current branch, Chart inherits from WithEqualityById but redefines __eq__ (in a rather complicated and possibly CPU time consuming way). Doesn't this defeat the purpose? In particular in view of comment:3.

In the code of __hash__, isn't

if not self.is_immutable():
    raise TypeError('Charts are unhashable until set_immutable() has been called')

an unnecessary limitation?
More generally, why do you want to make Chart mutable? It is logically immutable: a chart is entirely defined by its domain and its coordinates (i.e. a tuple of symbolic variables). Coordinate restrictions, which can be added only after __init__ via add_restrictions in the current implementation, do not (mathematically) modify the object: there cannot be two charts with the same domain and the same coordinates, but with different coordinate restrictions.

@egourgoulhon
Copy link
Member

comment:16

A possible option to get rid of the 2-stages construction of a chart (i.e. to get rid of the requirement of invoking add_restriction after __init__) could be to add the keyword argument restrictions to Chart.__init__, which can be either

  • a string (case with no predefined coordinate variables, i.e. chart created from scratch)
  • a nested list/tuple of symbolic expressions (case of a chart created from another one, as in Chart.restrict)
    After all, when creating a chart from scratch, we are already passing a string if we want to specify the coordinate ranges and LaTeX symbols; so maybe we could admit having a second string...
    For instance, we could have something like
sage: H = Manifold(2, 'H')  # half-disk
sage: X.<x,y> = H.chart(r"x y:(0,+oo)", restrictions="x^2 + y^2 < 1")

or equivalently

sage: X.<x,y> = H.chart(restrictions="[x^2 + y^2 < 1, y>0]")

Then we could get rid of the method add_restriction and consider that the chart is a fully immutable object (of course, technically speaking, the sheafy attributes can be muted).

@mkoeppe
Copy link
Contributor Author

mkoeppe commented Jul 2, 2021

comment:17

Replying to @egourgoulhon:

In the current branch, Chart inherits from WithEqualityById but redefines __eq__ (in a rather complicated and possibly CPU time consuming way). Doesn't this defeat the purpose?

Yes, this part is not finished. I started with WithEqualityById as per your suggestion, but I wanted to implement actual pickling (not just passing the pickling-related testsuite) as a step to actual pickling of manifolds. Then the identity and unique-representation tricks are not sufficient any more, and one needs to implement full comparison.

One can still have a fast path for equality using id, and fast lookup in sets etc. via a fast hash.

@mkoeppe
Copy link
Contributor Author

mkoeppe commented Jul 2, 2021

comment:18

Replying to @egourgoulhon:

More generally, why do you want to make Chart mutable?

I don't really, it was just an attempt to keep the current API (initialization, followed by add_restrictions) but implementing pickling.

@mkoeppe
Copy link
Contributor Author

mkoeppe commented Jul 2, 2021

comment:19

When discussing a possible redesign of chart initialization, I think a relevant issue is the current use of the symbolic assumptions facility for the coordinates.

Are two charts with a different tuple of variables allowed to share some variables? If yes, does the shared variable have to have the same global assumptions and periodicity?

Also I note that creating a RealChart puts global assumptions on the variables of the chart. So creating a subchart will affect simplification of expressions that relate to computation on the original chart. This should probably be revised.

@sagetrac-git
Copy link
Mannequin

sagetrac-git mannequin commented Jul 2, 2021

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

4b930ffMerge tag '9.4.beta4' into t/31688/pullbacks_of_manifold_subsets_under_continuous_maps
96e1fbfMerge branch 't/31688/pullbacks_of_manifold_subsets_under_continuous_maps' into t/31901/chart__no_longer_use_uniquerepresentation__implement___getstate_____setstate__

@mkoeppe
Copy link
Contributor Author

mkoeppe commented Jul 2, 2021

comment:23

Replying to @egourgoulhon:

Replying to @mkoeppe:

Also I note that creating a RealChart puts global assumptions on the variables of the chart. [...]

Yes, absolutely. This is something I have in mind for a long time but did not find the time to work on it. The assumptions should not be global but chart-wise. The way Sage's symbolic calculus is implemented, I guess this means that they should be switched on (via assume) before each calculus at the chart function level and switched off once the calculus is done (via forget).

There is already a context manager assuming for that. We could create it at initialization and invoke it using with whenever computations are done with this chart.

The tricky bit is when two charts are involved with overlapping variables. Then some renaming may have to be done first. A possible route is through #32089.

@mkoeppe
Copy link
Contributor Author

mkoeppe commented Jul 2, 2021

comment:24

I have opened #32102 (Chart: Add init argument coord_restrictions, deprecate method add_restrictions)

@mkoeppe
Copy link
Contributor Author

mkoeppe commented Jul 2, 2021

comment:25

Replying to @egourgoulhon:

A connected question is how to pass these chart-based assumptions to SymPy, in case the latter has been chosen as the default symbolic backend on the manifold (via M.set_calculus_method('sympy')) or on a given chart X (via X.calculus_method().set('sympy')). For the time being, calculus with SymPy is possible but the assumptions are not taken into account on the SymPy side...

I suppose we can go through a new method assuming of CalculusMethod that dispatches in the same way as the simplify method does.

@egourgoulhon
Copy link
Member

comment:26

Replying to @mkoeppe:

OK. Then I think there is little point in trying to provide better syntax for creating the variables than asking the user to do var('x, y, z') before creating a chart with coordinate restrictions.

The issue here is that on real manifolds (by far the most used ones), the symbolic variables are created with the extra parameter domain='real' (cf. RealChart._init_coordinates), so the user must actually do var('x y z', domain='real'), which is not very intuitive. For some reason, it is not equivalent to assume(x, 'real'), which is performed in RealChart._init_coordinates as well. Both proved to be necessary for some simplifications.

Then the restrictions can just be passed as symbolic inequalities to the constructor. (I don't think going the string route would be an improvement.)

I don't like strings either, but I would prefer this to a 2-stage declaration involving var. Anyway, let us continue the discussion on #32102.

We could deprecate add_restrictions then.

Yes.

@egourgoulhon
Copy link
Member

comment:27

Replying to @mkoeppe:

Replying to @egourgoulhon:

Replying to @mkoeppe:

Also I note that creating a RealChart puts global assumptions on the variables of the chart. [...]

Yes, absolutely. This is something I have in mind for a long time but did not find the time to work on it. The assumptions should not be global but chart-wise. The way Sage's symbolic calculus is implemented, I guess this means that they should be switched on (via assume) before each calculus at the chart function level and switched off once the calculus is done (via forget).

There is already a context manager assuming for that. We could create it at initialization and invoke it using with whenever computations are done with this chart.

The tricky bit is when two charts are involved with overlapping variables. Then some renaming may have to be done first.

I don't see why: two computations on two distinct charts can set different assumptions on the same variable. There is no ambiguity as long as assumptions are tied to charts.

@mkoeppe
Copy link
Contributor Author

mkoeppe commented Jul 3, 2021

comment:28

Replying to @egourgoulhon:

Replying to @mkoeppe:

When discussing a possible redesign of chart initialization, I think a relevant issue is the current use of the symbolic assumptions facility for the coordinates.

Are two charts with a different tuple of variables allowed to share some variables?

Yes, definitely. This is a desirable feature.

A follow-up question on this:

sage: M = Manifold(2, 'M', structure='topological')
....: U = M.open_subset('U')
....: V = M.open_subset('V')
sage: XU = U.chart(names=("x", "y"))
sage: XV = V.chart(names=("x", "y"))
sage: M.atlas()
[Chart (U, (x, y)), Chart (V, (x, y))]
sage: M.top_charts()
[Chart (U, (x, y))]

Clearly something went wrong here.
Should there have been a declaration of something on M regarding the intention to declare charts with the coordinate pair (x,y) on subsets?

@mkoeppe
Copy link
Contributor Author

mkoeppe commented Jul 3, 2021

comment:29

Replying to @egourgoulhon:

Replying to @mkoeppe:
I think there is little point in trying to provide better syntax for creating the variables than asking the user to do var('x, y, z') before creating a chart with coordinate restrictions.

The issue here is that on real manifolds (by far the most used ones), the symbolic variables are created with the extra parameter domain='real' (cf. RealChart._init_coordinates), so the user must actually do var('x y z', domain='real'), which is not very intuitive.

How about something like M.var('x y z') then?

@mkoeppe
Copy link
Contributor Author

mkoeppe commented Jul 3, 2021

Changed dependencies from #31688 to #31688, #32102

@egourgoulhon
Copy link
Member

comment:31

Replying to @mkoeppe:

A follow-up question on this:

sage: M = Manifold(2, 'M', structure='topological')
....: U = M.open_subset('U')
....: V = M.open_subset('V')
sage: XU = U.chart(names=("x", "y"))
sage: XV = V.chart(names=("x", "y"))
sage: M.atlas()
[Chart (U, (x, y)), Chart (V, (x, y))]
sage: M.top_charts()
[Chart (U, (x, y))]

Clearly something went wrong here.

Good catch!

Should there have been a declaration of something on M regarding the intention to declare charts with the coordinate pair (x,y) on subsets?

No, the above is actually a bug in Chart.__init__. I've opened #32112 for it and submitted a fix there.

@sagetrac-git
Copy link
Mannequin

sagetrac-git mannequin commented Jul 3, 2021

Changed commit from 96e1fbf to d2e3ff7

@sagetrac-git
Copy link
Mannequin

sagetrac-git mannequin commented Jul 3, 2021

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

4e316e9Fix bug in Chart.__init__ regarding the determination of top charts (Trac #32112)
d2e3ff7Merge #32112

@mkoeppe
Copy link
Contributor Author

mkoeppe commented Jul 3, 2021

Changed dependencies from #31688, #32102 to #31688, #32112, #32102

@sagetrac-git
Copy link
Mannequin

sagetrac-git mannequin commented Jul 3, 2021

Changed commit from d2e3ff7 to a328e91

@sagetrac-git
Copy link
Mannequin

sagetrac-git mannequin commented Jul 3, 2021

Branch pushed to git repo; I updated commit sha1. This was a forced push. New commits:

a328e91Merge #32112

@mkoeppe
Copy link
Contributor Author

mkoeppe commented Jul 3, 2021

Work Issues: redo without mutability on top of #32102

@mkoeppe
Copy link
Contributor Author

mkoeppe commented Jul 4, 2021

comment:36

Replying to @egourgoulhon:

creating a RealChart puts global assumptions on the variables of the chart. So creating a subchart will affect simplification of expressions that relate to computation on the original chart.

The assumptions should not be global but chart-wise.

I've opened #32120 (Chart-wise assumptions) for this.

@mkoeppe
Copy link
Contributor Author

mkoeppe commented Jul 4, 2021

comment:37

Replying to @egourgoulhon:

Replying to @mkoeppe:

The tricky bit is when two charts are involved with overlapping variables. Then some renaming may have to be done first.

I don't see why: two computations on two distinct charts can set different assumptions on the same variable. There is no ambiguity as long as assumptions are tied to charts.

I was thinking of computations with coordinate changes here, which involve two charts simultaneously.

@mkoeppe
Copy link
Contributor Author

mkoeppe commented Jul 4, 2021

comment:38

Replying to @egourgoulhon:

A connected question is how to pass these chart-based assumptions to SymPy, in case the latter has been chosen as the default symbolic backend on the manifold (via M.set_calculus_method('sympy')) or on a given chart X (via X.calculus_method().set('sympy')). For the time being, calculus with SymPy is possible but the assumptions are not taken into account on the SymPy side...

We already have a meta-ticket for this, #31958 (Use the SymPy assumptions facility). I was surprised that SymPy's assumptions are not really well connected to SymPy's sets (for which I have been building some glue code in #31926).

@mkoeppe
Copy link
Contributor Author

mkoeppe commented Jul 4, 2021

comment:39

Given that #32102 is making mutability unnecessary, it seems we can keep UniqueRepresentation for charts for now (until we see another reason to drop it - such as unbounded memory) and pickle through that.

I'm setting the ticket to "sage-wishlist" for this reason.

@mkoeppe mkoeppe removed this from the sage-9.4 milestone Jul 4, 2021
@egourgoulhon
Copy link
Member

comment:40

Replying to @mkoeppe:

Replying to @egourgoulhon:

Replying to @mkoeppe:

The tricky bit is when two charts are involved with overlapping variables. Then some renaming may have to be done first.

I don't see why: two computations on two distinct charts can set different assumptions on the same variable. There is no ambiguity as long as assumptions are tied to charts.

I was thinking of computations with coordinate changes here, which involve two charts simultaneously.

There should not by any issue with coordinate changes: a coordinate change X1 --> X2 involves only a set of chart functions based on X1 (stored as a MultiCoordFunction in the attribute CoordChange._transf). So only assumptions relative to X1 will be invoked in calculus involving the coordinate change X1 --> X2 (such as the Jacobian matrix). An exception is the
computation of the inverse in CoordChange.inverse. There only assumptions on X2 should matter. However, if X1 and X2 share the same variables, the inverse is trivial. To summarize, I don't think there is a case where both sets of assumptions are required simultaneously in coordinate changes.

@mkoeppe
Copy link
Contributor Author

mkoeppe commented Jul 4, 2021

comment:41

Thanks for the explanation, that's good news.

@sagetrac-git
Copy link
Mannequin

sagetrac-git mannequin commented Jul 12, 2021

Changed commit from a328e91 to 30a5d0b

@sagetrac-git
Copy link
Mannequin

sagetrac-git mannequin commented Jul 12, 2021

Branch pushed to git repo; I updated commit sha1. This was a forced push. Last 10 new commits:

bf62543Merge branch 't/32089/conditionset__conditionset_callable_symbolic_expression' into t/32009/eliminate_direct_use_of_the_chart__domain_attribute
2f47485Merge #32009
21297f3Merge #32009
deace83Chart: Replace _init_coordinates by _parse_coordinates
4db4995Chart: Fix up `__classcall__` and _parse_coordinates by avoiding unhashable things
fc59c9dChart.__classcall__: Add doctest
1742fdcMerge #32009
9ac1834src/sage/manifolds/chart.py: Add raw string marker
d7f9d17Merge #32116
30a5d0bChart: WIP getstate/setstate, no UniqueRepresentation

@mkoeppe
Copy link
Contributor Author

mkoeppe commented Jul 13, 2021

Changed dependencies from #31688, #32112, #32102 to #31894

@mkoeppe

This comment has been minimized.

@mkoeppe
Copy link
Contributor Author

mkoeppe commented Jul 13, 2021

Changed work issues from redo without mutability on top of #32102 to redo on top of #31894

@mkoeppe mkoeppe changed the title Chart: No longer use UniqueRepresentation; implement __getstate__/__setstate__ Chart: Implement pickling via __getstate__/__setstate__ Jul 13, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants