@@ -1805,7 +1805,7 @@ def snap_to_grid(geom: Geometry, axis: Axis) -> Geometry:
18051805 # subdivide
18061806 subdivided_geometries = subdivide (geometry , background_structures )
18071807 # Create and add volumetric equivalents
1808- for subdivided_geometry in subdivided_geometries :
1808+ for i , subdivided_geometry in enumerate ( subdivided_geometries ) :
18091809 # Snap to the grid and create volumetric equivalent
18101810 snapped_geometry = snap_to_grid (subdivided_geometry [0 ], axis )
18111811 snapped_center = get_bounds (snapped_geometry , axis )[0 ]
@@ -1819,7 +1819,12 @@ def snap_to_grid(geom: Geometry, axis: Axis) -> Geometry:
18191819
18201820 new_bounds = (snapped_center , snapped_center )
18211821 new_geometry = snapped_geometry ._update_from_bounds (bounds = new_bounds , axis = axis )
1822- new_structure = structure .updated_copy (geometry = new_geometry , medium = new_medium )
1822+ new_name = structure .name
1823+ if new_name :
1824+ new_name += f"_SUBDIVIDED[{ i } ]"
1825+ new_structure = structure .updated_copy (
1826+ geometry = new_geometry , medium = new_medium , name = new_name
1827+ )
18231828
18241829 new_structures .append (new_structure )
18251830
@@ -2131,6 +2136,99 @@ def _invalidate_solver_cache(self) -> None:
21312136 """Clear cached attributes that become stale when subpixel changes."""
21322137 self ._cached_properties .pop ("_mode_solver" , None )
21332138
2139+ def validate_pre_upload (self ) -> None :
2140+ """Validate the fully initialized simulation is ok for upload to our servers."""
2141+ log .begin_capture ()
2142+ self ._validate_finalized ()
2143+ log .end_capture (self )
2144+
2145+ def _make_pec_frame (self , obj : Union [ModeSource , InternalAbsorber ]) -> Structure :
2146+ """Make a pec frame around a mode source or an internal absorber. For mode sources,
2147+ the frame is added around the injection plane. For internal absorbers, a backing pec
2148+ plate is also added on the non-absorbing side.
2149+ """
2150+ span_inds = np .array (self .grid .discretize_inds (obj ))
2151+
2152+ coords = self .grid .boundaries .to_list
2153+ direction = obj .direction
2154+ if isinstance (obj , ModeSource ):
2155+ axis = obj .injection_axis
2156+ length = obj .frame .length
2157+ if direction == "+" :
2158+ span_inds [axis ][1 ] += length - 1
2159+ else :
2160+ span_inds [axis ][0 ] -= length - 1
2161+ else :
2162+ axis = obj .size .index (0.0 )
2163+
2164+ box_bounds = [
2165+ [
2166+ c [beg ],
2167+ c [end ],
2168+ ]
2169+ for c , (beg , end ) in zip (coords , span_inds )
2170+ ]
2171+
2172+ box = Box .from_bounds (* np .transpose (box_bounds ))
2173+
2174+ surfaces = Box .surfaces (box .size , box .center )
2175+ if isinstance (obj , ModeSource ):
2176+ del surfaces [2 * axis : 2 * axis + 2 ]
2177+ else :
2178+ if direction == "-" :
2179+ del surfaces [2 * axis + 1 ]
2180+ else :
2181+ del surfaces [2 * axis ]
2182+
2183+ structure = Structure (
2184+ geometry = GeometryGroup (
2185+ geometries = surfaces ,
2186+ ),
2187+ medium = PECMedium (),
2188+ )
2189+
2190+ return structure
2191+
2192+ @cached_property
2193+ def _modal_plane_frames (self ) -> list [Structure ]:
2194+ """Return frames to add around mode sources and internal absorbers."""
2195+
2196+ pec_frames = [
2197+ self ._make_pec_frame (src )
2198+ for src in self .sources
2199+ if isinstance (src , ModeSource ) and isinstance (src .frame , PECFrame )
2200+ ]
2201+
2202+ pec_frames = pec_frames + [
2203+ self ._make_pec_frame (abc ) for abc in self ._shifted_internal_absorbers
2204+ ]
2205+
2206+ return pec_frames
2207+
2208+ @cached_property
2209+ def _finalized (self ) -> Simulation :
2210+ """Return the finalized version of the simulation setup. That is, including automatic frames around mode sources and internal absorbers, and 2d strutures converted into volumetric analogues."""
2211+
2212+ modal_frames = self ._modal_plane_frames
2213+
2214+ if len (modal_frames ) == 0 and not self ._contains_converted_volumetric_structures :
2215+ return self
2216+
2217+ structures = list (self .volumetric_structures ) + modal_frames
2218+
2219+ return self .updated_copy (grid_spec = GridSpec .from_grid (self .grid ), structures = structures )
2220+
2221+ def _validate_finalized (self ):
2222+ """Validate that after adding pec frames simulation setup is still valid."""
2223+
2224+ try :
2225+ _ = self ._finalized
2226+ except Exception :
2227+ log .error (
2228+ "Simulation fails after requested mode source PEC frames are added. "
2229+ "Please inspect '._finalized'."
2230+ )
2231+
21342232
21352233class Simulation (AbstractYeeGridSimulation ):
21362234 """
@@ -4336,6 +4434,7 @@ def validate_pre_upload(self, source_required: bool = True) -> None:
43364434 source_required: bool = True
43374435 If ``True``, validation will fail in case no sources are found in the simulation.
43384436 """
4437+ super ().validate_pre_upload ()
43394438 log .begin_capture ()
43404439 self ._validate_size ()
43414440 self ._validate_monitor_size ()
@@ -4346,7 +4445,6 @@ def validate_pre_upload(self, source_required: bool = True) -> None:
43464445 self ._warn_time_monitors_outside_run_time ()
43474446 self ._validate_time_monitors_num_steps ()
43484447 self ._validate_freq_monitors_freq_range ()
4349- self ._validate_finalized ()
43504448 log .end_capture (self )
43514449 if source_required and len (self .sources ) == 0 :
43524450 raise SetupError ("No sources in simulation." )
@@ -5655,90 +5753,3 @@ def from_scene(cls, scene: Scene, **kwargs) -> Simulation:
56555753 )
56565754
56575755 _boundaries_for_zero_dims = validate_boundaries_for_zero_dims ()
5658-
5659- def _make_pec_frame (self , obj : Union [ModeSource , InternalAbsorber ]) -> Structure :
5660- """Make a pec frame around a mode source or an internal absorber. For mode sources,
5661- the frame is added around the injection plane. For internal absorbers, a backing pec
5662- plate is also added on the non-absorbing side.
5663- """
5664- span_inds = np .array (self .grid .discretize_inds (obj ))
5665-
5666- coords = self .grid .boundaries .to_list
5667- direction = obj .direction
5668- if isinstance (obj , ModeSource ):
5669- axis = obj .injection_axis
5670- length = obj .frame .length
5671- if direction == "+" :
5672- span_inds [axis ][1 ] += length - 1
5673- else :
5674- span_inds [axis ][0 ] -= length - 1
5675- else :
5676- axis = obj .size .index (0.0 )
5677-
5678- box_bounds = [
5679- [
5680- c [beg ],
5681- c [end ],
5682- ]
5683- for c , (beg , end ) in zip (coords , span_inds )
5684- ]
5685-
5686- box = Box .from_bounds (* np .transpose (box_bounds ))
5687-
5688- surfaces = Box .surfaces (box .size , box .center )
5689- if isinstance (obj , ModeSource ):
5690- del surfaces [2 * axis : 2 * axis + 2 ]
5691- else :
5692- if direction == "-" :
5693- del surfaces [2 * axis + 1 ]
5694- else :
5695- del surfaces [2 * axis ]
5696-
5697- structure = Structure (
5698- geometry = GeometryGroup (
5699- geometries = surfaces ,
5700- ),
5701- medium = PECMedium (),
5702- )
5703-
5704- return structure
5705-
5706- @cached_property
5707- def _modal_plane_frames (self ) -> list [Structure ]:
5708- """Return frames to add around mode sources and internal absorbers."""
5709-
5710- pec_frames = [
5711- self ._make_pec_frame (src )
5712- for src in self .sources
5713- if isinstance (src , ModeSource ) and isinstance (src .frame , PECFrame )
5714- ]
5715-
5716- pec_frames = pec_frames + [
5717- self ._make_pec_frame (abc ) for abc in self ._shifted_internal_absorbers
5718- ]
5719-
5720- return pec_frames
5721-
5722- @cached_property
5723- def _finalized (self ) -> Simulation :
5724- """Return the finalized version of the simulation setup. That is, including automatic frames around mode sources and internal absorbers, and 2d strutures converted into volumetric analogues."""
5725-
5726- modal_frames = self ._modal_plane_frames
5727-
5728- if len (modal_frames ) == 0 and not self ._contains_converted_volumetric_structures :
5729- return self
5730-
5731- structures = list (self .volumetric_structures ) + modal_frames
5732-
5733- return self .updated_copy (grid_spec = GridSpec .from_grid (self .grid ), structures = structures )
5734-
5735- def _validate_finalized (self ):
5736- """Validate that after adding pec frames simulation setup is still valid."""
5737-
5738- try :
5739- _ = self ._finalized
5740- except Exception :
5741- log .error (
5742- "Simulation fails after requested mode source PEC frames are added. "
5743- "Please inspect '._finalized'."
5744- )
0 commit comments