Skip to content

ENH: Improved __str__ for polynomials#15666

Merged
mattip merged 4 commits intonumpy:masterfrom
rossbar:enh/poly_str
Jun 4, 2020
Merged

ENH: Improved __str__ for polynomials#15666
mattip merged 4 commits intonumpy:masterfrom
rossbar:enh/poly_str

Conversation

@rossbar
Copy link
Contributor

@rossbar rossbar commented Feb 28, 2020

This PR follows up on some ideas originally introduced in #8893.

Since numpy v1.19 drops support for python2, one of the original ideas of #8893 can be revisited - using unicode sub/superscript values to prettify the printing of polynomials in np.polynomial.

This PR implements this feature by modifying the __str__ method of the ABCPolyBase as well as introducing a few character mappings/helper methods to the ABCPolyBase class.

Please let me know if this is a desired feature at this time.

Note that I modified the test suite to reflect the changes while trying to maintain some similarity to the original structure of the tests. I erred on the side of being explicit, though some reorganization of the tests could likely improve things. Also, I'm not sure if having unicode characters directly in the test suite will cause problems on some platforms vis-a-vis file encoding.

@charris
Copy link
Member

charris commented Mar 2, 2020

Out of curiosity, how is line breaking handled?

@charris charris changed the title Improved __str__ for polynomials w/ unicode ENH: Improved __str__ for polynomials w/ unicode Mar 2, 2020
@rossbar
Copy link
Contributor Author

rossbar commented Mar 2, 2020

As of right now, line breaking is not explicitly handled. That's certainly something that I would have to follow up on.

Also, after reading around some of the open issues/PRs surrounding np.polynomial, I would also propose adding info about the domain and window to __str__. The default __repr__ already includes this, but I think it should be included in __str__ as well.

@charris charris added the 56 - Needs Release Note. Needs an entry in doc/release/upcoming_changes label Mar 2, 2020
@charris
Copy link
Member

charris commented Mar 2, 2020

This will also need a release note.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make the keys strings, and then you can use str.translate below

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a great idea - better to use built-in functionality than reinvent the wheel. Addressed in 380c9f0d3

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're not going to include the character directly, can you use the \N escapes which are more readable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point - including the characters directly is probably the most readable, so I've made that change in 380c9f0d3

@rossbar
Copy link
Contributor Author

rossbar commented Mar 2, 2020

Thanks for the initial feedback @eric-wieser , those are definitely good ideas.

Re: linebreaks - it would be fairly straightforward to manually enforce 80-char limits and results in nice (e.g. no breaks in the middle of a single polynomial term), aligned output. However - introducing this manually here means that the printing wouldn't respect np.printoptions unless extra care was taken to do so. Do you know if there is any precedent for how other non-array classes in numpy behave wrt to printoptions?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit weird to assume that a lack of basis_name implies a specific subclass.

It might be more consistent to define a _str_term to match the _repr_latex_term, and put the power series special case in the subclass override of that method

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, I'll take a look.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I followed your suggestion in 795ceb6. I defined _str_term slightly differently than _repr_latex_term. since the __str__ doesn't (yet) support the variable mapping between domain <-> window, I left off the need_parens argument.

@rossbar
Copy link
Contributor Author

rossbar commented Mar 19, 2020

To give a better sense of the effects of this PR, I'm including a few examples below to show the difference between the current string representation of polynomials, and how they look after this PR:

Currently:

>>> from numpy.polynomial import Polynomial, Chebyshev
>>> p, c = Polynomial([1, 2, 3]), Chebyshev([4, 5, 6])
>>> print(p)
poly([1. 2. 3.])
>>> print(c)
cheb([4. 5. 6.])
>>> print(p**2)
poly([ 1.  4. 10. 12.  9.])
>>> print(Polynomial([-1, 0, 0, 1]))
poly([-1.  0.  0.  1.])
>>> print(Chebyshev(np.arange(20)))
cheb([ 0.  1.  2.  3.  4.  5.  6.  7.  8.  9. 10. 11. 12. 13. 14. 15. 16. 17.
 18. 19.])

With this PR:

