Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
fd04199
Adding new topo design region.
QuentinWach Sep 28, 2025
af40496
Setting up topo.
QuentinWach Sep 28, 2025
eab5e8d
Adding a topology module with helpers.
QuentinWach Sep 28, 2025
4a2d988
Filling in the topo module.
QuentinWach Sep 28, 2025
d04df3b
.
QuentinWach Sep 28, 2025
ca82e5f
Adding more debugging visualization to debugging.
QuentinWach Sep 28, 2025
0f54c38
.
QuentinWach Sep 28, 2025
2656155
.
QuentinWach Sep 29, 2025
3d2a189
Added ascii art for random fun + more thoughts on topology optimizati…
QuentinWach Sep 29, 2025
01707e1
Allow FDTD to take in Grid objects.
QuentinWach Sep 29, 2025
aa49aa1
Split design module into design + devices modules.
QuentinWach Sep 29, 2025
3d16cd4
Allow for inserting devices directly into the FDTD simulation.
QuentinWach Sep 29, 2025
182b8a6
.
QuentinWach Sep 29, 2025
d99f428
Major improvements to the topo example!
QuentinWach Sep 29, 2025
a26278f
.
QuentinWach Sep 29, 2025
7dadd1e
.
QuentinWach Sep 29, 2025
569eded
Adding an objective function.
QuentinWach Sep 29, 2025
de5bd56
Minor changes + fixed imports.
QuentinWach Sep 30, 2025
4e9d30a
Adding filter operations and minor changes.
QuentinWach Sep 30, 2025
c9a6531
Remove generated topology optimization images
QuentinWach Oct 1, 2025
55b81f6
Merge pull request #16 from QuentinWach/codex/improve-topology-optimi…
QuentinWach Oct 1, 2025
7e63f36
Remove generated topology optimization plots
QuentinWach Oct 1, 2025
f090e18
Merge branch 'new_topo_codex' into codex/improve-topology-optimizatio…
QuentinWach Oct 1, 2025
67c118c
Merge pull request #18 from QuentinWach/codex/improve-topology-optimi…
QuentinWach Oct 1, 2025
ff162eb
Update topology optimization to apply gradient updates
QuentinWach Oct 1, 2025
0a293dc
Merge branch 'new_topo_codex' into codex/improve-topology-optimizatio…
QuentinWach Oct 1, 2025
fc7af6a
Merge pull request #19 from QuentinWach/codex/improve-topology-optimi…
QuentinWach Oct 1, 2025
c89b920
Update permittivity fix. WIP.
QuentinWach Oct 1, 2025
aecde92
.
QuentinWach Oct 1, 2025
aa13c02
.
QuentinWach Oct 1, 2025
3c764d3
.
QuentinWach Oct 1, 2025
12b4c00
Fix in-place permittivity updates in topology example
QuentinWach Oct 1, 2025
94604d6
Merge pull request #20 from QuentinWach/codex/debug-design-region-per…
QuentinWach Oct 1, 2025
90c558d
Update topology grid in place and log changes
QuentinWach Oct 1, 2025
9d965cf
Merge branch 'new_topo_codex' into codex/debug-design-region-permitti…
QuentinWach Oct 1, 2025
688aca9
Merge pull request #21 from QuentinWach/codex/debug-design-region-per…
QuentinWach Oct 1, 2025
4f87f1e
Fix topology example permittivity updates and headless plotting
QuentinWach Oct 1, 2025
996922e
Merge branch 'new_topo_codex' into codex/debug-design-region-permitti…
QuentinWach Oct 1, 2025
4a4d467
Merge pull request #22 from QuentinWach/codex/debug-design-region-per…
QuentinWach Oct 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 0 additions & 58 deletions .github/workflows/tests.yml

This file was deleted.

27 changes: 27 additions & 0 deletions ascii.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@

@@@@@@@: @@@@@@@ =@@@ @@@# @@@@
@@@ :@@% @@@ @@@@@ @@@@. %@@@@
@@@@@@@- @@@@@@* @@@-@@. @@=@@+@@%@@
@@@ @@@ @@@ @@@@@@@@ @@ %@@@:%@@
@@@@@@@: @@@@@@@+@@ @@@.@@ @@@ #@@

*@@@@@@@@@%*=::::---:::---::---+%@*=*+
-%#-=@@@@@@#+=------==-=%%+::::..-@@#
*@+-*@+=#++*#***+===++%@@@= .*@=-%-
#@@@+.=--#@: -+-+. .=%: -@@
#@@---:=@- #=#= ..-%- @@@
#@@*=+@= #+**:..-=*- *%*
*@@%@+ +*##-::-+#+ +#+
@@+ *%#%*+==+** =+=
-%@%@@%#***#. :+=.
.@@@@@@%%%#%= #=-
%@@@@@@@@@# .*==
+%@@@@@@@@@- .**+. @*-
=*%@@@@@@@@*. +%#- *@@@#
=%%@#%@@@@@#..+#%= =**@@--
*@@@%+%@@@@@:.:*%* -#=@+ =::
@@@@:-*@@@@=.:*#* -%=%% +::
=#@--+%@@=..@%%%**+=--==*##%@@. .**-
-#@#%@@@#*+=#@*=++==-==+**#%=:.:=+%@@=
@@*@@%+*#%@@@%+-:.......::-+=:=#@#@@@@

15 changes: 11 additions & 4 deletions beamz/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,19 @@
Rectangle, Circle, Ring,
CircularBend, Polygon, Taper
)
from beamz.design.sources import ModeSource, GaussianSource
from beamz.design.monitors import Monitor
from beamz.design.signals import ramped_cosine, plot_signal
from beamz.design.mode import solve_modes, slab_mode_source
from beamz.devices.sources import ModeSource, GaussianSource
from beamz.devices.monitors import Monitor
from beamz.devices.signals import ramped_cosine, plot_signal
from beamz.devices.mode import solve_modes, slab_mode_source

# Import simulation-related classes and functions
from beamz.simulation.meshing import RegularGrid
from beamz.simulation.fdtd import FDTD
from beamz.simulation.backends import get_backend

from beamz.optimization.optimizers import Optimizer
from beamz.optimization.topology import compute_overlap_gradient

# Import optimization-related classes
# (Currently empty, to be filled as the module grows)

Expand Down Expand Up @@ -81,6 +84,10 @@
'RegularGrid': RegularGrid,
'FDTD': FDTD,
'get_backend': get_backend,

# Optimization
'Optimizer': Optimizer,
'compute_overlap_gradient': compute_overlap_gradient,

# UI helpers
'display_header': display_header,
Expand Down
8 changes: 4 additions & 4 deletions beamz/design/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
Rectangle, Circle, Ring, CircularBend, Polygon, Taper
)
from beamz.design.pml import PML
from beamz.design.sources import ModeSource, GaussianSource
from beamz.design.monitors import Monitor
from beamz.design.signals import ramped_cosine, plot_signal
from beamz.design.mode import solve_modes, slab_mode_source
from beamz.devices.sources import ModeSource, GaussianSource
from beamz.devices.monitors import Monitor
from beamz.devices.signals import ramped_cosine, plot_signal
from beamz.devices.mode import solve_modes, slab_mode_source

