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})
|
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(
|
def realize(
|
||||||
self,
|
self,
|
||||||
symbol_values: dict[spux.Symbol, typ.Any] = MappingProxyType({}),
|
symbol_values: dict[spux.Symbol, typ.Any] = MappingProxyType({}),
|
||||||
|
|
|
@ -55,10 +55,7 @@ class NodeType(blender_type_enum.BlenderTypeEnum):
|
||||||
JSONFileExporter = enum.auto()
|
JSONFileExporter = enum.auto()
|
||||||
|
|
||||||
# Sources
|
# Sources
|
||||||
## Sources / Temporal Shapes
|
TemporalShape = enum.auto()
|
||||||
PulseTemporalShape = enum.auto()
|
|
||||||
WaveTemporalShape = enum.auto()
|
|
||||||
ExprTemporalShape = enum.auto()
|
|
||||||
## Sources /
|
## Sources /
|
||||||
PointDipoleSource = enum.auto()
|
PointDipoleSource = enum.auto()
|
||||||
PlaneWaveSource = enum.auto()
|
PlaneWaveSource = enum.auto()
|
||||||
|
|
|
@ -374,7 +374,7 @@ class MapMathNode(base.MaxwellSimNode):
|
||||||
# - Properties
|
# - Properties
|
||||||
####################
|
####################
|
||||||
operation: MapOperation = bl_cache.BLField(
|
operation: MapOperation = bl_cache.BLField(
|
||||||
prop_ui=True, enum_cb=lambda self, _: self.search_operations()
|
enum_cb=lambda self, _: self.search_operations()
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -22,7 +22,7 @@ import jaxtyping as jtyp
|
||||||
import matplotlib.axis as mpl_ax
|
import matplotlib.axis as mpl_ax
|
||||||
import sympy as sp
|
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 blender_maxwell.utils import extra_sympy_units as spux
|
||||||
|
|
||||||
from ... import contracts as ct
|
from ... import contracts as ct
|
||||||
|
@ -210,8 +210,8 @@ class VizNode(base.MaxwellSimNode):
|
||||||
input_sockets: typ.ClassVar = {
|
input_sockets: typ.ClassVar = {
|
||||||
'Expr': sockets.ExprSocketDef(
|
'Expr': sockets.ExprSocketDef(
|
||||||
active_kind=ct.FlowKind.LazyValueFunc,
|
active_kind=ct.FlowKind.LazyValueFunc,
|
||||||
symbols={_x := sp.Symbol('x', real=True)},
|
default_symbols=[sim_symbols.x],
|
||||||
default_value=2 * _x,
|
default_value=2 * sim_symbols.x.sp_symbol,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
output_sockets: typ.ClassVar = {
|
output_sockets: typ.ClassVar = {
|
||||||
|
@ -295,8 +295,8 @@ class VizNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
@events.on_value_changed(
|
@events.on_value_changed(
|
||||||
socket_name='Expr',
|
socket_name='Expr',
|
||||||
input_sockets={'Expr'},
|
|
||||||
run_on_init=True,
|
run_on_init=True,
|
||||||
|
input_sockets={'Expr'},
|
||||||
input_socket_kinds={'Expr': {ct.FlowKind.Info, ct.FlowKind.Params}},
|
input_socket_kinds={'Expr': {ct.FlowKind.Info, ct.FlowKind.Params}},
|
||||||
input_sockets_optional={'Expr': True},
|
input_sockets_optional={'Expr': True},
|
||||||
)
|
)
|
||||||
|
|
|
@ -19,11 +19,11 @@ from . import (
|
||||||
gaussian_beam_source,
|
gaussian_beam_source,
|
||||||
plane_wave_source,
|
plane_wave_source,
|
||||||
point_dipole_source,
|
point_dipole_source,
|
||||||
temporal_shapes,
|
temporal_shape,
|
||||||
)
|
)
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
*temporal_shapes.BL_REGISTER,
|
*temporal_shape.BL_REGISTER,
|
||||||
*plane_wave_source.BL_REGISTER,
|
*plane_wave_source.BL_REGISTER,
|
||||||
*point_dipole_source.BL_REGISTER,
|
*point_dipole_source.BL_REGISTER,
|
||||||
# *uniform_current_source.BL_REGISTER,
|
# *uniform_current_source.BL_REGISTER,
|
||||||
|
@ -32,7 +32,7 @@ BL_REGISTER = [
|
||||||
# *tfsf_source.BL_REGISTER,
|
# *tfsf_source.BL_REGISTER,
|
||||||
]
|
]
|
||||||
BL_NODES = {
|
BL_NODES = {
|
||||||
**temporal_shapes.BL_NODES,
|
**temporal_shape.BL_NODES,
|
||||||
**plane_wave_source.BL_NODES,
|
**plane_wave_source.BL_NODES,
|
||||||
**point_dipole_source.BL_NODES,
|
**point_dipole_source.BL_NODES,
|
||||||
# **uniform_current_source.BL_NODES,
|
# **uniform_current_source.BL_NODES,
|
||||||
|
|
|
@ -28,6 +28,8 @@ from ... import contracts as ct
|
||||||
from ... import managed_objs, sockets
|
from ... import managed_objs, sockets
|
||||||
from .. import base, events
|
from .. import base, events
|
||||||
|
|
||||||
|
log = logger.get(__name__)
|
||||||
|
|
||||||
|
|
||||||
class PlaneWaveSourceNode(base.MaxwellSimNode):
|
class PlaneWaveSourceNode(base.MaxwellSimNode):
|
||||||
"""An infinite-extent angled source simulating an plane wave with linear polarization.
|
"""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 = {
|
managed_obj_types: typ.ClassVar = {
|
||||||
'mesh': managed_objs.ManagedBLMesh,
|
|
||||||
'modifier': managed_objs.ManagedBLModifier,
|
'modifier': managed_objs.ManagedBLModifier,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,18 +133,14 @@ class PlaneWaveSourceNode(base.MaxwellSimNode):
|
||||||
# Trigger
|
# Trigger
|
||||||
prop_name='preview_active',
|
prop_name='preview_active',
|
||||||
# Loaded
|
# Loaded
|
||||||
managed_objs={'mesh'},
|
managed_objs={'modifier'},
|
||||||
props={'preview_active'},
|
props={'preview_active'},
|
||||||
)
|
)
|
||||||
def on_preview_changed(self, managed_objs, props):
|
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']:
|
if props['preview_active']:
|
||||||
mesh.show_preview()
|
managed_objs['modifier'].show_preview()
|
||||||
else:
|
else:
|
||||||
mesh.hide_preview()
|
managed_objs['modifier'].hide_preview()
|
||||||
|
|
||||||
@events.on_value_changed(
|
@events.on_value_changed(
|
||||||
# Trigger
|
# Trigger
|
||||||
|
@ -151,7 +148,7 @@ class PlaneWaveSourceNode(base.MaxwellSimNode):
|
||||||
prop_name={'injection_axis', 'injection_direction'},
|
prop_name={'injection_axis', 'injection_direction'},
|
||||||
run_on_init=True,
|
run_on_init=True,
|
||||||
# Loaded
|
# Loaded
|
||||||
managed_objs={'mesh', 'modifier'},
|
managed_objs={'modifier'},
|
||||||
props={'injection_axis', 'injection_direction'},
|
props={'injection_axis', 'injection_direction'},
|
||||||
input_sockets={'Temporal Shape', 'Center', 'Spherical', 'Pol ∡'},
|
input_sockets={'Temporal Shape', 'Center', 'Spherical', 'Pol ∡'},
|
||||||
unit_systems={'BlenderUnits': ct.UNITS_BLENDER},
|
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):
|
def on_inputs_changed(self, managed_objs, props, input_sockets, unit_systems):
|
||||||
# Push Input Values to GeoNodes Modifier
|
# Push Input Values to GeoNodes Modifier
|
||||||
managed_objs['modifier'].bl_modifier(
|
managed_objs['modifier'].bl_modifier(
|
||||||
managed_objs['mesh'].bl_object(location=input_sockets['Center']),
|
|
||||||
'NODES',
|
'NODES',
|
||||||
{
|
{
|
||||||
'node_group': import_geonodes(GeoNodes.SourcePlaneWave),
|
'node_group': import_geonodes(GeoNodes.SourcePlaneWave),
|
||||||
|
@ -175,6 +171,7 @@ class PlaneWaveSourceNode(base.MaxwellSimNode):
|
||||||
'Pol Angle': input_sockets['Pol ∡'],
|
'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 pydantic as pyd
|
||||||
import sympy as sp
|
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 blender_maxwell.utils import extra_sympy_units as spux
|
||||||
|
|
||||||
from .. import contracts as ct
|
from .. import contracts as ct
|
||||||
|
@ -936,8 +936,11 @@ class ExprSocketDef(base.SocketDef):
|
||||||
physical_type: spux.PhysicalType = spux.PhysicalType.NonPhysical
|
physical_type: spux.PhysicalType = spux.PhysicalType.NonPhysical
|
||||||
|
|
||||||
default_unit: spux.Unit | None = None
|
default_unit: spux.Unit | None = None
|
||||||
# symbols: list[sim_symbols.SimSymbol] = frozenset()
|
default_symbols: list[sim_symbols.SimSymbol] = pyd.Field(default_factory=list)
|
||||||
symbols: frozenset[spux.SympyExpr] = frozenset()
|
|
||||||
|
@property
|
||||||
|
def symbols(self) -> set[sp.Symbol]:
|
||||||
|
return {sim_symbol.sp_symbol for sim_symbol in self.default_symbols}
|
||||||
|
|
||||||
# FlowKind: Value
|
# FlowKind: Value
|
||||||
default_value: spux.SympyExpr = 0
|
default_value: spux.SympyExpr = 0
|
||||||
|
|
|
@ -173,6 +173,17 @@ class MathType(enum.StrEnum):
|
||||||
MT.Complex: complex,
|
MT.Complex: complex,
|
||||||
}[self]
|
}[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
|
@staticmethod
|
||||||
def to_str(value: typ.Self) -> type:
|
def to_str(value: typ.Self) -> type:
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
import dataclasses
|
import dataclasses
|
||||||
import enum
|
import enum
|
||||||
|
import sys
|
||||||
import typing as typ
|
import typing as typ
|
||||||
|
|
||||||
import sympy as sp
|
import sympy as sp
|
||||||
|
@ -23,9 +24,19 @@ import sympy as sp
|
||||||
from . import extra_sympy_units as spux
|
from . import extra_sympy_units as spux
|
||||||
|
|
||||||
|
|
||||||
class SimSymbolNames(enum.StrEnum):
|
####################
|
||||||
|
# - Simulation Symbols
|
||||||
|
####################
|
||||||
|
class SimSymbolName(enum.StrEnum):
|
||||||
LowerA = enum.auto()
|
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
|
@staticmethod
|
||||||
def to_name(v: typ.Self) -> str:
|
def to_name(v: typ.Self) -> str:
|
||||||
|
@ -37,11 +48,28 @@ class SimSymbolNames(enum.StrEnum):
|
||||||
Returns:
|
Returns:
|
||||||
A human-friendly name corresponding to the enum value.
|
A human-friendly name corresponding to the enum value.
|
||||||
"""
|
"""
|
||||||
SSN = SimSymbolNames
|
return SimSymbolName(v).name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
SSN = SimSymbolName
|
||||||
return {
|
return {
|
||||||
SSN.LowerA: 'a',
|
SSN.LowerA: 'a',
|
||||||
SSN.LowerLambda: 'λ',
|
SSN.LowerT: 't',
|
||||||
}[v]
|
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
|
@staticmethod
|
||||||
def to_icon(_: typ.Self) -> str:
|
def to_icon(_: typ.Self) -> str:
|
||||||
|
@ -58,19 +86,214 @@ class SimSymbolNames(enum.StrEnum):
|
||||||
|
|
||||||
@dataclasses.dataclass(kw_only=True, frozen=True)
|
@dataclasses.dataclass(kw_only=True, frozen=True)
|
||||||
class SimSymbol:
|
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
|
mathtype: spux.MathType = spux.MathType.Real
|
||||||
|
|
||||||
## TODO:
|
physical_type: spux.PhysicalType = spux.PhysicalType.NonPhysical
|
||||||
## -> Physical Type: Track unit dimension information on the side.
|
|
||||||
## -> Domain: Ability to constrain mathtype ex. (-pi,pi]
|
## TODO: Shape/size support? Incl. MatrixSymbol.
|
||||||
## -> Shape: For using sp.MatrixSymbol w/predefined rows/cols.
|
|
||||||
|
# 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
|
@property
|
||||||
def sp_symbol(self):
|
def sim_symbol(self) -> SimSymbol:
|
||||||
mathtype_kwarg = {}
|
"""Retrieve the `SimSymbol` associated with the `CommonSimSymbol`."""
|
||||||
match self.mathtype:
|
CSS = CommonSimSymbol
|
||||||
case spux.MathType.Real:
|
return {
|
||||||
mathtype_kwarg = {}
|
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