>>> from numpy.polynomial import Polynomial, Chebyshev
>>> p, c = Polynomial([1, 2, 3]), Chebyshev([4, 5, 6])
>>> print(p)
1.0 + 2.0x¹ + 3.0x²
>>> print(c)
4.0 + 5.0T₁(x) + 6.0T₂(x)
>>> print(p**2)
1.0 + 4.0x¹ + 10.0x² + 12.0x³ + 9.0x⁴
>>> print(Polynomial([-1, 0, 0, 1]))
-1.0 + 0.0x¹ + 0.0x² + 1.0x³
>>> print(Chebyshev(np.arange(20)))
0.0 + 1.0T₁(x) + 2.0T₂(x) + 3.0T₃(x) + 4.0T₄(x) + 5.0T₅(x) + 6.0T₆(x) +
7.0T₇(x) + 8.0T₈(x) + 9.0T₉(x) + 10.0T₁₀(x) + 11.0T₁₁(x) + 12.0T₁₂(x) +
13.0T₁₃(x) + 14.0T₁₄(x) + 15.0T₁₅(x) + 16.0T₁₆(x) + 17.0T₁₇(x) + 18.0T₁₈(x) +
19.0T₁₉(x)

@mattip
Copy link
Member

mattip commented Mar 19, 2020

This is an improvement, as the representation is more "mathematical" than "programatical". I wonder how this will affect useability for vision-impaired users.

Maybe it is my aging eyes, but the unicode sub/superscript values in some fonts seem to be very small. The wikipedia page for these symbols explains that they were designed for fractions, and quotes the recommendation "When used in mathematical context (MathML) it is recommended to consistently use style markup for superscripts and subscripts...". When I copy-paste the rendered string into my terminal (which I uses Monospace Regular) the rendered string seems to be more readable than on this web page (using Deja Vu Sans). Scanning across the monospace fonts shows variation between them. It seems there is an opportunity for someone to tweak the existing fonts to render these symbols a little larger.

Note that espeak "1.0 + 2.0x¹ + 3.0x²" ignores the special formatting and says "one point zero plus two point zero x one plus three point zero x two".

I notice that IPython uses latex (I think, how do you choose between latex and html?) when outputting display(p), due to rich display and the _repr_latex_ method from gh-11528.

The original issue gh-8893 mentions using print("{:ascii}".format(p)). Maybe we could use that to render as 1.0 + 2.0x**1 + 3.0x**2

@rossbar
Copy link
Contributor Author

rossbar commented Mar 19, 2020

I wonder how this will affect useability for vision-impaired users.

This is a great point and something that I had not considered. I went the unicode route due to it's ubiquity in an attempt to come up with a solution that would be terminal-independent. It's doubly-unfortunate that espeak doesn't pick up the unicode super/subscripts. It would definitely be good to support the :ascii format option, which could hopefully mitigate some of these issues (though then there are other issues that would need to be addressed: discoverability of print options, how to set defaults, etc.). Another alternative is to use the poly1d approach, which uses full-sized ascii characters on a separate line:

   2
1 x + 2 x + 3

Although the font sizes are larger, in my opinion the readability suffers from the spacing. Any opinions/suggestions re: this issue would be very helpful.

I notice that IPython uses latex (I think, how do you choose between latex and html?) when outputting display(p), due to rich display and the repr_latex method from gh-11528.

Yes, LaTeX math is already used for the IPython front-ends that support it: namely Jupyter notebook and the QtConsole. As far as I know, most terminal emulators outside the frontends that are from the Jupyter project do not support these features, so the _repr_latex_ (or any of the other _repr_*_ methods) have no effect.

The original issue gh-8893 mentions using print("{:ascii}".format(p)). Maybe we could use that to render as 1.0 + 2.0x**1 + 3.0x**2

This is a really nice suggestion: I like the idea of defaulting to Python syntax for exponents when formatting with ascii. I think it would be also reasonable to use _ for the other polynomials with different basis names, e.g. 1.0 + 2.0T_1(x) + 3.0T_2(x) for Chebyshev series.

