CPP-snippets 0.0.1
A silly C++ project to use for demonstrating code integration
Loading...
Searching...
No Matches
PostProcessor Class Reference

Public Member Functions

 __init__ (self, folder="./output", csv_dict=None, cell_fields=None)
 
 plot_timestep (self, name, t, ax=None)
 
 animate (self, name, step=1, interval=80)
 
 plot_columns_over_time (self, str name, columns=None)
 
 animate_histogram (self, str name, step=1, interval=80)
 

Data Fields

 colormap = plt.cm.viridis
 
 folder = folder
 
 x = None
 
 time = None
 
 dx = None
 
float x_cell = None
 

Protected Member Functions

 _get_x_for_field (self, name)
 

Protected Attributes

 _names = list(csv_dict.keys())
 
 _cell_fields = cell_fields
 

Detailed Description

Loads multiple simulation CSV outputs and provides visualization tools.

CSV dictionary example:
    {"phi": "phi.csv", "efield": "efield.csv", "x": "x.csv", "time": "time.csv", "dx": "dx.csv"}

Node-based fields are plotted at nodes (x.csv).  
Cell-based fields are plotted at cell centers (dx.csv used to compute positions).

Definition at line 6 of file postprocessor.py.

Constructor & Destructor Documentation

◆ __init__()

__init__ ( self,
folder = "./output",
csv_dict = None,
cell_fields = None )
Parameters
----------
folder : str
    Relative path to folder containing the CSV files.
csv_dict : dict
    Dictionary mapping attribute names -> CSV filenames.
    Special keys:
        - "x": spatial node positions (single column)
        - "time": time array (single column)
        - "dx": cell sizes (single column)
cell_fields : list
    List of names that are defined on cells (not nodes).

Definition at line 16 of file postprocessor.py.

16 def __init__(self, folder="./output", csv_dict=None, cell_fields=None):
17 """
18 Parameters
19 ----------
20 folder : str
21 Relative path to folder containing the CSV files.
22 csv_dict : dict
23 Dictionary mapping attribute names -> CSV filenames.
24 Special keys:
25 - "x": spatial node positions (single column)
26 - "time": time array (single column)
27 - "dx": cell sizes (single column)
28 cell_fields : list
29 List of names that are defined on cells (not nodes).
30 """
31 self.colormap = plt.cm.viridis
32 plt.rcParams.update({'font.size':15,'lines.linewidth':2.5,'figure.autolayout':True})
33
34 self.folder = folder
35 if csv_dict is None:
36 raise ValueError("You must provide a csv_dict mapping names to CSV files.")
37 if cell_fields is None:
38 cell_fields = []
39
40 self._names = list(csv_dict.keys())
41 self._cell_fields = cell_fields
42
43 # Initialize default x, time, dx arrays
44 self.x = None
45 self.time = None
46 self.dx = None
47 self.x_cell = None
48
49 # Load CSV files
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) # first row includes metadata comment
56
57 # Flatten for 1D arrays
58 if name in ("x", "time", "dx"):
59 data = data.flatten()
60 setattr(self, name, data)
61 else:
62 setattr(self, name, data)
63
64 # Set default x and time if not provided
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').")
68
69 data_field = getattr(self, first_field)
70
71 if self.x is None:
72 self.x = np.arange(data_field.shape[1])
73 if self.time is None:
74 self.time = np.arange(data_field.shape[0])
75
76 # Compute cell-centered positions if dx is provided
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")
80 self.x_cell = self.x[:-1] + 0.5*self.dx
81

Member Function Documentation

◆ _get_x_for_field()

_get_x_for_field ( self,
name )
protected

Definition at line 85 of file postprocessor.py.

85 def _get_x_for_field(self, name):
86 if name in self._cell_fields:
87 if self.x_cell is None:
88 raise ValueError(f"Field '{name}' is cell-based but dx/x not defined")
89 return self.x_cell
90 else:
91 return self.x
92

◆ animate()

animate ( self,
name,
step = 1,
interval = 80 )

Definition at line 136 of file postprocessor.py.

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)
140
141 x_axis = self._get_x_for_field(name)
142 num_nodes = len(x_axis)
143
144 fig, ax = plt.subplots()
145 line, = ax.plot([], [], lw=2)
146
147 # Extend last cell for cell fields
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")
153 ax.set_ylabel(name)
154 ax.grid(True)
155
156 frames = range(0, data.shape[0], step)
157
158 def init():
159 line.set_data([], [])
160 return line,
161
162 def update(frame):
163 if name in self._cell_fields:
164 x_step = np.zeros(len(x_axis)+1)
165 x_step[:-1] = x_axis
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)
171 else:
172 line.set_data(x_axis, data[frame])
173 ax.set_title(f"{name} at t = {self.time[frame]:.3e}")
174 return line,
175
176 anim = FuncAnimation(
177 fig, update, frames=frames,
178 init_func=init, blit=True, interval=interval
179 )
180
181 plt.show()
182 return anim
183

◆ animate_histogram()

animate_histogram ( self,
str name,
step = 1,
interval = 80 )
Create an animated histogram where each row of `data` is one timestep.

