ComPWA / expertsystem

Rule based particle reaction problem solver on a quantum number level
http://expertsystem.rtfd.io
1 stars 3 forks source link

Implement Serializable interface with asdict and fromdict #384

Closed redeboer closed 3 years ago

redeboer commented 3 years ago

The original idea was to use a function (not a method) like attr.asdict (with recurse=True) to recursively serialize any data structure into a dict. Data structures that are not constructed through an attr.s constructor, can be serialized by defining a 'serializer', see https://www.attrs.org/en/stable/examples.html#converting-to-collections-types.

There are two big problems with this implementation:

  1. We also want to construct the original data structures back from any dict that was created through this asdict function. If AttrObject is some class that is constructed with an attr.s decorator, this can be done as follows:

    output_dict = attr.asdict(some_attr_object)
    AttrObject(**output_dict)

    However, this only works if AttrObject is not a nested data structure.

  2. Classes such as ParticleCollection, Spin, and Parity are not created through attr.s. We would have to provide some serializer for them, but this still doesn't really address #383, as we would have to put that serializer under some submodule other than. See here for the syntax that would be required.

Conclusion: it would be better to define some Protocol or ABC that defines fromdict and asdict methods for any class that we want to be serializable, _including classes that were decorated with attr.s.

Some snippets of the function implementation ```python # expertsystem 0.6.4 from typing import Any, Callable, Optional import attr import expertsystem as es from expertsystem.particle import Parity, ParticleCollection, Spin Serializer = Callable[[type, attr.Attribute, Any], Any] """https://www.attrs.org/en/stable/extending.html#customize-value-serialization-in-asdict""" def asdict( instance: object, serializer: Optional[Serializer] = None, ) -> dict: if serializer is None: if not attr.has(type(instance)): serializer = value_serializer if not attr.has(type(instance)): return value_serializer("", "", instance) return attr.asdict(instance, recurse=True, value_serializer=serializer) def value_serializer(_: type, __: attr.Attribute, value: Any) -> Any: if isinstance(value, ParticleCollection): return { "particles": [ attr.asdict(p, value_serializer=value_serializer) for p in value ] } if isinstance(value, Parity): return {"value": int(value)} if isinstance(value, Spin): return { "magnitude": value.magnitude, "projection": value.projection, } return value pdg = es.io.load_pdg() definition = asdict(pdg) pdg_new = ParticleCollection(**definition) # <-- FAIL because nested assert pdg == pdg_new ```
redeboer commented 3 years ago

Moved to issue description

spflueger commented 3 years ago

With the switch to sympy and pickle this issue would also disappear right?

redeboer commented 3 years ago

I would say so yes. Although it's unfortunate that we have no way to write the amplitude model to disk, other than pickling.

Generating the allowed transitions takes most time though. So we may have to open an issue for reading and writing StateTransitionGraphs (see also #458).

spflueger commented 3 years ago

Or are there other things that need to be serializable separately? Because it's not exactly clear from the title and that comment alone

redeboer commented 3 years ago

I made https://github.com/ComPWA/expertsystem/issues/384#issuecomment-734913355 into the issue description. That issue description would indeed become irrelevant once SymPy has been implemented.