As mentioned above, maybe this mechanism can also help address the usability concerns for vision-impaired users.

@eric-wieser
Copy link
Member

eric-wieser commented Mar 19, 2020

I'm a little concerned that these characters are not going to work properly in a terminal on windows.

I'm finding that I'm unable to even test if your code works, because copy-pasting your code into the windows terminal host strips all of the unicode characters. I suspect that it won't be able to display them either.

@eric-wieser
Copy link
Member

eric-wieser commented Mar 19, 2020

Digging further, the paste failing appears to be an ipython bug. The large superscripts / subscripts are rendered as normal digits for me in the default windows console font.

@rossbar
Copy link
Contributor Author

rossbar commented Mar 19, 2020

Thanks for the feedback. Platform-dependent encoding differences would definitely throw a wrench in the works. This is also a case where I'm not sure the tests will catch encoding differences.

@rossbar
Copy link
Contributor Author

rossbar commented Mar 19, 2020

Out of curiosity, do you see paste problems in IPython with the %paste command or just using the standard clipboard?

@eric-wieser
Copy link
Member

%paste appears to work correctly, thanks for reminding me why that existed.

Unfortunately depending on which of my open terminals I use and possibly the phase of the moon, I sometimes get

image

That's probably a bug in either IPython, prompt-toolkit, or the terminal itself - but it's still possibly something we don't want to lead our users right into.

@eric-wieser
Copy link
Member

eric-wieser commented Mar 19, 2020

Ah, it's a font issue

  • the raster font contains only the first 3 superscripts, reuses these for the subscripts, and shows the remainder as regular numbers
  • lucida console shows only the three superscripts, and boxes for everything else
  • consolas is fine

@charris
Copy link
Member

charris commented Mar 20, 2020

Part of the reason I wished to leave such things to the display :) @eric-wieser Are there any standard display calls on any of the windows terminals that you know of? I know Jupyter has such things. I am not a fan of the ascii form.

IIRC, back when I was looked at this SymPy was recommended for having a good solution, it might be worth looking at how they do things and maybe stealing it. @asmeurer Thoughts?

@asmeurer
Copy link
Member

SymPy doesn't use unicode characters for superscript exponents. I think the font support for them isn't great, as you discovered. SymPy can also have arbitrary exponents, not just polynomials, which doesn't apply here.

We do use for multiplication

>>> pprint(2*x**2)
   2
2⋅x

As far as implementation, the SymPy printing system is far more generic than what would be needed here (it has to be able to print arbitrary symbolic expressions). Although you might steal the Unicode support detection code https://github.com/sympy/sympy/blob/c02515a8cfaa28c47300084b09b0d57798b867dd/sympy/printing/pretty/pretty_symbology.py#L50 (I don't know how it works, but I haven't heard any complaints about it).

@rossbar
Copy link
Contributor Author

rossbar commented Mar 20, 2020

Thanks for all the additional info! In terms of character support, I had been most worried about platform-dependent encoding issues, though that was addressed in PEP 528.

I hadn't considered font issues however. As mentioned, it seems like that will be the main sticking point as it can't be counted on for the fonts used in various shells/terminals to support the range of unicode necessary for the full set of numerical super/subscripts.

Although you might steal the Unicode support detection code https://github.com/sympy/sympy/blob/c02515a8cfaa28c47300084b09b0d57798b867dd/sympy/printing/pretty/pretty_symbology.py#L50 (I don't know how it works, but I haven't heard any complaints about it).

Thanks @asmeurer , this is a nice thing to be aware of. It looks like the checks in the linked function are limited to a small subset of specific unicode characters and doesn't probe the range for super/subscripts, but the concept could certainly be ported over.

The font consideration really seems to be the limiting factor for a generic approach that will be supported in all terminals. To me, the only viable options seem to be to stick to ascii either printing with Python syntax as mentioned above (i.e. Polynomial([1, 2, 3]) looks like 1.0 + 2.0x + 3.0x**2) or to use the np.poly1d/sympy approach:

   2
3 x + 2 x + 1

It's a shame as, though it's not perfect, I found the unicode much more visually appealing.

