Skip to content

Fix NumPy Serialization#71

Open
ax3l wants to merge 5 commits into
pals-project:mainfrom
ax3l:fix-multipole-numpy-serialization
Open

Fix NumPy Serialization#71
ax3l wants to merge 5 commits into
pals-project:mainfrom
ax3l:fix-multipole-numpy-serialization

Conversation

@ax3l
Copy link
Copy Markdown
Member

@ax3l ax3l commented May 12, 2026

Fix #67

  • add test with reproducer for MultipoleP
  • add fix for MultipoleP
  • develop a more generic serializer that can be used for any location where we have floats and lists

@ax3l ax3l added bug Something isn't working element parameters element parameters (properties, attributes) labels May 12, 2026
@ax3l ax3l changed the title Test: NumPy Multipole Serialization Fix NumPy Multipole Serialization May 12, 2026
@ax3l ax3l force-pushed the fix-multipole-numpy-serialization branch from ba33f5f to f514cc2 Compare May 12, 2026 18:09
@ax3l ax3l requested a review from EZoni May 12, 2026 19:41
@ax3l
Copy link
Copy Markdown
Member Author

ax3l commented May 12, 2026

cc @ruisardkj @adb-xkc85723 NumPy fix attempt :)

@ax3l ax3l changed the title Fix NumPy Multipole Serialization Fix NumPy Serialization May 13, 2026
Copy link
Copy Markdown
Member

@EZoni EZoni left a comment

Choose a reason for hiding this comment

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

First review batch to suggest using pytest's tmp_path fixture to avoid polluting and cleaning the root directory (no try/finally, no os.remove, etc.). I think we should do this in pre-existing tests as well, in a separate PR.

Comment on lines +358 to +389
def test_yaml_roundtrip_with_numpy():
"""Regression test for issue #67: writing YAML with numpy-typed values
must not produce !!python/object tags, and round-tripping must yield
Python-native floats with the correct numeric values."""
np = pytest.importorskip("numpy")

line = _build_numpy_lattice(np)
yaml_file = "numpy_roundtrip.pals.yaml"
line.to_file(yaml_file)
try:
with open(yaml_file, "r") as f:
text = f.read()

# The bug symptom: YAML contains opaque numpy object tags.
assert "!!python/object" not in text, (
f"YAML output still contains unsafe numpy object tags:\n{text}"
)
assert "numpy" not in text, f"YAML output still references numpy:\n{text}"

loaded = pals.BeamLine.from_file(yaml_file)
loaded_quad = loaded.line[0]
assert loaded_quad.MagneticMultipoleP.Bn1 == -26.0
assert type(loaded_quad.MagneticMultipoleP.Bn1) is float
assert loaded_quad.MagneticMultipoleP.Bs1 == 0.5
assert loaded_quad.MagneticMultipoleP.Kn0 == -1

loaded_oct = loaded.line[1]
assert loaded_oct.ElectricMultipoleP.En3 == 0.75
assert type(loaded_oct.ElectricMultipoleP.En3) is float
finally:
if os.path.exists(yaml_file):
os.remove(yaml_file)
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.

We could use pytest's tmp_path fixture to avoid polluting and cleaning the root directory (no try/finally, no os.remove, etc.):

Suggested change
def test_yaml_roundtrip_with_numpy():
"""Regression test for issue #67: writing YAML with numpy-typed values
must not produce !!python/object tags, and round-tripping must yield
Python-native floats with the correct numeric values."""
np = pytest.importorskip("numpy")
line = _build_numpy_lattice(np)
yaml_file = "numpy_roundtrip.pals.yaml"
line.to_file(yaml_file)
try:
with open(yaml_file, "r") as f:
text = f.read()
# The bug symptom: YAML contains opaque numpy object tags.
assert "!!python/object" not in text, (
f"YAML output still contains unsafe numpy object tags:\n{text}"
)
assert "numpy" not in text, f"YAML output still references numpy:\n{text}"
loaded = pals.BeamLine.from_file(yaml_file)
loaded_quad = loaded.line[0]
assert loaded_quad.MagneticMultipoleP.Bn1 == -26.0
assert type(loaded_quad.MagneticMultipoleP.Bn1) is float
assert loaded_quad.MagneticMultipoleP.Bs1 == 0.5
assert loaded_quad.MagneticMultipoleP.Kn0 == -1
loaded_oct = loaded.line[1]
assert loaded_oct.ElectricMultipoleP.En3 == 0.75
assert type(loaded_oct.ElectricMultipoleP.En3) is float
finally:
if os.path.exists(yaml_file):
os.remove(yaml_file)
def test_yaml_roundtrip_with_numpy(tmp_path):
"""Regression test for issue #67: writing YAML with numpy-typed values
must not produce !!python/object tags, and round-tripping must yield
Python-native floats with the correct numeric values."""
np = pytest.importorskip("numpy")
line = _build_numpy_lattice(np)
yaml_file = str(tmp_path / "numpy_roundtrip.pals.yaml")
line.to_file(yaml_file)
with open(yaml_file, "r") as f:
text = f.read()
# The bug symptom: YAML contains opaque numpy object tags.
assert "!!python/object" not in text, (
f"YAML output still contains unsafe numpy object tags:\n{text}"
)
assert "numpy" not in text, f"YAML output still references numpy:\n{text}"
loaded = pals.BeamLine.from_file(yaml_file)
loaded_quad = loaded.line[0]
assert loaded_quad.MagneticMultipoleP.Bn1 == -26.0
assert type(loaded_quad.MagneticMultipoleP.Bn1) is float
assert loaded_quad.MagneticMultipoleP.Bs1 == 0.5
assert loaded_quad.MagneticMultipoleP.Kn0 == -1
loaded_oct = loaded.line[1]
assert loaded_oct.ElectricMultipoleP.En3 == 0.75
assert type(loaded_oct.ElectricMultipoleP.En3) is float

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.

