Skip to content

Commit 7c96576

Browse files
feat(tidy3d): Mutable attrs bypass _json_string cache, causing stale hashes and exports
- hash also sensitive to attrs changes Jira: FXC-3374
1 parent c35c6c5 commit 7c96576

File tree

3 files changed

+10
-26
lines changed

3 files changed

+10
-26
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010

1111
### Changed
1212
- Improved performance of antenna metrics calculation by utilizing cached wave amplitude calculations instead of recomputing wave amplitudes for each port excitation in the `TerminalComponentModelerData`.
13-
- Fixed bug for `Tidy3dBaseModel`: Hashes are independent from `attrs`. Still `.json_string` does include them and is cached, but cache is updated if `attrs` are updated.
13+
- Fixed: In `Tidy3dBaseModel` the hash (and cached `.json_string`) are now sensitive to changes in `.attrs`.
1414

1515
### Fixed
1616
- More robust `Sellmeier` and `Debye` material model, and prevent very large pole parameters in `PoleResidue` material model.

tests/test_components/test_base.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -262,17 +262,16 @@ def test_scientific_notation(min_val, max_val, min_digits, expected):
262262
assert result == expected
263263

264264

265-
def test_stable_hash_with_changed_attr():
265+
def test_updated_hash_and_json_with_changed_attr():
266266
obj = td.Medium(attrs={"foo": "attr"})
267+
267268
old_hash = obj._hash_self()
268-
obj.attrs["foo"] = "changed"
269-
new_hash = obj._hash_self()
270-
assert new_hash == old_hash
269+
json_old = obj._json_string
271270

271+
obj.attrs["foo"] = "changed"
272272

273-
def test_updated_json_for_mutable_attr():
274-
obj = td.Medium(attrs={"foo": "attr"})
275-
json_old = obj._json_string
276-
obj.attrs["foo"] = np.array([0])
273+
new_hash = obj._hash_self()
277274
json_new = obj._json_string
275+
276+
assert new_hash != old_hash
278277
assert json_old != json_new

tidy3d/components/base.py

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ def __hash__(self) -> int:
173173
def _hash_self(self) -> str:
174174
"""Hash this component with ``hashlib`` in a way that is the same every session."""
175175
bf = io.BytesIO()
176-
self.to_hdf5(bf, for_hashing=True)
176+
self.to_hdf5(bf)
177177
return hashlib.sha256(bf.getvalue()).hexdigest()
178178

179179
def __init__(self, **kwargs):
@@ -776,7 +776,6 @@ def to_hdf5(
776776
self,
777777
fname: str,
778778
custom_encoders: Optional[list[Callable]] = None,
779-
for_hashing: bool = False,
780779
) -> None:
781780
"""Exports :class:`Tidy3dBaseModel` instance to .hdf5 file.
782781
@@ -787,16 +786,14 @@ def to_hdf5(
787786
custom_encoders : List[Callable]
788787
List of functions accepting (fname: str, group_path: str, value: Any) that take
789788
the ``value`` supplied and write it to the hdf5 ``fname`` at ``group_path``.
790-
for_hashing : bool = False
791-
Whether to use hdf5 for hashing.
792789
793790
Example
794791
-------
795792
>>> simulation.to_hdf5(fname='folder/sim.hdf5') # doctest: +SKIP
796793
"""
797794

798795
with h5py.File(fname, "w") as f_handle:
799-
json_str = self._json_string if not for_hashing else self._json_string_for_hashing
796+
json_str = self._json_string
800797
for ind in range(ceil(len(json_str) / MAX_STRING_LENGTH)):
801798
ind_start = int(ind * MAX_STRING_LENGTH)
802799
ind_stop = min(int(ind + 1) * MAX_STRING_LENGTH, len(json_str))
@@ -991,18 +988,6 @@ def check_equal(dict1: dict, dict2: dict) -> bool:
991988

992989
return check_equal(self.dict(), other.dict())
993990

994-
@cached_property
995-
def _json_string_for_hashing(self) -> str:
996-
"""Returns string representation of a :class:`Tidy3dBaseModel`.
997-
998-
Returns
999-
-------
1000-
str
1001-
Json-formatted string holding :class:`Tidy3dBaseModel` data.
1002-
"""
1003-
obj_no_attrs = self._updated_copy(attrs={}, deep=True, validate=False)
1004-
return obj_no_attrs._json()
1005-
1006991
@cached_property_guarded(lambda self: self._attrs_digest())
1007992
def _json_string(self) -> str:
1008993
"""Returns string representation of a :class:`Tidy3dBaseModel`.

0 commit comments

Comments
 (0)