@rossbar rossbar changed the title ENH: Improved __str__ for polynomials w/ unicode ENH: Improved __str__ for polynomials Apr 1, 2020
@rossbar
Copy link
Contributor Author

rossbar commented Apr 1, 2020

Summary

Using unicode to represent super/subscripts for polynomial printing in the terminal is not a good approach due to font support issues and readability concerns. Two alternate approaches to Polynomial printing in the (non-Jupyter) terminal are presented below.


After the most recent round of revisions, I went back and reformulated things away from the unicode approach. I've implemented two options: "Python-style" formatting and "poly1d-style" printing (which, according to the above discussion appears to be consistent with sympy's approach). The two styles are detailed below.

"Python-style" printing uses standard Python syntax for exponents and underscores to represent subscripts. Using similar examples to those above, the printing would look something like:

>>> from numpy.polynomial import Polynomial, Chebyshev
>>> p, c = Polynomial([1, 2, 3]), Chebyshev([4, 5, 6])
>>> print(p)
1.0 + 2.0x**1 + 3.0x**2
>>> print(c)
4.0 + 5.0T_1(x) + 6.0T_2(x)
>>> print(p**2)
1.0 + 4.0x**1 + 10.0x**2 + 12.0x**3 + 9.0x**4
>>> print(Polynomial([-1, 0, 0, 1]))
-1.0 + 0.0x**1 + 0.0x**2 + 1.0x**3
>>> print(Polynomial(np.arange(10)))
0.0 + 1.0x**1 + 2.0x**2 + 3.0x**3 + 4.0x**4 + 5.0x**5 + 6.0x**6 + 7.0x**7 +
8.0x**8 + 9.0x**9
>>> print(Chebyshev(np.arange(10)))
0.0 + 1.0T_1(x) + 2.0T_2(x) + 3.0T_3(x) + 4.0T_4(x) + 5.0T_5(x) +
6.0T_6(x) + 7.0T_7(x) + 8.0T_8(x) + 9.0T_9(x)

"poly1d-style" printing uses only ascii characters, and denotes superscripts and subscripts on separate lines:

>>> from numpy.polynomial import Polynomial, Chebyshev
>>> p, c = Polynomial([1, 2, 3]), Chebyshev([4, 5, 6])
>>> print(p)
          1       2
1.0 + 2.0x  + 3.0x 

>>> print(c)
4.0 + 5.0T (x) + 6.0T (x)
          1          2   

>>> print(p**2)
          1        2        3       4
1.0 + 4.0x  + 10.0x  + 12.0x  + 9.0x 

>>> print(Polynomial([-1, 0, 0, 1]))
           1       2       3
-1.0 + 0.0x  + 0.0x  + 1.0x 

>>> print(Polynomial(np.arange(10)))
          1       2       3       4       5       6       7       8
0.0 + 1.0x  + 2.0x  + 3.0x  + 4.0x  + 5.0x  + 6.0x  + 7.0x  + 8.0x  + 

    9
9.0x 

>>> print(Chebyshev(np.arange(10)))
0.0 + 1.0T (x) + 2.0T (x) + 3.0T (x) + 4.0T (x) + 5.0T (x) + 6.0T (x) + 
          1          2          3          4          5          6   

7.0T (x) + 8.0T (x) + 9.0T (x)
    7          8          9   

To me, the "Python-style" printing has the advantage that it's more vertically compact, though it may be harder to interpret, especially for polynomials with many terms. The "poly1d-style" is nearer the traditional mathematical representation, but is not very compact vertically. One drawback of the "poly1d-style" is that it does not work well with non-monospace fonts (for example - take one of the printed outputs from the literal block above and paste it into an empty GitHub comment window - note that the alignment between the line containing the terms/coefficients and the line containing the powers will become misaligned).

Please let me know if you have thoughts/opinions about the relative merit of these two approaches. I like the "Python-style" best as it's the simplest and most portable, even though it's not very aesthetically pleasing. Another option is to leave the printing as-is and close the book on this particular attempt to update it. If you have thoughts, please chime in!

N.B. - Since there are two different options, I haven't modified the test suite, which is why it's currently failing. I will update it once a decision has been made.

