Skip to content

Support structured quantity, including scalars. - #12509

Merged
nstarman merged 1 commit into
astropy:mainfrom
mhvk:io-misc-yaml-support-structured-quantity
Nov 29, 2021
Merged

nstarman merged 1 commit into
astropy:mainfrom
mhvk:io-misc-yaml-support-structured-quantity

Conversation

@mhvk

@mhvk mhvk commented Nov 21, 2021 •

Copy link
Copy Markdown
Contributor

Description

As the title indicates, this allows structured quantities, including scalars, to be saved to yaml. This is independent of whether the unit is structured. I ended up deciding not to try to represent the void as a tuple, but rather as an opaque binary blob, since in principle the structure can be quite large and include arrays.

Fixes #12497

Checklist for package maintainer(s)

This checklist is meant to remind the package maintainer(s) who will review this pull request of some common things to look for. This list is not exhaustive.

  • Do the proposed changes actually accomplish desired goals?
  • Do the proposed changes follow the Astropy coding guidelines?
  • Are tests added/updated as required? If so, do they follow the Astropy testing guidelines?
  • Are docs added/updated as required? If so, do they follow the Astropy documentation guidelines?
  • Is rebase and/or squash necessary? If so, please provide the author with appropriate instructions. Also see "When to rebase and squash commits".
  • Did the CI pass? If no, are the failures related? If you need to run daily and weekly cron jobs as part of the PR, please apply the Extra CI label.
  • Is a change log needed? If yes, did the change log check pass? If no, add the no-changelog-entry-needed label. If this is a manual backport, use the skip-changelog-checks label unless special changelog handling is necessary.
  • Is a milestone set? Milestone must be set but astropy-bot check might be missing; do not let the green checkmark fool you.
  • At the time of adding the milestone, if the milestone set requires a backport to release branch(es), apply the appropriate backport-X.Y.x label(s) before merge.

@nstarman

Copy link
Copy Markdown
Member

Looks good, but hard to tell with the CI. #12511 is merged.

@mhvk
mhvk force-pushed the io-misc-yaml-support-structured-quantity branch from 08e51ea to 4594632 Compare November 22, 2021 13:29
@mhvk

mhvk commented Nov 22, 2021

Copy link
Copy Markdown
Contributor Author

OK, thanks. I rebased now - so we can actually see all the tests!

@taldcroft taldcroft left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks generally good and non-controversial.

Comment thread astropy/io/misc/yaml.py Resolved
Comment thread astropy/io/misc/yaml.py

def _ndarray_constructor(loader, node):
map = loader.construct_mapping(node)
map = loader.construct_mapping(node, deep=True)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you add a comment about what the deep=True is doing? I see the comment below about deep but it wasn't enough to make me understand.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I had the same question.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Without deep=True, the list produced by the structured dtype is not interpreted. It took me a while to figure out why I got dtype=[], so I should indeed have added a comment!

Actually, might as well ask: my first attempt kept just str(dtype) in the dumper, which means a structured dtype looks something like:

import numpy as np
dt = np.dtype('f8,f8')
str(dt)
# "[('f0', '<f8'), ('f1', '<f8')]"

But to interpret this in the loader, one would have to eval the string, which seemed less good than just letting yaml take care of the lists and tuples, even though that is substantially longer,

from astropy.io.misc.yaml import dump

print(dump(dt.descr))
- !!python/tuple [f0, <f8]
- !!python/tuple [f1, <f8]

If you think keeping just the string is better (and ideally have a better suggestion that eval!), I can do that too.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can ast's safe eval work here?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Even though the ast.literal_eval is totally safe, I still prefer representing the dtype with "native" YAML lists and tuples.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ast.literal_eval works indeed:

np.dtype(ast.literal_eval(str(np.dtype('f8, f8'))))
dtype([('f0', '<f8'), ('f1', '<f8')])

and implementing it would mean adding the following to the constructor:

    if map['dtype'].startswith('['):
        map['dtype'] = ast.literal_eval(map['dtype'])

So, it seems it would be mostly a matter of preference:

print(dump([(1., 2.)] * u.Unit('m, m/s')))
# this PR
!astropy.units.Quantity
unit: !astropy.units.Unit {unit: '(m, m / s)'}
value: !numpy.ndarray
  buffer: !!binary |
    QUFBQUFBQUE4RDhBQUFBQUFBQUFRQT09
  dtype:
  - !!python/tuple [f0, <f8]
  - !!python/tuple [f1, <f8]
  order: C
  shape: !!python/tuple [1]

