Skip to content

Commit 9b38123

Browse files
authored
feature: added an automatic logger that saves the simulation parameters in a logfile (#31)
* WIP: logfile * Created Logfile and amended the test * Delete build/lib/wakis/__init__.py * Delete build/lib/wakis/_version.py * Delete build/lib/wakis/conductors.py * Delete build/lib/wakis/conductors3d.py * Delete build/lib/wakis/field.py * Delete build/lib/wakis/pmlBlock2D.py * Delete build/lib/wakis/pmlBlock3D.py * Delete build/lib/wakis/routines.py * Delete build/lib/wakis/plotting.py * Delete build/lib/wakis/geometry.py * Delete build/lib/wakis/grid2D.py * Delete build/lib/wakis/grid3D.py * Delete build/lib/wakis/gridFIT3D.py * Delete build/lib/wakis/materials.py * Delete build/lib/wakis/solver2D.py * Delete build/lib/wakis/solver3D.py * Delete build/lib/wakis/solverFIT3D.py * Delete build/lib/wakis/sources.py * Delete build/lib/wakis/wakeSolver.py * Refactor log variable names in test file * Refactor logger to use 'grid' instead of 'grid_logs' * Refactor Logger class to stick to camelCase * Fix logger attribute assignment for wakeSolver * Refactor logger attribute assignments in solverFIT3D * Change logger dictionary from wakeSolver_logs to wakeSolver * Fix logger reference for wakeSolver * Fix logger grid assignment in solverFIT3D.py * Rename bg_log to bgLog in solverFIT3D.py * Update logger attribute access in test case * Fix logfile path in MPI lossy cavity test * Exchanged function assign_logger by update_logger * Fixed bugs * Fixed bug * Exchange list by dictionary in comparison * Fix indentation in gridLogs dictionary * Fix indentation in solverLogs dictionary * Separate changes from another pull request
1 parent c23b583 commit 9b38123

File tree

8 files changed

+169
-44
lines changed

8 files changed

+169
-44
lines changed

tests/test_001_pec_cubic_cavity.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ def test_simulation(self):
9999
skip_cells = 12 # no. cells to skip in WP integration
100100
wake = WakeSolver(q=q, sigmaz=sigmaz, beta=beta,
101101
xsource=xs, ysource=ys, xtest=xt, ytest=yt,
102-
save=False, logfile=False, Ez_file='tests/001_Ez.h5',
102+
save=False, Ez_file='tests/001_Ez.h5',
103103
skip_cells=skip_cells,
104104
)
105105

tests/test_007_mpi_lossy_cavity.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,18 @@ class TestMPILossyCavity:
8282
-6.04105997e+01 ,-3.06532160e+01 ,-1.17749936e+01 ,-3.12574866e+00,
8383
-7.35339521e-01 ,-1.13085658e-01 , 7.18247535e-01 , 8.73829036e-02])
8484

85+
gridLogs = {'use_mesh_refinement': False, 'Nx': 60, 'Ny': 60, 'Nz': 140, 'dx': 0.008666666348775227, 'dy': 0.008666666348775227,
86+
'dz': 0.005714285799435207, 'stl_solids': {'cavity': 'tests/stl/007_vacuum_cavity.stl', 'shell': 'tests/stl/007_lossymetal_shell.stl'},
87+
'stl_materials': {'cavity': 'vacuum', 'shell': [30, 1.0, 30]}, 'gridInitializationTime': 0}
88+
89+
solverLogs = {'use_gpu': False, 'use_mpi': False, 'background': 'pec','bc_low': ['pec', 'pec', 'pec'],
90+
'bc_high': ['pec', 'pec', 'pec'],
91+
'dt': 6.970326728398968e-12, 'solverInitializationTime': 0}
92+
93+
wakeSolverLogs = {'ti': 2.8516132094735135e-09, 'q': 1e-09, 'sigmaz': 0.1, 'beta': 1.0,
94+
'xsource': 0.0, 'ysource': 0.0, 'xtest': 0.0, 'ytest': 0.0, 'chargedist': None,
95+
'skip_cells': 10, 'results_folder': 'tests/007_results/', 'wakelength': 10.0, 'simulationTime': 0}
96+
8597
img_folder = 'tests/007_img/'
8698

