feat: mode solving / thesis demo ready

main
Sofus Albert Høgsbro Rose 2024-09-05 17:20:46 +02:00
parent 81a71b2c47
commit 4fc0528f6e
Signed by: so-rose
GPG Key ID: AD901CB0F3701434
27 changed files with 984 additions and 147 deletions

View File

@ -6,6 +6,8 @@
# features: [] # features: []
# all-features: false # all-features: false
# with-sources: false # with-sources: false
# generate-hashes: false
# universal: false
absl-py==2.1.0 absl-py==2.1.0
# via chex # via chex
@ -138,6 +140,7 @@ numpy==1.24.3
# via opt-einsum # via opt-einsum
# via optax # via optax
# via orbax-checkpoint # via orbax-checkpoint
# via pandas
# via patsy # via patsy
# via pydantic-tensor # via pydantic-tensor
# via scipy # via scipy

View File

@ -6,6 +6,8 @@
# features: [] # features: []
# all-features: false # all-features: false
# with-sources: false # with-sources: false
# generate-hashes: false
# universal: false
absl-py==2.1.0 absl-py==2.1.0
# via chex # via chex
@ -114,6 +116,7 @@ numpy==1.24.3
# via opt-einsum # via opt-einsum
# via optax # via optax
# via orbax-checkpoint # via orbax-checkpoint
# via pandas
# via patsy # via patsy
# via pydantic-tensor # via pydantic-tensor
# via scipy # via scipy

View File

@ -57,3 +57,7 @@ class OperatorType(enum.StrEnum):
NodeReleaseUploadedTask = enum.auto() NodeReleaseUploadedTask = enum.auto()
NodeRunSimulation = enum.auto() NodeRunSimulation = enum.auto()
NodeReloadTrackedTask = enum.auto() NodeReloadTrackedTask = enum.auto()
# Node: ModeSolver
NodeSolveModes = enum.auto()
NodeReleaseSolvedModes = enum.auto()

View File

@ -197,7 +197,7 @@ class FlowKind(enum.StrEnum):
): ):
"""Perform unit-system scaling per-`FlowKind`.""" """Perform unit-system scaling per-`FlowKind`."""
match self: match self:
case FlowKind.Value if isinstance(spux.SympyType): case FlowKind.Value if isinstance(flow, spux.SympyType):
return spux.scale_to_unit_system( return spux.scale_to_unit_system(
flow, flow,
unit_system, unit_system,

View File

@ -17,7 +17,6 @@
import enum import enum
import functools import functools
import typing as typ import typing as typ
from types import MappingProxyType
import jax.numpy as jnp import jax.numpy as jnp
import jaxtyping as jtyp import jaxtyping as jtyp
@ -294,8 +293,8 @@ class RangeFlow(pyd.BaseModel):
and array.is_sorted and array.is_sorted
): ):
return RangeFlow( return RangeFlow(
start=sp.S(array.values[0]), start=sp.S(array.values.item(0)),
stop=sp.S(array.values[-1]), stop=sp.S(array.values.item(-1)),
steps=len(array.values), steps=len(array.values),
unit=array.unit, unit=array.unit,
) )
@ -338,7 +337,7 @@ class RangeFlow(pyd.BaseModel):
@method_lru(maxsize=16) @method_lru(maxsize=16)
def nearest_idx_of(self, value: spux.SympyType, require_sorted: bool = True) -> int: def nearest_idx_of(self, value: spux.SympyType, require_sorted: bool = True) -> int:
raise NotImplementedError return (value - self.start) / self.realize_step_size()
@functools.cached_property @functools.cached_property
def bound_fourier_transform(self): def bound_fourier_transform(self):
@ -632,6 +631,9 @@ class RangeFlow(pyd.BaseModel):
symbols=self.symbols, symbols=self.symbols,
) )
if isinstance(subscript, int) and self.scaling == ScalingMode.Lin:
return self.start + subscript * self.realize_step_size()
raise NotImplementedError raise NotImplementedError
#################### ####################

View File

@ -125,7 +125,8 @@ class ParamsFlow(pyd.BaseModel):
return [ return [
sp.lambdify( sp.lambdify(
self.all_sorted_sp_symbols, self.all_sorted_sp_symbols,
target_sym.conform(func_arg, strip_unit=True), spux.strip_unit_system(func_arg), ## TODO: THIS IS A WORKAROUND
# target_sym.conform(func_arg, strip_unit=True),
'jax', 'jax',
) )
for func_arg, target_sym in zip(self.func_args, arg_targets, strict=True) for func_arg, target_sym in zip(self.func_args, arg_targets, strict=True)

View File

@ -49,6 +49,7 @@ SOCKET_COLORS = {
ST.MaxwellSimGrid: (0.5, 0.4, 0.3, 1.0), # Dark Gold ST.MaxwellSimGrid: (0.5, 0.4, 0.3, 1.0), # Dark Gold
ST.MaxwellSimGridAxis: (0.4, 0.3, 0.25, 1.0), # Darkest Gold ST.MaxwellSimGridAxis: (0.4, 0.3, 0.25, 1.0), # Darkest Gold
ST.MaxwellSimDomain: (0.4, 0.3, 0.25, 1.0), # Darkest Gold ST.MaxwellSimDomain: (0.4, 0.3, 0.25, 1.0), # Darkest Gold
ST.MaxwellMode: (0.5, 0.3, 0.25, 1.0),
# Tidy3D # Tidy3D
ST.Tidy3DCloudTask: (0.4, 0.3, 0.25, 1.0), # Darkest Gold ST.Tidy3DCloudTask: (0.4, 0.3, 0.25, 1.0), # Darkest Gold
} }

View File

@ -60,6 +60,8 @@ class SocketType(blender_type_enum.BlenderTypeEnum):
MaxwellSimGrid = enum.auto() MaxwellSimGrid = enum.auto()
MaxwellSimGridAxis = enum.auto() MaxwellSimGridAxis = enum.auto()
MaxwellMode = enum.auto()
# Tidy3D # Tidy3D
Tidy3DCloudTask = enum.auto() Tidy3DCloudTask = enum.auto()

View File

@ -197,7 +197,7 @@ class FilterOperation(enum.StrEnum):
return [dim for dim in info.dims if not info.has_idx_labels(dim)] return [dim for dim in info.dims if not info.has_idx_labels(dim)]
case FO.SliceIdx: case FO.SliceIdx:
return [dim for dim in info.dims if not info.has_idx_labels(dim)] return list(info.dims)
# Pin # Pin
case FO.PinLen1: case FO.PinLen1:

View File

@ -47,48 +47,107 @@ FS = ct.FlowSignal
#################### ####################
# - Monitor Labelling # - Monitor Labelling
#################### ####################
def valid_monitor_variants(monitor_type: str) -> list[str]:
"""Deduce the valid monitor variants."""
match monitor_type:
case (
'Field'
| 'FieldTime'
| 'FieldProjectionAngle'
| 'FieldProjectionKSpace'
| 'Diffraction'
):
return ['E', 'H']
case 'Mode' | 'ModeSolver':
return ['E', 'H', 'n_complex', 'n_eff', 'k_eff']
case 'Flux' | 'FluxTime':
return ['flux']
case 'Permittivity':
return ['eps']
def monitor_variant_symbol(monitor_variant: str) -> sim_symbols.SimSymbol: # noqa: PLR0911
"""Deduce the correct output symbol based on the monitor variant."""
match monitor_variant:
case 'E':
return sim_symbols.field_e(spu.volt / spu.micrometer)
case 'H':
return sim_symbols.field_h(spu.ampere / spu.micrometer)
case 'n_complex':
return sim_symbols.rel_eps(None)
case 'n_eff':
return sim_symbols.rel_eps_re(None)
case 'k_eff':
return sim_symbols.rel_eps_im(None)
case 'flux':
return sim_symbols.flux(spu.watt)
case 'eps':
return sim_symbols.rel_eps(None)
def valid_monitor_attrs( def valid_monitor_attrs(
example_sim_data: td.SimulationData, monitor_name: str example_sim_data: td.SimulationData, monitor_name: str, monitor_variant: str
) -> tuple[str, ...]: ) -> tuple[str, ...]:
"""Retrieve the valid attributes of `sim_data.monitor_data' from a valid `sim_data` of type `td.SimulationData`. """Retrieve the valid attributes of `sim_data.monitor_data' from a valid `sim_data` of type `td.SimulationData`.
Parameters: Parameters:
monitor_type: The name of the monitor type, with the 'Data' prefix removed. example_sim_data: A representative simulation data from a batch.
In particular, the shape of its monitor data should be representative.
monitor_name: The name of the monitor, whose attributes should be checked for validity.
monitor_variant: The variant of the monitor's output to extract attributes of.
""" """
monitor_data = example_sim_data.monitor_data[monitor_name] monitor_data = example_sim_data.monitor_data[monitor_name]
monitor_type = monitor_data.type.removesuffix('Data') monitor_type = monitor_data.type.removesuffix('Data')
match monitor_type: match (monitor_variant, monitor_type):
case 'Field' | 'FieldTime' | 'Mode': # E: Electric Field
## TODO: flux, poynting, intensity case ('E', 'Field' | 'FieldTime' | 'Mode' | 'ModeSolver'):
return tuple( return tuple(
[ [
field_component field_component
for field_component in ['Ex', 'Ey', 'Ez', 'Hx', 'Hy', 'Hz'] for field_component in ['Ex', 'Ey', 'Ez']
if getattr(monitor_data, field_component, None) is not None if getattr(monitor_data, field_component, None) is not None
] ]
) )
case 'Permittivity': # H: Electric Field
return ('eps_xx', 'eps_yy', 'eps_zz') case ('H', 'Field' | 'FieldTime' | 'Mode' | 'ModeSolver'):
return tuple(
[
field_component
for field_component in ['Hx', 'Hy', 'Hz']
if getattr(monitor_data, field_component, None) is not None
]
)
case 'Flux' | 'FluxTime': # n_complex | n_eff | k_eff: Effective Complex IOR
case ('n_complex', 'Mode' | 'ModeSolver'):
return ('n_complex',)
case ('n_eff', 'Mode' | 'ModeSolver'):
return ('n_eff',)
case ('k_eff', 'Mode' | 'ModeSolver'):
return ('k_eff',)
case ('flux', 'Flux' | 'FluxTime'):
return ('flux',) return ('flux',)
case ( case ('eps', 'Permittivity'):
'FieldProjectionAngle' return ('eps_xx', 'eps_yy', 'eps_zz')
| 'FieldProjectionCartesian'
| 'FieldProjectionKSpace' # case (
| 'Diffraction' # 'FieldProjectionAngle'
): # | 'FieldProjectionCartesian'
return ( # | 'FieldProjectionKSpace'
'Er', # | 'Diffraction'
'Etheta', # ):
'Ephi', # return (
'Hr', # 'Er',
'Htheta', # 'Etheta',
'Hphi', # 'Ephi',
) # 'Hr',
# 'Htheta',
# 'Hphi',
# )
raise TypeError raise TypeError
@ -98,7 +157,8 @@ def valid_monitor_attrs(
#################### ####################
MONITOR_SYMBOLS: dict[str, sim_symbols.SimSymbol] = { MONITOR_SYMBOLS: dict[str, sim_symbols.SimSymbol] = {
# Field Label # Field Label
'EH*': sim_symbols.sim_axis_idx(None), 'sim_axis': sim_symbols.sim_axis_idx(None),
'mode_index': sim_symbols.mode_idx(None),
# Cartesian # Cartesian
'x': sim_symbols.space_x(spu.micrometer), 'x': sim_symbols.space_x(spu.micrometer),
'y': sim_symbols.space_y(spu.micrometer), 'y': sim_symbols.space_y(spu.micrometer),
@ -126,6 +186,8 @@ MONITOR_SYMBOLS: dict[str, sim_symbols.SimSymbol] = {
def _mk_idx_array(xarr: xarray.DataArray, axis: str) -> ct.RangeFlow | ct.ArrayFlow: def _mk_idx_array(xarr: xarray.DataArray, axis: str) -> ct.RangeFlow | ct.ArrayFlow:
if axis == 'mode_index':
return [str(i) for i in xarr.get_index(axis).values]
return ct.RangeFlow.try_from_array( return ct.RangeFlow.try_from_array(
ct.ArrayFlow( ct.ArrayFlow(
jax_bytes=xarr.get_index(axis).values, jax_bytes=xarr.get_index(axis).values,
@ -135,121 +197,129 @@ def _mk_idx_array(xarr: xarray.DataArray, axis: str) -> ct.RangeFlow | ct.ArrayF
) )
def output_symbol_by_type(monitor_type: str) -> sim_symbols.SimSymbol:
match monitor_type:
case 'Field' | 'FieldProjectionCartesian' | 'Permittivity' | 'Mode':
return MONITOR_SYMBOLS['field_e']
case 'FieldTime':
return MONITOR_SYMBOLS['field']
case 'Flux':
return MONITOR_SYMBOLS['flux']
case 'FluxTime':
return MONITOR_SYMBOLS['flux']
case 'FieldProjectionAngle':
return MONITOR_SYMBOLS['field']
case 'FieldProjectionKSpace':
return MONITOR_SYMBOLS['field']
case 'Diffraction':
return MONITOR_SYMBOLS['field']
return None
def _extract_info( def _extract_info(
example_xarr: xarray.DataArray, example_xarr: xarray.DataArray,
monitor_type: str, monitor_type: str,
monitor_variant: str,
monitor_attrs: tuple[str, ...], monitor_attrs: tuple[str, ...],
batch_dims: dict[sim_symbols.SimSymbol, ct.RangeFlow | ct.ArrayFlow], batch_dims: dict[sim_symbols.SimSymbol, ct.RangeFlow | ct.ArrayFlow],
) -> ct.InfoFlow | None: ) -> ct.InfoFlow | None:
log.debug([monitor_type, monitor_attrs, batch_dims])
mk_idx_array = functools.partial(_mk_idx_array, example_xarr) mk_idx_array = functools.partial(_mk_idx_array, example_xarr)
match monitor_type: match (monitor_variant, monitor_type):
case 'Field' | 'FieldProjectionCartesian' | 'Permittivity' | 'Mode': case ('E' | 'H', 'Field'):
return ct.InfoFlow( return ct.InfoFlow(
dims=batch_dims dims=batch_dims
| { | {
MONITOR_SYMBOLS['EH*']: monitor_attrs, MONITOR_SYMBOLS['sim_axis']: monitor_attrs,
MONITOR_SYMBOLS['x']: mk_idx_array('x'), MONITOR_SYMBOLS['x']: mk_idx_array('x'),
MONITOR_SYMBOLS['y']: mk_idx_array('y'), MONITOR_SYMBOLS['y']: mk_idx_array('y'),
MONITOR_SYMBOLS['z']: mk_idx_array('z'), MONITOR_SYMBOLS['z']: mk_idx_array('z'),
MONITOR_SYMBOLS['f']: mk_idx_array('f'), MONITOR_SYMBOLS['f']: mk_idx_array('f'),
}, },
output=MONITOR_SYMBOLS['field_e'], output=monitor_variant_symbol(monitor_variant),
) )
case 'FieldTime': case ('E' | 'H', 'FieldTime'):
return ct.InfoFlow( return ct.InfoFlow(
dims=batch_dims dims=batch_dims
| { | {
MONITOR_SYMBOLS['EH*']: monitor_attrs, MONITOR_SYMBOLS['sim_axis']: monitor_attrs,
MONITOR_SYMBOLS['x']: mk_idx_array('x'), MONITOR_SYMBOLS['x']: mk_idx_array('x'),
MONITOR_SYMBOLS['y']: mk_idx_array('y'), MONITOR_SYMBOLS['y']: mk_idx_array('y'),
MONITOR_SYMBOLS['z']: mk_idx_array('z'), MONITOR_SYMBOLS['z']: mk_idx_array('z'),
MONITOR_SYMBOLS['t']: mk_idx_array('t'), MONITOR_SYMBOLS['t']: mk_idx_array('t'),
}, },
output=MONITOR_SYMBOLS['field'], output=monitor_variant_symbol(monitor_variant),
) )
case 'Flux': case ('E' | 'H', 'Mode' | 'ModeSolver'):
return ct.InfoFlow(
dims=batch_dims
| {
MONITOR_SYMBOLS['sim_axis']: monitor_attrs,
MONITOR_SYMBOLS['x']: mk_idx_array('x'),
MONITOR_SYMBOLS['y']: mk_idx_array('y'),
MONITOR_SYMBOLS['z']: mk_idx_array('z'),
MONITOR_SYMBOLS['f']: mk_idx_array('f'),
MONITOR_SYMBOLS['mode_index']: mk_idx_array('mode_index'),
},
output=monitor_variant_symbol(monitor_variant),
)
case ('n_complex', 'Mode' | 'ModeSolver'):
return ct.InfoFlow(
dims=batch_dims
| {
MONITOR_SYMBOLS['f']: mk_idx_array('f'),
MONITOR_SYMBOLS['mode_index']: mk_idx_array('mode_index'),
},
output=monitor_variant_symbol(monitor_variant),
)
case ('n_eff' | 'k_eff', 'Mode' | 'ModeSolver'):
return ct.InfoFlow(
dims=batch_dims
| {
MONITOR_SYMBOLS['f']: mk_idx_array('f'),
MONITOR_SYMBOLS['mode_index']: mk_idx_array('mode_index'),
},
output=monitor_variant_symbol(monitor_variant),
)
case ('flux', 'Flux'):
return ct.InfoFlow( return ct.InfoFlow(
dims=batch_dims dims=batch_dims
| { | {
MONITOR_SYMBOLS['f']: mk_idx_array('f'), MONITOR_SYMBOLS['f']: mk_idx_array('f'),
}, },
output=MONITOR_SYMBOLS['flux'], output=monitor_variant_symbol(monitor_variant),
) )
case 'FluxTime': case ('flux', 'FluxTime'):
return ct.InfoFlow( return ct.InfoFlow(
dims=batch_dims dims=batch_dims
| { | {
MONITOR_SYMBOLS['t']: mk_idx_array('t'), MONITOR_SYMBOLS['t']: mk_idx_array('t'),
}, },
output=MONITOR_SYMBOLS['flux'], output=monitor_variant_symbol(monitor_variant),
) )
case 'FieldProjectionAngle': case ('E' | 'H', 'FieldProjectionAngle'):
return ct.InfoFlow( return ct.InfoFlow(
dims=batch_dims dims=batch_dims
| { | {
MONITOR_SYMBOLS['EH*']: monitor_attrs, MONITOR_SYMBOLS['sim_axis']: monitor_attrs,
MONITOR_SYMBOLS['r']: mk_idx_array('r'), MONITOR_SYMBOLS['r']: mk_idx_array('r'),
MONITOR_SYMBOLS['theta']: mk_idx_array('theta'), MONITOR_SYMBOLS['theta']: mk_idx_array('theta'),
MONITOR_SYMBOLS['phi']: mk_idx_array('phi'), MONITOR_SYMBOLS['phi']: mk_idx_array('phi'),
MONITOR_SYMBOLS['f']: mk_idx_array('f'), MONITOR_SYMBOLS['f']: mk_idx_array('f'),
}, },
output=MONITOR_SYMBOLS['field'], output=monitor_variant_symbol(monitor_variant),
) )
case 'FieldProjectionKSpace': case ('E' | 'H', 'FieldProjectionKSpace'):
return ct.InfoFlow( return ct.InfoFlow(
dims=batch_dims dims=batch_dims
| { | {
MONITOR_SYMBOLS['EH*']: monitor_attrs, MONITOR_SYMBOLS['sim_axis']: monitor_attrs,
MONITOR_SYMBOLS['ux']: mk_idx_array('ux'), MONITOR_SYMBOLS['ux']: mk_idx_array('ux'),
MONITOR_SYMBOLS['uy']: mk_idx_array('uy'), MONITOR_SYMBOLS['uy']: mk_idx_array('uy'),
MONITOR_SYMBOLS['r']: mk_idx_array('r'), MONITOR_SYMBOLS['r']: mk_idx_array('r'),
MONITOR_SYMBOLS['f']: mk_idx_array('f'), MONITOR_SYMBOLS['f']: mk_idx_array('f'),
}, },
output=MONITOR_SYMBOLS['field'], output=monitor_variant_symbol(monitor_variant),
) )
case 'Diffraction': case ('E' | 'H', 'Diffraction'):
return ct.InfoFlow( return ct.InfoFlow(
dims=batch_dims dims=batch_dims
| { | {
MONITOR_SYMBOLS['EH*']: monitor_attrs, MONITOR_SYMBOLS['sim_axis']: monitor_attrs,
MONITOR_SYMBOLS['orders_x']: mk_idx_array('orders_x'), MONITOR_SYMBOLS['orders_x']: mk_idx_array('orders_x'),
MONITOR_SYMBOLS['orders_y']: mk_idx_array('orders_y'), MONITOR_SYMBOLS['orders_y']: mk_idx_array('orders_y'),
MONITOR_SYMBOLS['f']: mk_idx_array('f'), MONITOR_SYMBOLS['f']: mk_idx_array('f'),
}, },
output=MONITOR_SYMBOLS['field'], output=monitor_variant_symbol(monitor_variant),
) )
raise TypeError raise TypeError
@ -268,7 +338,9 @@ def extract_monitor_xarrs(
def extract_info( def extract_info(
monitor_datas: dict[RealizedSymsVals, typ.Any], monitor_attrs: tuple[str, ...] monitor_datas: dict[RealizedSymsVals, typ.Any],
monitor_attrs: tuple[str, ...],
monitor_variant: str,
) -> dict[RealizedSymsVals, ct.InfoFlow]: ) -> dict[RealizedSymsVals, ct.InfoFlow]:
"""Extract an InfoFlow describing monitor data from a batch of simulations.""" """Extract an InfoFlow describing monitor data from a batch of simulations."""
# Extract Dimension from Batched Values # Extract Dimension from Batched Values
@ -318,6 +390,7 @@ def extract_info(
return _extract_info( return _extract_info(
example_xarr, example_xarr,
example_monitor_data.type.removesuffix('Data'), example_monitor_data.type.removesuffix('Data'),
monitor_variant,
monitor_attrs, monitor_attrs,
batch_dims, batch_dims,
) )
@ -489,6 +562,39 @@ class ExtractDataNode(base.MaxwellSimNode):
return [] return []
monitor_variant: enum.StrEnum = bl_cache.BLField(
enum_cb=lambda self, _: self.search_monitor_variants(),
cb_depends_on={'monitor_name', 'monitor_types'},
)
def search_monitor_variants(self) -> list[ct.BLEnumElement]:
"""Compute valid values for `self.monitor_attr`, for a dynamic `EnumProperty`.
Notes:
Should be reset (via `self.monitor_attr`) with (after) `self.sim_data_monitor_nametypes`, `self.monitor_data_attrs`, and (implicitly) `self.monitor_type`.
See `bl_cache.BLField` for more on dynamic `EnumProperty`.
Returns:
Valid `self.monitor_attr` in a format compatible with dynamic `EnumProperty`.
"""
if self.monitor_name is not None and self.monitor_types is not None:
monitor_type = self.monitor_types.get(self.monitor_name)
if monitor_type is not None:
return [
(
monitor_variant,
monitor_variant,
monitor_variant,
'',
i,
)
for i, monitor_variant in enumerate(
valid_monitor_variants(monitor_type)
)
]
return []
#################### ####################
# - Properties: Monitor Information # - Properties: Monitor Information
#################### ####################
@ -502,11 +608,19 @@ class ExtractDataNode(base.MaxwellSimNode):
} }
return None return None
@bl_cache.cached_bl_property(depends_on={'example_sim_data', 'monitor_name'}) @bl_cache.cached_bl_property(
depends_on={'example_sim_data', 'monitor_name', 'monitor_variant'}
)
def valid_monitor_attrs(self) -> SimDataArrayInfo | None: def valid_monitor_attrs(self) -> SimDataArrayInfo | None:
"""Valid attributes of the monitor, from the example sim data under the presumption that the entire batch shares the same attribute validity.""" """Valid attributes of the monitor, from the example sim data under the presumption that the entire batch shares the same attribute validity."""
if self.example_sim_data is not None and self.monitor_name is not None: if (
return valid_monitor_attrs(self.example_sim_data, self.monitor_name) self.example_sim_data is not None
and self.monitor_name is not None
and self.monitor_variant is not None
):
return valid_monitor_attrs(
self.example_sim_data, self.monitor_name, self.monitor_variant
)
return None return None
#################### ####################
@ -530,6 +644,7 @@ class ExtractDataNode(base.MaxwellSimNode):
col: UI target for drawing. col: UI target for drawing.
""" """
col.prop(self, self.blfields['monitor_name'], text='') col.prop(self, self.blfields['monitor_name'], text='')
col.prop(self, self.blfields['monitor_variant'], text='')
#################### ####################
# - FlowKind.Func # - FlowKind.Func
@ -538,21 +653,19 @@ class ExtractDataNode(base.MaxwellSimNode):
'Expr', 'Expr',
kind=FK.Func, kind=FK.Func,
# Loaded # Loaded
props={'monitor_datas', 'valid_monitor_attrs'}, props={'monitor_datas', 'valid_monitor_attrs', 'monitor_variant'},
) )
def compute_extracted_data_func(self, props) -> ct.FuncFlow | FS: def compute_extracted_data_func(self, props) -> ct.FuncFlow | FS:
"""Aggregates the selected monitor's data across all batched symbolic realizations, into a single FuncFlow.""" """Aggregates the selected monitor's data across all batched symbolic realizations, into a single FuncFlow."""
monitor_datas = props['monitor_datas'] monitor_datas = props['monitor_datas']
valid_monitor_attrs = props['valid_monitor_attrs'] valid_monitor_attrs = props['valid_monitor_attrs']
monitor_variant = props['monitor_variant']
if monitor_datas is not None and valid_monitor_attrs is not None: if monitor_datas is not None and valid_monitor_attrs is not None:
monitor_datas_xarrs = extract_monitor_xarrs( monitor_datas_xarrs = extract_monitor_xarrs(
monitor_datas, valid_monitor_attrs monitor_datas, valid_monitor_attrs
) )
output_sym = monitor_variant_symbol(monitor_variant)
example_monitor_data = next(iter(monitor_datas.values()))
monitor_type = example_monitor_data.type.removesuffix('Data')
output_sym = output_symbol_by_type(monitor_type)
# Stack Inner Dimensions: components | * # Stack Inner Dimensions: components | *
## -> Each realization maps to exactly one xarray. ## -> Each realization maps to exactly one xarray.
@ -648,7 +761,7 @@ class ExtractDataNode(base.MaxwellSimNode):
'Expr', 'Expr',
kind=FK.Info, kind=FK.Info,
# Loaded # Loaded
props={'monitor_datas', 'valid_monitor_attrs'}, props={'monitor_datas', 'valid_monitor_attrs', 'monitor_variant'},
) )
def compute_extracted_data_info(self, props) -> ct.InfoFlow | FS: def compute_extracted_data_info(self, props) -> ct.InfoFlow | FS:
"""Declare `Data:Info` by manually selecting appropriate axes, units, etc. for each monitor type. """Declare `Data:Info` by manually selecting appropriate axes, units, etc. for each monitor type.
@ -658,9 +771,10 @@ class ExtractDataNode(base.MaxwellSimNode):
""" """
monitor_datas = props['monitor_datas'] monitor_datas = props['monitor_datas']
valid_monitor_attrs = props['valid_monitor_attrs'] valid_monitor_attrs = props['valid_monitor_attrs']
monitor_variant = props['monitor_variant']
if monitor_datas is not None and valid_monitor_attrs is not None: if monitor_datas is not None and valid_monitor_attrs is not None:
return extract_info(monitor_datas, valid_monitor_attrs) return extract_info(monitor_datas, valid_monitor_attrs, monitor_variant)
return FS.FlowPending return FS.FlowPending
#################### ####################

View File

@ -137,7 +137,7 @@ class Tidy3DFileImporterNode(base.MaxwellSimNode):
# - UI # - UI
#################### ####################
def draw_props(self, _: bpy.types.Context, col: bpy.types.UILayout): def draw_props(self, _: bpy.types.Context, col: bpy.types.UILayout):
col.prop(self, 'tidy3d_type', text='') col.prop(self, self.blfields['tidy3d_type'], text='')
#################### ####################
# - Event Methods: Setup Output Socket # - Event Methods: Setup Output Socket

View File

@ -79,7 +79,7 @@ class FDTDSimNode(base.MaxwellSimNode):
"""Definition of a complete FDTD simulation, including boundary conditions, domain, sources, structures, monitors, and other configuration.""" """Definition of a complete FDTD simulation, including boundary conditions, domain, sources, structures, monitors, and other configuration."""
node_type = ct.NodeType.FDTDSim node_type = ct.NodeType.FDTDSim
bl_label = 'FDTD Simulation' bl_label = 'Maxwell Simulation'
#################### ####################
# - Sockets # - Sockets
@ -158,7 +158,10 @@ class FDTDSimNode(base.MaxwellSimNode):
def min_wl(self) -> SimArrayInfo | None: def min_wl(self) -> SimArrayInfo | None:
"""The smallest wavelength that occurs in the simulation.""" """The smallest wavelength that occurs in the simulation."""
if self.sims is not None: if self.sims is not None:
return {k: sim.wvl_mat_min * spu.um for k, sim in self.sims.items()} return {
k: sim.wvl_mat_min * spu.um if sim.sources else None
for k, sim in self.sims.items()
}
return None return None
#################### ####################
@ -265,7 +268,7 @@ class FDTDSimNode(base.MaxwellSimNode):
for k, sim in self.sims.items(): # noqa: B007 for k, sim in self.sims.items(): # noqa: B007
try: try:
pass ## TODO: VERY slow, batch checking is infeasible pass ## TODO: VERY slow, batch checking is infeasible
# sim.validate_pre_upload(source_required=True) # sim.validate_pre_upload(source_required=False)
except td.exceptions.SetupError: except td.exceptions.SetupError:
validity[k] = False validity[k] = False
else: else:
@ -322,7 +325,14 @@ class FDTDSimNode(base.MaxwellSimNode):
('max t', spux.sp_to_str(self.time_range[syms_vals][1])), ('max t', spux.sp_to_str(self.time_range[syms_vals][1])),
('min f', spux.sp_to_str(self.freq_range[syms_vals][0])), ('min f', spux.sp_to_str(self.freq_range[syms_vals][0])),
('max f', spux.sp_to_str(self.freq_range[syms_vals][1])), ('max f', spux.sp_to_str(self.freq_range[syms_vals][1])),
('min λ', spux.sp_to_str(self.min_wl[syms_vals].n(2))), (
'min λ',
spux.sp_to_str(
self.min_wl[syms_vals].n(2)
if self.min_wl[syms_vals] is not None
else None
),
),
] ]
labels += [ labels += [

View File

@ -14,5 +14,367 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
BL_REGISTER = [] """Implements `ModeSolverNode`."""
BL_NODES = {}
import enum
import typing as typ
import bpy
import sympy as sp
import tidy3d as td
from blender_maxwell.utils import bl_cache, logger
from blender_maxwell.utils import sympy_extra as spux
from blender_maxwell.utils.frozendict import frozendict
from ... import contracts as ct
from ... import sockets
from .. import base, events
log = logger.get(__name__)
FK = ct.FlowKind
FS = ct.FlowSignal
MT = spux.MathType
PT = spux.PhysicalType
class ModePol(enum.StrEnum):
"""Polarization filter with which to arrange mode indices with."""
Arbitrary = enum.auto()
TE = enum.auto()
TM = enum.auto()
@property
def tidy3d_pol(self) -> str | None:
MP = ModePol
return {
MP.Arbitrary: None,
MP.TE: 'te',
MP.TM: 'tm',
}[self]
####################
# - UI
####################
@staticmethod
def to_name(value: typ.Self) -> str:
"""A human-readable UI-oriented name for a physical type."""
MP = ModePol
return {
MP.Arbitrary: 'Arbitrary',
MP.TE: 'TE',
MP.TM: 'TM',
}[value]
@staticmethod
def to_icon(_: typ.Self) -> str:
"""No icons."""
return ''
class RunModalSolve(bpy.types.Operator):
"""Run a modal solver on data provided to a `ModeSolverNode`."""
bl_idname = ct.OperatorType.NodeSolveModes
bl_label = 'Solve Modes'
bl_description = 'Solve for the eigenmodes of the simulation setup.'
@classmethod
def poll(cls, context):
"""Allow running when there are runnable tasks."""
return (
# Check FDTDSolverNode is Accessible
hasattr(context, 'node')
and hasattr(context.node, 'node_type')
and context.node.node_type == ct.NodeType.ModeSolver
# Check Task is Runnable
and not context.node.solved
and context.node.mode_solver is not None
)
def execute(self, context):
"""Run all uploaded, runnable tasks."""
node = context.node
node.trigger_event(ct.FlowEvent.EnableLock)
node.locked = False
try:
node.mode_solver.solve()
except: # noqa: E722
node.trigger_event(ct.FlowEvent.DisableLock)
self.report({'ERROR'}, 'Modal solution failed. Please check logs.')
return {'FINISHED'}
else:
node.solved = True
return {'FINISHED'}
class ReleaseSolvedModes(bpy.types.Operator):
"""Run a modal solver on data provided to a `ModeSolverNode`."""
bl_idname = ct.OperatorType.NodeReleaseSolvedModes
bl_label = 'Release Solved Modes'
bl_description = 'Release the solved eigenmodes.'
@classmethod
def poll(cls, context):
"""Allow running when there are runnable tasks."""
return (
# Check FDTDSolverNode is Accessible
hasattr(context, 'node')
and hasattr(context.node, 'node_type')
and context.node.node_type == ct.NodeType.ModeSolver
# Check Task is Runnable
and context.node.solved
and context.node.mode_solver is not None
)
def execute(self, context):
"""Reset the modal solver."""
node = context.node
node.solved = False
node.trigger_event(ct.FlowEvent.DisableLock)
node.mode_solver = bl_cache.Signal.InvalidateCache
return {'FINISHED'}
class ModeSolverNode(base.MaxwellSimNode):
"""Definition of a complete FDTD simulation, including boundary conditions, domain, sources, structures, monitors, and other configuration."""
node_type = ct.NodeType.ModeSolver
bl_label = 'Mode Solver'
####################
# - Sockets
####################
input_sockets: typ.ClassVar = {
'Sim': sockets.MaxwellFDTDSimSocketDef(active_kind=FK.Value),
'Center': sockets.ExprSocketDef(
size=spux.NumberSize1D.Vec3,
mathtype=MT.Real,
physical_type=PT.Length,
default_value=sp.ImmutableMatrix([0, 0, 0]),
),
'Plane Size': sockets.ExprSocketDef(
size=spux.NumberSize1D.Vec2,
mathtype=MT.Real,
physical_type=PT.Length,
default_value=sp.ImmutableMatrix([1, 1]),
),
'Freqs': sockets.ExprSocketDef(
active_kind=FK.Range,
physical_type=PT.Freq,
default_unit=spux.THz,
default_min=374.7406, ## 800nm
default_max=1498.962, ## 200nm
default_steps=100,
),
'# Modes': sockets.ExprSocketDef(
mathtype=MT.Integer,
default_value=3,
abs_min=1,
),
'Guess neff': sockets.ExprSocketDef(
mathtype=MT.Real,
default_value=2.0,
),
## TODO: Spherical, bend, mode tracking, group index, extra PML
}
output_sockets: typ.ClassVar = {
'Solved Modes': sockets.MaxwellModeSocketDef(),
'Sim Data': sockets.MaxwellFDTDSimDataSocketDef(),
}
####################
# - Properties: UI
####################
injection_axis: ct.SimSpaceAxis = bl_cache.BLField(ct.SimSpaceAxis.X)
injection_direction: ct.SimAxisDir = bl_cache.BLField(ct.SimAxisDir.Plus)
mode_pol: ModePol = bl_cache.BLField(ModePol.Arbitrary)
solved: bool = bl_cache.BLField(False)
####################
# - Properties: Mode Solver
####################
@events.on_value_changed(
socket_name={
'Sim': {FK.Func, FK.Params},
'Center': {FK.Func, FK.Params},
'Plane Size': {FK.Func, FK.Params},
'Freqs': {FK.Func, FK.Params},
'# Modes': {FK.Func, FK.Params},
'Guess neff': {FK.Func, FK.Params},
},
)
def on_input_changed(self) -> None:
"""Recomputes the mode solver in response to inputs."""
self.mode_solver = bl_cache.Signal.InvalidateCache
@bl_cache.cached_bl_property(
depends_on={'injection_axis', 'injection_direction', 'mode_pol'}
)
def mode_solver(self) -> td.plugins.mode.ModeSolver | None:
sim = self._compute_input('Sim', kind=FK.Value)
center = events.realize_known(
frozendict(
{
kind: self._compute_input(
'Center', kind=kind, unit_system=ct.UNITS_TIDY3D
)
for kind in [FK.Func, FK.Params]
}
),
freeze=True,
)
size_2d = events.realize_known(
frozendict(
{
kind: self._compute_input(
'Plane Size', kind=kind, unit_system=ct.UNITS_TIDY3D
)
for kind in [FK.Func, FK.Params]
}
),
freeze=True,
)
freqs = events.realize_known(
frozendict(
{
kind: self._compute_input(
'Freqs', kind=kind, unit_system=ct.UNITS_TIDY3D
)
for kind in [FK.Func, FK.Params]
}
),
freeze=True,
)
num_modes = events.realize_known(
frozendict(
{
kind: self._compute_input('# Modes', kind=kind)
for kind in [FK.Func, FK.Params]
}
),
freeze=True,
)
guess_neff = events.realize_known(
frozendict(
{
kind: self._compute_input('Guess neff', kind=kind)
for kind in [FK.Func, FK.Params]
}
),
freeze=True,
)
if not FS.check(sim) and all(
flow is not None
for flow in [
center,
size_2d,
num_modes,
guess_neff,
freqs,
]
):
mode_pol = self.mode_pol
injection_direction = self.injection_direction
_size_2d = sp.flatten(size_2d)
size = {
ct.SimSpaceAxis.X: (0, *_size_2d),
ct.SimSpaceAxis.Y: (_size_2d[0], 0, _size_2d[1]),
ct.SimSpaceAxis.Z: (*_size_2d, 0),
}[self.injection_axis]
return td.plugins.mode.ModeSolver(
simulation=sim,
plane=td.Box(center=sp.flatten(center), size=size),
mode_spec=td.ModeSpec(
num_modes=num_modes,
target_neff=guess_neff,
filter_pol=mode_pol.tidy3d_pol,
),
freqs=freqs,
direction=injection_direction.plus_or_minus,
)
return None
####################
# - UI
####################
def draw_operators(self, _: bpy.types.Context, layout: bpy.types.UILayout):
row = layout.row(align=True)
row.operator(
ct.OperatorType.NodeSolveModes,
text='Solve',
)
if self.solved:
row.operator(
ct.OperatorType.NodeReleaseSolvedModes,
icon='LOOP_BACK',
text='',
)
def draw_props(self, _: bpy.types.Context, layout: bpy.types.UILayout):
"""Present choices of injection axis, direction, and polarization filter."""
row = layout.row(align=True)
row.alignment = 'CENTER'
row.label(text='Injection')
layout.prop(self, self.blfields['injection_axis'], expand=True)
layout.prop(self, self.blfields['injection_direction'], expand=True)
row = layout.row(align=True)
row.alignment = 'CENTER'
row.label(text='Pol Filter')
layout.prop(self, self.blfields['mode_pol'], text='')
####################
# - FlowKind.Value: Solved Modes
####################
@events.computes_output_socket(
'Solved Modes',
kind=FK.Value,
# Loaded
props={'mode_solver', 'solved'},
)
def compute_solved_modes(self, props) -> td.ModeSolverData | FS:
mode_solver = props['mode_solver']
solved = props['solved']
if mode_solver is not None and solved:
return mode_solver.data
return FS.FlowPending
####################
# - FlowKind.Value: Sim Data
####################
@events.computes_output_socket(
'Sim Data',
kind=FK.Value,
# Loaded
props={'mode_solver', 'solved'},
)
def compute_sim_data_value(self, props) -> td.SimulationData | FS:
"""Compute the particular value of the simulation domain from strictly non-symbolic inputs."""
mode_solver = props['mode_solver']
solved = props['solved']
if mode_solver is not None and solved:
return mode_solver.sim_data
return FS.FlowPending
####################
# - Blender Registration
####################
BL_REGISTER = [
RunModalSolve,
ReleaseSolvedModes,
ModeSolverNode,
]
BL_NODES = {ct.NodeType.ModeSolver: (ct.NodeCategory.MAXWELLSIM_SOLVERS)}

View File

@ -0,0 +1,128 @@
# blender_maxwell
# Copyright (C) 2024 blender_maxwell Project Contributors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""Implements `ModeSolverNode`."""
import typing as typ
import tidy3d as td
from blender_maxwell.utils import bl_cache, logger
from blender_maxwell.utils import sympy_extra as spux
from blender_maxwell.utils.frozendict import frozendict
from ... import contracts as ct
from ... import sockets
from .. import base, events
log = logger.get(__name__)
FK = ct.FlowKind
FS = ct.FlowSignal
MT = spux.MathType
PT = spux.PhysicalType
class ModeSolverNode(base.MaxwellSimNode):
"""Definition of a complete FDTD simulation, including boundary conditions, domain, sources, structures, monitors, and other configuration."""
node_type = ct.NodeType.ModeSolver
bl_label = 'Mode Solver'
use_sim_node_name = True
####################
# - Sockets
####################
input_sockets: typ.ClassVar = {
'Solved Modes': sockets.MaxwellModeSocketDef(),
'Inj Mode': sockets.ExprSocketDef(
mathtype=MT.Integer,
abs_min=0,
default_value=0,
),
'Guess neff': sockets.ExprSocketDef(
mathtype=MT.Integer,
abs_min=1,
),
## TODO: Spherical, bend, mode tracking, group index
}
output_sockets: typ.ClassVar = {
'Angled Source': sockets.MaxwellSourceSocketDef(active_kind=FK.Value),
}
####################
# - Properties: UI
####################
injection_axis: ct.SimSpaceAxis = bl_cache.BLField(ct.SimSpaceAxis.X)
injection_direction: ct.SimAxisDir = bl_cache.BLField(ct.SimAxisDir.Plus)
####################
# - FlowKind.Value
####################
@events.computes_output_socket(
'Sim',
kind=FK.Value,
# Loaded
props={'active_socket_set'},
outscks_kinds={'Sim': {FK.Func, FK.Params}},
all_loose_input_sockets=True,
loose_input_sockets_kind={FK.Func, FK.Params},
)
def compute_value(
self, props, loose_input_sockets, output_sockets
) -> td.Simulation | FS:
"""Compute the particular value of the simulation domain from strictly non-symbolic inputs."""
func = output_sockets['Sim'][FK.Func]
params = output_sockets['Sim'][FK.Params]
has_func = not FS.check(func)
has_params = not FS.check(params)
active_socket_set = props['active_socket_set']
if has_func and has_params and active_socket_set == 'Single':
symbol_values = {
sym: events.realize_known(loose_input_sockets[sym.name])
for sym in params.sorted_symbols
}
return func.realize(
params,
symbol_values=frozendict(
{
sym: events.realize_known(loose_input_sockets[sym.name])
for sym in params.sorted_symbols
}
),
).updated_copy(
attrs=dict(
ct.SimMetadata(
realizations=ct.SimRealizations(
syms=tuple(symbol_values.keys()),
vals=tuple(symbol_values.values()),
)
)
)
)
return FS.FlowPending
####################
# - Blender Registration
####################
BL_REGISTER = [
ModeSolverNode,
]
BL_NODES = {ct.NodeType.ModeSolver: (ct.NodeCategory.MAXWELLSIM_SIMS)}

View File

@ -48,7 +48,7 @@ class BoxStructureNode(base.MaxwellSimNode):
# - Sockets # - Sockets
#################### ####################
input_sockets: typ.ClassVar = { input_sockets: typ.ClassVar = {
'Medium': sockets.MaxwellMediumSocketDef(), 'Medium': sockets.MaxwellMediumSocketDef(active_kind=FK.Func),
'Center': sockets.ExprSocketDef( 'Center': sockets.ExprSocketDef(
size=spux.NumberSize1D.Vec3, size=spux.NumberSize1D.Vec3,
default_unit=spu.micrometer, default_unit=spu.micrometer,

View File

@ -19,6 +19,7 @@
import typing as typ import typing as typ
import bpy import bpy
import jax.numpy as jnp
import sympy as sp import sympy as sp
import sympy.physics.units as spu import sympy.physics.units as spu
@ -208,46 +209,84 @@ class WaveConstantNode(base.MaxwellSimNode):
#################### ####################
# - FlowKind.Func # - FlowKind.Func
#################### ####################
# @events.computes_output_socket( @events.computes_output_socket(
# 'WL', 'WL',
# kind=FK.Func, kind=FK.Func,
# # Loaded # Loaded
# inscks_kinds={'WL': FK.Func, 'Freq': FK.Func}, props={'active_socket_set', 'use_range'},
# input_sockets_optional={'WL', 'Freq'}, inscks_kinds={'WL': FK.Func, 'Freq': FK.Func},
# ) input_sockets_optional={'WL', 'Freq'},
# def compute_wl_func(self, input_sockets: dict) -> ct.FuncFlow | FS: )
# """Compute a single wavelength value from either wavelength/frequency.""" def compute_wl_func(self, props, input_sockets) -> ct.FuncFlow | FS:
# wl = input_sockets['WL'] """Compute a single wavelength value from either wavelength/frequency."""
# has_wl = not FS.check(wl) active_socket_set = props['active_socket_set']
# if has_wl: use_range = props['use_range']
# return wl
# freq = input_sockets['Freq'] wl = input_sockets['WL']
# has_freq = not FS.check(freq) freq = input_sockets['Freq']
# if has_freq:
# return wl.compose_within(
# return spu.convert_to(
# sci_constants.vac_speed_of_light / input_sockets['Freq'], spu.um
# )
# return FS.FlowPending match active_socket_set:
case 'Wavelength' if not FS.check(wl):
return wl
# @events.computes_output_socket( case 'Frequency' if not FS.check(freq):
# 'Freq', a = MT.Real.sp_symbol_a
# kind=FK.Value, scaling_expr = spux.scale_to_unit(
# # Loaded sci_constants.vac_speed_of_light / (a * freq.func_output.unit),
# inscks_kinds={'WL': FK.Value, 'Freq': FK.Value}, spu.um,
# input_sockets_optional={'WL', 'Freq'}, )
# ) scaler = sp.lambdify(a, scaling_expr, 'jax')
# def compute_freq_value(self, input_sockets: dict) -> sp.Expr:
# """Compute a single frequency value from either wavelength/frequency."""
# has_freq = not FS.check(input_sockets['Freq'])
# if has_freq:
# return input_sockets['Freq']
# return spu.convert_to( if use_range:
# sci_constants.vac_speed_of_light / input_sockets['WL'], spux.THz return freq.compose_within(
# ) lambda _freq: jnp.flip(scaler(_freq)),
enclosing_func_output=sim_symbols.wl(spu.um),
)
return freq.compose_within(
lambda _freq: scaler(_freq),
enclosing_func_output=sim_symbols.wl(spu.um),
)
return FS.FlowPending
@events.computes_output_socket(
'Freq',
kind=FK.Func,
# Loaded
props={'active_socket_set', 'use_range'},
inscks_kinds={'WL': FK.Func, 'Freq': FK.Func},
input_sockets_optional={'WL', 'Freq'},
)
def compute_freq_func(self, props, input_sockets) -> ct.FuncFlow | FS:
"""Compute a single wavelength value from either wavelength/frequency."""
active_socket_set = props['active_socket_set']
use_range = props['use_range']
wl = input_sockets['WL']
freq = input_sockets['Freq']
match active_socket_set:
case 'Wavelength' if not FS.check(wl):
a = MT.Real.sp_symbol_a
scaling_expr = spux.scale_to_unit(
sci_constants.vac_speed_of_light / (a * wl.func_output.unit),
spux.THz,
)
scaler = sp.lambdify(a, scaling_expr, 'jax')
if use_range:
return wl.compose_within(
lambda _wl: jnp.flip(scaler(_wl)),
enclosing_func_output=sim_symbols.freq(spux.THz),
)
return wl.compose_within(
lambda _wl: scaler(_wl),
enclosing_func_output=sim_symbols.freq(spux.THz),
)
case 'Frequency' if not FS.check(freq):
return freq
return FS.FlowPending
#################### ####################
# - FlowKind.Info # - FlowKind.Info
@ -272,6 +311,55 @@ class WaveConstantNode(base.MaxwellSimNode):
output=sim_symbols.freq(spux.THz), output=sim_symbols.freq(spux.THz),
) )
####################
# - FlowKind.Params
####################
@events.computes_output_socket(
'WL',
kind=FK.Params,
# Loaded
props={'active_socket_set'},
inscks_kinds={'WL': FK.Params, 'Freq': FK.Params},
input_sockets_optional={'WL', 'Freq'},
)
def compute_freq_params(self, props, input_sockets) -> ct.FuncFlow | FS:
"""Compute a single wavelength value from either wavelength/frequency."""
wl = input_sockets['WL']
freq = input_sockets['Freq']
active_socket_set = props['active_socket_set']
match active_socket_set:
case 'Wavelength' if not FS.check(wl):
return wl
case 'Frequency' if not FS.check(freq):
return freq
return FS.FlowPending
@events.computes_output_socket(
'Freq',
kind=FK.Params,
# Loaded
props={'active_socket_set'},
inscks_kinds={'WL': FK.Params, 'Freq': FK.Params},
input_sockets_optional={'WL', 'Freq'},
)
def compute_freq_params(self, props, input_sockets) -> ct.FuncFlow | FS:
"""Compute a single wavelength value from either wavelength/frequency."""
wl = input_sockets['WL']
freq = input_sockets['Freq']
active_socket_set = props['active_socket_set']
match active_socket_set:
case 'Wavelength' if not FS.check(wl):
return wl
case 'Frequency' if not FS.check(freq):
return freq
return FS.FlowPending
#################### ####################
# - Blender Registration # - Blender Registration

View File

@ -832,6 +832,8 @@ class MaxwellSimSocket(bpy.types.NodeSocket, bl_instance.BLInstance):
if FS.check_single(flow, FS.FlowPending) and not self.flow_error: if FS.check_single(flow, FS.FlowPending) and not self.flow_error:
bpy.app.timers.register(self.declare_flow_error) bpy.app.timers.register(self.declare_flow_error)
# elif self.flow_error:
# bpy.app.timers.register(self.clear_flow_error)
return flow return flow

View File

@ -987,12 +987,6 @@ class ExprBLSocket(base.MaxwellSimSocket):
func_output=self.output_sym, func_output=self.output_sym,
supports_jax=True, supports_jax=True,
) )
return ct.FuncFlow(
func=lambda v: v,
func_args=[self.output_sym],
func_output=self.output_sym,
supports_jax=True,
)
return ct.FlowSignal.FlowPending return ct.FlowSignal.FlowPending
@ -1709,9 +1703,9 @@ class ExprSocketDef(base.SocketDef):
bl_socket.size = self.size bl_socket.size = self.size
bl_socket.mathtype = self.mathtype bl_socket.mathtype = self.mathtype
bl_socket.physical_type = self.physical_type bl_socket.physical_type = self.physical_type
bl_socket.active_unit = bl_cache.Signal.ResetEnumItems # bl_socket.active_unit = bl_cache.Signal.InvalidateCache
bl_socket.unit = bl_cache.Signal.InvalidateCache # bl_socket.unit = bl_cache.Signal.InvalidateCache
bl_socket.unit_factor = bl_cache.Signal.InvalidateCache # bl_socket.unit_factor = bl_cache.Signal.InvalidateCache
bl_socket.symbols = self.default_symbols bl_socket.symbols = self.default_symbols
# Domain # Domain

View File

@ -21,6 +21,7 @@ from . import (
fdtd_sim_data, fdtd_sim_data,
medium, medium,
medium_non_linearity, medium_non_linearity,
mode,
monitor, monitor,
monitor_data, monitor_data,
sim_domain, sim_domain,
@ -47,6 +48,7 @@ MaxwellSimGridAxisSocketDef = sim_grid_axis.MaxwellSimGridAxisSocketDef
MaxwellSourceSocketDef = source.MaxwellSourceSocketDef MaxwellSourceSocketDef = source.MaxwellSourceSocketDef
MaxwellStructureSocketDef = structure.MaxwellStructureSocketDef MaxwellStructureSocketDef = structure.MaxwellStructureSocketDef
MaxwellTemporalShapeSocketDef = temporal_shape.MaxwellTemporalShapeSocketDef MaxwellTemporalShapeSocketDef = temporal_shape.MaxwellTemporalShapeSocketDef
MaxwellModeSocketDef = mode.MaxwellModeSocketDef
BL_REGISTER = [ BL_REGISTER = [
@ -56,6 +58,7 @@ BL_REGISTER = [
*fdtd_sim_data.BL_REGISTER, *fdtd_sim_data.BL_REGISTER,
*medium.BL_REGISTER, *medium.BL_REGISTER,
*medium_non_linearity.BL_REGISTER, *medium_non_linearity.BL_REGISTER,
*mode.BL_REGISTER,
*monitor.BL_REGISTER, *monitor.BL_REGISTER,
*monitor_data.BL_REGISTER, *monitor_data.BL_REGISTER,
*sim_domain.BL_REGISTER, *sim_domain.BL_REGISTER,

View File

@ -0,0 +1,46 @@
# blender_maxwell
# Copyright (C) 2024 blender_maxwell Project Contributors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import typing as typ
from ... import contracts as ct
from .. import base
class MaxwellModeBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.MaxwellMode
bl_label = 'Maxwell FDTD Simulation'
####################
# - Socket Configuration
####################
class MaxwellModeSocketDef(base.SocketDef):
socket_type: ct.SocketType = ct.SocketType.MaxwellMode
def init(self, bl_socket: MaxwellModeBLSocket) -> None:
pass
def local_compare(self, _: MaxwellModeBLSocket) -> None:
return True
####################
# - Blender Registration
####################
BL_REGISTER = [
MaxwellModeBLSocket,
]

View File

@ -14,14 +14,35 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from blender_maxwell.utils import bl_cache
from ... import contracts as ct from ... import contracts as ct
from .. import base from .. import base
FK = ct.FlowKind
FS = ct.FlowSignal
class MaxwellMonitorBLSocket(base.MaxwellSimSocket): class MaxwellMonitorBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.MaxwellMonitor socket_type = ct.SocketType.MaxwellMonitor
bl_label = 'Maxwell Monitor' bl_label = 'Maxwell Monitor'
@bl_cache.cached_bl_property()
def array(self) -> list:
return []
@bl_cache.cached_bl_property()
def lazy_func(self) -> ct.FuncFlow | FS:
if self.active_kind is FK.Array:
return ct.FuncFlow(func=list)
return FS.NoFlow
@bl_cache.cached_bl_property()
def params(self) -> ct.ParamsFlow:
if self.active_kind is FK.Array:
return ct.ParamsFlow()
return FS.NoFlow
#################### ####################
# - Socket Configuration # - Socket Configuration

View File

@ -14,14 +14,35 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from blender_maxwell.utils import bl_cache
from ... import contracts as ct from ... import contracts as ct
from .. import base from .. import base
FK = ct.FlowKind
FS = ct.FlowSignal
class MaxwellSourceBLSocket(base.MaxwellSimSocket): class MaxwellSourceBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.MaxwellSource socket_type = ct.SocketType.MaxwellSource
bl_label = 'Maxwell Source' bl_label = 'Maxwell Source'
@bl_cache.cached_bl_property()
def array(self) -> list:
return []
@bl_cache.cached_bl_property()
def lazy_func(self) -> ct.FuncFlow | FS:
if self.active_kind is FK.Array:
return ct.FuncFlow(func=list)
return FS.NoFlow
@bl_cache.cached_bl_property()
def params(self) -> ct.ParamsFlow:
if self.active_kind is FK.Array:
return ct.ParamsFlow()
return FS.NoFlow
#################### ####################
# - Socket Configuration # - Socket Configuration

View File

@ -163,7 +163,7 @@ def pinned_labels(pinned_data) -> str:
'\n' '\n'
+ ', '.join( + ', '.join(
[ [
f'{sym.name_pretty}:' + _parse_val(val) f'{sym.name_pretty}=' + _parse_val(val)
for sym, val in pinned_data.items() for sym, val in pinned_data.items()
] ]
) )
@ -257,7 +257,7 @@ def plot_heatmap_2d(data, ax: mpl_ax.Axis) -> None:
x_sym, y_sym, c_sym, pinned = list(data.keys()) x_sym, y_sym, c_sym, pinned = list(data.keys())
heatmap = ax.imshow(data[c_sym], aspect='equal', interpolation='none') heatmap = ax.imshow(data[c_sym], aspect='equal', interpolation='none')
# ax.figure.colorbar(heatmap, ax=ax) ax.figure.colorbar(heatmap, ax=ax)
ax.set_title( ax.set_title(
f'({x_sym.name_pretty}, {y_sym.name_pretty}) → {c_sym.plot_label} {pinned_labels(data[pinned])}' f'({x_sym.name_pretty}, {y_sym.name_pretty}) → {c_sym.plot_label} {pinned_labels(data[pinned])}'

View File

@ -37,6 +37,8 @@ from .common import (
flux, flux,
freq, freq,
idx, idx,
mode_idx,
rel_eps,
rel_eps_im, rel_eps_im,
rel_eps_re, rel_eps_re,
sim_axis_idx, sim_axis_idx,
@ -60,6 +62,8 @@ from .utils import (
__all__ = [ __all__ = [
'CommonSimSymbol', 'CommonSimSymbol',
'idx', 'idx',
'mode_idx',
'rel_eps',
'rel_eps_im', 'rel_eps_im',
'rel_eps_re', 'rel_eps_re',
'sim_axis_idx', 'sim_axis_idx',

View File

@ -46,6 +46,7 @@ class CommonSimSymbol(enum.StrEnum):
Index = enum.auto() Index = enum.auto()
SimAxisIdx = enum.auto() SimAxisIdx = enum.auto()
ModeIdx = enum.auto()
# Space|Time # Space|Time
SpaceX = enum.auto() SpaceX = enum.auto()
@ -88,6 +89,7 @@ class CommonSimSymbol(enum.StrEnum):
DiffOrderX = enum.auto() DiffOrderX = enum.auto()
DiffOrderY = enum.auto() DiffOrderY = enum.auto()
RelEps = enum.auto()
RelEpsRe = enum.auto() RelEpsRe = enum.auto()
RelEpsIm = enum.auto() RelEpsIm = enum.auto()
@ -128,6 +130,7 @@ class CommonSimSymbol(enum.StrEnum):
return { return {
CSS.Index: SSN.LowerI, CSS.Index: SSN.LowerI,
CSS.SimAxisIdx: SSN.SimAxisIdx, CSS.SimAxisIdx: SSN.SimAxisIdx,
CSS.ModeIdx: SSN.ModeIdx,
# Space|Time # Space|Time
CSS.SpaceX: SSN.LowerX, CSS.SpaceX: SSN.LowerX,
CSS.SpaceY: SSN.LowerY, CSS.SpaceY: SSN.LowerY,
@ -156,6 +159,7 @@ class CommonSimSymbol(enum.StrEnum):
CSS.Flux: SSN.Flux, CSS.Flux: SSN.Flux,
CSS.DiffOrderX: SSN.DiffOrderX, CSS.DiffOrderX: SSN.DiffOrderX,
CSS.DiffOrderY: SSN.DiffOrderY, CSS.DiffOrderY: SSN.DiffOrderY,
CSS.RelEps: SSN.Perm,
CSS.RelEpsRe: SSN.RelEpsRe, CSS.RelEpsRe: SSN.RelEpsRe,
CSS.RelEpsIm: SSN.RelEpsIm, CSS.RelEpsIm: SSN.RelEpsIm,
}[self] }[self]
@ -201,6 +205,11 @@ class CommonSimSymbol(enum.StrEnum):
mathtype=spux.MathType.Integer, mathtype=spux.MathType.Integer,
domain=spux.BlessedSet(sp.FiniteSet(0, 1, 2)), domain=spux.BlessedSet(sp.FiniteSet(0, 1, 2)),
), ),
CSS.ModeIdx: SimSymbol(
sym_name=self.name,
mathtype=spux.MathType.Integer,
domain=spux.BlessedSet(sp.Naturals0),
),
# Space|Time # Space|Time
CSS.SpaceX: sym_space, CSS.SpaceX: sym_space,
CSS.SpaceY: sym_space, CSS.SpaceY: sym_space,
@ -275,6 +284,11 @@ class CommonSimSymbol(enum.StrEnum):
mathtype=spux.MathType.Integer, mathtype=spux.MathType.Integer,
domain=spux.BlessedSet(sp.Integers), domain=spux.BlessedSet(sp.Integers),
), ),
CSS.RelEps: SimSymbol(
sym_name=self.name,
mathtype=spux.MathType.Complex,
domain=spux.BlessedSet(sp.Complexes),
),
CSS.RelEpsRe: SimSymbol( CSS.RelEpsRe: SimSymbol(
sym_name=self.name, sym_name=self.name,
mathtype=spux.MathType.Real, mathtype=spux.MathType.Real,
@ -293,6 +307,8 @@ class CommonSimSymbol(enum.StrEnum):
#################### ####################
idx = CommonSimSymbol.Index.sim_symbol idx = CommonSimSymbol.Index.sim_symbol
sim_axis_idx = CommonSimSymbol.SimAxisIdx.sim_symbol sim_axis_idx = CommonSimSymbol.SimAxisIdx.sim_symbol
mode_idx = CommonSimSymbol.ModeIdx.sim_symbol
t = CommonSimSymbol.Time.sim_symbol t = CommonSimSymbol.Time.sim_symbol
wl = CommonSimSymbol.Wavelength.sim_symbol wl = CommonSimSymbol.Wavelength.sim_symbol
freq = CommonSimSymbol.Frequency.sim_symbol freq = CommonSimSymbol.Frequency.sim_symbol
@ -323,5 +339,6 @@ flux = CommonSimSymbol.Flux.sim_symbol
diff_order_x = CommonSimSymbol.DiffOrderX.sim_symbol diff_order_x = CommonSimSymbol.DiffOrderX.sim_symbol
diff_order_y = CommonSimSymbol.DiffOrderY.sim_symbol diff_order_y = CommonSimSymbol.DiffOrderY.sim_symbol
rel_eps = CommonSimSymbol.RelEps.sim_symbol
rel_eps_re = CommonSimSymbol.RelEpsRe.sim_symbol rel_eps_re = CommonSimSymbol.RelEpsRe.sim_symbol
rel_eps_im = CommonSimSymbol.RelEpsIm.sim_symbol rel_eps_im = CommonSimSymbol.RelEpsIm.sim_symbol

View File

@ -95,6 +95,7 @@ class SimSymbolName(enum.StrEnum):
RelEpsIm = enum.auto() RelEpsIm = enum.auto()
SimAxisIdx = enum.auto() SimAxisIdx = enum.auto()
ModeIdx = enum.auto()
#################### ####################
# - UI # - UI
@ -177,6 +178,7 @@ class SimSymbolName(enum.StrEnum):
SSN.RelEpsRe: 'eps_r_re', SSN.RelEpsRe: 'eps_r_re',
SSN.RelEpsIm: 'eps_r_im', SSN.RelEpsIm: 'eps_r_im',
SSN.SimAxisIdx: '[xyz]', SSN.SimAxisIdx: '[xyz]',
SSN.ModeIdx: 'mode',
} }
)[self] )[self]

View File

@ -675,6 +675,13 @@ class SimSymbol(pyd.BaseModel):
else: else:
res = sp.S(obj) res = sp.S(obj)
## TODO: THIS IS A WORKAROUND
## TODO: Only a plain MatrixSymbol is detected, this is **not enough**.
## -- We also need to handle expressions containing MatrixSymbols.
## -- We do this before adding units to catch some cases.
## -- This is rather fragile right now.
is_matrix = isinstance(res, sp.MatrixBase | sp.MatrixSymbol)
# Unit Conversion # Unit Conversion
match (spux.uses_units(res), self.unit is not None): match (spux.uses_units(res), self.unit is not None):
case (True, True): case (True, True):
@ -694,10 +701,12 @@ class SimSymbol(pyd.BaseModel):
self.depths == () self.depths == ()
and (self.rows > 1 or self.cols > 1) and (self.rows > 1 or self.cols > 1)
and not isinstance(res, sp.MatrixBase | sp.MatrixSymbol) and not isinstance(res, sp.MatrixBase | sp.MatrixSymbol)
and not is_matrix
): ):
res = sp.ImmutableMatrix.ones(self.rows, self.cols).applyfunc( res = res * sp.ImmutableMatrix.ones(self.rows, self.cols)
lambda el: 5 * el # res = sp.ImmutableMatrix.ones(self.rows, self.cols).applyfunc(
) # lambda el: res * el
# )
return res return res