8 Loads multiple simulation CSV outputs and provides visualization tools.
10 CSV dictionary example:
11 {"phi": "phi.csv", "efield": "efield.csv", "x": "x.csv", "time": "time.csv", "dx": "dx.csv"}
13 Node-based fields are plotted at nodes (x.csv).
14 Cell-based fields are plotted at cell centers (dx.csv used to compute positions).
16 def __init__(self, folder="./output", csv_dict=None, cell_fields=None):
21 Relative path to folder containing the CSV files.
23 Dictionary mapping attribute names -> CSV filenames.
25 - "x": spatial node positions (single column)
26 - "time": time array (single column)
27 - "dx": cell sizes (single column)
29 List of names that are defined on cells (not nodes).
32 plt.rcParams.update({
'font.size':15,
'lines.linewidth':2.5,
'figure.autolayout':
True})
36 raise ValueError(
"You must provide a csv_dict mapping names to CSV files.")
37 if cell_fields
is None:
40 self.
_names = list(csv_dict.keys())
50 for name, fname
in csv_dict.items():
51 path = os.path.join(folder, fname)
52 if not os.path.exists(path):
53 raise FileNotFoundError(f
"CSV file not found: {path}")
54 print(f
"[PostProcessor] Loading {name} from {path}")
55 data = np.loadtxt(path, skiprows=1)
58 if name
in (
"x",
"time",
"dx"):
60 setattr(self, name, data)
62 setattr(self, name, data)
65 first_field = next((n
for n
in self.
_names if n
not in (
"x",
"time",
"dx")),
None)
66 if first_field
is None:
67 raise ValueError(
"No field CSVs found (excluding 'x', 'time', 'dx').")
69 data_field = getattr(self, first_field)
72 self.
x = np.arange(data_field.shape[1])
74 self.
time = np.arange(data_field.shape[0])
77 if hasattr(self,
"dx")
and self.
dx is not None:
78 if len(self.
dx) != len(self.
x)-1:
79 raise ValueError(
"dx length must be number of nodes - 1")
85 def _get_x_for_field(self, name):
88 raise ValueError(f
"Field '{name}' is cell-based but dx/x not defined")
96 def plot_timestep(self, name, t, ax=None):
97 if not hasattr(self, name):
98 raise AttributeError(f
"No field named '{name}' loaded.")
99 data = getattr(self, name)
101 if t < 0
or t >= data.shape[0]:
102 raise IndexError(f
"Timestep {t} is out of range")
107 fig, ax = plt.subplots()
111 x_step = np.zeros(len(x_axis)+1)
114 last_dx = self.
dx[-1]
if self.
dx is not None else x_axis[-1]-x_axis[-2]
115 x_step[-1] = x_axis[-1] + last_dx
117 y_step = np.zeros(len(x_axis)+1)
118 y_step[:-1] = data[t, :]
119 y_step[-1] = data[t, -1]
121 ax.step(x_step, y_step, where=
'post', lw=2)
123 ax.plot(x_axis, data[t, :], lw=2)
125 ax.set_title(f
"{name} at t = {self.time[t]:.3e}")
126 ax.set_xlabel(
"x-position")
136 def animate(self, name, step=1, interval=80):
137 if not hasattr(self, name):
138 raise AttributeError(f
"No field named '{name}' loaded.")
139 data = getattr(self, name)
142 num_nodes = len(x_axis)
144 fig, ax = plt.subplots()
145 line, = ax.plot([], [], lw=2)
148 last_dx = self.
dx[-1]
if (name
in self.
_cell_fields and self.
dx is not None)
else (x_axis[-1]-x_axis[-2])
149 ax.set_xlim(np.min(x_axis), np.max(x_axis) + last_dx)
150 ymin, ymax = np.min(data), np.max(data)
151 ax.set_ylim(ymin, ymax)
152 ax.set_xlabel(
"x-position")
156 frames = range(0, data.shape[0], step)
159 line.set_data([], [])
164 x_step = np.zeros(len(x_axis)+1)
166 x_step[-1] = x_axis[-1] + last_dx
167 y_step = np.zeros(len(x_axis)+1)
168 y_step[:-1] = data[frame, :]
169 y_step[-1] = data[frame, -1]
170 line.set_data(x_step, y_step)
172 line.set_data(x_axis, data[frame])
173 ax.set_title(f
"{name} at t = {self.time[frame]:.3e}")
176 anim = FuncAnimation(
177 fig, update, frames=frames,
178 init_func=init, blit=
True, interval=interval
186 Plot selected columns of a variable over time.
191 Name of the variable loaded from CSV.
192 columns : list or array-like, optional
193 Indices of columns to plot. If None, all columns are plotted.
195 if not hasattr(self, name):
196 raise AttributeError(f
"No field named '{name}' loaded.")
198 data = getattr(self, name)
200 if self.
time is None:
201 raise ValueError(
"Time vector not loaded. Provide a 'time' CSV in initialization.")
203 n_cols = data.shape[1]
207 columns = list(range(n_cols))
210 columns = list(columns)
212 if c < 0
or c >= n_cols:
213 raise IndexError(f
"Column index {c} out of range (0 to {n_cols-1}).")
216 colors = self.
colormap(np.linspace(0, 1, len(columns)))
218 plt.figure(figsize=(8, 5))
220 for idx, col
in enumerate(columns):
221 plt.plot(self.
time, data[:, col], label=f
"Col {col}", color=colors[idx])
225 plt.title(f
"{name}: selected columns vs. time")
233 Create an animated histogram where each row of `data` is one timestep.
238 2D array of shape (timesteps, bins).
239 bin_edges : np.ndarray, optional
240 1D array specifying the histogram bin edges. If None, bins = [0, 1, ..., N].
242 Delay between frames in milliseconds.
244 Whether the animation should loop.
246 if not hasattr(self, name):
247 raise AttributeError(f
"No field named '{name}' loaded.")
248 data = getattr(self, name)
249 if self.
time is None:
250 raise ValueError(
"Time vector not loaded. Provide a 'time' CSV in initialization.")
252 timesteps, nbins = data.shape
253 bin_edges = np.arange(nbins + 1)
255 fig, ax = plt.subplots()
256 bars = ax.bar(bin_edges[:-1], data[0], width=np.diff(bin_edges), align=
"edge")
258 ax.set_xlim(bin_edges[0], bin_edges[-1])
259 ax.set_ylim(0, 1.1 * data.max())
261 ax.set_ylabel(
"Counts")
262 ax.set_title(
"Animated Histogram")
265 for b, h
in zip(bars, data[frame]):
267 ax.set_title(f
"Timestep {frame}")
271 fig, update, frames=timesteps, interval=interval, blit=
False
_get_x_for_field(self, name)
animate_histogram(self, str name, step=1, interval=80)
__init__(self, folder="./output", csv_dict=None, cell_fields=None)
plot_columns_over_time(self, str name, columns=None)