8799
def test_mpi_import(self):
@@ -314,4 +326,14 @@ def test_long_impedance(self):
314326
assert np.allclose(np.real(wake.Z)[::20], np.real(self.Z), rtol=0.1), "Real Impedance samples failed"
315327
assert np.allclose(np.imag(wake.Z)[::20], np.imag(self.Z), rtol=0.1), "Imag Impedance samples failed"
316328
assert np.cumsum(np.abs(wake.Z))[-1] == pytest.approx(250910.51090497518, 0.1), "Abs Impedance cumsum failed"
317-
329+
330+
def test_log_file(self):
331+
global solver
332+
solver.logger.grid["gridInitializationTime"] = 0 #times can vary
333+
solver.logger.solver["solverInitializationTime"] = 0
334+
solver.logger.wakeSolver["simulationTime"] = 0
335+
logfile = os.path.join(solver.logger.wakeSolver["results_folder"], "wakis.log")
336+
assert os.path.exists(logfile), "Log file not created"
337+
assert solver.logger.grid == self.gridLogs, "Grid logs do not match expected values"
338+
assert solver.logger.solver == self.solverLogs, "Solver logs do not match expected values"
339+
assert solver.logger.wakeSolver == self.wakeSolverLogs, "WakeSolver logs do not match expected values"

wakis/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@
1010
from . import materials
1111
from . import wakeSolver
1212
from . import geometry
13+
from . import logger
1314
from . import field_monitors
1415

1516
from .field_monitors import FieldMonitor
1617
from .field import Field
1718
from .gridFIT3D import GridFIT3D
1819
from .solverFIT3D import SolverFIT3D
1920
from .wakeSolver import WakeSolver
21+
from .logger import Logger
2022

2123
from ._version import __version__

wakis/gridFIT3D.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
import pyvista as pv
88
from functools import partial
99
from scipy.optimize import least_squares
10+
import time
1011

1112
from .field import Field
13+
from .logger import Logger
1214

1315
try:
1416
from mpi4py import MPI
@@ -63,10 +65,13 @@ def __init__(self, xmin, xmax, ymin, ymax, zmin, zmax,
6365
stl_rotate=[0., 0., 0.], stl_translate=[0., 0., 0.], stl_scale=1.0,
6466
stl_colors=None, verbose=1, stl_tol=1e-3):
6567

68+
t0 = time.time()
69+
self.logger = Logger()
6670
if verbose: print('Generating grid...')
6771
self.verbose = verbose
6872
self.use_mpi = use_mpi
6973
self.use_mesh_refinement = use_mesh_refinement
74+
self.update_logger(['use_mesh_refinement'])
7075