# with str(dtype)
!astropy.units.Quantity
unit: !astropy.units.Unit {unit: '(m, m / s)'}
value: !numpy.ndarray
  buffer: !!binary |
    QUFBQUFBQUE4RDhBQUFBQUFBQUFRQT09
  dtype: '[(''f0'', ''<f8''), (''f1'', ''<f8'')]'
  order: C
  shape: !!python/tuple [1]

The literal_eval has the advantage of keeping the strings shorter and being closer to what one would see by just looking at the dtype, but what I have here is nicer in that one can just put the contents in np.dtype directly. I end up being torn.

But since you both seem to prefer the yaml list option, I just made some updates to that, so this should then be ready for final review. Obviously, if you change your mind, I'm happy to change.

p.s. Yet another option is to register the dtype with the dumper/loader, but the yaml then becomes even longer... Indeed, just for fun, if one just let's yaml.dump itself do it, one gets the following rather hopeless output:

In [6]: print(yaml.dump(np.dtype('f8,f8')))
!!python/object/apply:numpy.dtype
args:
- V16
- false
- true
state: !!python/tuple
- 3
- '|'
- null
- !!python/tuple
  - f0
  - f1
- f0: !!python/tuple
  - &id001 !!python/object/apply:numpy.dtype
    args:
    - f8
    - false
    - true
    state: !!python/tuple
    - 3
    - <
    - null
    - null
    - null
    - -1
    - -1
    - 0
  - 0
  f1: !!python/tuple
  - *id001
  - 8
- 16
- 1
- 16

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Agreed that registering the dtype is a mess!
Right now either the ast.literal_eval or the YAML look fine.
dtype can store metadata in a dictionary, for which the YAML option might be more suited...

@nstarman nstarman Nov 25, 2021 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@mhvk. Is this comment resolved and/or the PR ready for a review?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@nstarman - in my last comment I just thought I'd give you and @taldcroft a last look at how the two options of serializing the dtype look in practice. I think you both preferred to stick to native yaml as much as possible, so that is the version currently there. If after having a look, that is still the preference, then yes, this should be good to go. If not, and we go with literal_eval after all, then it is a very quick extra commit to change it to that.

@nstarman nstarman left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@mhvk, looks great. I think sticking to the YAML is best both for simplicity and also general robustness. Features like https://numpy.org/devdocs/reference/generated/numpy.dtype.metadata.html are better supported with YAML than ast.literal_eval.

@nstarman

Copy link
Copy Markdown
Member

I think the hanging tests are just a product of the branch requirements changing. A rebase should fix this.

@mhvk
mhvk force-pushed the io-misc-yaml-support-structured-quantity branch from 04b2b30 to bc3b050 Compare November 29, 2021 02:58
@mhvk

mhvk commented Nov 29, 2021

Copy link
Copy Markdown
Contributor Author

OK, thanks. Am quite happy to stick with this. I now rebased; hopefully all the tests will now run.

@nstarman

Copy link
Copy Markdown
Member

#12535 should fix the py3.8w/dev test. As @taldcroft agrees with the YAML representation... I'm good to merge this.

@nstarman

Copy link
Copy Markdown
Member

Thanks @mhvk! This will also be very useful for Cosmology. I'm trying to turn m_nu into a structured quantity and ran into these serialization problems.

@mhvk

mhvk commented Nov 29, 2021

Copy link
Copy Markdown
Contributor Author

Great! Hopefully it will indeed work well!

@mhvk
mhvk deleted the io-misc-yaml-support-structured-quantity branch November 29, 2021 17:04
@pllim

pllim commented Dec 2, 2021

Copy link
Copy Markdown
Member

@meeseeksdev backport to v5.0.x

@mhvk

mhvk commented Dec 3, 2021

Copy link
Copy Markdown
Contributor Author

Retry backport once #12559 is merged

@mhvk

mhvk commented Dec 3, 2021

Copy link
Copy Markdown
Contributor Author

@meeseeksdev backport to v5.0.x

mhvk added a commit that referenced this pull request Dec 4, 2021
…509-on-v5.0.x

Backport PR #12509 on branch v5.0.x (Support structured quantity, including scalars.)
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.

numpy.void Quantities cannot be yaml serialized

4 participants