feat: merged temporal shapes w/symbolic envelope
We now have a single node for all temporal shapes, which is extremely usable. Note that we found a bug where input socket caching seems to survive changes to loose inputs / socket set-driven alterations, which prevents output from being able to switch with the socket set. We'll make an issue for it whenever convenient. Work also continued very briskly with the `SimSymbol` abstraction, which is really, really working out. Closes #67.main
parent
e51ec8f43f
commit
2d26ea6ce8
|
@ -388,6 +388,12 @@ class LazyArrayRangeFlow:
|
|||
self.stop.subs({sym: symbol_values[sym.name] for sym in self.symbols})
|
||||
)
|
||||
|
||||
def realize_step_size(
|
||||
self,
|
||||
symbol_values: dict[spux.Symbol, typ.Any] = MappingProxyType({}),
|
||||
) -> ArrayFlow | LazyValueFuncFlow:
|
||||
return (self.realize_stop() - self.realize_start()) / self.steps
|
||||
|
||||
def realize(
|
||||
self,
|
||||
symbol_values: dict[spux.Symbol, typ.Any] = MappingProxyType({}),
|
||||
|
|
|
@ -55,10 +55,7 @@ class NodeType(blender_type_enum.BlenderTypeEnum):
|
|||
JSONFileExporter = enum.auto()
|
||||
|
||||
# Sources
|
||||
## Sources / Temporal Shapes
|
||||
PulseTemporalShape = enum.auto()
|
||||
WaveTemporalShape = enum.auto()
|
||||
ExprTemporalShape = enum.auto()
|
||||
TemporalShape = enum.auto()
|
||||
## Sources /
|
||||
PointDipoleSource = enum.auto()
|
||||
PlaneWaveSource = enum.auto()
|
||||
|
|
|
@ -374,7 +374,7 @@ class MapMathNode(base.MaxwellSimNode):
|
|||
# - Properties
|
||||
####################
|
||||
operation: MapOperation = bl_cache.BLField(
|
||||
prop_ui=True, enum_cb=lambda self, _: self.search_operations()
|
||||
enum_cb=lambda self, _: self.search_operations()
|
||||
)
|
||||
|
||||
@property
|
||||
|
|
|
@ -22,7 +22,7 @@ import jaxtyping as jtyp
|
|||
import matplotlib.axis as mpl_ax
|
||||
import sympy as sp
|
||||
|
||||
from blender_maxwell.utils import bl_cache, image_ops, logger
|
||||
from blender_maxwell.utils import bl_cache, image_ops, logger, sim_symbols
|
||||
from blender_maxwell.utils import extra_sympy_units as spux
|
||||
|
||||
from ... import contracts as ct
|
||||
|
@ -210,8 +210,8 @@ class VizNode(base.MaxwellSimNode):
|
|||
input_sockets: typ.ClassVar = {
|
||||
'Expr': sockets.ExprSocketDef(
|
||||
active_kind=ct.FlowKind.LazyValueFunc,
|
||||
symbols={_x := sp.Symbol('x', real=True)},
|
||||
default_value=2 * _x,
|
||||
default_symbols=[sim_symbols.x],
|
||||
default_value=2 * sim_symbols.x.sp_symbol,
|
||||
),
|
||||
}
|
||||
output_sockets: typ.ClassVar = {
|
||||
|
@ -295,8 +295,8 @@ class VizNode(base.MaxwellSimNode):
|
|||
####################
|
||||
@events.on_value_changed(
|
||||
socket_name='Expr',
|
||||
input_sockets={'Expr'},
|
||||
run_on_init=True,
|
||||
input_sockets={'Expr'},
|
||||
input_socket_kinds={'Expr': {ct.FlowKind.Info, ct.FlowKind.Params}},
|
||||
input_sockets_optional={'Expr': True},
|
||||
)
|
||||
|
|
|
@ -19,11 +19,11 @@ from . import (
|
|||
gaussian_beam_source,
|
||||
plane_wave_source,
|
||||
point_dipole_source,
|
||||
temporal_shapes,
|
||||
temporal_shape,
|
||||
)
|
||||
|
||||
BL_REGISTER = [
|
||||
*temporal_shapes.BL_REGISTER,
|
||||
*temporal_shape.BL_REGISTER,
|
||||
*plane_wave_source.BL_REGISTER,
|
||||
*point_dipole_source.BL_REGISTER,
|
||||
# *uniform_current_source.BL_REGISTER,
|
||||
|
@ -32,7 +32,7 @@ BL_REGISTER = [
|
|||
# *tfsf_source.BL_REGISTER,
|
||||
]
|
||||
BL_NODES = {
|
||||
**temporal_shapes.BL_NODES,
|
||||
**temporal_shape.BL_NODES,
|
||||
**plane_wave_source.BL_NODES,
|
||||
**point_dipole_source.BL_NODES,
|
||||
# **uniform_current_source.BL_NODES,
|
||||
|
|
|
@ -28,6 +28,8 @@ from ... import contracts as ct
|
|||
from ... import managed_objs, sockets
|
||||
from .. import base, events
|
||||
|
||||
log = logger.get(__name__)
|
||||
|
||||
|
||||
class PlaneWaveSourceNode(base.MaxwellSimNode):
|
||||
"""An infinite-extent angled source simulating an plane wave with linear polarization.
|
||||
|
@ -73,7 +75,6 @@ class PlaneWaveSourceNode(base.MaxwellSimNode):
|
|||
}
|
||||
|
||||
managed_obj_types: typ.ClassVar = {
|
||||
'mesh': managed_objs.ManagedBLMesh,
|
||||
'modifier': managed_objs.ManagedBLModifier,
|
||||
}
|
||||
|
||||
|
@ -132,18 +133,14 @@ class PlaneWaveSourceNode(base.MaxwellSimNode):
|
|||
# Trigger
|
||||
prop_name='preview_active',
|
||||
# Loaded
|
||||
managed_objs={'mesh'},
|
||||
managed_objs={'modifier'},
|
||||
props={'preview_active'},
|
||||
)
|
||||
def on_preview_changed(self, managed_objs, props):
|
||||
"""Enables/disables previewing of the GeoNodes-driven mesh, regardless of whether a particular GeoNodes tree is chosen."""
|
||||
mesh = managed_objs['mesh']
|
||||
|
||||
# Push Preview State to Managed Mesh
|
||||
if props['preview_active']:
|
||||
mesh.show_preview()
|
||||
managed_objs['modifier'].show_preview()
|
||||
else:
|
||||
mesh.hide_preview()
|
||||
managed_objs['modifier'].hide_preview()
|
||||
|
||||
@events.on_value_changed(
|
||||
# Trigger
|
||||
|
@ -151,7 +148,7 @@ class PlaneWaveSourceNode(base.MaxwellSimNode):
|
|||
prop_name={'injection_axis', 'injection_direction'},
|
||||
run_on_init=True,
|
||||
# Loaded
|
||||
managed_objs={'mesh', 'modifier'},
|
||||
managed_objs={'modifier'},
|
||||
props={'injection_axis', 'injection_direction'},
|
||||
input_sockets={'Temporal Shape', 'Center', 'Spherical', 'Pol ∡'},
|
||||
unit_systems={'BlenderUnits': ct.UNITS_BLENDER},
|
||||
|
@ -162,7 +159,6 @@ class PlaneWaveSourceNode(base.MaxwellSimNode):
|
|||
def on_inputs_changed(self, managed_objs, props, input_sockets, unit_systems):
|
||||
# Push Input Values to GeoNodes Modifier
|
||||
managed_objs['modifier'].bl_modifier(
|
||||
managed_objs['mesh'].bl_object(location=input_sockets['Center']),
|
||||
'NODES',
|
||||
{
|
||||
'node_group': import_geonodes(GeoNodes.SourcePlaneWave),
|
||||
|
@ -175,6 +171,7 @@ class PlaneWaveSourceNode(base.MaxwellSimNode):
|
|||
'Pol Angle': input_sockets['Pol ∡'],
|
||||
},
|
||||
},
|
||||
location=input_sockets['Center'],
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,197 @@
|
|||
# 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 the `TemporalShapeNode`."""
|
||||
|
||||
import typing as typ
|
||||
|
||||
import bpy
|
||||
import sympy as sp
|
||||
import sympy.physics.units as spu
|
||||
import tidy3d as td
|
||||
|
||||
from blender_maxwell.utils import extra_sympy_units as spux
|
||||
from blender_maxwell.utils import logger, sim_symbols
|
||||
|
||||
from ... import contracts as ct
|
||||
from ... import managed_objs, sockets
|
||||
from .. import base, events
|
||||
|
||||
log = logger.get(__name__)
|
||||
|
||||
|
||||
_max_e_socket_def = sockets.ExprSocketDef(
|
||||
mathtype=spux.MathType.Complex,
|
||||
physical_type=spux.PhysicalType.EField,
|
||||
default_value=1 + 0j,
|
||||
)
|
||||
_offset_socket_def = sockets.ExprSocketDef(default_value=5, abs_min=2.5)
|
||||
|
||||
|
||||
class TemporalShapeNode(base.MaxwellSimNode):
|
||||
"""Declare a source-time dependence for use in simulation source nodes."""
|
||||
|
||||
node_type = ct.NodeType.TemporalShape
|
||||
bl_label = 'Temporal Shape'
|
||||
|
||||
####################
|
||||
# - Sockets
|
||||
####################
|
||||
input_sockets: typ.ClassVar = {
|
||||
'μ Freq': sockets.ExprSocketDef(
|
||||
physical_type=spux.PhysicalType.Freq,
|
||||
default_unit=spux.THz,
|
||||
default_value=500,
|
||||
),
|
||||
'σ Freq': sockets.ExprSocketDef(
|
||||
physical_type=spux.PhysicalType.Freq,
|
||||
default_unit=spux.THz,
|
||||
default_value=200,
|
||||
),
|
||||
}
|
||||
input_socket_sets: typ.ClassVar = {
|
||||
'Pulse': {
|
||||
'max E': _max_e_socket_def,
|
||||
'Offset Time': _offset_socket_def,
|
||||
'Remove DC': sockets.BoolSocketDef(default_value=True),
|
||||
},
|
||||
'Constant': {
|
||||
'max E': _max_e_socket_def,
|
||||
'Offset Time': _offset_socket_def,
|
||||
},
|
||||
'Symbolic': {
|
||||
't Range': sockets.ExprSocketDef(
|
||||
active_kind=ct.FlowKind.LazyArrayRange,
|
||||
physical_type=spux.PhysicalType.Time,
|
||||
default_unit=spu.picosecond,
|
||||
default_min=0,
|
||||
default_max=10,
|
||||
default_steps=100,
|
||||
),
|
||||
'Envelope': sockets.ExprSocketDef(
|
||||
default_symbols=[sim_symbols.t_ps],
|
||||
default_value=10 * sim_symbols.t_ps.sp_symbol,
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
output_sockets: typ.ClassVar = {
|
||||
'Temporal Shape': sockets.MaxwellTemporalShapeSocketDef(),
|
||||
}
|
||||
|
||||
managed_obj_types: typ.ClassVar = {
|
||||
'plot': managed_objs.ManagedBLImage,
|
||||
}
|
||||
|
||||
def draw_info(self, _: bpy.types.Context, layout: bpy.types.UILayout) -> None:
|
||||
if self.active_socket_set != 'Symbolic':
|
||||
box = layout.box()
|
||||
row = box.row()
|
||||
row.alignment = 'CENTER'
|
||||
row.label(text='Parameter Scale')
|
||||
|
||||
# Split
|
||||
split = box.split(factor=0.3, align=False)
|
||||
|
||||
## LHS: Parameter Names
|
||||
col = split.column()
|
||||
col.alignment = 'RIGHT'
|
||||
col.label(text='Off t:')
|
||||
|
||||
## RHS: Parameter Units
|
||||
col = split.column()
|
||||
col.label(text='1 / 2π·σ(𝑓)')
|
||||
|
||||
####################
|
||||
# - FlowKind: Value
|
||||
####################
|
||||
@events.computes_output_socket(
|
||||
'Temporal Shape',
|
||||
# Loaded
|
||||
props={'active_socket_set'},
|
||||
input_sockets={
|
||||
'max E',
|
||||
'μ Freq',
|
||||
'σ Freq',
|
||||
'Offset Time',
|
||||
'Remove DC',
|
||||
't Range',
|
||||
'Envelope',
|
||||
},
|
||||
input_socket_kinds={
|
||||
't Range': ct.FlowKind.LazyArrayRange,
|
||||
'Envelope': ct.FlowKind.LazyValueFunc,
|
||||
},
|
||||
input_sockets_optional={
|
||||
'max E': True,
|
||||
'Offset Time': True,
|
||||
'Remove DC': True,
|
||||
't Range': True,
|
||||
'Envelope': True,
|
||||
},
|
||||
unit_systems={'Tidy3DUnits': ct.UNITS_TIDY3D},
|
||||
scale_input_sockets={
|
||||
'max E': 'Tidy3DUnits',
|
||||
'μ Freq': 'Tidy3DUnits',
|
||||
'σ Freq': 'Tidy3DUnits',
|
||||
't Range': 'Tidy3DUnits',
|
||||
'Offset Time': 'Tidy3DUnits',
|
||||
},
|
||||
)
|
||||
def compute_temporal_shape(
|
||||
self, props, input_sockets, unit_systems
|
||||
) -> td.GaussianPulse:
|
||||
match props['active_socket_set']:
|
||||
case 'Pulse':
|
||||
return td.GaussianPulse(
|
||||
amplitude=sp.re(input_sockets['max E']),
|
||||
phase=sp.im(input_sockets['max E']),
|
||||
freq0=input_sockets['μ Freq'],
|
||||
fwidth=input_sockets['σ Freq'],
|
||||
offset=input_sockets['Offset Time'],
|
||||
remove_dc_component=input_sockets['Remove DC'],
|
||||
)
|
||||
|
||||
case 'Constant':
|
||||
return td.ContinuousWave(
|
||||
amplitude=sp.re(input_sockets['max E']),
|
||||
phase=sp.im(input_sockets['max E']),
|
||||
freq0=input_sockets['μ Freq'],
|
||||
fwidth=input_sockets['σ Freq'],
|
||||
offset=input_sockets['Offset Time'],
|
||||
)
|
||||
|
||||
case 'Symbolic':
|
||||
lzrange = input_sockets['t Range']
|
||||
envelope_ps = input_sockets['Envelope'].func_jax
|
||||
|
||||
return td.CustomSourceTime.from_values(
|
||||
freq0=input_sockets['μ Freq'],
|
||||
fwidth=input_sockets['σ Freq'],
|
||||
values=envelope_ps(
|
||||
lzrange.rescale_to_unit(spu.ps).realize_array.values
|
||||
),
|
||||
dt=input_sockets['t Range'].realize_step_size(),
|
||||
)
|
||||
|
||||
|
||||
####################
|
||||
# - Blender Registration
|
||||
####################
|
||||
BL_REGISTER = [
|
||||
TemporalShapeNode,
|
||||
]
|
||||
BL_NODES = {ct.NodeType.TemporalShape: (ct.NodeCategory.MAXWELLSIM_SOURCES)}
|
|
@ -1,29 +0,0 @@
|
|||
# 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/>.
|
||||
|
||||
# from . import expr_temporal_shape, pulse_temporal_shape, wave_temporal_shape
|
||||
from . import pulse_temporal_shape, wave_temporal_shape
|
||||
|
||||
BL_REGISTER = [
|
||||
*pulse_temporal_shape.BL_REGISTER,
|
||||
*wave_temporal_shape.BL_REGISTER,
|
||||
# *expr_temporal_shape.BL_REGISTER,
|
||||
]
|
||||
BL_NODES = {
|
||||
**pulse_temporal_shape.BL_NODES,
|
||||
**wave_temporal_shape.BL_NODES,
|
||||
# **expr_temporal_shape.BL_NODES,
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
# 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/>.
|
||||
|
||||
####################
|
||||
# - Blender Registration
|
||||
####################
|
||||
BL_REGISTER = []
|
||||
BL_NODES = {}
|
|
@ -1,177 +0,0 @@
|
|||
# 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 the `PulseTemporalShapeNode`."""
|
||||
|
||||
import functools
|
||||
import typing as typ
|
||||
|
||||
import bpy
|
||||
import sympy as sp
|
||||
import sympy.physics.units as spu
|
||||
import tidy3d as td
|
||||
|
||||
from blender_maxwell.utils import extra_sympy_units as spux
|
||||
|
||||
from .... import contracts as ct
|
||||
from .... import managed_objs, sockets
|
||||
from ... import base, events
|
||||
|
||||
|
||||
class PulseTemporalShapeNode(base.MaxwellSimNode):
|
||||
node_type = ct.NodeType.PulseTemporalShape
|
||||
bl_label = 'Gaussian Pulse Temporal Shape'
|
||||
|
||||
####################
|
||||
# - Sockets
|
||||
####################
|
||||
input_sockets: typ.ClassVar = {
|
||||
'max E': sockets.ExprSocketDef(
|
||||
mathtype=spux.MathType.Complex,
|
||||
physical_type=spux.PhysicalType.EField,
|
||||
default_value=1 + 0j,
|
||||
),
|
||||
'μ Freq': sockets.ExprSocketDef(
|
||||
physical_type=spux.PhysicalType.Freq,
|
||||
default_unit=spux.THz,
|
||||
default_value=500,
|
||||
),
|
||||
'σ Freq': sockets.ExprSocketDef(
|
||||
physical_type=spux.PhysicalType.Freq,
|
||||
default_unit=spux.THz,
|
||||
default_value=200,
|
||||
),
|
||||
'Offset Time': sockets.ExprSocketDef(default_value=5, abs_min=2.5),
|
||||
'Remove DC': sockets.BoolSocketDef(
|
||||
default_value=True,
|
||||
),
|
||||
}
|
||||
output_sockets: typ.ClassVar = {
|
||||
'Temporal Shape': sockets.MaxwellTemporalShapeSocketDef(),
|
||||
'E(t)': sockets.ExprSocketDef(active_kind=ct.FlowKind.Array),
|
||||
}
|
||||
|
||||
managed_obj_types: typ.ClassVar = {
|
||||
'plot': managed_objs.ManagedBLImage,
|
||||
}
|
||||
|
||||
####################
|
||||
# - UI
|
||||
####################
|
||||
def draw_info(self, _: bpy.types.Context, layout: bpy.types.UILayout) -> None:
|
||||
box = layout.box()
|
||||
row = box.row()
|
||||
row.alignment = 'CENTER'
|
||||
row.label(text='Parameter Scale')
|
||||
|
||||
# Split
|
||||
split = box.split(factor=0.3, align=False)
|
||||
|
||||
## LHS: Parameter Names
|
||||
col = split.column()
|
||||
col.alignment = 'RIGHT'
|
||||
col.label(text='Off t:')
|
||||
|
||||
## RHS: Parameter Units
|
||||
col = split.column()
|
||||
col.label(text='1 / 2π·σ(𝑓)')
|
||||
|
||||
####################
|
||||
# - FlowKind: Value
|
||||
####################
|
||||
@events.computes_output_socket(
|
||||
'Temporal Shape',
|
||||
input_sockets={
|
||||
'max E',
|
||||
'μ Freq',
|
||||
'σ Freq',
|
||||
'Offset Time',
|
||||
'Remove DC',
|
||||
},
|
||||
unit_systems={'Tidy3DUnits': ct.UNITS_TIDY3D},
|
||||
scale_input_sockets={
|
||||
'max E': 'Tidy3DUnits',
|
||||
'μ Freq': 'Tidy3DUnits',
|
||||
'σ Freq': 'Tidy3DUnits',
|
||||
},
|
||||
)
|
||||
def compute_temporal_shape(self, input_sockets, unit_systems) -> td.GaussianPulse:
|
||||
return td.GaussianPulse(
|
||||
amplitude=sp.re(input_sockets['max E']),
|
||||
phase=sp.im(input_sockets['max E']),
|
||||
freq0=input_sockets['μ Freq'],
|
||||
fwidth=input_sockets['σ Freq'],
|
||||
offset=input_sockets['Offset Time'],
|
||||
remove_dc_component=input_sockets['Remove DC'],
|
||||
)
|
||||
|
||||
####################
|
||||
# - FlowKind: LazyValueFunc / Info / Params
|
||||
####################
|
||||
@events.computes_output_socket(
|
||||
'E(t)',
|
||||
kind=ct.FlowKind.LazyValueFunc,
|
||||
output_sockets={'Temporal Shape'},
|
||||
)
|
||||
def compute_time_to_efield_lazy(self, output_sockets) -> td.GaussianPulse:
|
||||
temporal_shape = output_sockets['Temporal Shape']
|
||||
jax_amp_time = functools.partial(ct.manual_amp_time, temporal_shape)
|
||||
|
||||
## TODO: Don't just partial() it up, do it property in the ParamsFlow!
|
||||
## -> Right now it's recompiled every time.
|
||||
|
||||
return ct.LazyValueFuncFlow(
|
||||
func=jax_amp_time,
|
||||
func_args=[spux.PhysicalType.Time],
|
||||
supports_jax=True,
|
||||
)
|
||||
|
||||
@events.computes_output_socket(
|
||||
'E(t)',
|
||||
kind=ct.FlowKind.Info,
|
||||
)
|
||||
def compute_time_to_efield_info(self) -> td.GaussianPulse:
|
||||
return ct.InfoFlow(
|
||||
dim_names=['t'],
|
||||
dim_idx={
|
||||
't': ct.LazyArrayRangeFlow(
|
||||
start=sp.S(0), stop=sp.oo, steps=0, unit=spu.second
|
||||
)
|
||||
},
|
||||
output_name='E',
|
||||
output_shape=None,
|
||||
output_mathtype=spux.MathType.Complex,
|
||||
output_unit=spu.volt / spu.um,
|
||||
)
|
||||
|
||||
@events.computes_output_socket(
|
||||
'E(t)',
|
||||
kind=ct.FlowKind.Params,
|
||||
)
|
||||
def compute_time_to_efield_params(self) -> td.GaussianPulse:
|
||||
sym_time = sp.Symbol('t', real=True, nonnegative=True)
|
||||
return ct.ParamsFlow(func_args=[sym_time], symbols={sym_time})
|
||||
|
||||
|
||||
####################
|
||||
# - Blender Registration
|
||||
####################
|
||||
BL_REGISTER = [
|
||||
PulseTemporalShapeNode,
|
||||
]
|
||||
BL_NODES = {
|
||||
ct.NodeType.PulseTemporalShape: (ct.NodeCategory.MAXWELLSIM_SOURCES_TEMPORALSHAPES)
|
||||
}
|
|
@ -1,169 +0,0 @@
|
|||
# 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 the `WaveTemporalShapeNode`."""
|
||||
|
||||
import functools
|
||||
import typing as typ
|
||||
|
||||
import bpy
|
||||
import sympy as sp
|
||||
import sympy.physics.units as spu
|
||||
import tidy3d as td
|
||||
|
||||
from blender_maxwell.utils import extra_sympy_units as spux
|
||||
|
||||
from .... import contracts as ct
|
||||
from .... import managed_objs, sockets
|
||||
from ... import base, events
|
||||
|
||||
|
||||
class WaveTemporalShapeNode(base.MaxwellSimNode):
|
||||
node_type = ct.NodeType.WaveTemporalShape
|
||||
bl_label = 'Continuous Wave Temporal Shape'
|
||||
|
||||
####################
|
||||
# - Sockets
|
||||
####################
|
||||
input_sockets: typ.ClassVar = {
|
||||
'max E': sockets.ExprSocketDef(
|
||||
mathtype=spux.MathType.Complex,
|
||||
physical_type=spux.PhysicalType.EField,
|
||||
default_value=1 + 0j,
|
||||
),
|
||||
'μ Freq': sockets.ExprSocketDef(
|
||||
physical_type=spux.PhysicalType.Freq,
|
||||
default_unit=spux.THz,
|
||||
default_value=500,
|
||||
),
|
||||
'σ Freq': sockets.ExprSocketDef(
|
||||
physical_type=spux.PhysicalType.Freq,
|
||||
default_unit=spux.THz,
|
||||
default_value=200,
|
||||
),
|
||||
'Offset Time': sockets.ExprSocketDef(default_value=5, abs_min=2.5),
|
||||
}
|
||||
output_sockets: typ.ClassVar = {
|
||||
'Temporal Shape': sockets.MaxwellTemporalShapeSocketDef(),
|
||||
'E(t)': sockets.ExprSocketDef(active_kind=ct.FlowKind.Array),
|
||||
}
|
||||
|
||||
managed_obj_types: typ.ClassVar = {
|
||||
'plot': managed_objs.ManagedBLImage,
|
||||
}
|
||||
|
||||
####################
|
||||
# - UI
|
||||
####################
|
||||
def draw_info(self, _: bpy.types.Context, layout: bpy.types.UILayout) -> None:
|
||||
box = layout.box()
|
||||
row = box.row()
|
||||
row.alignment = 'CENTER'
|
||||
row.label(text='Parameter Scale')
|
||||
|
||||
# Split
|
||||
split = box.split(factor=0.3, align=False)
|
||||
|
||||
## LHS: Parameter Names
|
||||
col = split.column()
|
||||
col.alignment = 'RIGHT'
|
||||
col.label(text='Off t:')
|
||||
|
||||
## RHS: Parameter Units
|
||||
col = split.column()
|
||||
col.label(text='1 / 2π·σ(𝑓)')
|
||||
|
||||
####################
|
||||
# - FlowKind: Value
|
||||
####################
|
||||
@events.computes_output_socket(
|
||||
'Temporal Shape',
|
||||
input_sockets={
|
||||
'max E',
|
||||
'μ Freq',
|
||||
'σ Freq',
|
||||
'Offset Time',
|
||||
},
|
||||
unit_systems={'Tidy3DUnits': ct.UNITS_TIDY3D},
|
||||
scale_input_sockets={
|
||||
'max E': 'Tidy3DUnits',
|
||||
'μ Freq': 'Tidy3DUnits',
|
||||
'σ Freq': 'Tidy3DUnits',
|
||||
},
|
||||
)
|
||||
def compute_temporal_shape(self, input_sockets, unit_systems) -> td.GaussianPulse:
|
||||
return td.ContinuousWave(
|
||||
amplitude=sp.re(input_sockets['max E']),
|
||||
phase=sp.im(input_sockets['max E']),
|
||||
freq0=input_sockets['μ Freq'],
|
||||
fwidth=input_sockets['σ Freq'],
|
||||
offset=input_sockets['Offset Time'],
|
||||
)
|
||||
|
||||
####################
|
||||
# - FlowKind: LazyValueFunc / Info / Params
|
||||
####################
|
||||
@events.computes_output_socket(
|
||||
'E(t)',
|
||||
kind=ct.FlowKind.LazyValueFunc,
|
||||
output_sockets={'Temporal Shape'},
|
||||
)
|
||||
def compute_time_to_efield_lazy(self, output_sockets) -> td.GaussianPulse:
|
||||
temporal_shape = output_sockets['Temporal Shape']
|
||||
jax_amp_time = functools.partial(ct.manual_amp_time, temporal_shape)
|
||||
|
||||
return ct.LazyValueFuncFlow(
|
||||
func=jax_amp_time,
|
||||
func_args=[spux.PhysicalType.Time],
|
||||
supports_jax=True,
|
||||
)
|
||||
|
||||
@events.computes_output_socket(
|
||||
'E(t)',
|
||||
kind=ct.FlowKind.Info,
|
||||
)
|
||||
def compute_time_to_efield_info(self) -> td.GaussianPulse:
|
||||
return ct.InfoFlow(
|
||||
dim_names=['t'],
|
||||
dim_idx={
|
||||
't': ct.LazyArrayRangeFlow(
|
||||
start=sp.S(0), stop=sp.oo, steps=0, unit=spu.second
|
||||
)
|
||||
},
|
||||
output_name='E',
|
||||
output_shape=None,
|
||||
output_mathtype=spux.MathType.Complex,
|
||||
output_unit=spu.volt / spu.um,
|
||||
)
|
||||
|
||||
@events.computes_output_socket(
|
||||
'E(t)',
|
||||
kind=ct.FlowKind.Params,
|
||||
)
|
||||
def compute_time_to_efield_params(self) -> td.GaussianPulse:
|
||||
sym_time = sp.Symbol('t', real=True, nonnegative=True)
|
||||
return ct.ParamsFlow(func_args=[sym_time], symbols={sym_time})
|
||||
|
||||
|
||||
####################
|
||||
# - Blender Registration
|
||||
####################
|
||||
BL_REGISTER = [
|
||||
WaveTemporalShapeNode,
|
||||
]
|
||||
BL_NODES = {
|
||||
ct.NodeType.WaveTemporalShape: (ct.NodeCategory.MAXWELLSIM_SOURCES_TEMPORALSHAPES)
|
||||
}
|
|
@ -23,7 +23,7 @@ import bpy
|
|||
import pydantic as pyd
|
||||
import sympy as sp
|
||||
|
||||
from blender_maxwell.utils import bl_cache, logger
|
||||
from blender_maxwell.utils import bl_cache, logger, sim_symbols
|
||||
from blender_maxwell.utils import extra_sympy_units as spux
|
||||
|
||||
from .. import contracts as ct
|
||||
|
@ -936,8 +936,11 @@ class ExprSocketDef(base.SocketDef):
|
|||
physical_type: spux.PhysicalType = spux.PhysicalType.NonPhysical
|
||||
|
||||
default_unit: spux.Unit | None = None
|
||||
# symbols: list[sim_symbols.SimSymbol] = frozenset()
|
||||
symbols: frozenset[spux.SympyExpr] = frozenset()
|
||||
default_symbols: list[sim_symbols.SimSymbol] = pyd.Field(default_factory=list)
|
||||
|
||||
@property
|
||||
def symbols(self) -> set[sp.Symbol]:
|
||||
return {sim_symbol.sp_symbol for sim_symbol in self.default_symbols}
|
||||
|
||||
# FlowKind: Value
|
||||
default_value: spux.SympyExpr = 0
|
||||
|
|
|
@ -173,6 +173,17 @@ class MathType(enum.StrEnum):
|
|||
MT.Complex: complex,
|
||||
}[self]
|
||||
|
||||
@property
|
||||
def symbolic_set(self) -> type:
|
||||
MT = MathType
|
||||
return {
|
||||
MT.Bool: sp.Set([sp.S(False), sp.S(True)]),
|
||||
MT.Integer: sp.Integers,
|
||||
MT.Rational: sp.Rationals,
|
||||
MT.Real: sp.Reals,
|
||||
MT.Complex: sp.Complexes,
|
||||
}[self]
|
||||
|
||||
@staticmethod
|
||||
def to_str(value: typ.Self) -> type:
|
||||
return {
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
import dataclasses
|
||||
import enum
|
||||
import sys
|
||||
import typing as typ
|
||||
|
||||
import sympy as sp
|
||||
|
@ -23,9 +24,19 @@ import sympy as sp
|
|||
from . import extra_sympy_units as spux
|
||||
|
||||
|
||||
class SimSymbolNames(enum.StrEnum):
|
||||
####################
|
||||
# - Simulation Symbols
|
||||
####################
|
||||
class SimSymbolName(enum.StrEnum):
|
||||
LowerA = enum.auto()
|
||||
LowerLambda = enum.auto()
|
||||
LowerT = enum.auto()
|
||||
LowerX = enum.auto()
|
||||
LowerY = enum.auto()
|
||||
LowerZ = enum.auto()
|
||||
|
||||
# Physics
|
||||
Wavelength = enum.auto()
|
||||
Frequency = enum.auto()
|
||||
|
||||
@staticmethod
|
||||
def to_name(v: typ.Self) -> str:
|
||||
|
@ -37,11 +48,28 @@ class SimSymbolNames(enum.StrEnum):
|
|||
Returns:
|
||||
A human-friendly name corresponding to the enum value.
|
||||
"""
|
||||
SSN = SimSymbolNames
|
||||
return SimSymbolName(v).name
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
SSN = SimSymbolName
|
||||
return {
|
||||
SSN.LowerA: 'a',
|
||||
SSN.LowerLambda: 'λ',
|
||||
}[v]
|
||||
SSN.LowerT: 't',
|
||||
SSN.LowerX: 'x',
|
||||
SSN.LowerY: 'y',
|
||||
SSN.LowerZ: 'z',
|
||||
SSN.Wavelength: 'wl',
|
||||
SSN.Frequency: 'freq',
|
||||
}[self]
|
||||
|
||||
@property
|
||||
def name_pretty(self) -> str:
|
||||
SSN = SimSymbolName
|
||||
return {
|
||||
SSN.Wavelength: 'λ',
|
||||
SSN.Frequency: '𝑓',
|
||||
}.get(self, self.name)
|
||||
|
||||
@staticmethod
|
||||
def to_icon(_: typ.Self) -> str:
|
||||
|
@ -58,19 +86,214 @@ class SimSymbolNames(enum.StrEnum):
|
|||
|
||||
@dataclasses.dataclass(kw_only=True, frozen=True)
|
||||
class SimSymbol:
|
||||
name: SimSymbolNames = SimSymbolNames.LowerLambda
|
||||
"""A declarative representation of a symbolic variable.
|
||||
|
||||
`sympy`'s symbols aren't quite flexible enough for our needs: The symbols that we're transporting often need exact domain information, an associated unit dimension, and a great deal of determinism in checks thereof.
|
||||
|
||||
This dataclass is UI-friendly, as it only uses field type annotations/defaults supported by `bl_cache.BLProp`.
|
||||
It's easy to persist, easy to transport, and has many helpful properties which greatly simplify working with symbols.
|
||||
"""
|
||||
|
||||
sim_node_name: SimSymbolName = SimSymbolName.LowerX
|
||||
mathtype: spux.MathType = spux.MathType.Real
|
||||
|
||||
## TODO:
|
||||
## -> Physical Type: Track unit dimension information on the side.
|
||||
## -> Domain: Ability to constrain mathtype ex. (-pi,pi]
|
||||
## -> Shape: For using sp.MatrixSymbol w/predefined rows/cols.
|
||||
physical_type: spux.PhysicalType = spux.PhysicalType.NonPhysical
|
||||
|
||||
## TODO: Shape/size support? Incl. MatrixSymbol.
|
||||
|
||||
# Domain
|
||||
interval_finite: tuple[float, float] = (0, 1)
|
||||
interval_inf: tuple[bool, bool] = (True, True)
|
||||
interval_closed: tuple[bool, bool] = (False, False)
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
####################
|
||||
@property
|
||||
def domain(self) -> sp.Interval | sp.Set:
|
||||
"""Return the domain of valid values for the symbol.
|
||||
|
||||
For integer/rational/real symbols, the domain is an interval defined using the `interval_*` properties.
|
||||
This interval **must** have the property`start <= stop`.
|
||||
|
||||
Otherwise, the domain is the symbolic set corresponding to `self.mathtype`.
|
||||
"""
|
||||
if self.mathtype in [
|
||||
spux.MathType.Integer,
|
||||
spux.MathType.Rational,
|
||||
spux.MathType.Real,
|
||||
]:
|
||||
return sp.Interval(
|
||||
start=self.interval_finite[0] if not self.interval_inf[0] else -sp.oo,
|
||||
end=self.interval_finite[1] if not self.interval_inf[1] else sp.oo,
|
||||
left_open=(
|
||||
True if self.interval_inf[0] else not self.interval_closed[0]
|
||||
),
|
||||
right_open=(
|
||||
True if self.interval_inf[1] else not self.interval_closed[1]
|
||||
),
|
||||
)
|
||||
|
||||
return self.mathtype.symbolic_set
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
####################
|
||||
@property
|
||||
def sp_symbol(self) -> sp.Symbol:
|
||||
"""Return a symbolic variable corresponding to this `SimSymbol`.
|
||||
|
||||
As much as possible, appropriate `assumptions` are set in the constructor of `sp.Symbol`, insofar as they can be determined.
|
||||
|
||||
However, the assumptions system alone is rather limited, and implementations should therefore also strongly consider transporting `SimSymbols` directly, instead of `sp.Symbol`.
|
||||
This allows making use of other properties like `self.domain`, when appropriate.
|
||||
"""
|
||||
# MathType Domain Constraint
|
||||
## -> We must feed the assumptions system.
|
||||
mathtype_kwargs = {}
|
||||
match self.mathtype:
|
||||
case spux.MathType.Integer:
|
||||
mathtype_kwargs |= {'integer': True}
|
||||
case spux.MathType.Rational:
|
||||
mathtype_kwargs |= {'rational': True}
|
||||
case spux.MathType.Real:
|
||||
mathtype_kwargs |= {'real': True}
|
||||
case spux.MathType.Complex:
|
||||
mathtype_kwargs |= {'complex': True}
|
||||
|
||||
# Interval Constraints
|
||||
if isinstance(self.domain, sp.Interval):
|
||||
# Assumption: Non-Zero
|
||||
if (
|
||||
(
|
||||
self.domain.left == 0
|
||||
and self.domain.left_open
|
||||
or self.domain.right == 0
|
||||
and self.domain.right_open
|
||||
)
|
||||
or self.domain.left > 0
|
||||
or self.domain.right < 0
|
||||
):
|
||||
mathtype_kwargs |= {'nonzero': True}
|
||||
|
||||
# Assumption: Positive/Negative
|
||||
if self.domain.left >= 0:
|
||||
mathtype_kwargs |= {'positive': True}
|
||||
elif self.domain.right <= 0:
|
||||
mathtype_kwargs |= {'negative': True}
|
||||
|
||||
# Construct the Symbol
|
||||
return sp.Symbol(self.sim_node_name.name, **mathtype_kwargs)
|
||||
|
||||
|
||||
####################
|
||||
# - Common Sim Symbols
|
||||
####################
|
||||
class CommonSimSymbol(enum.StrEnum):
|
||||
"""A set of pre-defined symbols that might commonly be used in the context of physical simulation.
|
||||
|
||||
Each entry maps directly to a particular `SimSymbol`.
|
||||
|
||||
The enum is compatible with `BLField`, making it easy to declare a UI-driven dropdown of symbols that behave as expected.
|
||||
|
||||
Attributes:
|
||||
Wavelength: A symbol representing a real-valued wavelength.
|
||||
Implicitly, this symbol often represents "vacuum wavelength" in particular.
|
||||
Wavelength: A symbol representing a real-valued frequency.
|
||||
Generally, this is the non-angular frequency.
|
||||
"""
|
||||
|
||||
X = enum.auto()
|
||||
TimePS = enum.auto()
|
||||
WavelengthNM = enum.auto()
|
||||
FrequencyTHZ = enum.auto()
|
||||
|
||||
@staticmethod
|
||||
def to_name(v: typ.Self) -> str:
|
||||
"""Convert the enum value to a human-friendly name.
|
||||
|
||||
Notes:
|
||||
Used to print names in `EnumProperty`s based on this enum.
|
||||
|
||||
Returns:
|
||||
A human-friendly name corresponding to the enum value.
|
||||
"""
|
||||
return CommonSimSymbol(v).sim_symbol_name.name
|
||||
|
||||
@staticmethod
|
||||
def to_icon(_: typ.Self) -> str:
|
||||
"""Convert the enum value to a Blender icon.
|
||||
|
||||
Notes:
|
||||
Used to print icons in `EnumProperty`s based on this enum.
|
||||
|
||||
Returns:
|
||||
A human-friendly name corresponding to the enum value.
|
||||
"""
|
||||
return ''
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
####################
|
||||
@property
|
||||
def sim_symbol_name(self) -> str:
|
||||
SSN = SimSymbolName
|
||||
CSS = CommonSimSymbol
|
||||
return {
|
||||
CSS.X: SSN.LowerX,
|
||||
CSS.TimePS: SSN.LowerT,
|
||||
CSS.WavelengthNM: SSN.Wavelength,
|
||||
CSS.FrequencyTHZ: SSN.Frequency,
|
||||
}[self]
|
||||
|
||||
@property
|
||||
def sp_symbol(self):
|
||||
mathtype_kwarg = {}
|
||||
match self.mathtype:
|
||||
case spux.MathType.Real:
|
||||
mathtype_kwarg = {}
|
||||
def sim_symbol(self) -> SimSymbol:
|
||||
"""Retrieve the `SimSymbol` associated with the `CommonSimSymbol`."""
|
||||
CSS = CommonSimSymbol
|
||||
return {
|
||||
CSS.X: SimSymbol(
|
||||
sim_node_name=self.sim_symbol_name,
|
||||
mathtype=spux.MathType.Real,
|
||||
physical_type=spux.PhysicalType.NonPhysical,
|
||||
## TODO: Unit of Picosecond
|
||||
interval_finite=(sys.float_info.min, sys.float_info.max),
|
||||
interval_inf=(True, True),
|
||||
interval_closed=(False, False),
|
||||
),
|
||||
CSS.TimePS: SimSymbol(
|
||||
sim_node_name=self.sim_symbol_name,
|
||||
mathtype=spux.MathType.Real,
|
||||
physical_type=spux.PhysicalType.Time,
|
||||
## TODO: Unit of Picosecond
|
||||
interval_finite=(0, sys.float_info.max),
|
||||
interval_inf=(False, True),
|
||||
interval_closed=(True, False),
|
||||
),
|
||||
CSS.WavelengthNM: SimSymbol(
|
||||
sim_node_name=self.sim_symbol_name,
|
||||
mathtype=spux.MathType.Real,
|
||||
physical_type=spux.PhysicalType.Length,
|
||||
## TODO: Unit of Picosecond
|
||||
interval_finite=(0, sys.float_info.max),
|
||||
interval_inf=(False, True),
|
||||
interval_closed=(False, False),
|
||||
),
|
||||
CSS.FrequencyTHZ: SimSymbol(
|
||||
sim_node_name=self.sim_symbol_name,
|
||||
mathtype=spux.MathType.Real,
|
||||
physical_type=spux.PhysicalType.Freq,
|
||||
## TODO: Unit of THz
|
||||
interval_finite=(0, sys.float_info.max),
|
||||
interval_inf=(False, True),
|
||||
interval_closed=(False, False),
|
||||
),
|
||||
}[self]
|
||||
|
||||
return sp.Symbol(self.name, **mathtype_kwarg)
|
||||
|
||||
####################
|
||||
# - Selected Direct Access
|
||||
####################
|
||||
x = CommonSimSymbol.X.sim_symbol
|
||||
t_ps = CommonSimSymbol.TimePS.sim_symbol
|
||||
wl_nm = CommonSimSymbol.WavelengthNM.sim_symbol
|
||||
freq_thz = CommonSimSymbol.FrequencyTHZ.sim_symbol
|
||||
|
|
Loading…
Reference in New Issue