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

mutable poset: a data structure for asymptotic expressions #17693

Closed
dkrenn opened this issue Jan 30, 2015 · 66 comments
Closed

mutable poset: a data structure for asymptotic expressions #17693

dkrenn opened this issue Jan 30, 2015 · 66 comments

Comments

@dkrenn
Copy link
Contributor

dkrenn commented Jan 30, 2015

The aim of this ticket is to implement a data structure which is to be used within an asymptotic expression (see Meta-Ticket #17601). This mutable poset stores elements together with its successors and predecessors. Those data is updated when a new element is inserted or an element is removed. It offers a couple of efficient routines for manipulations (all of which will be needed for the asymptotic expression).

CC: @behackl @cheuberg @jm58660

Component: asymptotic expansions

Author: Daniel Krenn

Branch/Commit: a0b3d7b

Reviewer: Benjamin Hackl, Clemens Heuberger

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

@dkrenn dkrenn added this to the sage-6.5 milestone Jan 30, 2015
@dkrenn
Copy link
Contributor Author

dkrenn commented Jan 30, 2015

Branch: u/dkrenn/asy/mutable_poset

@dkrenn
Copy link
Contributor Author

dkrenn commented Jan 30, 2015

Last 10 new commits:

67522abadd-method: allow cancellation of elements
91c071bmoved hook from add to __init__
bdc3f16rename: value --> element --> shell
434f4e1write method element
98e20b0improve/extend docstrings
5488f10conditional iterations for shells
f00d323rename element_exists_hook to merge_hook
1dc03d7introduce can_merge and rename merge_hook to merge
960b18ba couple of minor rewritings to make the code and doctests nicer
e2f884fwrite merge-methods

@dkrenn
Copy link
Contributor Author

dkrenn commented Jan 30, 2015

Commit: e2f884f

@dkrenn
Copy link
Contributor Author

dkrenn commented Jan 30, 2015

Author: Daniel Krenn

@nathanncohen
Copy link
Mannequin

nathanncohen mannequin commented Jan 30, 2015

comment:3

A mutable poset class should be stored in sage.combinat.posets.

Nathann

@dkrenn
Copy link
Contributor Author

dkrenn commented Feb 2, 2015

comment:4

This series of comments are the result of an email discussion. Its aim is to give a better understanding of the needs of the data structure used in an asymptotic expression.

An example of an asymptotic expression in say 2 variables (n->oo, t->oo) is

A = n^3*t^2 + 42*n^2*t^2 + 3*n*t^4 + O(t)

This is stored in the mutable poset in the following way: the elements of the poset are exactly the summands of the expression above. This means

sage: A.data  # or however this may be called
poset(n^3*t^2, 42*n^2*t^2, 3*n*t^4, O(t))

The growth of these summands is partially ordered:
a) n<sup>3*t</sup>2 >= n<sup>2*t</sup>2
b) n<sup>3*t</sup>2 >= t
c) n<sup>2*t</sup>2 >= t
d) n*t^4 >= t
The mutable poset stores/caches the direct successors/predecessors, i.e., only the relations a), c), d), but not b), since this follows by transitivity out of a) and c). Thus, for the example above, we have the following information stored

poset
- term O(t)
  successors: n^3*t^2, 42*n^2*t^2, 3*n*t^4
  no predecessors
- term 3*n*t^4
  no successors
  predecessors: O(t)
- term 42*n^2*t^2
  successors: n^3*t^2
  predecessors: O(t)
- term n^3*t^2
  no successors
  predecessors: 42*n^2*t^2

@dkrenn
Copy link
Contributor Author

dkrenn commented Feb 2, 2015

comment:5

We want to perform arithmetic with asymptotic expressions, in particular we need to do efficiently:

  1. addition (and multiplication)
  2. merging/absorbing terms, e.g.
n^4 + 2*n^2 + 3*n + O(1) + O(n^2) = n^4 + O(n^2)

or

O(n*t) + n + t = O(n*t)
  1. removing elements of the poset, because of 2)

To make them efficent, we store successors and predecessors of each element in the poset. These are updated when inserting/removing elements.

Note that an asymptotic expression can contain several O-terms, e.g.

O(n^3 t^2) + O(n^4 t^1) + O(n^2 t^3)

is a valid expression. In the poset each O-term is a minimal
element and they are all pairwise incomparable (thus, the O terms
will be an antichain in the poset). Anyhow, non-O-terms be be
compareable to other elements, e.g. in

3*n*t + O(n) + O(t)

we have n*t >= n and n*t >= t.

