@@ -884,7 +884,7 @@ def _validate_modulation_spec(cls, val, values):
884884 nonlinear_spec = values .get ("nonlinear_spec" )
885885 if val is not None and nonlinear_spec is not None :
886886 raise ValidationError (
887- f"For medium class { cls } , 'modulation_spec' of class { type (val )} and "
887+ f"For medium class { cls . __name__ } , 'modulation_spec' of class { type (val )} and "
888888 f"'nonlinear_spec' of class { type (nonlinear_spec )} are "
889889 "not simultaneously supported."
890890 )
@@ -1350,6 +1350,11 @@ def is_pec(self):
13501350 """Whether the medium is a PEC."""
13511351 return False
13521352
1353+ @cached_property
1354+ def is_pmc (self ):
1355+ """Whether the medium is a PMC."""
1356+ return False
1357+
13531358 def sel_inside (self , bounds : Bound ) -> AbstractMedium :
13541359 """Return a new medium that contains the minimal amount data necessary to cover
13551360 a spatial region defined by ``bounds``.
@@ -1740,7 +1745,7 @@ def _validate_modulation_spec(cls, val):
17401745 if val is not None :
17411746 raise ValidationError (
17421747 f"A 'modulation_spec' of class { type (val )} is not "
1743- f"currently supported for medium class { cls } ."
1748+ f"currently supported for medium class { cls . __name__ } ."
17441749 )
17451750 return val
17461751
@@ -1767,6 +1772,52 @@ def is_pec(self):
17671772PEC = PECMedium (name = "PEC" )
17681773
17691774
1775+ # PMC keyword
1776+ class PMCMedium (AbstractMedium ):
1777+ """Perfect magnetic conductor class.
1778+
1779+ Note
1780+ ----
1781+
1782+ To avoid confusion from duplicate PMCs, must import ``tidy3d.PMC`` instance directly.
1783+
1784+
1785+
1786+ """
1787+
1788+ @pd .validator ("modulation_spec" , always = True )
1789+ def _validate_modulation_spec (cls , val ):
1790+ """Check compatibility with modulation_spec."""
1791+ if val is not None :
1792+ raise ValidationError (
1793+ f"A 'modulation_spec' of class { type (val )} is not "
1794+ f"currently supported for medium class { cls .__name__ } ."
1795+ )
1796+ return val
1797+
1798+ @ensure_freq_in_range
1799+ def eps_model (self , frequency : float ) -> complex :
1800+ # permittivity of a PMC.
1801+ return 1.0 + 0j
1802+
1803+ @cached_property
1804+ def n_cfl (self ):
1805+ """This property computes the index of refraction related to CFL condition, so that
1806+ the FDTD with this medium is stable when the time step size that doesn't take
1807+ material factor into account is multiplied by ``n_cfl``.
1808+ """
1809+ return 1.0
1810+
1811+ @cached_property
1812+ def is_pmc (self ):
1813+ """Whether the medium is a PMC."""
1814+ return True
1815+
1816+
1817+ # PEC builtin instance
1818+ PMC = PMCMedium (name = "PMC" )
1819+
1820+
17701821class Medium (AbstractMedium ):
17711822 """Dispersionless medium. Mediums define the optical properties of the materials within the simulation.
17721823
@@ -5643,9 +5694,10 @@ def plot(
56435694 return ax
56445695
56455696
5646- IsotropicUniformMediumType = Union [
5697+ IsotropicUniformMediumFor2DType = Union [
56475698 Medium , LossyMetalMedium , PoleResidue , Sellmeier , Lorentz , Debye , Drude , PECMedium
56485699]
5700+ IsotropicUniformMediumType = Union [IsotropicUniformMediumFor2DType , PMCMedium ]
56495701IsotropicCustomMediumType = Union [
56505702 CustomPoleResidue ,
56515703 CustomSellmeier ,
@@ -5719,7 +5771,7 @@ def _validate_modulation_spec(cls, val):
57195771 if val is not None :
57205772 raise ValidationError (
57215773 f"A 'modulation_spec' of class { type (val )} is not "
5722- f"currently supported for medium class { cls } . "
5774+ f"currently supported for medium class { cls . __name__ } . "
57235775 "Please add modulation to each component."
57245776 )
57255777 return val
@@ -5852,10 +5904,19 @@ def is_pec(self):
58525904 """Whether the medium is a PEC."""
58535905 return any (self .is_comp_pec (i ) for i in range (3 ))
58545906
5907+ @cached_property
5908+ def is_pmc (self ):
5909+ """Whether the medium is a PMC."""
5910+ return any (self .is_comp_pmc (i ) for i in range (3 ))
5911+
58555912 def is_comp_pec (self , comp : Axis ):
58565913 """Whether the medium is a PEC."""
58575914 return isinstance (self .components [["xx" , "yy" , "zz" ][comp ]], PECMedium )
58585915
5916+ def is_comp_pmc (self , comp : Axis ):
5917+ """Whether the medium is a PMC."""
5918+ return isinstance (self .components [["xx" , "yy" , "zz" ][comp ]], PMCMedium )
5919+
58595920 def sel_inside (self , bounds : Bound ):
58605921 """Return a new medium that contains the minimal amount data necessary to cover
58615922 a spatial region defined by ``bounds``.
@@ -5945,7 +6006,7 @@ def _validate_modulation_spec(cls, val):
59456006 if val is not None :
59466007 raise ValidationError (
59476008 f"A 'modulation_spec' of class { type (val )} is not "
5948- f"currently supported for medium class { cls } ."
6009+ f"currently supported for medium class { cls . __name__ } ."
59496010 )
59506011 return val
59516012
@@ -6970,6 +7031,7 @@ def perturbed_copy(
69707031 Medium ,
69717032 AnisotropicMedium ,
69727033 PECMedium ,
7034+ PMCMedium ,
69737035 PoleResidue ,
69747036 Sellmeier ,
69757037 Lorentz ,
@@ -7004,7 +7066,7 @@ class Medium2D(AbstractMedium):
70047066
70057067 """
70067068
7007- ss : IsotropicUniformMediumType = pd .Field (
7069+ ss : IsotropicUniformMediumFor2DType = pd .Field (
70087070 ...,
70097071 title = "SS Component" ,
70107072 description = "Medium describing the ss-component of the diagonal permittivity tensor. "
@@ -7015,7 +7077,7 @@ class Medium2D(AbstractMedium):
70157077 discriminator = TYPE_TAG_STR ,
70167078 )
70177079
7018- tt : IsotropicUniformMediumType = pd .Field (
7080+ tt : IsotropicUniformMediumFor2DType = pd .Field (
70197081 ...,
70207082 title = "TT Component" ,
70217083 description = "Medium describing the tt-component of the diagonal permittivity tensor. "
@@ -7032,7 +7094,7 @@ def _validate_modulation_spec(cls, val):
70327094 if val is not None :
70337095 raise ValidationError (
70347096 f"A 'modulation_spec' of class { type (val )} is not "
7035- f"currently supported for medium class { cls } ."
7097+ f"currently supported for medium class { cls . __name__ } ."
70367098 )
70377099 return val
70387100
@@ -7049,7 +7111,7 @@ def _validate_inplane_pec(cls, val, values):
70497111
70507112 @classmethod
70517113 def _weighted_avg (
7052- cls , meds : list [IsotropicUniformMediumType ], weights : list [float ]
7114+ cls , meds : list [IsotropicUniformMediumFor2DType ], weights : list [float ]
70537115 ) -> Union [PoleResidue , PECMedium ]:
70547116 """Average ``meds`` with weights ``weights``."""
70557117 eps_inf = 1
@@ -7103,7 +7165,7 @@ def volumetric_equivalent(
71037165 The 3D material corresponding to this 2D material.
71047166 """
71057167
7106- def get_component (med : MediumType3D , comp : Axis ) -> IsotropicUniformMediumType :
7168+ def get_component (med : MediumType3D , comp : Axis ) -> IsotropicUniformMediumFor2DType :
71077169 """Extract the ``comp`` component of ``med``."""
71087170 if isinstance (med , AnisotropicMedium ):
71097171 dim = "xyz" [comp ]
@@ -7365,7 +7427,7 @@ def sigma_model(self, freq: float) -> complex:
73657427 return np .mean ([self .ss .sigma_model (freq ), self .tt .sigma_model (freq )], axis = 0 )
73667428
73677429 @property
7368- def elements (self ) -> dict [str , IsotropicUniformMediumType ]:
7430+ def elements (self ) -> dict [str , IsotropicUniformMediumFor2DType ]:
73697431 """The diagonal elements of the 2D medium as a dictionary."""
73707432 return {"ss" : self .ss , "tt" : self .tt }
73717433
0 commit comments