@stefanv
Copy link
Contributor

stefanv commented Apr 1, 2020

My preference, for what it's worth, is the Python-like syntax, with spaces before the symbol:

0.0 + 1.0 x**1 + 2.0 x**2 + 3.0 x**3 + 4.0 x**4 + 5.0 x**5 + 6.0 x**6
+ 7.0 x**7 + 8.0 x**8 + 9.0 x**9

(Given that we cannot rely on unicode in fonts.)

@asmeurer
Copy link
Member

asmeurer commented Apr 1, 2020

The superscript numbers would probably be fine, so long as you detect if the terminal supports Unicode.

The detection itself is really about if the terminal supports Unicode encoding (i.e., you don't have LANG=C or something like that). It tells you nothing if the user will actually see the symbol, because that depends on if their computer has font support. I think the superscript numeric characters are pretty well supported, though it's something that you might want to verify. As I said, we primarily don't use them with SymPy because they don't extend well to general symbolic exponents. Actually, I think SymPy should consider using them in some cases sympy/sympy#19049.

@WarrenWeckesser
Copy link
Member

@rossbar, here's an example where the linewidth logic doesn't quite work as expected:

In [26]: p = np.polynomial.Polynomial([np.pi, np.e, -9999, 1/3])

In [27]: np.set_printoptions(linewidth=41)

In [28]: print(p)
3.141592653589793 +
2.718281828459045x**1 - 9999.0x**2 + 0.3333333333333333x**3

The linewidth is set to 41, but that last line is 60 characters.

@seberg seberg removed the 56 - Needs Release Note. Needs an entry in doc/release/upcoming_changes label May 6, 2020
Copy link
Member

@WarrenWeckesser WarrenWeckesser left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. I think the history can be squashed to just one or two commits. @rossbar, can you rebase with a squash? That way you can edit the commit message(s) to include just the essential information, without the churn. (No need to preserve my commit authorship for the "suggested edits".)

@rossbar
Copy link
Contributor Author

rossbar commented May 6, 2020

Will do @WarrenWeckesser , thank you (and others) for the careful review!

@rossbar
Copy link
Contributor Author

rossbar commented May 6, 2020

I've squashed everything down into acdaff7 and written a new commit message to give an overview of the changes (and preserving co-authorship, assuming I've done that correctly).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be thread-local?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As long as not even print options seem to be, not sure its worth worrying about?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fair.