We also need to deal with situations where we want to insert an
element of the same growth: E.g. 4n+3n = 7n, 4n+O(n) = O(n),
thus, the elements themselves can change during addition. When
merging/absorbing and working with O-Terms with explicit
O-constants, this also occurrs frequently (even with terms of
different growth).

@dkrenn
Copy link
Contributor Author

dkrenn commented Feb 2, 2015

comment:6

Note that all the terms (individual summands) support one
operation, namely these can be multiplied, maybe coercion is
used, e.g.

4*n^2*t * O(n) --> O(n^2*t) * O(n) --> O(n^3*t)

Thus we have monoids here. "Addition" of terms is more complicated
since not always possible. E.g.

n + O(t) = n + O(t)

But since this is an operation we want to have, the data
structure has to take this into consideration.

Here an example of the addition of two asymptotic expressions, to
see what the poset should do:

B = n^2*t + n + O(1)
C = n*t^2 + O(n)

Steps in computing B + C:

  1. calculate: B.poset union C.poset
    This gives
poset(n^2*t, n, O(1), n*t^2, O(n)).
  1. simplify: returns
poset(n^2*t, n*t^2, O(n))

To achieve this, we have to search for what can be "absorbed" by the
O-term O(n). These are exactly all the predecessors of O(n) (not only
the direct, but also the predecessors of the predecessors...)
Thus some kind of caching of predecessors is necessary, but "cache"
has to be updated when modifying the poset.

Note that this data structure does a preprocessing of its relations, which is definitely good for nonsmall instances. At the moment this class should provide an interface for the needed routines. A fine tuning (w.r.t. speed optimization for various sizes) can be done later (if the performance is too bad).

To conclude, the data structure needed in an asymptotic expression needs to deal with dynamic changes (mutability) and do some caching to provide efficient methods. And it needs to do more than a poset which is made mutable. In particular, it has to provide the methods for the above mentioned merging and absorbing.

@dkrenn
Copy link
Contributor Author

dkrenn commented Feb 2, 2015

comment:7

Replying to @nathanncohen:

A mutable poset class should be stored in sage.combinat.posets.

This class has nothing to do with combinatorics, therefore I am against sage.combinat. And it implements a data structure and we have sage.data_structures, so it seems the right place.

@nathanncohen
Copy link
Mannequin

nathanncohen mannequin commented Feb 2, 2015

comment:8

This class has nothing to do with combinatorics, therefore I am against sage.combinat. And it implements a data structure and we have sage.data_structures, so it seems the right place.

Graphs are a data structure, they are in graphs/. Incidence Structures are data structure, and they are in combinat/designs. Matrices are a data structure, and they are in matrix/. Posets and Hasse Diagrams are a data structure, and they are in combinat/posets/.

Also, you wrote in your branch a 'todo note' to implement in your MutablePoset data structure that 20+ methods from the current Posets should be implemented for your new class: the proper way to do that would be to inherit them from posets.

Could you also explain what "shells" are, and how they differ from the 'facade' boolean argument of Posets ?

Thanks,

Nathann

@cheuberg
Copy link
Contributor

cheuberg commented Feb 3, 2015

comment:9

Replying to @nathanncohen:

Also, you wrote in your branch a 'todo note' to implement in your MutablePoset data structure that 20+ methods from the current Posets should be implemented for your new class: the proper way to do that would be to inherit them from posets.

A FinitePoset inherits from Parent and my understanding is that parents must be immutable.
Thus, IMHO, this mutable poset here should not inherit from FinitePoset.

@nathanncohen
Copy link
Mannequin

nathanncohen mannequin commented Feb 3, 2015

comment:10

A FinitePoset inherits from Parent and my understanding is that parents must be immutable.

Indeed, but it may actually be time to change that. We are also having problem with this, because apparently the construction of Parent/UniqueRepresentation instances is known to be very slow, and you "should not build too many of them at once" or it will slow code down a lot.

That is what is already preventing us from implementing a proper iterator over all non-isomorphic posets of a given size: this operation creates too many parents and is the bottleneck in the computations.

If it seems that you are bothered by the same thing, it gives all the more reasons to make that change.

Nathann

@dkrenn
Copy link
Contributor Author

dkrenn commented Feb 5, 2015

comment:11

Replying to @nathanncohen:

Could you also explain what "shells" are, and how they differ from the 'facade' boolean argument of Posets ?

There are similarities between shells and facades, but a facade is something in Sage that represent other things and again uses parents/elements (thus immutable). It has well-defined properties and there is even a category for facade sets.
The shells in the mutable poset are more like a container for the elements. Ideally a user of the poset does not need them at all (and only a few MutablePoset-methods use them). They are only used inside the poset-algorithms.
At one point I thought about making shells and the related methods private, but never did this...