Parameters
----------
data : np.ndarray
    2D array of shape (timesteps, bins).
bin_edges : np.ndarray, optional
    1D array specifying the histogram bin edges. If None, bins = [0, 1, ..., N].
interval : int
    Delay between frames in milliseconds.
repeat : bool
    Whether the animation should loop.

Definition at line 231 of file postprocessor.py.

231 def animate_histogram(self, name: str, step=1, interval=80):
232 """
233 Create an animated histogram where each row of `data` is one timestep.
234
235 Parameters
236 ----------
237 data : np.ndarray
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].
241 interval : int
242 Delay between frames in milliseconds.
243 repeat : bool
244 Whether the animation should loop.
245 """
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.")
251
252 timesteps, nbins = data.shape
253 bin_edges = np.arange(nbins + 1)
254
255 fig, ax = plt.subplots()
256 bars = ax.bar(bin_edges[:-1], data[0], width=np.diff(bin_edges), align="edge")
257
258 ax.set_xlim(bin_edges[0], bin_edges[-1])
259 ax.set_ylim(0, 1.1 * data.max())
260 ax.set_xlabel("Bin")
261 ax.set_ylabel("Counts")
262 ax.set_title("Animated Histogram")
263
264 def update(frame):
265 for b, h in zip(bars, data[frame]):
266 b.set_height(h)
267 ax.set_title(f"Timestep {frame}")
268 return bars
269
270 ani = FuncAnimation(
271 fig, update, frames=timesteps, interval=interval, blit=False
272 )
273
274 plt.show()
275 return ani

◆ plot_columns_over_time()

plot_columns_over_time ( self,
str name,
columns = None )
Plot selected columns of a variable over time.

Parameters
----------
name : str
    Name of the variable loaded from CSV.
columns : list or array-like, optional
    Indices of columns to plot. If None, all columns are plotted.

Definition at line 184 of file postprocessor.py.

184 def plot_columns_over_time(self, name: str, columns=None):
185 """
186 Plot selected columns of a variable over time.
187
188 Parameters
189 ----------
190 name : str
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.
194 """
195 if not hasattr(self, name):
196 raise AttributeError(f"No field named '{name}' loaded.")
197
198 data = getattr(self, name)
199
200 if self.time is None:
201 raise ValueError("Time vector not loaded. Provide a 'time' CSV in initialization.")
202
203 n_cols = data.shape[1]
204
205 # If no specific columns given → use all
206 if columns is None:
207 columns = list(range(n_cols))
208 else:
209 # Convert to list and validate
210 columns = list(columns)
211 for c in columns:
212 if c < 0 or c >= n_cols:
213 raise IndexError(f"Column index {c} out of range (0 to {n_cols-1}).")
214
215 # Colormap for selected columns only
216 colors = self.colormap(np.linspace(0, 1, len(columns)))
217
218 plt.figure(figsize=(8, 5))
219
220 for idx, col in enumerate(columns):
221 plt.plot(self.time, data[:, col], label=f"Col {col}", color=colors[idx])
222
223 plt.xlabel("Time")
224 plt.ylabel(name)
225 plt.title(f"{name}: selected columns vs. time")
226 plt.legend()
227 plt.grid(True)
228 plt.tight_layout()
229 plt.show()
230

◆ plot_timestep()

plot_timestep ( self,
name,
t,
ax = None )

Definition at line 96 of file postprocessor.py.

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)
100
101 if t < 0 or t >= data.shape[0]:
102 raise IndexError(f"Timestep {t} is out of range")
103
104 x_axis = self._get_x_for_field(name)
105
106 if ax is None:
107 fig, ax = plt.subplots()
108
109 if name in self._cell_fields:
110 # Plot as constant per cell using step function
111 x_step = np.zeros(len(x_axis)+1)
112 x_step[:-1] = x_axis
113 # extend last cell to keep value constant
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
116
117 y_step = np.zeros(len(x_axis)+1)
118 y_step[:-1] = data[t, :]
119 y_step[-1] = data[t, -1]
120
121 ax.step(x_step, y_step, where='post', lw=2)
122 else:
123 ax.plot(x_axis, data[t, :], lw=2)
124
125 ax.set_title(f"{name} at t = {self.time[t]:.3e}")
126 ax.set_xlabel("x-position")
127 ax.set_ylabel(name)
128 ax.grid(True)
129
130 if ax is None:
131 plt.show()
132

Field Documentation

◆ _cell_fields

_cell_fields = cell_fields
protected

Definition at line 41 of file postprocessor.py.

◆ _names

_names = list(csv_dict.keys())
protected

Definition at line 40 of file postprocessor.py.

◆ colormap

colormap = plt.cm.viridis

Definition at line 31 of file postprocessor.py.

◆ dx

dx = None

Definition at line 46 of file postprocessor.py.

◆ folder

folder = folder

Definition at line 34 of file postprocessor.py.

◆ time

time = None

Definition at line 45 of file postprocessor.py.

◆ x

x = None

Definition at line 44 of file postprocessor.py.

◆ x_cell

float x_cell = None

Definition at line 47 of file postprocessor.py.


The documentation for this class was generated from the following file: