Repository navigation
Allow vector-like string input in Quantity parsing - #19214
Conversation
|
Thank you for your contribution to Astropy! 🌌 This checklist is meant to remind the package maintainers who will review this pull request of some common things to look for.
|
nstarman
left a comment
There was a problem hiding this comment.
Thanks @kartavayv. I like this and think it's useful for users.
Can you post the timing stats in this thread to show the speed comparison?
Thanks!
|
Hey @nstarman , I made some changes just like you suggested. Thank you very much for guiding me! 😉 Some timing stats: Scalar String Parsing:
List-like inputs (u.Quantity([4, 8 , 9])) (no regression):
New Vector-like strings:
All timings are mean ± std over 7 runs, 100k loops each. Apart from that, I have also tried to make regex a little bit different (using NUMS and VECTOR), I wonder if that's good.... |
|
Thanks @kartavayv! IMO, since scalar string input to a Quantity isn't meant to be in a hot loop, the minor performance regression is fine. Moreover, since this now enables a string vector input to Quantity, people can actually get a performance increase by moving the Quantity(str) out of the loop! |
mhvk
left a comment
There was a problem hiding this comment.
I like the idea! However, should it not work recursively, so that it can deal with multi-dimensional quantities too? I.e., u.Quantity(str(q)) == q for any quantity for which the array is not summarized? Also, I think one might as well be generous on input and also accept commas (like [1., 2., 3.], as one gets in the __repr__).
Note that this needs a changelog fragment (19214.feature.rst in docs/changes/units). I think it might also make sense to have the tests be a bit more exhaustive. Could be of the form
@pytest.mark.parametrize(q, [VAROUS QUANTITIES])
def test_string_roundtrip(q):
s = str(q)
assert_array_allclose(u.Quantity(s), q)
r = repr(q)
assert_array_allclose(u.Quantity(r[9:-1]), q)
|
|
||
| def _parse_floats_from_whitespace(data:str)->list[float]: | ||
| data = data.strip("[]") | ||
| return [float(a) for a in data.split()] |
There was a problem hiding this comment.
If we were to use data.replace(",", " ").split() one could have "," inside as well.
|
If we go with replacing white space with ",", we could use ast.literal_eval to do some of the parsing. |
I feel like we should do that, people might use commas (,) by habit, a lot, and it's a bit odd to allow something like
This also looks like a good idea. I am working on it (interesting challenge!) I will update shortly after making the suggested improvements! |
|
Also, I was thinking, since the current code allows one dot, plus or minus Example
OUTPUT: Because ```v.group()`` is 56.. (dot-dot) I think it's a good idea to make some changes in the pipeline to remove these extra characters, should I go in that direction?...or should I remove, [.+-]? entirely, which makes it a little bit strict (don't feel like it's a good idea) and creates a problem in |
|
Start simple and build from there :). I recommend implementing changes as discrete commits, which makes building / reviewing changes much simpler. |
|
@kartavayv - the inclusion of some of the extra stuff was on purpose, so that |
IMPROVEMENTS
NEW FEATURES...?
SOMETHING UNUSUALSince now the parser accepts both spaces and commas as separators, so things like |
|
@nstarman - while trying to remove mixed separators, I found out that whitespace should either work for grammar (parsing) or as a formatter. CONTEXT:Code snippet: We want a same separator for the whole input to prevent mixed separators. If we allow multiple white spaces as separators ( But regex still parses the string in both the cases mentioned above, because of So, either we need to use whitespaces as only separators or to do just formatting (i.e. allow any number of spaces between the [] and numbers. I guess using only commas as separator and allowing any number of white spaces before and after it is a good idea. I might be missing something, any design suggestions will help a lot 😉 |
|
@kartavayv the regex can distinguish between space between |
|
I was trying to add an indefinite number of white spaces as a delimiter 😢🫤, now it's fine! Should I also try to add multi-dim vector like strings or tackle that in some other PR...? Thanks a lot for helping me ❤️ |
|
@kartavayv - sorry to comment without actually having had time to look at the PR again, but my tendency would be to try to do the multi-D as well. I think you could do it by iteratively interpreting strings inside |
|
Sure, fine to do it in steps! |
There was a problem hiding this comment.
There's some edge cases that will fail
- "[7 8 9] eV"
- "[1. 2. 3.] eV"
I think the following will help catch this
NUM = r"""
[+-]?
(?:
(?:\d+\.?\d*) | (?:\.\d+) |
(?:[nN][aA][nN]) |
(?:[iI][nN][fF](?:[iI][nN][iI][tT][yY])?)
)
(?:[eE][+-]?\d+)?
[.+-]?
"""
SEP = r"(?:\s*,\s*|\s+)" # comma OR (one+ whitespace)
VECTOR_1D = rf"""
\[
\s*
{NUM}
(?: {SEP} {NUM} )*
(?: {SEP} )? # allow a trailing separator (covers "[1. 2. 3.]" and "[1,2,3,]")
\s*
\]
"""
I'm not so good at regex, so other maintainer eyes on this will be useful!
|
@nstarman - I tried the edge cases that you suggested, and they are getting parsed 😊. Probably you are pointing to something else that I am unable to see yet!
|
7080cef to
d055e1b
Compare
mhvk
left a comment
There was a problem hiding this comment.
@kartavayv - sorry for the very delayed review. There were some nits that bugged me, and I also thought your changes gave the opportunity to clean the lot up a bit. Anyway, I ended up just pushing a commit to your branch with my clean-up, hope that's OK (and squashed your commits together).
Since I now made changes, perhaps @nstarman can have a last look, approve and merge...
| "[7 8 9]", | ||
| ), | ||
| ) | ||
| def test_quantity_initialisation_string_array_dimensionless(array): |
There was a problem hiding this comment.
How about combing the two test functions by rewriting as
@pytest.mark.parametrize("array_str",
# what you have above
)
@pytest.mark.parametrize("unit_str", ["", "eV", " cm"])
def test_quantity_initialisation_string_array(array_str, unit_str):
input_str = array_str + unit_str
q = u.Quantity(array)
assert q.unit == unit_str
assert_array_equal(q.value, np.array([7.0, 8.0, 9.0]))
| VECTOR_COMMA: Final = rf""" | ||
| \[\s* | ||
| {NUM} | ||
| (?: |
There was a problem hiding this comment.
I'd put this part on one line -- makes it more readable for me at least.
| VECTOR_WSPACE: Final = rf""" | ||
| \[\s* | ||
| {NUM} | ||
| (?: |
| unit_string = None | ||
| try: | ||
| value = float(v.group()) | ||
| value_str = v.group() |
There was a problem hiding this comment.
Hmm, still feels like it could just call the one function, including doing the string...
|
Just to be clear, my last commit implements all my comments... |
| if "[" not in value: | ||
| msg += " It does not start with a number." |
There was a problem hiding this comment.
| if "[" not in value: | |
| msg += " It does not start with a number." | |
| if "[" not in value: | |
| msg += " It does not start with an array." |
?
There was a problem hiding this comment.
I wanted to keep the original message for the original case, of just a number, but didn't see much point in elaborating for the case where an array was passed in... I could have an else clause along the lines of "Failed to parse the list of values."
But if I'd go back in time, I would probably not have had the "It does not start with a number" either...
nstarman
left a comment
There was a problem hiding this comment.
Thanks @kartavayv and @mhvk for this nice addition to Quantity's string parsing!

Description
Before this PR, we couldn't do
u.Quantity("[ 7 8 9]")and get a nice Quantity, howeveru.Quantity(np.array([ 7 8 9]))andu.Quantity([ 7 8 9])were allowed. The changes made in this PR specifically help in parsing back the LaTeX strings into Quantity objects; in short, it supports the LaTeX reader. (More context: #19110)Also includes, some new tests covering the new accepted inputs