@jdemeyer
Copy link

jdemeyer commented Feb 5, 2015

comment:12

Replying to @nathanncohen:

Graphs are a data structure

No, graphs are mathematical objects. "a binary symmetric matrix implemented using bitsets" (which could be used for dense graphs) is a data structure.

@nathanncohen
Copy link
Mannequin

nathanncohen mannequin commented Feb 5, 2015

comment:13

Graphs are a data structure

No, graphs are mathematical objects.

I was not thinking of a graph as it is defined in a book, but of Sage's graphs. I should have said "the Graph class" probably.

Nathann

@dkrenn
Copy link
Contributor Author

dkrenn commented Jul 28, 2015

Changed commit from e2f884f to 1c6b196

@dkrenn
Copy link
Contributor Author

dkrenn commented Jul 28, 2015

Changed branch from u/dkrenn/asy/mutable_poset to u/dkrenn/asy/mutable-poset

@dkrenn
Copy link
Contributor Author

dkrenn commented Jul 28, 2015

Last 10 new commits:

143dfeawrite method element
b348d14improve/extend docstrings
520bb00conditional iterations for shells
e443f7erename element_exists_hook to merge_hook
b79f1bcintroduce can_merge and rename merge_hook to merge
82ed0f3a couple of minor rewritings to make the code and doctests nicer
13d4a36write merge-methods
8584f57write __len__
a1678edremove TODO-list of methods (list is now in separat branch)
1c6b196write methods for maximal/minimal elements

@jm58660
Copy link
Mannequin

jm58660 mannequin commented Jul 28, 2015

comment:15

If union() does same thing as disjoint_union() in posets and in graphs, please change name accordingly. It seems unnecessary to have INPUT-part at all, if the function has no arguments at all.

Just my two cents. I would give three, if I would have more time.

@dkrenn
Copy link
Contributor Author

dkrenn commented Jul 29, 2015

comment:16

Replying to @jm58660:

If union() does same thing as disjoint_union() in posets and in graphs, please change name accordingly.

No, it does not do the same. Union takes only one instance of equal elements, whereas disjoint_union makes each one of the equal elements distinct.

It seems unnecessary to have INPUT-part at all, if the function has no arguments at all.

The developer guide

http://doc.sagemath.org/html/en/developer/coding_basics.html#documentation-strings

says

An INPUT and an OUTPUT block describing the input/output of the function. This is not optional.

Therefore skipping one of these blocks seems to be against the rules (and hopefully helps avoiding the confusion of a missing input block).

Just my two cents. I would give three, if I would have more time.

Many thanks for all your cents.

Best, Daniel

@sagetrac-git
Copy link
Mannequin

sagetrac-git mannequin commented Jul 29, 2015

Changed commit from 1c6b196 to 19fd155

@sagetrac-git
Copy link
Mannequin

sagetrac-git mannequin commented Jul 29, 2015

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

19fd155allow merge to go over all elements

@jm58660
Copy link
Mannequin

jm58660 mannequin commented Jul 29, 2015

comment:18

Replying to @dkrenn:

Replying to @jm58660:

If union() does same thing as disjoint_union() in posets and in graphs - -

No, it does not do the same.

OK.

It seems unnecessary to have INPUT-part at all, if the function has no arguments at all.

The developer guide says

An INPUT and an OUTPUT block describing the input/output of the function. This is not optional.

True. But I don't think if it has been thinked that "no input" must be documented. Is it in other places? Or should this part of developer guide be changed?

And should for example docstring of .cardinality() really contain OUTPUT-block saying that output type is sage's Integer?

@dkrenn
Copy link
Contributor Author

dkrenn commented Aug 18, 2015

comment:20

Replying to @jm58660:

The developer guide says

An INPUT and an OUTPUT block describing the input/output of the function. This is not optional.

True. But I don't think if it has been thinked that "no input" must be documented. Is it in other places? Or should this part of developer guide be changed?

I would keep it. (At the moment) the developer guide says to include it and I find it very nice to have constistent docstrings starting with one-liner, INPUT, OUTPUT, more details, EXAMPLES, ...

(And: there are other places.)

And should for example docstring of .cardinality() really contain OUTPUT-block saying that output type is sage's Integer?

What do you suggest?

@jm58660
Copy link
Mannequin

jm58660 mannequin commented Aug 18, 2015

comment:21

Replying to @dkrenn:

Replying to @jm58660:

The developer guide says

An INPUT and an OUTPUT block describing the input/output of the function. This is not optional.

True. But I don't think if it has been thinked that "no input" must be documented. Is it in other places? Or should this part of developer guide be changed?