Changes the printing style of instances of the convenience classes in
the polynomial package to a more "human-readable" format.
__str__ has been modified and __format__ added to ABCPolyBase, modifying
the string representation of polynomial instances, e.g. when printed.
__repr__ and the _repr_latex method (which is used in the Jupyter
environment are unchanged.

Two print formats have been added: 'unicode' and 'ascii'. 'unicode' is
the default mode on *nix systems, and uses unicode values for numeric
subscripts and superscripts in the polynomial expression. The 'ascii'
format is the default on Windows (due to font considerations) and uses
Python-style syntax to represent powers, e.g. x**2. The default
printing style can be controlled at the package-level with the
set_default_printstyle function.

The ABCPolyBase.__str__ has also been made to respect the linewidth
printoption. Other parameters from the printoptions dictionary are not
used.

Co-Authored-By: Warren Weckesser <warren.weckesser@gmail.com>
Co-authored-by: Eric Wieser <wieser.eric@gmail.com>
@rossbar
Copy link
Contributor Author

rossbar commented May 7, 2020

Okay, 9c83b13 incorporates @eric-wieser 's suggestion and adds a test to verify that complex coefficients are printed correctly. I've re-squashed everything into 9c83b13 and (hopefully) maintained attribution correctly.

I admit I don't entirely understand the implications in the thread-local discussion, so I'll leave the conversation unresolved in case someone wants to expand on it.

@seberg
Copy link
Member

seberg commented May 7, 2020

Do we have to do more about x >= 0 possibly failing? I realize its fine for numpy complex numbers...

As to the threads, it is about things such changing the setting in a subthread modifying the global state. If this was a context manager (or used like one), then surprising things can happen unless you make it thread-local. I think we should not worry about it. Although, it might mean that we need to turn this into a property or a global getter function at some point.
I suppose subclasses (made by users) in theory could use this underscored item. Although, though should be fine if we ever turn it into a property...

@rossbar
Copy link
Contributor Author

rossbar commented May 7, 2020

Do we have to do more about x >= 0 possibly failing? I realize its fine for numpy complex numbers...

This is a good point. In principle, the coefficient arrays can be object arrays, which means that it is possible to construct polynomials with non-numeric coefficients (even if this doesn't make sense):

>>> p = np.polynomial.Polynomial(np.array([1, 'f', 2.0], dtype=object))
>>> p
Polynomial([1, 'f', 2], dtype=object, domain=[-1,  1], window=[-1,  1])
>>> print(p)
TypeError: '>=' not supported between instances of 'str' and 'int'

Since this currently allowed, then __str__ should work in this case as well. I'd be inclined to wrap _generate_string() in a try/except that falls back to the __repr__.

@rossbar
Copy link
Contributor Author

rossbar commented May 7, 2020

Edit - the stricken text is now outdated.

f923959 wraps _generate_string() in a try/except that catches TypeErrors from failed >= comparisons between incompatible types. In that case, __str__ falls back to __repr__, which simply gives the array of coefficients rather than trying to construct a nice polynomial expression from them.

I've also updated the tests to verify that this fallback works, as well as test object arrays that contain "numerical" objects like Decimals or Fractions.

Add a fallback for TypeErrors that are raised when attempting
to compare arbitrary elements (e.g. strings or Python complex)
to 0 in _generate_str.
@rossbar
Copy link
Contributor Author

rossbar commented May 13, 2020

I've reworked how _generate_str handles coefficients for which >= 0 raises an exception (e.g. strings or Python complex). A try/except catches TypeError exceptions from this comparison and falls back to simply representing the coefficient in its current form.

I've also added tests for the Python complex case.

@seberg
Copy link
Member

seberg commented May 13, 2020

@WarrenWeckesser I am happy with the local try/except solution and IIRC the rest was pretty far along. But I never reviewed this in detail. Do you want to do the honors?

Update routines.polynomials.classes doc in the refguide to reflect
changes to polynomial printing.

Add additional information to the document about the various ways that
the string representation of polynomial expressions can be controlled
via formatting.
@WarrenWeckesser
Copy link
Member

@rossbar, the change in doc/source/reference/routines.polynomials.classes.rst looks good.

@charris or anyone else: do you have any objections to merging this so close to the branching of 1.19?

@mattip mattip merged commit 821a18e into numpy:master Jun 4, 2020
@mattip
Copy link
Member

mattip commented Jun 4, 2020

Thanks @rossbar

rossbar added a commit to rossbar/numpy that referenced this pull request Jun 12, 2020
The convenience classes derived from ABCPolyBase had a nickname
attribute that was only used internally in the previous
implementation of __str__. After the overhaul of __str__ in numpy#15666,
this attr is no longer used.
seberg pushed a commit that referenced this pull request Jul 8, 2020
* MAINT: Remove nickname from polynomial classes.

The convenience classes derived from ABCPolyBase had a nickname
attribute that was only used internally in the previous
implementation of __str__. After the overhaul of __str__ in #15666,
this attr is no longer used.

* DOC: Add release note.

Add release note to notify users of removal of the abstract
property, and highlight users that may be affected by the
change.

* DOC: fixed rST in release note
@charris charris mentioned this pull request Oct 10, 2020
20 tasks
seberg pushed a commit that referenced this pull request Jun 1, 2022
Adds a symbol attribute to the polynomials from the np.polynomial package to allow the user to control/modify the symbol used to represent the independent variable for a polynomial expression. This attribute corresponds to the variable attribute of the poly1d class from the old np.lib.polynomial module.

Marked as draft for now as it depends on #15666 - all _str* and _repr* methods of ABCPolyBase and derived classes would need to be modified (and tested) to support this change.

Co-authored-by: Warren Weckesser <warren.weckesser@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants