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
parent
f60b736584
commit
2f42c9d91b
|
@ -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',
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from . import (
|
from . import (
|
||||||
#absorbing_bound_cond,
|
absorbing_bound_cond,
|
||||||
# bloch_bound_cond,
|
# bloch_bound_cond,
|
||||||
pml_bound_cond,
|
pml_bound_cond,
|
||||||
)
|
)
|
||||||
|
@ -7,10 +7,10 @@ from . import (
|
||||||
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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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)',
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue