Skip to content

Commit 55b81f6

Browse files
authored
Merge pull request #16 from QuentinWach/codex/improve-topology-optimization-in-4_topo.py
Remove generated topology optimization images
2 parents 4e9d30a + c9a6531 commit 55b81f6

File tree

4 files changed

+454
-87
lines changed

4 files changed

+454
-87
lines changed

beamz/devices/monitors.py

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
1+
from typing import Callable, Optional
2+
13
import numpy as np
24
import matplotlib.pyplot as plt
35
from matplotlib.patches import Rectangle as MatplotlibRectangle
46

5-
class Monitor():
6-
def __init__(self, design=None, start=(0,0), end=None, plane_normal=None, plane_position=0,
7-
size=None, record_fields=True, accumulate_power=True, live_update=False,
8-
record_interval=1, max_history_steps=None):
7+
class Monitor():
8+
def __init__(self, design=None, start=(0,0), end=None, plane_normal=None, plane_position=0,
9+
size=None, record_fields=True, accumulate_power=True, live_update=False,
10+
record_interval=1, max_history_steps=None,
11+
objective_function: Optional[Callable[["Monitor"], float]] = None,
12+
name: Optional[str] = None):
913
self.design = design
1014
self.should_record_fields = record_fields
1115
self.accumulate_power = accumulate_power
1216
self.live_update = live_update
1317
self.record_interval = record_interval
1418
self.max_history_steps = max_history_steps
19+
self.objective_function = objective_function
20+
self.objective_value: Optional[float] = None
21+
self.name = name
1522

