Assisted-by: DeepSeek V4 Flash Free
Hi @thliebig I was running a test for my project and the test was failing intermittently with AttributeError.
I asked AI to investigate and it found a bug in CSXCAD. below is investigation form AI
Summary
CSProperties.fromPtr() is keyed on the raw C++ address and returns whatever wrapper is cached in the never-evicted, process-global _instances dict. When a property's C++ object is freed (its owning structure is destroyed) and the allocator reuses that address for a property of a different type, GetProperty() returns the old wrapper: GetTypeString()/GetName() still work (virtual dispatch), but the Python class is wrong, so any type-specific method raises AttributeError.
Environment
- csxcad 0.7.0rc1.post1.dev8+gcb931b747
- Python 3.11, Linux
Reproducer
import gc
from pathlib import Path
from tempfile import NamedTemporaryFile, TemporaryDirectory
from CSXCAD import ContinuousStructure
from openEMS.openEMS import openEMS
ACCESSOR = {"Material": "GetEpsilon", "Excitation": "GetEnabled",
"ProbeBox": "GetProbeType", "LumpedElement": "GetResistance"}
def build_model():
csx = ContinuousStructure()
csx.AddMaterial("substrate")
csx.AddMetal("ground").AddBox([0, 0, 0], [20, 20, 0.1])
csx.AddMetal("trace").AddBox([0, 0, 0.1], [1, 1, 0.2])
for n in (1, 2):
le = csx.AddLumpedElement(f"port_resist_{n}", ny="z", caps=True, R=50)
le.AddBox([0, 0, 0], [1, 1, 1])
csx.AddExcitation(f"port_excite_{n}", exc_type=0, exc_val=(1.0, 0.0, 0.0))
csx.AddProbe(f"port_ut_{n}", 0)
csx.AddProbe(f"port_it_{n}", 1)
grid = csx.GetGrid()
grid.SetDeltaUnit(1e-3)
for axis in "xyz":
grid.AddLine(axis, [-5.0, 0.0, 5.0, 10.0, 15.0])
return csx
def reload_roundtrip(model_xml):
"""Load a saved model into an openEMS object, wrap its CSX properties, write back."""
loader = openEMS()
loader.ReadFromXML(str(model_xml))
csx = loader.GetCSX()
for j in range(csx.GetQtyProperties()):
csx.GetProperty(j)
with NamedTemporaryFile(suffix=".xml") as tmp:
csx.Write2XML(tmp.name)
loader.Write2XML(tmp.name)
with TemporaryDirectory() as d:
d = Path(d)
xml = d / "structure.xml"
model_xml = d / "model.xml"
for i in range(50):
csx = build_model()
csx.Write2XML(str(xml))
fdtd = openEMS(NrTS=300, EndCriteria=1e-3)
fdtd.SetCSX(csx) # the FDTD owns the CSX
fdtd.SetBoundaryCond(["PML_8"] * 6)
fdtd.SetGaussExcite(2.5e9, 0.5e9)
fdtd.Write2XML(str(model_xml))
reload_roundtrip(model_xml)
del fdtd # frees the CSX and all its properties
gc.collect()
csx2 = ContinuousStructure()
csx2.ReadFromXML(str(xml))
for j in range(csx2.GetQtyProperties()):
prop = csx2.GetProperty(j)
type_str = prop.GetTypeString()
if type_str not in ACCESSOR:
continue
expected = {"Material": "CSPropMaterial", "Excitation": "CSPropExcitation",
"ProbeBox": "CSPropProbeBox", "LumpedElement": "CSPropLumpedElement"}[type_str]
if type(prop).__name__ != expected:
print(f"iteration {i}: property {j} ({prop.GetName()!r}) is a live "
f"{type_str} but was returned as a {type(prop).__name__}")
getattr(prop, ACCESSOR[type_str])() # <- crashes here
del csx2
else:
print("no stale wrapper in 50 iterations")
Output: iteration 2: property 8 ('port_excite_2') is a live Excitation but was returned as a CSPropMaterial → AttributeError: 'CSXCAD.CSProperties.CSPropMaterial' object has no attribute 'GetEnabled' (reverse direction also observed: CSPropExcitation for a Material).
Root cause
- python/CSXCAD/CSProperties.pyx:143-145 — fromPtr returns the cached wrapper on an address hit with no type check; entries are added in _SetPtr (:182) and never removed (_instances = {} at :54).
- A ContinuousStructure has no dealloc; its C++ properties are only freed when the owning openEMS FDTD is destroyed (openEMS.pyx:113-116).
- Same pattern in python/CSXCAD/CSPrimitives.pyx:51,109,137.
Assisted-by: DeepSeek V4 Flash Free
Hi @thliebig I was running a test for my project and the test was failing intermittently with
AttributeError.I asked AI to investigate and it found a bug in CSXCAD. below is investigation form AI
Summary
CSProperties.fromPtr() is keyed on the raw C++ address and returns whatever wrapper is cached in the never-evicted, process-global _instances dict. When a property's C++ object is freed (its owning structure is destroyed) and the allocator reuses that address for a property of a different type, GetProperty() returns the old wrapper: GetTypeString()/GetName() still work (virtual dispatch), but the Python class is wrong, so any type-specific method raises AttributeError.
Environment
Reproducer
Output: iteration 2: property 8 ('port_excite_2') is a live Excitation but was returned as a CSPropMaterial → AttributeError: 'CSXCAD.CSProperties.CSPropMaterial' object has no attribute 'GetEnabled' (reverse direction also observed: CSPropExcitation for a Material).
Root cause