For convenience, the diff without whitespace would be:

-def test_yaml_roundtrip_with_numpy():
+def test_yaml_roundtrip_with_numpy(tmp_path):
     """Regression test for issue #67: writing YAML with numpy-typed values
     must not produce !!python/object tags, and round-tripping must yield
     Python-native floats with the correct numeric values."""
     np = pytest.importorskip("numpy")
 
     line = _build_numpy_lattice(np)
-    yaml_file = "numpy_roundtrip.pals.yaml"
+    yaml_file = str(tmp_path / "numpy_roundtrip.pals.yaml")
     line.to_file(yaml_file)
-    try:
+
     with open(yaml_file, "r") as f:
         text = f.read()
 
@@ -384,25 +384,19 @@ def test_yaml_roundtrip_with_numpy():
     loaded_oct = loaded.line[1]
     assert loaded_oct.ElectricMultipoleP.En3 == 0.75
     assert type(loaded_oct.ElectricMultipoleP.En3) is float
-    finally:
-        if os.path.exists(yaml_file):
-            os.remove(yaml_file)

Comment on lines +392 to +408
def test_json_roundtrip_with_numpy():
"""JSON path also needs to handle numpy values cleanly (defense-in-depth)."""
np = pytest.importorskip("numpy")

line = _build_numpy_lattice(np)
json_file = "numpy_roundtrip.pals.json"
line.to_file(json_file)
try:
loaded = pals.BeamLine.from_file(json_file)
loaded_quad = loaded.line[0]
assert loaded_quad.MagneticMultipoleP.Bn1 == -26.0
assert type(loaded_quad.MagneticMultipoleP.Bn1) is float
loaded_oct = loaded.line[1]
assert loaded_oct.ElectricMultipoleP.En3 == 0.75
finally:
if os.path.exists(json_file):
os.remove(json_file)
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.

We could use pytest's tmp_path fixture to avoid polluting and cleaning the root directory (no try/finally, no os.remove, etc.):

Suggested change
def test_json_roundtrip_with_numpy():
"""JSON path also needs to handle numpy values cleanly (defense-in-depth)."""
np = pytest.importorskip("numpy")
line = _build_numpy_lattice(np)
json_file = "numpy_roundtrip.pals.json"
line.to_file(json_file)
try:
loaded = pals.BeamLine.from_file(json_file)
loaded_quad = loaded.line[0]
assert loaded_quad.MagneticMultipoleP.Bn1 == -26.0
assert type(loaded_quad.MagneticMultipoleP.Bn1) is float
loaded_oct = loaded.line[1]
assert loaded_oct.ElectricMultipoleP.En3 == 0.75
finally:
if os.path.exists(json_file):
os.remove(json_file)
def test_json_roundtrip_with_numpy(tmp_path):
"""JSON path also needs to handle numpy values cleanly (defense-in-depth)."""
np = pytest.importorskip("numpy")
line = _build_numpy_lattice(np)
json_file = str(tmp_path / "numpy_roundtrip.pals.json")
line.to_file(json_file)
loaded = pals.BeamLine.from_file(json_file)
loaded_quad = loaded.line[0]
assert loaded_quad.MagneticMultipoleP.Bn1 == -26.0
assert type(loaded_quad.MagneticMultipoleP.Bn1) is float
loaded_oct = loaded.line[1]
assert loaded_oct.ElectricMultipoleP.En3 == 0.75

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.

For convenience, the diff without whitespace would be:

-def test_json_roundtrip_with_numpy():
+def test_json_roundtrip_with_numpy(tmp_path):
     """JSON path also needs to handle numpy values cleanly (defense-in-depth)."""
     np = pytest.importorskip("numpy")
 
     line = _build_numpy_lattice(np)
-    json_file = "numpy_roundtrip.pals.json"
+    json_file = str(tmp_path / "numpy_roundtrip.pals.json")
     line.to_file(json_file)
-    try:
+
     loaded = pals.BeamLine.from_file(json_file)
     loaded_quad = loaded.line[0]
     assert loaded_quad.MagneticMultipoleP.Bn1 == -26.0
     assert type(loaded_quad.MagneticMultipoleP.Bn1) is float
     loaded_oct = loaded.line[1]
     assert loaded_oct.ElectricMultipoleP.En3 == 0.75
-    finally:
-        if os.path.exists(json_file):
-            os.remove(json_file)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working element parameters element parameters (properties, attributes)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Can't use type(numpy.float64) for MagneticMultipoleP parameter

2 participants