I would keep it. (At the moment) the developer guide says to include it and I find it very nice to have constistent docstrings starting with one-liner, INPUT, OUTPUT, more details, EXAMPLES, ...

This is discussed at #19041. But don't let this stop coding -- Sage will propably never get finished so that questions like this would have The Final Answer.

And should for example docstring of .cardinality() really contain OUTPUT-block saying that output type is sage's Integer?

What do you suggest?

Every cardinality() should return Integer. For other integer-valued functions there were discussion about int vs. Integer without consensus.

I don't know. For input it is explicitly said that the docstring can say "x in integer", not "x must be Integer or int". These are hard questions, as what is "clear" varies from one person to other.

@sagetrac-git
Copy link
Mannequin

sagetrac-git mannequin commented Sep 28, 2015

Changed commit from f7bce7e to 3c69f6b

@sagetrac-git
Copy link
Mannequin

sagetrac-git mannequin commented Sep 28, 2015

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

34e34dcTrac #17693, comment 36, 19--23: improve docstring of covers (former `_search_covers_`)
490c223Trac #17693, comment 36, 28--31: document role of self
3de0a1dTrac #17693, comment 36, 33: explicitly mention merge and can_merge at top of docstring
485b46fTrac #17693, comment 36, 34: raise exceptions when merge is not possible
d5d6beccheck=False in MutablePoset.merge since checked already before
6da3db1Trac #17693, comment 36, 37: extend doc to mention more explicitly that merge is not allowed to change keys
f1e9f9eTrac #17693, comment 36, 38: clarify parameter key
ae0cad9Trac #17693, comment 36, 60: mention motivation asymptoic expansions in merge method
ec15597Trac #17693: alternative implementation add
3c69f6bMerge remote-tracking branch 'origin/u/cheuberg/asy/mutable-poset-add' into t/17693/asy/mutable-poset

@dkrenn
Copy link
Contributor Author

dkrenn commented Sep 28, 2015

comment:39

Replying to @cheuberg:

I read the documentation and the code. I pushed a few reviewer commits.

Cross-reviewed. One commit added.

I have a number of rather minor issues with the documentation which I ask you to fix in order facilitate future maintainance of the code. In three instances, I propose alternative implementations, which might be more readable or slightly more efficient.

Thanks; see my comments below.

  1. Please add .. seealso:: blocks between corresponding methods of shell and poset as well as between the accessor methods (keys, elements, shells)

Done.

  1. there is an empty "Introduction" section at the beginning of the module documentation, the next heading "Example" is on the same level.

Deleted.

  1. this module was written as part of the asymptotic expansion effort. In contrast to the asymptotic expansion, it does not have the "experimental" decorator. I'd feel more comfortable having it, at least until a larger portion of the asymptotic expansions have been merged.

Activated it; it seems (for some (to me) unknown reason) the warning appears now in every doctest and not only once in the file. Thus, deactivated it again; and I am for keeping it this way.

  1. MutablePoset.__init__ accepts a list of elements. This could be used in several doctests (in particular, in the set operations) as an abbreviation.

Good idea. Done.

  1. MutablePosetShell.__init__: shall we check that element is not None? Otherwise, handling of special elements would probably be affected.

MutablePosetShell is typically used by the MutablePoset; and there, adding a None-element is forbidden (see :meth:add).

  1. MutablePosetShell.key: I do not understand the sentence "The element is converted by the poset to the key".

Rewritten.

  1. MutablePosetShell.key: I am surprised that the key is not cached. I imagine that many comparisons will be needed in the lifetime of a MutablePoset, and in every case, the property key has to be resolved, which calls the property poset, which calls get_key of the poset.