__all__ = [
'Material', 'CustomMaterial',
Expand Down
8 changes: 4 additions & 4 deletions beamz/design/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
from beamz.helpers import display_status

from beamz.design.materials import Material
from beamz.design.sources import ModeSource, GaussianSource
from beamz.design.monitors import Monitor
from beamz.devices.sources import ModeSource, GaussianSource
from beamz.devices.monitors import Monitor
from beamz.design.structures import Polygon, Rectangle, Circle, Ring, CircularBend, Taper
from beamz.design.pml import PML

Expand Down Expand Up @@ -178,8 +178,8 @@ def unify_polygons(self):
non_polygon_structures.append(structure)
continue
# Import at function level to avoid circular imports
from beamz.design.sources import ModeSource, GaussianSource
from beamz.design.monitors import Monitor
from beamz.devices.sources import ModeSource, GaussianSource
from beamz.devices.monitors import Monitor
if isinstance(structure, (ModeSource, GaussianSource, Monitor)):
non_polygon_structures.append(structure)
continue
Expand Down
6 changes: 3 additions & 3 deletions beamz/design/structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def _process_vertices(self, vertices, z=0):
vertices_3d = [(x, y, vertices_3d[i][2] if len(vertices_3d[i]) > 2 else z)
for i, (x, y) in enumerate(vertices_2d)]
return vertices_3d

def _process_vertices_preserve_orientation(self, vertices, z=0):
if not vertices: return []
vertices_3d = self._ensure_3d_vertices(vertices)
Expand Down Expand Up @@ -345,7 +345,7 @@ def add_to_plot(self, ax, facecolor=None, edgecolor="black", alpha=None, linesty
if alpha is None: alpha = 1
if linestyle is None: linestyle = '-'
return super().add_to_plot(ax, facecolor=facecolor, edgecolor=edgecolor, alpha=alpha, linestyle=linestyle)

def copy(self):
return Ring(position=self.position,
inner_radius=self.inner_radius,
Expand Down Expand Up @@ -412,7 +412,7 @@ def add_to_plot(self, ax, facecolor=None, edgecolor="black", alpha=None, linesty
if linestyle is None: linestyle = '-'
# Use parent polygon drawing
return super().add_to_plot(ax, facecolor=facecolor, edgecolor=edgecolor, alpha=alpha, linestyle=linestyle)

def copy(self):
return CircularBend(self.position, self.inner_radius, self.outer_radius,
self.angle, self.rotation, self.material, self.color, self.optimize,
Expand Down
File renamed without changes.
64 changes: 50 additions & 14 deletions beamz/design/monitors.py → beamz/devices/monitors.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
from typing import Callable, Optional

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle as MatplotlibRectangle

class Monitor():
def __init__(self, design=None, start=(0,0), end=None, plane_normal=None, plane_position=0,
size=None, record_fields=True, accumulate_power=True, live_update=False,
record_interval=1, max_history_steps=None):
class Monitor():
def __init__(self, design=None, start=(0,0), end=None, plane_normal=None, plane_position=0,
size=None, record_fields=True, accumulate_power=True, live_update=False,
record_interval=1, max_history_steps=None,
objective_function: Optional[Callable[["Monitor"], float]] = None,
name: Optional[str] = None):
self.design = design
self.should_record_fields = record_fields
self.accumulate_power = accumulate_power
self.live_update = live_update
self.record_interval = record_interval
self.max_history_steps = max_history_steps
self.objective_function = objective_function
self.objective_value: Optional[float] = None
self.name = name

# Determine if this is a 3D monitor based on input parameters
self.is_3d = self._determine_3d_mode(start, end, design)
Expand All @@ -32,6 +39,7 @@ def __init__(self, design=None, start=(0,0), end=None, plane_normal=None, plane_
self.power_accumulated = None
self.energy_history = []
self.power_history = []
self.power_timestamps = []
self.power_accumulation_count = 0
# Recording control
self.step_count = 0
Expand All @@ -44,8 +52,26 @@ def __init__(self, design=None, start=(0,0), end=None, plane_normal=None, plane_

if self.is_3d:
self._init_3d_monitor(start, end, plane_normal, plane_position, size)
else:
else:
self._init_2d_monitor(start, end)

def evaluate_objective(self) -> Optional[float]:
"""Evaluate the objective function associated with this monitor, if any."""
if self.objective_function is None:
return None
try:
value = self.objective_function(self)
except Exception as exc:
print(f"Warning: monitor objective evaluation failed: {exc}")
return None
if value is None:
return None
try:
self.objective_value = float(value)
except (TypeError, ValueError):
print(f"Warning: monitor objective returned non-numeric value: {value}")
return None
return self.objective_value

def _determine_3d_mode(self, start, end, design):
"""Determine if this should be a 3D monitor based on inputs."""
Expand Down Expand Up @@ -280,7 +306,7 @@ def record_fields_2d(self, Ez, Hx, Hy, t, dx, dy, step=0):
self.fields['t'].append(t)

if self.accumulate_power:
self._calculate_power_2d(Ez_values, Hx_values, Hy_values)
self._calculate_power_2d(Ez_values, Hx_values, Hy_values, t)

self.last_record_step = step
self._manage_memory()
Expand Down Expand Up @@ -361,7 +387,7 @@ def slice_field(arr):
self.fields['t'].append(t)

if self.accumulate_power:
self._calculate_power_3d(Ex_slice, Ey_slice, Ez_slice, Hx_slice, Hy_slice, Hz_slice)
self._calculate_power_3d(Ex_slice, Ey_slice, Ez_slice, Hx_slice, Hy_slice, Hz_slice, t)

self.last_record_step = step
self._manage_memory()
Expand All @@ -378,7 +404,7 @@ def record_fields(self, *args, **kwargs):
# 2D: Ez, Hx, Hy, t, dx, dy, step
self.record_fields_2d(*args, **kwargs)

def _calculate_power_2d(self, Ez_values, Hx_values, Hy_values):
def _calculate_power_2d(self, Ez_values, Hx_values, Hy_values, t):
"""Calculate Poynting vector and power for 2D fields."""
Ez_array = np.array(Ez_values)
Hx_array = np.array(Hx_values)
Expand All @@ -394,9 +420,10 @@ def _calculate_power_2d(self, Ez_values, Hx_values, Hy_values):
else:
self.power_accumulated += power_mag
self.power_history.append(total_power)
self.power_timestamps.append(float(t))
self.power_accumulation_count += 1
def _calculate_power_3d(self, Ex, Ey, Ez, Hx, Hy, Hz):

def _calculate_power_3d(self, Ex, Ey, Ez, Hx, Hy, Hz, t):
"""Calculate Poynting vector and power for 3D fields."""
# Poynting vector S = E × H
Sx = Ey * Hz - Ez * Hy
Expand All @@ -408,6 +435,7 @@ def _calculate_power_3d(self, Ex, Ey, Ez, Hx, Hy, Hz):
if self.power_accumulated is None: self.power_accumulated = power_mag.copy()
else: self.power_accumulated += power_mag
self.power_history.append(total_power)
self.power_timestamps.append(float(t))
self.power_accumulation_count += 1

def _manage_memory(self):
Expand All @@ -422,6 +450,7 @@ def _manage_memory(self):
if len(self.power_history) > self.max_history_steps:
excess = len(self.power_history) - self.max_history_steps
self.power_history = self.power_history[excess:]
self.power_timestamps = self.power_timestamps[excess:]

def start_live_visualization(self, field_component='Ez'):
"""Start live field visualization."""
Expand Down Expand Up @@ -540,17 +569,24 @@ def get_field_statistics(self):
def save_data(self, filename, format='npz'):
"""Save recorded data to file."""
if format == 'npz':
np.savez(filename,
fields=self.fields,
power_history=self.power_history,
monitor_info={'type': self.monitor_type, 'is_3d': self.is_3d})
np.savez(
filename,
fields=self.fields,
power_history=self.power_history,
power_timestamps=self.power_timestamps,
monitor_info={'type': self.monitor_type, 'is_3d': self.is_3d}
)
else: raise ValueError(f"Unsupported format: {format}")

def load_data(self, filename):
"""Load data from file."""
data = np.load(filename, allow_pickle=True)
self.fields = data['fields'].item()
self.power_history = list(data['power_history'])
if 'power_timestamps' in data:
self.power_timestamps = list(data['power_timestamps'])
else:
self.power_timestamps = list(range(len(self.power_history)))

def add_to_plot(self, ax, facecolor="navy", edgecolor="navy", alpha=1, linestyle="-"):
"""Add monitor visualization to 2D plot."""
Expand Down
File renamed without changes.
4 changes: 2 additions & 2 deletions beamz/design/sources.py → beamz/devices/sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from beamz.const import LIGHT_SPEED, µm
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from beamz.design.mode import solve_modes
from beamz.devices.mode import solve_modes

class GaussianSource():
"""A Gaussian current distribution in space.
Expand Down Expand Up @@ -126,7 +126,7 @@ def __init__(self, design, position=None, width=None, height=None, wavelength=1.
# This assumes a simple rectangular waveguide structure
# Extract core/cladding indices for analytical solver
try:
from beamz.design.mode import slab_mode_source
from beamz.devices.mode import slab_mode_source

# Sample x coordinates along the cross-section
num_points = eps_1d.size
Expand Down
10 changes: 4 additions & 6 deletions beamz/optimization/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
"""
Optimization module for BEAMZ - Contains adjoint optimization functionality.
"""
"""Adjoint-based optimization helpers for BEAMZ."""

# Currently empty as the module is under development
# Will be populated as optimization features are added
from . import topology
from .optimizers import Optimizer

__all__ = []
__all__ = ["topology", "Optimizer"]
Loading