7176
# domain limits
7277
self.xmin = xmin
@@ -81,6 +86,7 @@ def __init__(self, xmin, xmax, ymin, ymax, zmin, zmax,
8186
self.dx = (xmax - xmin) / Nx
8287
self.dy = (ymax - ymin) / Ny
8388
self.dz = (zmax - zmin) / Nz
89+
self.update_logger(['Nx', 'Ny', 'Nz', 'dx', 'dy', 'dz'])
8490

8591
# stl info
8692
self.stl_solids = stl_solids
@@ -89,6 +95,14 @@ def __init__(self, xmin, xmax, ymin, ymax, zmin, zmax,
8995
self.stl_translate = stl_translate
9096
self.stl_scale = stl_scale
9197
self.stl_colors = stl_colors
98+
self.update_logger(['stl_solids', 'stl_materials'])
99+
if stl_rotate != [0., 0., 0.]:
100+
self.update_logger(['stl_rotate'])
101+
if stl_translate != [0., 0., 0.]:
102+
self.update_logger(['stl_translate'])
103+
if stl_scale != 1.0:
104+
self.update_logger(['stl_scale'])
105+
92106
if stl_solids is not None:
93107
self._prepare_stl_dicts()
94108

@@ -143,6 +157,9 @@ def __init__(self, xmin, xmax, ymin, ymax, zmin, zmax,
143157
if stl_colors is None:
144158
self.assign_colors()
145159

160+
self.gridInitializationTime = time.time()-t0
161+
self.update_logger(['gridInitializationTime'])
162+
146163
def compute_grid(self):
147164
X, Y, Z = np.meshgrid(self.x, self.y, self.z, indexing='ij')
148165
self.grid = pv.StructuredGrid(X.transpose(), Y.transpose(), Z.transpose())
@@ -838,4 +855,11 @@ def clip(widget):
838855
if offscreen:
839856
pl.export_html('grid_inspect.html')
840857
else:
841-
pl.show()
858+
pl.show()
859+
860+
def update_logger(self, attrs):
861+
"""
862+
Assigns the parameters handed via attrs to the logger
863+
"""
864+
for atr in attrs:
865+
self.logger.grid[atr] = getattr(self, atr)

wakis/logger.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# copyright ################################# #
2+
# This file is part of the wakis Package. #
3+
# Copyright (c) CERN, 2025. #
4+
# ########################################### #
5+
6+
from tqdm import tqdm
7+
8+
import numpy as np
9+
import time
10+
import h5py
11+
import os
12+
import json
13+
14+
from scipy.constants import c as c_light, epsilon_0 as eps_0, mu_0 as mu_0
15+
from scipy.sparse import csc_matrix as sparse_mat
16+
from scipy.sparse import diags, hstack, vstack
17+
18+
19+
20+
class Logger():
21+
22+
def __init__(self):
23+
self.grid = {}
24+
self.solver = {}
25+
self.wakeSolver = {}
26+
27+
def save_logs(self):
28+
"""
29+
Save all logs (grid, solver, wakeSolver) into log-file inside the results folder.
30+
"""
31+
logfile = os.path.join(self.wakeSolver["results_folder"], "wakis.log")
32+
33+
# Write sections
34+
if not os.path.exists(self.wakeSolver["results_folder"]):
35+
os.mkdir(self.wakeSolver["results_folder"])
36+
37+
with open(logfile, "w", encoding="utf-8") as fh:
38+
fh.write("Simulation Parameters\n")
39+
fh.write("""=====================\n\n""")
40+
41+
sections = [
42+
("WakeSolver Logs", self.wakeSolver),
43+
("Solver Logs", self.solver),
44+
("Grid Logs", self.grid),
45+
]
46+
47+
for title, data in sections:
48+
fh.write(f"\n## {title} ##\n")
49+
if not data:
50+
fh.write("(empty)\n")
51+
continue
52+
53+
# convert non-serializable values to strings recursively
54+
def _convert(obj):
55+
if isinstance(obj, dict):
56+
return {k: _convert(v) for k, v in obj.items()}
57+
if isinstance(obj, (list, tuple)):
58+
return [_convert(v) for v in obj]
59+
try:
60+
json.dumps(obj)
61+
return obj
62+
except Exception:
63+
return str(obj)
64+
65+
clean = _convert(data)
66+
fh.write(json.dumps(clean, indent=2, ensure_ascii=False))
67+
fh.write("\n")

wakis/routines.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import numpy as np
77
import h5py
8+
import time
89
from tqdm import tqdm
910
from scipy.constants import c as c_light
1011
from wakis.sources import Beam
@@ -297,6 +298,7 @@ def save_to_h5(self, hf, field, x, y, z, comp, n):
297298
if plot_from is None: plot_from = int(self.ti/self.dt)
298299

299300
print('Running electromagnetic time-domain simulation...')
301+
t0 = time.time()
300302
for n in tqdm(range(Nt)):
301303

302304
# Initial condition
@@ -340,4 +342,9 @@ def save_to_h5(self, hf, field, x, y, z, comp, n):
340342

341343
# Compute wakefield magnitudes is done inside WakeSolver
342344
self.wake.solve(compute_plane=compute_plane)
343-
345+
346+
# Forward parameters to logger
347+
self.logger.wakeSolver=self.wake.logger.wakeSolver
348+
self.logger.wakeSolver["wakelength"]=wakelength
349+
self.logger.wakeSolver["simulationTime"]=time.time()-t0
350+
self.logger.save_logs()

wakis/solverFIT3D.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from .materials import material_lib
1818
from .plotting import PlotMixin
1919
from .routines import RoutinesMixin
20+
from .logger import Logger
2021

2122
try:
2223
from cupyx.scipy.sparse import csc_matrix as gpu_sparse_mat
@@ -93,7 +94,8 @@ def __init__(self, grid, wake=None, cfln=0.5, dt=None,
9394
'''
9495

9596
self.verbose = verbose
96-
if verbose: t0 = time.time()
97+
t0 = time.time()
98+
self.logger = Logger()
9799

98100
# Flags
99101
self.step_0 = True
@@ -110,10 +112,11 @@ def __init__(self, grid, wake=None, cfln=0.5, dt=None,
110112
self.one_step = self._one_step
111113
if use_stl:
112114
self.use_conductors = False
115+
self.update_logger(['use_gpu', 'use_mpi'])
113116

114117
# Grid
115118
self.grid = grid
116-
119+
self.background = bg
117120
self.Nx = self.grid.Nx
118121
self.Ny = self.grid.Ny
119122
self.Nz = self.grid.Nz
@@ -131,6 +134,7 @@ def __init__(self, grid, wake=None, cfln=0.5, dt=None,
131134
self.iA = self.grid.iA
132135
self.tL = self.grid.tL
133136
self.itA = self.grid.itA
137+
self.update_logger(['grid','background'])
134138

135139
# Wake computation
136140
self.wake = wake
@@ -175,6 +179,7 @@ def __init__(self, grid, wake=None, cfln=0.5, dt=None,
175179
if verbose: print('Applying boundary conditions...')
176180
self.bc_low = bc_low
177181
self.bc_high = bc_high
182+
self.update_logger(['bc_low', 'bc_high'])
178183
self.apply_bc_to_C()
179184

180185
# Materials
@@ -203,6 +208,7 @@ def __init__(self, grid, wake=None, cfln=0.5, dt=None,
203208
self.pml_hi = 1.e-1
204209
self.pml_func = np.geomspace
205210
self.fill_pml_sigmas()
211+
self.update_logger(['n_pml'])
206212

207213
# Timestep calculation
208214
if verbose: print('Calculating maximal stable timestep...')
@@ -212,6 +218,7 @@ def __init__(self, grid, wake=None, cfln=0.5, dt=None,
212218
else:
213219
self.dt = dt
214220
self.dt = dtype(self.dt)
221+
self.update_logger(['dt'])
215222

216223
if self.use_conductivity: # relaxation time criterion tau
217224

@@ -252,6 +259,9 @@ def __init__(self, grid, wake=None, cfln=0.5, dt=None,
252259

253260
if verbose: print(f'Total initialization time: {time.time() - t0} s')
254261

262+
self.solverInitializationTime = time.time() - t0
263+
self.update_logger(['solverInitializationTime'])
264+
255265
def update_tensors(self, tensor='all'):
256266
'''Update tensor matrices after
257267
Field ieps, imu or sigma have been modified
@@ -1107,4 +1117,14 @@ def reset_fields(self):
11071117
for d in ['x', 'y', 'z']:
11081118
self.E[:, :, :, d] = 0.0
11091119
self.H[:, :, :, d] = 0.0
1110-
self.J[:, :, :, d] = 0.0
1120+
self.J[:, :, :, d] = 0.0
1121+
1122+
def update_logger(self, attrs):
1123+
"""
1124+
Assigns the parameters handed via attrs to the logger
1125+
"""
1126+
for atr in attrs:
1127+
if atr == 'grid':
1128+
self.logger.grid = self.grid.logger.grid
1129+
else:
1130+
self.logger.solver[atr] = getattr(self, atr)

0 commit comments

Comments
 (0)