1623
# Determine if this is a 3D monitor based on input parameters
1724
self.is_3d = self._determine_3d_mode(start, end, design)
@@ -44,8 +51,26 @@ def __init__(self, design=None, start=(0,0), end=None, plane_normal=None, plane_
4451

4552
if self.is_3d:
4653
self._init_3d_monitor(start, end, plane_normal, plane_position, size)
47-
else:
54+
else:
4855
self._init_2d_monitor(start, end)
56+
57+
def evaluate_objective(self) -> Optional[float]:
58+
"""Evaluate the objective function associated with this monitor, if any."""
59+
if self.objective_function is None:
60+
return None
61+
try:
62+
value = self.objective_function(self)
63+
except Exception as exc:
64+
print(f"Warning: monitor objective evaluation failed: {exc}")
65+
return None
66+
if value is None:
67+
return None
68+
try:
69+
self.objective_value = float(value)
70+
except (TypeError, ValueError):
71+
print(f"Warning: monitor objective returned non-numeric value: {value}")
72+
return None
73+
return self.objective_value
4974

5075
def _determine_3d_mode(self, start, end, design):
5176
"""Determine if this should be a 3D monitor based on inputs."""

beamz/simulation/fdtd.py

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Dict, Optional
1+
from typing import Dict, Optional, Sequence
22
import datetime
33
import numpy as np
44

@@ -309,12 +309,12 @@ def _update_3d_fields(self):
309309
Ey[1:-1, :, 1:-1] = factor_ey * Ey[1:-1, :, 1:-1] + source_ey * (curlH_y)
310310
Ez[:, 1:-1, 1:-1] = factor_ez * Ez[:, 1:-1, 1:-1] + source_ez * (curlH_z)
311311

312-
def initialize_simulation(self, save=True, live=True, axis_scale=[-1,1], save_animation=False,
313-
animation_filename='fdtd_animation.mp4', clean_visualization=True,
312+
def initialize_simulation(self, save=True, live=True, axis_scale=[-1,1], save_animation=False,
313+
animation_filename='fdtd_animation.mp4', clean_visualization=True,
314314
save_fields=None, decimate_save=1, accumulate_power=False,
315-
save_memory_mode=False):
315+
save_memory_mode=False, fields_to_cache: Optional[Sequence[str]] = None):
316316
"""Initialize the simulation before running steps.
317-
317+
318318
Args:
319319
save: Whether to save field data at each step.
320320
live: Whether to show live animation of the simulation.
@@ -325,14 +325,16 @@ def initialize_simulation(self, save=True, live=True, axis_scale=[-1,1], save_an
325325
decimate_save: Save only every nth time step (1 = save all, 10 = save every 10th step)
326326
accumulate_power: Instead of saving all fields, accumulate power and save that
327327
save_memory_mode: If True, avoid storing all field data and only keep monitors/power
328+
fields_to_cache: Fields that should always be cached (complex values preserved)
328329
"""
329330
# Set default save_fields based on dimensionality
330331
if save_fields is None:
331332
if self.is_3d:
332333
save_fields = ['Ex', 'Ey', 'Ez', 'Hx', 'Hy', 'Hz']
333334
else:
334335
save_fields = ['Ez', 'Hx', 'Hy']
335-
336+
self._cache_fields = list(fields_to_cache) if fields_to_cache else []
337+
336338
# Record start time
337339
self.start_time = datetime.datetime.now()
338340
# Initialize simulation state
@@ -344,11 +346,15 @@ def initialize_simulation(self, save=True, live=True, axis_scale=[-1,1], save_an
344346
self._decimate_save = decimate_save
345347
self._live = live
346348
self._axis_scale = axis_scale
347-
349+
350+
# Reset stored results for a new run
351+
for key in list(self.results.keys()):
352+
self.results[key] = []
353+
348354
# Save mode flags as class attributes for monitor access
349355
self.save_memory_mode = save_memory_mode
350356
self.accumulate_power = accumulate_power
351-
357+
352358
# Display simulation header and parameters
353359
sim_params = {
354360
"Domain size": f"{self.design.width:.2e} x {self.design.height:.2e} m",
@@ -392,9 +398,10 @@ def initialize_simulation(self, save=True, live=True, axis_scale=[-1,1], save_an
392398
self._effective_save_freq = save_freq * decimate_save
393399

394400
# If in save_memory_mode, clear any existing results to start fresh
395-
if save_memory_mode:
401+
if save_memory_mode and not self._cache_fields:
396402
for field in self.results:
397-
if field != 't': self.results[field] = []
403+
if field != 't':
404+
self.results[field] = []
398405
display_status("Memory-saving mode active: Only storing monitor data and/or power accumulation", "info")
399406

400407
def step(self) -> bool:
@@ -429,14 +436,29 @@ def finalize_simulation(self):
429436
# Display memory usage estimate
430437
memory_usage = self.estimate_memory_usage(time_steps=self.num_steps, save_fields=self._save_fields)
431438
display_status(f"Estimated memory usage: {memory_usage['Full simulation']['Total memory (MB)']:.2f} MB", "info")
439+
objective_results: Dict[str, float] = {}
440+
for idx, monitor in enumerate(self.monitors):
441+
if hasattr(monitor, 'evaluate_objective'):
442+
value = monitor.evaluate_objective()
443+
else:
444+
value = None
445+
if value is None:
446+
continue
447+
key = getattr(monitor, 'name', None) or f"monitor_{idx}"
448+
objective_results[key] = value
449+
if objective_results:
450+
self.results['objectives'] = objective_results
451+
else:
452+
self.results.pop('objectives', None)
453+
self.last_objectives = objective_results
432454
return self.results
433455

434-
def run(self, steps: Optional[int] = None, save=True, live=True, axis_scale=[-1,1], save_animation=False,
435-
animation_filename='fdtd_animation.mp4', clean_visualization=True,
456+
def run(self, steps: Optional[int] = None, save=True, live=True, axis_scale=[-1,1], save_animation=False,
457+
animation_filename='fdtd_animation.mp4', clean_visualization=True,
436458
save_fields=None, decimate_save=1, accumulate_power=False,
437-
save_memory_mode=False) -> Dict:
459+
save_memory_mode=False, fields_to_cache: Optional[Sequence[str]] = None) -> Dict:
438460
"""Run the complete simulation using the new step-by-step approach.
439-
461+
440462
Args:
441463
steps: Number of steps to run. If None, run until the end of the time array.
442464
save: Whether to save field data at each step.
@@ -448,18 +470,20 @@ def run(self, steps: Optional[int] = None, save=True, live=True, axis_scale=[-1,
448470
decimate_save: Save only every nth time step (1 = save all, 10 = save every 10th step)
449471
accumulate_power: Instead of saving all fields, accumulate power and save that
450472
save_memory_mode: If True, avoid storing all field data and only keep monitors/power
451-
473+
fields_to_cache: Fields that should always be cached even when memory saving
474+
452475
Returns:
453476
Dictionary containing the simulation results.
454477
"""
455478
# Initialize the simulation
456-
self.initialize_simulation(save=save, live=live, axis_scale=axis_scale,
457-
save_animation=save_animation,
479+
self.initialize_simulation(save=save, live=live, axis_scale=axis_scale,
480+
save_animation=save_animation,
458481
animation_filename=animation_filename,
459482
clean_visualization=clean_visualization,
460483
save_fields=save_fields, decimate_save=decimate_save,
461-
accumulate_power=accumulate_power,
462-
save_memory_mode=save_memory_mode)
484+
accumulate_power=accumulate_power,
485+
save_memory_mode=save_memory_mode,
486+
fields_to_cache=fields_to_cache)
463487

464488
# Run the simulation with progress tracking
465489
with create_rich_progress() as progress:

beamz/simulation/helper.py

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -184,15 +184,39 @@ def accumulate_power(fdtd) -> None:
184184

185185
def save_step_results(fdtd) -> None:
186186
"""Save results for this time step if requested and at the right frequency."""
187-
if (fdtd._save_results and not fdtd.save_memory_mode and
188-
(fdtd.current_step % fdtd._effective_save_freq == 0 or fdtd.current_step == fdtd.num_steps - 1)):
189-
fdtd.results['t'].append(fdtd.t)
190-
for field in fdtd._save_fields:
191-
arr = getattr(fdtd, field)
192-
arr_np = fdtd.backend.to_numpy(fdtd.backend.copy(arr))
193-
if np.iscomplexobj(arr_np):
194-
arr_np = np.abs(arr_np)
195-
fdtd.results[field].append(arr_np)
187+
should_save_full = (
188+
fdtd._save_results
189+
and not fdtd.save_memory_mode
190+
and (fdtd.current_step % fdtd._effective_save_freq == 0 or fdtd.current_step == fdtd.num_steps - 1)
191+
)
192+
should_cache = (
193+
fdtd._save_results
194+
and fdtd._cache_fields
195+
and (fdtd.current_step % fdtd._effective_save_freq == 0 or fdtd.current_step == fdtd.num_steps - 1)
196+
)
197+
if not should_save_full and not should_cache:
198+
return
199+
200+
if 't' not in fdtd.results:
201+
fdtd.results['t'] = []
202+
fdtd.results['t'].append(fdtd.t)
203+
204+
fields_to_store = []
205+
if should_save_full:
206+
fields_to_store.extend(fdtd._save_fields)
207+
if should_cache:
208+
for field in fdtd._cache_fields:
209+
if field not in fields_to_store:
210+
fields_to_store.append(field)
211+
212+
for field in fields_to_store:
213+
arr = getattr(fdtd, field)
214+
arr_np = fdtd.backend.to_numpy(fdtd.backend.copy(arr))
215+
if np.iscomplexobj(arr_np) and (field not in fdtd._cache_fields):
216+
arr_np = np.abs(arr_np)
217+
if field not in fdtd.results:
218+
fdtd.results[field] = []
219+
fdtd.results[field].append(arr_np)
196220

197221
def record_monitor_data(fdtd, step: int) -> None:
198222
"""Record field data at monitor locations for current step."""

0 commit comments

Comments
 (0)