Now it is cached (see also follow up ticket #19281).

  1. MutablePosetShell.__repr__: The note box in the docstring is surprising at this point: reading the source file from the top, this is the first point where the representation of the data is explained, after guessing it from is_special, is_oo, is_null above. I think that it would be better to document these conventions closer to the top, perhaps in the __init__ method or perhaps only as a comment in the source code.

Added a note in the class description.

  1. MutablePosetShell.__repr__: The code replicates the behaviour of is_null and is_oo. As the __repr__ method is hardly time critical, I'd prefer using is_null and is_oo, here and thus hiding the internal convention.

Rewritten.

  1. MutablePosetShell.le: the note box could be simplified by suppressing implementation details and speaking about the special elements.

Done.

  1. MutablePosetShell.le: right <= left: neither right nor left are defined here.

Rewritten.

  1. MutablePosetShell.le: the part if other.element is None could be simplified to return not other.successors() as self is already known not to be special here.

True; rewritten.

  1. MutablePosetShell.le: If this is time critical, self._predecessors_ could be used instead of self.predecessors().

Changed.

  1. MutablePosetShell.eq: note box, see above.

Simplified.

  1. MutablePosetShell.eq: emphasize that elements with equal keys are considered equal? Currently, this information is somewhat hidden in the note box which at first glance only seems to explain handling of special elements.

Rewritten note box.

  1. MutablePosetShell._copy_all_linked_: Short description: "Return a copy of all shells" does not correspond to the actual return value, which is only the copy of this shell.

Rewritten.

  1. MutablePosetShell._copy_all_linked_: the interplay between this method and MutablePoset._copy_shells_ is not well documented: in particular, poset in _copy_all_linked_ is only used for setting the containing poset, but not for actual inserting into this poset.

Extended description of parameter poset.

  1. MutablePosetShell._copy_all_linked_: I do not understand why you test oo == P.oo: I think that oo is an element of the new poset Q.

oo == P.oo tests that Q.oo is mapped to P.oo.

So oo is Q.oo would be more interesting?

oo is Q.oo is False since oo is not in Q, but just a copy of the oo in P with parent-poset Q. Inserting this into Q is done in _copy_shells_.

The current test demonstrates that the poset is not used in comparison, so that would rather belong to .eq?

I do not understand what you mean (but it is already late...).

  1. MutablePosetShell._search_covers_: what is the role of self in this method? It trivially influences the return value, but what else?

_search_covers_ does not exist anymore (see 27).

  1. MutablePosetShell._search_covers_: The explanation of the return value is unclear to me. Is the sentence "Note that..." meant to be a complete description? Does it change if reverse is set?

_search_covers_ does not exist anymore (see 27).

  1. MutablePosetShell._search_covers_: I think that the notions of "upper cover" and "lower cover" need a brief definition; I did not find them in Wikipedia and sage.combinat.posets.posets.FinitePoset.upper_covers defines the notion.

_search_covers_ does not exist anymore (see 27). Incooperated in covers.

  1. MutablePosetShell._search_covers_: According to Wikipedia, a cover is what is called an upper cover here. This is in contrast to the default behaviour here.

_search_covers_ does not exist anymore (see 27).

  1. MutablePosetShell._search_covers_: what is the difference between this method and .predecessors? Is the shell necessarily an element of the poset? If not, then there should be a doctest covering this case.

_search_covers_ does not exist anymore (see 27). Incooperated in covers.

  1. MutablePosetShell.covers: "originate" is somewhat foreign to the description of the poset.

Rewritten.

  1. MutablePosetShell.covers: "which are at most the given shell": should that be "which are less than the given shell"?

Rewritten.

  1. MutablePosetShell.covers see comments on MutablePosetShell._search_covers_.

Rewritten.

  1. MutablePosetShell.covers: I think that the following would be an equivalent implementation of this method, which does not need a helper function with side effects.
        if self == shell:
            return set()
        covers = set().union(*(e.covers(shell, reverse)
                               for e in self.successors(reverse)
                               if e.le(shell, reverse)))
        if covers:
            return covers
        else:
            return set([self])

(pushed as branch u/cheuberg/asy/mutable-poset-cover)

Merged and minor simplification.

  1. MutablePosetShell._iter_depth_first_visit_: document the role of self in this method (only shells >= this shell are visited).

Documented.

  1. MutablePosetShell.iter_depth_first: document the role of self in this method (only shells >= this shell are visited).

Documented.

  1. MutablePosetShell._iter_topological_visit_: document the role of self in this method (only shells <= this shell are visited, emphasize the contrast to depth first.).

Documented.

  1. MutablePosetShell.iter_topological_first: document the role of self in this method (only shells <= this shell are visited, emphasize the contrast to depth first.).

Documented.

  1. MutablePosetShell.iter_topological_sort, last example: function C is defined, but not used.

Deleted.

  1. MutablePosetShell.merge: explain what merge is, in particular, that this is defined when constructing the poset.

Documented.

  1. MutablePosetShell.merge: I am surprised that check=True leads to a silent failure instead of an exception and that poset._merge_ is None does not raise an exception. Document and test this behaviour?

Behavior changed.

  1. MutablePosetShell.merge: what is the difference between poset.remove and poset.discard?

See Python's set.

  1. MutablePosetShell.merge: document that the merge function resulting in None leads to deletion of the element.

Done.

  1. MutablePosetShell.merge: document that the merge function must not change the position of the element in the poset.

Mentioned this more explicitly in docs

  1. MutablePoset.__init__: Clarify the role of key: in particular, key does not only influence sorting, but that no two elements are allowed to have the same key.

Done.

  1. MutablePoset.__len__: Clarify that null and oo are not counted.

Done.

  1. MutablePoset.shells_topological: The sentence "If this is None, no sorting according to the representation string occurs." is unclear to me, in particular the role of the representation string.

Rewritten.

  1. MutablePoset.keys_topological: Due to the chosen key, some of the elements added to the poset are ignored. I do not know why this is demonstrated here.

Removed.

  1. MutablePoset.repr: INPUT section is incomplete.

Completed.

  1. MutablePoset.repr_full: INPUT section is incomplete.

Completed.

  1. MutablePoset.add: the loop for reverse in (False, True) simplifies in the second iteration: as all pairs of elements (s, l) with new covering s and l covering new have already been broken up while reverse=False. Thus it might be more straightforward to do it only once, not using reverse at all and saving a few intersections:
        new._predecessors_ = self.null.covers(new, reverse=False)
        new._successors_ = self.oo.covers(new, reverse=True)

        for s in new.predecessors():
            for l in s.successors().intersection(new.successors()):
                l.predecessors().remove(s)
                s.successors().remove(l)
            s.successors().add(new)
        for l in new.successors():
            l.predecessors().add(new)

(pushed as branch u/cheuberg/asy/mutable-poset-add)

Merged and cross-reviewed....ok.

  1. MutablePoset.remove: the loop over all (not necessarily direct) successors via the depth first search iterator seems to be rather inefficient, especially if the poset is large. I propose an alternative based on the order relation and not based on the data structure:
        for upper in shell.successors():
            upper.predecessors().remove(shell)

        for lower in shell.predecessors():
            lower.successors().remove(shell)
            for upper in shell.successors():
                if not any(s <= upper 
                           for s in lower.successors()):
                    lower.successors().add(upper)
                    upper.predecessors().add(lower)

(pushed as branch u/cheuberg/asy/mutable-poset-remove)

This needs comparisons of the elements, but one of the main ideas of this data structure is that comparisions are only needed for inserting an element into the poset and none needed once it is inside. Thus I am for keeping the current solution. However, I am fine with introducing (now or at any point later) an algorithm option which makes it possible to select different removing algorithms depending on the situation.

  1. MutablePoset.remove, MutablePoset.discard: add "see also blocks", but also an explanation of the difference between discard and remove. I guess that both methods are available due to some compatibility concern (in that case, please add a remark in the code or the docstring). Otherwise, I'd suggest removing discard, removing the parameter raise_key_error and let the user catch the exception.

Python's set has both, remove and discard. Thus this mutable poset should have it as well.
Note-block added. Seealso added.

  1. MutablePoset.pop: remove key=lambda c: -c from this example as it is not pertinent to pop.

Done.

  1. MutablePoset.pop: mention that special elements cannot be popped.

Done.

  1. MutablePoset.pop: IMHO you can remove the first four line (the try: block deleting kwargs['include_special']) because setting kwargs['include_special'] = False should result in the same result.

Deleted.

  1. MutablePoset.union: The doctest P.union(P, Q, Q, P) neither fits the description "a poset" or "an iterable of future elements".

Rewritten INPUT-block

  1. MutablePoset.union: the word "union" sounds symmetric, however, due to keys and merge functions, it might not be commutative. The note box is not helpful for me.

Note added/changed.

  1. MutablePoset.union: the TODO box from union_update also applies here.

Copied.

  1. MutablePoset.union_update: why is the semantics of other different w.r.t union, but has the same description? It would seem more logical to me if union would simply call copy and then pass its argument to update_union and let the latter function sort out the iterators?

Indeed. Code rewritten.

  1. MutablePoset.difference and MutablePoset.difference_update: see comments on union and union_update

Done.

  1. MutablePoset.intersection and MutablePoset.intersection_update: see comments on union and union_update

Done.

  1. MutablePoset.intersection_update: how can the AttributeError occur? After all, self is a MutablePoset, so the method keys will be available unless something very strange happens ...

True. Code simplified.

  1. MutablePoset.merge: documentation of reverse: what is the default direction?

Documented.

  1. MutablePoset.merge: add doctest for RuntimeError.

Added.

  1. MutablePoset.merge: document that can_merge is applied in the sense of the condition of depth first iteration, i.e., once can_merge fails, the successors are no longer tested. This is some kind of monotonicity condition on can_merge.

Added note.

  1. MutablePoset.merge: it should be mentioned somewhere that this (at first sight strange) merge magic is motivated by asymptotic expansions.

Mentioned.

  1. MutablePoset.map: IMHO the example does alter the keys, because the key was the identity map here.

Example changed.

  1. MutablePoset.mapping: does the map have to be key preserving as in the method map? Is this method actually needed, see also the recent discussion on inplace vs. non-inplace operations on sage-devel?

Added a note on key-order preservation.

I think having MutablePoset.mapping is useful (from a ui point of view) since one would not naturally thinks that the copy method has a parameter for applying a mapping function.

@cheuberg
Copy link
Contributor

Changed branch from u/dkrenn/asy/mutable-poset to u/cheuberg/asy/mutable-poset

@sagetrac-git
Copy link
Mannequin

sagetrac-git mannequin commented Sep 28, 2015

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

db042e0Trac #17693: fix ReSt and hyperlink issues
bbe02efTrac #17693: minor language issues and typos
4deddbdTrac #17693: remove comment on non-commutativity for difference and update_difference

@sagetrac-git
Copy link
Mannequin

sagetrac-git mannequin commented Sep 28, 2015

Changed commit from 3c69f6b to 4deddbd

@cheuberg
Copy link
Contributor

comment:43

Thank you for your changes. I added three commits. See my remaining comments below.

Replying to @dkrenn:

Replying to @cheuberg:

  1. this module was written as part of the asymptotic expansion effort. In contrast to the asymptotic expansion, it does not have the "experimental" decorator. I'd feel more comfortable having it, at least until a larger portion of the asymptotic expansions have been merged.

Activated it; it seems (for some (to me) unknown reason) the warning appears now in every doctest and not only once in the file. Thus, deactivated it again; and I am for keeping it this way.

ok.

  1. MutablePosetShell.key: I am surprised that the key is not cached. I imagine that many comparisons will be needed in the lifetime of a MutablePoset, and in every case, the property key has to be resolved, which calls the property poset, which calls get_key of the poset.

Now it is cached (see also follow up ticket #19281).

I rather thought about calling key in __init__ as I guess that the key will be needed at least once in the lifetime of every MutablePosetShell.

  1. MutablePosetShell._copy_all_linked_: I do not understand why you test oo == P.oo: I think that oo is an element of the new poset Q.

oo == P.oo tests that Q.oo is mapped to P.oo.

wouldn't oo.is_oo do it without comparing shells of different posets, which might be confusing?

So oo is Q.oo would be more interesting?

oo is Q.oo is False since oo is not in Q, but just a copy of the oo in P with parent-poset Q.

that would be good to test (and comment on).

Inserting this into Q is done in _copy_shells_.

The current test demonstrates that the poset is not used in comparison, so that would rather belong to .eq?

I do not understand what you mean (but it is already late...).

I mean that shells e and f might be equal even if they do belong to different posets; this might be an interesting doctest or example for equality.

  1. MutablePosetShell._search_covers_: According to Wikipedia, a cover is what is called an upper cover here. This is in contrast to the default behaviour here.

_search_covers_ does not exist anymore (see 27).

This does not solve the problem.

What about renaming the method covers to lower_covers (and adapting the docstring slightly, removing "upper " from the one-sentence-description as well as from the definition?) For symmetry, a method upper_covers would be nice.

  1. MutablePoset.remove: the loop over all (not necessarily direct) successors via the depth first search iterator seems to be rather inefficient, especially if the poset is large. I propose an alternative based on the order relation and not based on the data structure:
        for upper in shell.successors():
            upper.predecessors().remove(shell)

        for lower in shell.predecessors():
            lower.successors().remove(shell)
            for upper in shell.successors():
                if not any(s <= upper 
                           for s in lower.successors()):
                    lower.successors().add(upper)
                    upper.predecessors().add(lower)

(pushed as branch u/cheuberg/asy/mutable-poset-remove)

This needs comparisons of the elements, but one of the main ideas of this data structure is that comparisions are only needed for inserting an element into the poset and none needed once it is inside. Thus I am for keeping the current solution. However, I am fine with introducing (now or at any point later) an algorithm option which makes it possible to select different removing algorithms depending on the situation.

This should be discussed when more benchmarking results are available. I opened #19300 for that.

  1. MutablePoset.difference and MutablePoset.difference_update: see comments on union and union_update

Done.

removed comment on non-commutativity because difference is non-commutative by definition.

  1. MutablePoset.intersection and MutablePoset.intersection_update: see comments on union and union_update

Done.

Remove comment on non-commutativity? merge does not play a role here, and keys might be covered in the previous sentence?

  1. MutablePoset.update_union: "Due to keys and a merge function... this operation might not be commutative": This method is non-commutative by definition, as self is changed and other is not. So perhaps: "left.update_union(right)" and "right.update_union(left) might result in different posets"? The same for other update_... methods where non-commutativity might be surprising.

@dkrenn
Copy link
Contributor Author

dkrenn commented Sep 29, 2015

Changed branch from u/cheuberg/asy/mutable-poset to u/dkrenn/asy/mutable-poset

@dkrenn
Copy link
Contributor Author

dkrenn commented Sep 29, 2015

comment:45

Replying to @cheuberg:

Thank you for your changes. I added three commits. See my remaining comments below.

Cross-review...ok.

  1. MutablePosetShell.key: I am surprised that the key is not cached. I imagine that many comparisons will be needed in the lifetime of a MutablePoset, and in every case, the property key has to be resolved, which calls the property poset, which calls get_key of the poset.

Now it is cached (see also follow up ticket #19281).

I rather thought about calling key in __init__ as I guess that the key will be needed at least once in the lifetime of every MutablePosetShell.

Oh...yes, I agree; changed.

  1. MutablePosetShell._copy_all_linked_: I do not understand why you test oo == P.oo: I think that oo is an element of the new poset Q.

oo == P.oo tests that Q.oo is mapped to P.oo.

wouldn't oo.is_oo do it without comparing shells of different posets, which might be confusing?

True. Changed.

So oo is Q.oo would be more interesting?

oo is Q.oo is False since oo is not in Q, but just a copy of the oo in P with parent-poset Q.

that would be good to test (and comment on).

Inserted.

Inserting this into Q is done in _copy_shells_.

The current test demonstrates that the poset is not used in comparison, so that would rather belong to .eq?

I do not understand what you mean (but it is already late...).

I mean that shells e and f might be equal even if they do belong to different posets; this might be an interesting doctest or example for equality.

Now I understand what you mean. Inserted a doctest there.

  1. MutablePosetShell._search_covers_: According to Wikipedia, a cover is what is called an upper cover here. This is in contrast to the default behaviour here.

_search_covers_ does not exist anymore (see 27).

This does not solve the problem.

What about renaming the method covers to lower_covers (and adapting the docstring slightly, removing "upper " from the one-sentence-description as well as from the definition?) For symmetry, a method upper_covers would be nice.

Good idea; changed and inserted upper_covers.

  1. MutablePoset.intersection and MutablePoset.intersection_update: see comments on union and union_update

Done.

Remove comment on non-commutativity? merge does not play a role here, and keys might be covered in the previous sentence?

Removed.

  1. MutablePoset.update_union: "Due to keys and a merge function... this operation might not be commutative": This method is non-commutative by definition, as self is changed and other is not. So perhaps: "left.update_union(right)" and "right.update_union(left) might result in different posets"? The same for other update_... methods where non-commutativity might be surprising.

Changed.


New commits:

f7bd83aTrac #17693, comment 43, 7: do caching of key in __init__
a554d69rac #17693, comment 43, 18: use .is_oo to test for oo in doctest
3ac7ed1rac #17693, comment 43, 18: add a doctest "oo is Q.oo"
2f675b3Trac #17693, comment 43, 18: add a doctest in eq (comparing elements in different posets)
f064a3bTrac #17693, comment 43, 22: covers --> lower_covers, upper_covers
bea925cTrac #17693, comment 43, 55: remove comment on non-commutativity
4e73b45Trac #17693, comment 43, 63: extend/rewrite non-commutativity note

@dkrenn
Copy link
Contributor Author

dkrenn commented Sep 29, 2015

Changed commit from 4deddbd to 4e73b45

@cheuberg
Copy link
Contributor

Changed branch from u/dkrenn/asy/mutable-poset to u/cheuberg/asy/mutable-poset

@sagetrac-git
Copy link
Mannequin

sagetrac-git mannequin commented Sep 29, 2015

Changed commit from 4e73b45 to a0b3d7b

@sagetrac-git
Copy link
Mannequin

sagetrac-git mannequin commented Sep 29, 2015

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

e8d8d2cTrac #17693: typos
4b7f929Trac #17693: fix two copy&paste errors
a0b3d7bTrac #17693: ReSt improvement

@cheuberg
Copy link
Contributor

comment:49

Added three final reviewer commits.

Doctests pass, documentation builds, documentation seems to be fine, code seems to be fine.

@dkrenn
Copy link
Contributor Author

dkrenn commented Sep 29, 2015

comment:50

Replying to @cheuberg:

Added three final reviewer commits.

Thanks; checked.

Doctests pass, documentation builds, documentation seems to be fine, code seems to be fine.

:)

@vbraun
Copy link
Member

vbraun commented Oct 12, 2015

Changed branch from u/cheuberg/asy/mutable-poset to a0b3d7b

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

5 participants