22
22
from autograd .builtins import dict as dict_ag
23
23
from autograd .tracer import isbox
24
24
from pydantic .v1 .fields import ModelField
25
+ from pydantic .v1 .json import custom_pydantic_encoder
25
26
26
27
from tidy3d .exceptions import FileError
27
28
from tidy3d .log import log
@@ -68,13 +69,47 @@ def cached_property(cached_property_getter):
68
69
return property (cache (cached_property_getter ))
69
70
70
71
72
+ def cached_property_guarded (key_func ):
73
+ """Like cached_property, but invalidates when the key_func(self) changes."""
74
+
75
+ def _decorator (getter ):
76
+ prop_name = getter .__name__
77
+
78
+ @wraps (getter )
79
+ def _guarded (self ):
80
+ cache_store = self ._cached_properties .get (prop_name )
81
+ current_key = key_func (self )
82
+ if cache_store is not None :
83
+ cached_key , cached_value = cache_store
84
+ if cached_key == current_key :
85
+ return cached_value
86
+ value = getter (self )
87
+ self ._cached_properties [prop_name ] = (current_key , value )
88
+ return value
89
+
90
+ return property (_guarded )
91
+
92
+ return _decorator
93
+
94
+
71
95
def ndarray_encoder (val ):
72
96
"""How a ``np.ndarray`` gets handled before saving to json."""
73
97
if np .any (np .iscomplex (val )):
74
98
return {"real" : val .real .tolist (), "imag" : val .imag .tolist ()}
75
99
return val .real .tolist ()
76
100
77
101
102
+ def make_json_compatible (json_string : str ) -> str :
103
+ """Makes the string compatible with json standards, notably for infinity."""
104
+
105
+ tmp_string = "<<TEMPORARY_INFINITY_STRING>>"
106
+ json_string = json_string .replace ("-Infinity" , tmp_string )
107
+ json_string = json_string .replace ('""-Infinity""' , tmp_string )
108
+ json_string = json_string .replace ("Infinity" , '"Infinity"' )
109
+ json_string = json_string .replace ('""Infinity""' , '"Infinity"' )
110
+ return json_string .replace (tmp_string , '"-Infinity"' )
111
+
112
+
78
113
def _get_valid_extension (fname : str ) -> str :
79
114
"""Return the file extension from fname, validated to accepted ones."""
80
115
valid_extensions = [".json" , ".yaml" , ".hdf5" , ".h5" , ".hdf5.gz" ]
@@ -217,6 +252,24 @@ def _special_characters_not_in_name(cls, values):
217
252
"by calling ``obj.json()``." ,
218
253
)
219
254
255
+ def _attrs_digest (self ) -> str :
256
+ """Stable digest of `attrs` using the same JSON encoding rules as pydantic .json()."""
257
+ encoders = getattr (self .__config__ , "json_encoders" , {}) or {}
258
+
259
+ def _default (o ):
260
+ return custom_pydantic_encoder (encoders , o )
261
+
262
+ json_str = json .dumps (
263
+ self .attrs ,
264
+ default = _default ,
265
+ sort_keys = True ,
266
+ separators = ("," , ":" ),
267
+ ensure_ascii = False ,
268
+ )
269
+ json_str = make_json_compatible (json_str )
270
+
271
+ return hashlib .sha256 (json_str .encode ("utf-8" )).hexdigest ()
272
+
220
273
def copy (self , deep : bool = True , validate : bool = True , ** kwargs ) -> Tidy3dBaseModel :
221
274
"""Copy a Tidy3dBaseModel. With ``deep=True`` and ``validate=True`` as default."""
222
275
kwargs .update (deep = deep )
@@ -719,7 +772,11 @@ def from_hdf5(
719
772
)
720
773
return cls .parse_obj (model_dict , ** parse_obj_kwargs )
721
774
722
- def to_hdf5 (self , fname : str , custom_encoders : Optional [list [Callable ]] = None ) -> None :
775
+ def to_hdf5 (
776
+ self ,
777
+ fname : str ,
778
+ custom_encoders : Optional [list [Callable ]] = None ,
779
+ ) -> None :
723
780
"""Exports :class:`Tidy3dBaseModel` instance to .hdf5 file.
724
781
725
782
Parameters
@@ -931,7 +988,7 @@ def check_equal(dict1: dict, dict2: dict) -> bool:
931
988
932
989
return check_equal (self .dict (), other .dict ())
933
990
934
- @cached_property
991
+ @cached_property_guarded ( lambda self : self . _attrs_digest ())
935
992
def _json_string (self ) -> str :
936
993
"""Returns string representation of a :class:`Tidy3dBaseModel`.
937
994
@@ -955,16 +1012,6 @@ def _json(self, indent=INDENT, exclude_unset=False, **kwargs) -> str:
955
1012
Json-formatted string holding :class:`Tidy3dBaseModel` data.
956
1013
"""
957
1014
958
- def make_json_compatible (json_string : str ) -> str :
959
- """Makes the string compatible with json standards, notably for infinity."""
960
-
961
- tmp_string = "<<TEMPORARY_INFINITY_STRING>>"
962
- json_string = json_string .replace ("-Infinity" , tmp_string )
963
- json_string = json_string .replace ('""-Infinity""' , tmp_string )
964
- json_string = json_string .replace ("Infinity" , '"Infinity"' )
965
- json_string = json_string .replace ('""Infinity""' , '"Infinity"' )
966
- return json_string .replace (tmp_string , '"-Infinity"' )
967
-
968
1015
json_string = self .json (indent = indent , exclude_unset = exclude_unset , ** kwargs )
969
1016
json_string = make_json_compatible (json_string )
970
1017
return json_string
0 commit comments