|
| 1 | +import pytest |
| 2 | + |
1 | 3 | import pals |
2 | 4 |
|
3 | 5 |
|
@@ -322,3 +324,74 @@ def test_comprehensive_lattice(tmp_path): |
322 | 324 | assert unionele_loaded_json.elements[1].name == "union_drift" |
323 | 325 | assert unionele_loaded_json.elements[1].kind == "Drift" |
324 | 326 | assert unionele_loaded_json.elements[1].length == 0.1 |
| 327 | + |
| 328 | + |
| 329 | +def _build_numpy_lattice(np): |
| 330 | + """Build a small lattice using numpy-typed scalar values throughout.""" |
| 331 | + quad = pals.Quadrupole( |
| 332 | + name="q_np", |
| 333 | + length=np.float64(0.061), |
| 334 | + MagneticMultipoleP=pals.MagneticMultipoleParameters( |
| 335 | + Bn1=np.float64(-26.0), Bs1=np.float32(0.5), Kn0=np.int64(-1) |
| 336 | + ), |
| 337 | + ) |
| 338 | + oct_ = pals.Octupole( |
| 339 | + name="o_np", |
| 340 | + length=np.float64(0.25), |
| 341 | + ElectricMultipoleP=pals.ElectricMultipoleParameters( |
| 342 | + En3=np.float64(0.75), Es3=np.float32(0.125) |
| 343 | + ), |
| 344 | + ) |
| 345 | + return pals.BeamLine(name="line_np", line=[quad, oct_]) |
| 346 | + |
| 347 | + |
| 348 | +def test_yaml_roundtrip_with_numpy(tmp_path): |
| 349 | + """Round-trip numpy-typed values through YAML (regression for issue #67). |
| 350 | +
|
| 351 | + Writing YAML with numpy-typed values must not produce !!python/object tags. |
| 352 | + Round-tripping must yield Python-native floats with the correct numeric values. |
| 353 | + """ |
| 354 | + np = pytest.importorskip("numpy") |
| 355 | + |
| 356 | + line = _build_numpy_lattice(np) |
| 357 | + yaml_file = tmp_path / "numpy_roundtrip.pals.yaml" |
| 358 | + line.to_file(yaml_file) |
| 359 | + |
| 360 | + with open(yaml_file, "r") as f: |
| 361 | + text = f.read() |
| 362 | + |
| 363 | + # The bug symptom: YAML contains opaque numpy object tags. |
| 364 | + assert "!!python/object" not in text, ( |
| 365 | + f"YAML output still contains unsafe numpy object tags:\n{text}" |
| 366 | + ) |
| 367 | + assert "numpy" not in text, f"YAML output still references numpy:\n{text}" |
| 368 | + |
| 369 | + loaded = pals.BeamLine.from_file(yaml_file) |
| 370 | + loaded_quad = loaded.line[0] |
| 371 | + assert loaded_quad.MagneticMultipoleP.Bn1 == -26.0 |
| 372 | + assert type(loaded_quad.MagneticMultipoleP.Bn1) is float |
| 373 | + assert loaded_quad.MagneticMultipoleP.Bs1 == 0.5 |
| 374 | + assert loaded_quad.MagneticMultipoleP.Kn0 == -1 |
| 375 | + |
| 376 | + loaded_oct = loaded.line[1] |
| 377 | + assert loaded_oct.ElectricMultipoleP.En3 == 0.75 |
| 378 | + assert type(loaded_oct.ElectricMultipoleP.En3) is float |
| 379 | + |
| 380 | + |
| 381 | +def test_json_roundtrip_with_numpy(tmp_path): |
| 382 | + """Round-trip numpy-typed values through JSON (companion for issue #67). |
| 383 | +
|
| 384 | + JSON also needs to handle numpy values cleanly (defense-in-depth). |
| 385 | + """ |
| 386 | + np = pytest.importorskip("numpy") |
| 387 | + |
| 388 | + line = _build_numpy_lattice(np) |
| 389 | + json_file = tmp_path / "numpy_roundtrip.pals.json" |
| 390 | + line.to_file(json_file) |
| 391 | + |
| 392 | + loaded = pals.BeamLine.from_file(json_file) |
| 393 | + loaded_quad = loaded.line[0] |
| 394 | + assert loaded_quad.MagneticMultipoleP.Bn1 == -26.0 |
| 395 | + assert type(loaded_quad.MagneticMultipoleP.Bn1) is float |
| 396 | + loaded_oct = loaded.line[1] |
| 397 | + assert loaded_oct.ElectricMultipoleP.En3 == 0.75 |
0 commit comments