feat: Added adiabatic absorber.

It's useful for scenarios where we need to intersect geometry with the
simulation boundary.
Generally, not preferred, which we make clear in the docs :)

Also fixed a bug in `events.py` that was misjudging `FlowInitializing`,
and causing `BoundConds` to fail.
Weird.
Anyway, fixed!
main
Sofus Albert Høgsbro Rose 2024-05-01 16:38:11 +02:00
parent f60b736584
commit 2f42c9d91b
Signed by: so-rose
GPG Key ID: AD901CB0F3701434
6 changed files with 160 additions and 15 deletions

View File

@ -25,7 +25,7 @@ NODE_CAT_LABELS = {
NC.MAXWELLSIM_STRUCTURES_PRIMITIVES: 'Primitives', NC.MAXWELLSIM_STRUCTURES_PRIMITIVES: 'Primitives',
# Bounds/ # Bounds/
NC.MAXWELLSIM_BOUNDS: 'Bounds', NC.MAXWELLSIM_BOUNDS: 'Bounds',
NC.MAXWELLSIM_BOUNDS_BOUNDCONDS: 'Bound Conds', NC.MAXWELLSIM_BOUNDS_BOUNDCONDS: 'Conds',
# Monitors/ # Monitors/
NC.MAXWELLSIM_MONITORS: 'Monitors', NC.MAXWELLSIM_MONITORS: 'Monitors',
NC.MAXWELLSIM_MONITORS_PROJECTED: 'Projected', NC.MAXWELLSIM_MONITORS_PROJECTED: 'Projected',

View File

@ -91,7 +91,7 @@ class NodeType(blender_type_enum.BlenderTypeEnum):
## Bounds / Bound Conds ## Bounds / Bound Conds
PMLBoundCond = enum.auto() PMLBoundCond = enum.auto()
BlochBoundCond = enum.auto() BlochBoundCond = enum.auto()
AbsorbingBoundCond = enum.auto() AdiabAbsorbBoundCond = enum.auto()
# Monitors # Monitors
EHFieldMonitor = enum.auto() EHFieldMonitor = enum.auto()

View File

@ -1,16 +1,16 @@
from . import ( from . import (
#absorbing_bound_cond, absorbing_bound_cond,
#bloch_bound_cond, # bloch_bound_cond,
pml_bound_cond, pml_bound_cond,
) )
BL_REGISTER = [ BL_REGISTER = [
*pml_bound_cond.BL_REGISTER, *pml_bound_cond.BL_REGISTER,
#*bloch_bound_cond.BL_REGISTER, # *bloch_bound_cond.BL_REGISTER,
#*absorbing_bound_cond.BL_REGISTER, *absorbing_bound_cond.BL_REGISTER,
] ]
BL_NODES = { BL_NODES = {
**pml_bound_cond.BL_NODES, **pml_bound_cond.BL_NODES,
#**bloch_bound_cond.BL_NODES, # **bloch_bound_cond.BL_NODES,
#**absorbing_bound_cond.BL_NODES, **absorbing_bound_cond.BL_NODES,
} }

View File

@ -1,5 +1,149 @@
"""Implements `AdiabAbsorbBoundCondNode`."""
import typing as typ
import bpy
import sympy as sp
import tidy3d as td
from blender_maxwell.utils import extra_sympy_units as spux
from blender_maxwell.utils import logger
from .... import contracts as ct
from .... import sockets
from ... import base, events
log = logger.get(__name__)
class AdiabAbsorbBoundCondNode(base.MaxwellSimNode):
r"""A boundary condition that generically (adiabatically) absorbs outgoing energy, by gradually ramping up the strength of the conductor over many layers, until a final PEC layer.
Compared to PML, this boundary is more computationally expensive, and may result in higher reflectivity (and thus lower accuracy).
The general reason to use it is **to fix divergence in cases where dispersive materials intersect the simulation boundary**.
For more theoretical details, please refer to the `tidy3d` resource: <https://docs.flexcompute.com/projects/tidy3d/en/latest/api/_autosummary/tidy3d.Absorber.html>
Notes:
**Ensure** that all simulation structures are $\approx \frac{\lambda}{2}$ from any PML boundary.
This helps avoid the amplification of stray evanescent waves.
Socket Sets:
Simple: Only specify the number of absorption layers.
$40$ should generally be sufficient, but in the case of divergence issues, bumping up the number of layers should be the go-to remedy to try.
Full: Specify the conductivity min/max that makes up the absorption up the PML, as well as the order of the polynomial used to scale the effect through the layers.
You should probably leave this alone.
Since the value units are sim-relative, we've opted to show the scaling information in the node's UI, instead of coercing the values into any particular unit.
"""
node_type = ct.NodeType.AdiabAbsorbBoundCond
bl_label = 'Absorber Bound Cond'
####################
# - Sockets
####################
input_sockets: typ.ClassVar = {
'Layers': sockets.ExprSocketDef(
shape=None,
mathtype=spux.MathType.Integer,
abs_min=1,
default_value=40,
),
}
input_socket_sets: typ.ClassVar = {
'Simple': {},
'Full': {
'σ Order': sockets.ExprSocketDef(
shape=None,
mathtype=spux.MathType.Integer,
abs_min=1,
default_value=3,
),
'σ Range': sockets.ExprSocketDef(
shape=(2,),
mathtype=spux.MathType.Real,
default_value=sp.Matrix([0, 1.5]),
abs_min=0,
),
},
}
output_sockets: typ.ClassVar = {
'BC': sockets.MaxwellBoundCondSocketDef(),
}
####################
# - UI
####################
def draw_info(self, _: bpy.types.Context, layout: bpy.types.UILayout) -> None:
if self.active_socket_set == 'Full':
box = layout.box()
row = box.row()
row.alignment = 'CENTER'
row.label(text='Parameter Scale')
# Split
split = box.split(factor=0.4, align=False)
## LHS: Parameter Names
col = split.column()
col.alignment = 'RIGHT'
col.label(text='σ:')
## RHS: Parameter Units
col = split.column()
col.label(text='2ε₀/Δt')
####################
# - Output
####################
@events.computes_output_socket(
'BC',
props={'active_socket_set'},
input_sockets={
'Layers',
'σ Order',
'σ Range',
},
input_sockets_optional={
'σ Order': True,
'σ Range': True,
},
)
def compute_adiab_absorber_bound_cond(self, props, input_sockets) -> td.Absorber:
r"""Computes the adiabatic absorber boundary condition based on the active socket set.
- **Simple**: Use `tidy3d`'s default parameters for defining the absorber parameters (apart from number of layers).
- **Full**: Use the user-defined $\sigma$ parameters, specifically polynomial order and sim-relative min/max conductivity values.
"""
log.debug(
'%s: Computing "%s" Adiabatic Absorber Boundary Condition (Input Sockets = %s)',
self.sim_node_name,
props['active_socket_set'],
input_sockets,
)
# Simple PML
if props['active_socket_set'] == 'Simple':
return td.Absorber(num_layers=input_sockets['Layers'])
# Full PML
return td.Absorber(
num_layers=input_sockets['Layers'],
parameters=td.AbsorberParams(
sigma_order=input_sockets['σ Order'],
sigma_min=input_sockets['σ Range'][0],
sigma_max=input_sockets['σ Range'][1],
),
)
#################### ####################
# - Blender Registration # - Blender Registration
#################### ####################
BL_REGISTER = [] BL_REGISTER = [
BL_NODES = {} AdiabAbsorbBoundCondNode,
]
BL_NODES = {
ct.NodeType.AdiabAbsorbBoundCond: (ct.NodeCategory.MAXWELLSIM_BOUNDS_BOUNDCONDS)
}

View File

@ -147,11 +147,11 @@ class PMLBoundCondNode(base.MaxwellSimNode):
'α Range': True, 'α Range': True,
}, },
) )
def compute_pml_boundary_cond(self, props, input_sockets) -> td.BoundarySpec: def compute_pml_boundary_cond(self, props, input_sockets) -> td.PML:
r"""Computes the PML boundary condition based on the active socket set. r"""Computes the PML boundary condition based on the active socket set.
- **Simple**: Use `tidy3d`'s default parameters for defining the PML conductor. - **Simple**: Use `tidy3d`'s default parameters for defining the PML conductor (apart from number of layers).
- **Full**: Use the user-defined $\sigma$, $\kappa$, and $\alpha$ parameters, specifically polynomial order, and sim-relative min/max conductivity values. - **Full**: Use the user-defined $\sigma$, $\kappa$, and $\alpha$ parameters, specifically polynomial order and sim-relative min/max conductivity values.
""" """
log.debug( log.debug(
'%s: Computing "%s" PML Boundary Condition (Input Sockets = %s)', '%s: Computing "%s" PML Boundary Condition (Input Sockets = %s)',

View File

@ -272,13 +272,14 @@ def event_decorator(
## If there is a FlowInitializing, then the method would fail. ## If there is a FlowInitializing, then the method would fail.
## Therefore, propagate FlowInitializing if found. ## Therefore, propagate FlowInitializing if found.
if any( if any(
ct.FlowSignal.FlowInitializing in sockets.values() ct.FlowSignal.check_single(value, ct.FlowSignal.FlowInitializing)
for sockets in [ for sockets in [
method_kw_args.get('input_sockets', {}), method_kw_args.get('input_sockets', {}),
method_kw_args.get('loose_input_sockets', {}), method_kw_args.get('loose_input_sockets', {}),
method_kw_args.get('output_sockets', {}), method_kw_args.get('output_sockets', {}),
method_kw_args.get('loose_output_sockets', {}), method_kw_args.get('loose_output_sockets', {}),
] ]
for value in sockets.values()
): ):
return ct.FlowSignal.FlowInitializing return ct.FlowSignal.FlowInitializing