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',
|
||||
# Bounds/
|
||||
NC.MAXWELLSIM_BOUNDS: 'Bounds',
|
||||
NC.MAXWELLSIM_BOUNDS_BOUNDCONDS: 'Bound Conds',
|
||||
NC.MAXWELLSIM_BOUNDS_BOUNDCONDS: 'Conds',
|
||||
# Monitors/
|
||||
NC.MAXWELLSIM_MONITORS: 'Monitors',
|
||||
NC.MAXWELLSIM_MONITORS_PROJECTED: 'Projected',
|
||||
|
|
|
@ -91,7 +91,7 @@ class NodeType(blender_type_enum.BlenderTypeEnum):
|
|||
## Bounds / Bound Conds
|
||||
PMLBoundCond = enum.auto()
|
||||
BlochBoundCond = enum.auto()
|
||||
AbsorbingBoundCond = enum.auto()
|
||||
AdiabAbsorbBoundCond = enum.auto()
|
||||
|
||||
# Monitors
|
||||
EHFieldMonitor = enum.auto()
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
from . import (
|
||||
#absorbing_bound_cond,
|
||||
#bloch_bound_cond,
|
||||
pml_bound_cond,
|
||||
absorbing_bound_cond,
|
||||
# bloch_bound_cond,
|
||||
pml_bound_cond,
|
||||
)
|
||||
|
||||
BL_REGISTER = [
|
||||
*pml_bound_cond.BL_REGISTER,
|
||||
#*bloch_bound_cond.BL_REGISTER,
|
||||
#*absorbing_bound_cond.BL_REGISTER,
|
||||
# *bloch_bound_cond.BL_REGISTER,
|
||||
*absorbing_bound_cond.BL_REGISTER,
|
||||
]
|
||||
BL_NODES = {
|
||||
**pml_bound_cond.BL_NODES,
|
||||
#**bloch_bound_cond.BL_NODES,
|
||||
#**absorbing_bound_cond.BL_NODES,
|
||||
# **bloch_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
|
||||
####################
|
||||
BL_REGISTER = []
|
||||
BL_NODES = {}
|
||||
BL_REGISTER = [
|
||||
AdiabAbsorbBoundCondNode,
|
||||
]
|
||||
BL_NODES = {
|
||||
ct.NodeType.AdiabAbsorbBoundCond: (ct.NodeCategory.MAXWELLSIM_BOUNDS_BOUNDCONDS)
|
||||
}
|
||||
|
|
|
@ -147,11 +147,11 @@ class PMLBoundCondNode(base.MaxwellSimNode):
|
|||
'α 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.
|
||||
|
||||
- **Simple**: Use `tidy3d`'s default parameters for defining the PML conductor.
|
||||
- **Full**: Use the user-defined $\sigma$, $\kappa$, and $\alpha$ parameters, specifically polynomial order, and sim-relative min/max conductivity values.
|
||||
- **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.
|
||||
"""
|
||||
log.debug(
|
||||
'%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.
|
||||
## Therefore, propagate FlowInitializing if found.
|
||||
if any(
|
||||
ct.FlowSignal.FlowInitializing in sockets.values()
|
||||
ct.FlowSignal.check_single(value, ct.FlowSignal.FlowInitializing)
|
||||
for sockets in [
|
||||
method_kw_args.get('input_sockets', {}),
|
||||
method_kw_args.get('loose_input_sockets', {}),
|
||||
method_kw_args.get('output_sockets', {}),
|
||||
method_kw_args.get('loose_output_sockets', {}),
|
||||
]
|
||||
for value in sockets.values()
|
||||
):
|
||||
return ct.FlowSignal.FlowInitializing
|
||||
|
||||
|
|
Loading…
Reference in New Issue