Skip to content

Commit eaeaa74

Browse files
Merge pull request IHP-GmbH#629 from p-fath/parallelization_improvements_06_08
ngspice parallelization python script improvements + bug fixing
2 parents ff67e33 + 64840cc commit eaeaa74

File tree

2 files changed

+133
-112
lines changed

2 files changed

+133
-112
lines changed

ihp-sg13g2/libs.tech/xschem/sg13g2_tests/ngspice_parallel_mc.py

Lines changed: 62 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -40,19 +40,19 @@
4040
os.remove(final_result_file)
4141

4242
#get nr_workers from netlist
43-
match = re.search(r"^\s*(\*\*nr_workers)\s*=\s*(.+)$", netlist, re.MULTILINE)
43+
match = re.search(r"^[ \t]*\*\*nr_workers[ \t]*=[ \t]*(\d+)[ \t]*$", netlist, re.MULTILINE)
4444
if match:
45-
nr_workers = int(match.group(2).strip())
45+
nr_workers = int(match.group(1).strip())
4646
else:
47-
print("Info: **nr_workers statement not found, setting it to 10.")
47+
print("Info: **nr_workers statement not found or erroneous, setting it to 10.")
4848
nr_workers = 10
4949

5050
#get nr_mc_sims from netlist
51-
match = re.search(r"^\s*(\*\*nr_mc_sims)\s*=\s*(.+)$", netlist, re.MULTILINE)
51+
match = re.search(r"^[ \t]*\*\*nr_mc_sims[ \t]*=[ \t]*(\d+)[ \t]*$", netlist, re.MULTILINE)
5252
if match:
53-
nr_mc_sims = int(match.group(2).strip())
53+
nr_mc_sims = int(match.group(1).strip())
5454
else:
55-
print("Info: **nr_mc_sims statement not found, setting it 100.")
55+
print("Info: **nr_mc_sims statement not found or erroneous, setting it 100.")
5656
nr_mc_sims = 100
5757

5858
#get plot parameters from netlist
@@ -107,9 +107,11 @@ def run_worker(args):
107107
writer.writerow(list(result.values()))
108108
else:
109109
writer.writerow(['N/A', 'ERROR in receiving results'])
110+
writeHeadings = False
110111
except subprocess.CalledProcessError:
111112
if writeHeadings:
112113
writer.writerow(['N/A', 'ERROR'])
114+
writeHeadings = False
113115
writer.writerow(['N/A', 'ERROR in process handling'])
114116

115117
if __name__ == "__main__":
@@ -131,6 +133,10 @@ def run_worker(args):
131133
out.writelines([line for line in lines if "ERROR" not in line])
132134
else: # skip the header
133135
out.writelines([line for line in lines[1:] if "ERROR" not in line])
136+
error_lines = [line for line in lines[1:] if "ERROR" in line]
137+
if error_lines:
138+
print(f"Errors found in worker {i} results:")
139+
print("".join(error_lines))
134140
os.remove(part_file)
135141

136142
# plot results
@@ -148,58 +154,56 @@ def run_worker(args):
148154
for row in reader:
149155
for header in reader.fieldnames:
150156
results_dict[header].append(float(row[header]))
151-
# Set the number of columns for subplot grid
152-
n_cols = 2 # Change this to 1, 2, 3, etc.
153-
n_plots = len(results_plot_list)
154-
n_rows = math.ceil(n_plots / n_cols)
155-
156-
# Create subplots
157-
fig, axs = plt.subplots(n_rows, n_cols, figsize=(6 * n_cols, 4 * n_rows))
158-
axs = axs.flatten() # Flatten in case of multiple rows/columns
159-
i = 0
160-
for var in results_plot_list:
161-
data = np.array(results_dict[var.lower()])
162-
mean = data.mean()
163-
std = data.std()
164-
axs[i].hist(data, bins=50, color='skyblue', edgecolor='black')
165-
axs[i].set_title(f"Histogram of {var}")
166-
axs[i].set_xlabel(f"{var}")
167-
axs[i].set_ylabel("Count")
168-
ymax = axs[i].get_ylim()[1]
169-
170-
# Plot mean line (solid green)
171-
axs[i].axvline(mean, color='green', linestyle='-', linewidth=2)
172-
axs[i].text(mean, ymax * 0.95, 'Mean', color='green', rotation=90,
173-
verticalalignment='top', horizontalalignment='center')
174-
175-
# Plot sigma lines (dashed red)
176-
for sigma_mult in [1, 2, 3]:
177-
pos = mean + sigma_mult * std
178-
neg = mean - sigma_mult * std
179-
180-
axs[i].axvline(pos, color='red', linestyle='--', linewidth=1)
181-
axs[i].axvline(neg, color='red', linestyle='--', linewidth=1)
182-
183-
# Labels for sigma lines with a slight vertical offset to avoid overlap
184-
y_pos = ymax * (1 - 0.1 * sigma_mult)
185-
186-
axs[i].text(pos, y_pos, f'+{sigma_mult}σ', color='red', rotation=90,
187-
verticalalignment='top', horizontalalignment='right')
188-
axs[i].text(neg, y_pos, f'-{sigma_mult}σ', color='red', rotation=90,
189-
verticalalignment='top', horizontalalignment='left')
190-
191-
# Set more x-axis ticks for better readability
192-
x_min = mean - 4 * std
193-
x_max = mean + 4 * std
194-
ticks = np.arange(x_min, x_max + std/2, std/2)
195-
axs[i].set_xticks(ticks)
196-
i = i + 1
197-
198-
# Hide unused subplots
199-
for j in range(i, len(axs)):
200-
axs[j].axis('off')
201-
plt.tight_layout()
202-
plt.show()
157+
158+
# Set the number of columns for subplot grid
159+
n_cols = 2 # Change this to 1, 2, 3, etc.
160+
n_plots = len(results_plot_list)
161+
n_rows = math.ceil(n_plots / n_cols)
162+
163+
# Create subplots
164+
fig, axs = plt.subplots(n_rows, n_cols, figsize=(6 * n_cols, 4 * n_rows))
165+
axs = axs.flatten() # Flatten in case of multiple rows/columns
166+
i = 0
167+
for var in results_plot_list:
168+
data = np.array(results_dict[var.lower()])
169+
mean = data.mean()
170+
std = data.std()
171+
axs[i].hist(data, bins=50, color='skyblue', edgecolor='black')
172+
axs[i].set_title(f"Histogram of {var}")
173+
axs[i].set_xlabel(f"{var}")
174+
axs[i].set_ylabel("Count")
175+
ymax = axs[i].get_ylim()[1]
176+
177+
# Plot mean line (solid green)
178+
axs[i].axvline(mean, color='green', linestyle='-', linewidth=2)
179+
axs[i].text(mean, ymax * 0.95, 'Mean', color='green', rotation=90, verticalalignment='top', horizontalalignment='center')
180+
181+
# Plot sigma lines (dashed red)
182+
for sigma_mult in [1, 2, 3]:
183+
pos = mean + sigma_mult * std
184+
neg = mean - sigma_mult * std
185+
186+
axs[i].axvline(pos, color='red', linestyle='--', linewidth=1)
187+
axs[i].axvline(neg, color='red', linestyle='--', linewidth=1)
188+
189+
# Labels for sigma lines with a slight vertical offset to avoid overlap
190+
y_pos = ymax * (1 - 0.1 * sigma_mult)
191+
192+
axs[i].text(pos, y_pos, f'+{sigma_mult}σ', color='red', rotation=90, verticalalignment='top', horizontalalignment='right')
193+
axs[i].text(neg, y_pos, f'-{sigma_mult}σ', color='red', rotation=90, verticalalignment='top', horizontalalignment='left')
194+
195+
# Set more x-axis ticks for better readability
196+
x_min = mean - 4 * std
197+
x_max = mean + 4 * std
198+
ticks = np.arange(x_min, x_max + std/2, std/2)
199+
axs[i].set_xticks(ticks)
200+
i = i + 1
201+
202+
# Hide unused subplots
203+
for j in range(i, len(axs)):
204+
axs[j].axis('off')
205+
plt.tight_layout()
206+
plt.show()
203207

204208
end_time = time.time()
205209
runtime = end_time - start_time

ihp-sg13g2/libs.tech/xschem/sg13g2_tests/ngspice_parallel_sweep.py

Lines changed: 71 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
from quantiphy import Quantity
1010
import sys
1111
import matplotlib.pyplot as plt
12+
from scipy.interpolate import interp1d
1213
import math
14+
import pandas as pd
1315

1416
#begin/end tags
1517
parameter_begin_tag = "**parameter_sweep_begin"
@@ -46,46 +48,46 @@
4648
os.remove(final_result_file_sorted)
4749

4850
#get nr_workers from netlist
49-
match = re.search(r"^\s*(\*\*nr_workers)\s*=\s*(.+)$", netlist, re.MULTILINE)
51+
match = re.search(r"^[ \t]*\*\*nr_workers[ \t]*=[ \t]*(\d+)[ \t]*$", netlist, re.MULTILINE)
5052
if match:
51-
nr_workers = int(match.group(2).strip())
53+
nr_workers = int(match.group(1).strip())
5254
else:
53-
print("Info: **nr_workers statement not found, setting it to 10.")
55+
print("Info: **nr_workers statement not found or erroneous, setting it to 10.")
5456
nr_workers = 10
5557

5658
#get sort_results_index from netlist
57-
match = re.search(r"^\s*(\*\*sort_results_index)\s*=\s*(.+)$", netlist, re.MULTILINE)
59+
match = re.search(r"^[ \t]*\*\*sort_results_index[ \t]*=[ \t]*(\d+)[ \t]*$", netlist, re.MULTILINE)
5860
if match:
59-
sort_results_index = int(match.group(2).strip())
61+
sort_results_index = int(match.group(1).strip())
6062
else:
61-
print("Info: **sort_results_index statement not found, setting it to index 0.")
63+
print("Info: **sort_results_index statement not found or erroneous, setting it to index 0.")
6264
sort_results_index = 0
6365

6466
#get results_plot_contour_index from netlist
65-
match = re.search(r"^\s*(\*\*results_plot_contour_index)\s*=\s*(.+)$", netlist, re.MULTILINE)
67+
match = re.search(r"^[ \t]*\*\*results_plot_contour_index[ \t]*=[ \t]*(\d+(?:,[ \t]*\d+)*)[ \t]*$", netlist, re.MULTILINE)
6668
if match:
67-
results_plot_contour_index_str = match.group(2).strip()
69+
results_plot_contour_index_str = match.group(1).strip()
6870
results_plot_contour_index = [int(i.strip()) for i in results_plot_contour_index_str.split(',') if i.strip().isdigit()]
6971
else:
70-
print("Info: **results_plot_contour_index statement not found, setting it to be empty.")
72+
print("Info: **results_plot_contour_index statement not found or erroneous, setting it to be empty.")
7173
results_plot_contour_index = []
7274

7375
#get results_plot_logx_index from netlist
74-
match = re.search(r"^\s*(\*\*results_plot_logx_index)\s*=\s*(.+)$", netlist, re.MULTILINE)
76+
match = re.search(r"^[ \t]*\*\*results_plot_logx_index[ \t]*=[ \t]*(\d+(?:,[ \t]*\d+)*)[ \t]*$", netlist, re.MULTILINE)
7577
if match:
76-
results_plot_logx_index_str = match.group(2).strip()
78+
results_plot_logx_index_str = match.group(1).strip()
7779
results_plot_logx_index = [int(i.strip()) for i in results_plot_logx_index_str.split(',') if i.strip().isdigit()]
7880
else:
79-
print("Info: **results_plot_logx_index statement not found, setting it to be empty.")
81+
print("Info: **results_plot_logx_index statement not found or erroneous, setting it to be empty.")
8082
results_plot_logx_index = []
8183

8284
#get results_plot_logy_index from netlist
83-
match = re.search(r"^\s*(\*\*results_plot_logy_index)\s*=\s*(.+)$", netlist, re.MULTILINE)
85+
match = re.search(r"^[ \t]*\*\*results_plot_logy_index[ \t]*=[ \t]*(\d+(?:,[ \t]*\d+)*)[ \t]*$", netlist, re.MULTILINE)
8486
if match:
85-
results_plot_logy_index_str = match.group(2).strip()
87+
results_plot_logy_index_str = match.group(1).strip()
8688
results_plot_logy_index = [int(i.strip()) for i in results_plot_logy_index_str.split(',') if i.strip().isdigit()]
8789
else:
88-
print("Info: **results_plot_logy_index statement not found, setting it to be empty.")
90+
print("Info: **results_plot_logy_index statement not found or erroneous, setting it to be empty.")
8991
results_plot_logy_index = []
9092

9193
#get sweep parameters from netlist
@@ -115,7 +117,7 @@
115117
if list_type == "auto":
116118
sweep_list = np.linspace(Quantity(values_splited[1]), Quantity(values_splited[3]), int(values_splited[2]))
117119
elif list_type == "lin":
118-
sweep_list = np.arange(Quantity(values_splited[1]), Quantity(values_splited[3]) + Quantity(values_splited[2]), Quantity(values_splited[2]))
120+
sweep_list = np.arange(Quantity(values_splited[1]), (Quantity(values_splited[3]) + Quantity(values_splited[2])*0.1), Quantity(values_splited[2]))
119121
elif list_type == "dec":
120122
total_points = int(int(values_splited[2]) * (np.log10(Quantity(values_splited[3]))-np.log10(Quantity(values_splited[1])))) + 1
121123
sweep_list = np.logspace(np.log10(Quantity(values_splited[1])), np.log10(Quantity(values_splited[3])), total_points)
@@ -184,15 +186,19 @@ def run_worker(args):
184186
result[result_name.strip()] = float(result_value.strip())
185187
# write in csv file
186188
if writeHeadings:
187-
writer.writerow(param_name_list + list(result.keys()))
189+
writer.writerow(param_name_list + list(result.keys()))
188190
writeHeadings = False
189191
writer.writerow([str(x) for x in param_set] + list(result.values()))
190192
else:
191-
writer.writerow([param_name_list, 'N/A', 'ERROR in receiving results'])
193+
if writeHeadings:
194+
writer.writerow(param_name_list)
195+
writeHeadings = False
196+
writer.writerow([",".join(str(x) for x in param_set), 'N/A', 'ERROR in receiving results'])
192197
except subprocess.CalledProcessError:
193198
if writeHeadings:
194-
writer.writerow([param_name_list, 'N/A', 'ERROR'])
195-
writer.writerow([param_name_list, 'N/A', 'ERROR in process handling'])
199+
writer.writerow(param_name_list)
200+
writeHeadings = False
201+
writer.writerow([",".join(str(x) for x in param_set), 'N/A', 'ERROR in process handling'])
196202
os.remove(cir_file)
197203

198204
if __name__ == "__main__":
@@ -214,36 +220,19 @@ def run_worker(args):
214220
out.writelines([line for line in lines if "ERROR" not in line])
215221
else: # skip the header
216222
out.writelines([line for line in lines[1:] if "ERROR" not in line])
223+
error_lines = [line for line in lines[1:] if "ERROR" in line]
224+
if error_lines:
225+
print(f"Errors found in worker {i} results:")
226+
print("".join(error_lines))
217227
os.remove(part_file)
218-
219-
with open(final_result_file, newline='') as csvfile:
220-
reader = csv.reader(csvfile, delimiter=';')
221-
header = next(reader)
222-
data = list(reader)
223-
224-
data.sort(key=lambda x: float(x[sort_results_index]), reverse=False)
225228

226-
# create new file with sorted data
227-
with open(final_result_file_sorted, mode='w', newline='') as csvfile:
228-
writer = csv.writer(csvfile, delimiter=';')
229-
writer.writerow(header)
230-
writer.writerows(data)
229+
df = pd.read_csv(final_result_file, delimiter=';')
230+
df_sorted = df.sort_values(by=df.columns[sort_results_index], ascending=True)
231+
df_sorted.to_csv(final_result_file_sorted, sep=';', index=False)
231232

232233
# plot results
233234
if len(results_plot_list) > 0:
234-
# make dictionary with results
235-
results_dict = {}
236-
with open(final_result_file_sorted, mode='r', newline='') as csvfile:
237-
reader = csv.DictReader(csvfile, delimiter=';')
238-
239-
# Initialize dictionary with keys from the header
240-
for header in reader.fieldnames:
241-
results_dict[header] = []
242-
243-
# Fill the dictionary
244-
for row in reader:
245-
for header in reader.fieldnames:
246-
results_dict[header].append(float(row[header]))
235+
df_sorted = df.sort_values(by=param_name_list)
247236

248237
# Set the number of columns for subplot grid
249238
n_cols = 2 # Change this to 1, 2, 3, etc.
@@ -256,20 +245,25 @@ def run_worker(args):
256245
i = 0
257246
if len(param_name_list) == 1:
258247
for var in results_plot_list:
248+
actual_size = len(df_sorted[var])
249+
expected_size = len(next(iter(param_list.values())))
250+
if actual_size != expected_size:
251+
print(f"Error with variable {var}: Expected {expected_size} data points but got {actual_size}.")
252+
print(f"Hint: This could be due to missing or failed simulations.")
259253
axs[i].set_title(var)
260254
axs[i].grid(True, which='both', linestyle='--', linewidth=0.5)
261255
axs[i].minorticks_on()
262256
axs[i].set_xlabel(f"{param_name_list[0]}")
263257
axs[i].set_ylabel(f"{var}")
264258
var = var.lower()
265259
if i in results_plot_logx_index and i in results_plot_logy_index:
266-
axs[i].loglog(results_dict[param_name_list[0]], results_dict[var])
260+
axs[i].loglog(df_sorted[param_name_list[0]], df_sorted[var])
267261
elif i in results_plot_logx_index:
268-
axs[i].semilogx(results_dict[param_name_list[0]], results_dict[var])
262+
axs[i].semilogx(df_sorted[param_name_list[0]], df_sorted[var])
269263
elif i in results_plot_logy_index:
270-
axs[i].semilogy(results_dict[param_name_list[0]], results_dict[var])
264+
axs[i].semilogy(df_sorted[param_name_list[0]], df_sorted[var])
271265
else:
272-
axs[i].plot(results_dict[param_name_list[0]], results_dict[var])
266+
axs[i].plot(df_sorted[param_name_list[0]], df_sorted[var])
273267
i = i + 1
274268
# Hide unused subplots
275269
for j in range(i, len(axs)):
@@ -282,18 +276,41 @@ def run_worker(args):
282276
axs[i].set_xlabel(f"{param_name_list[0]}")
283277
axs[i].grid(True, which='both', linestyle='--', alpha=0.5)
284278
axs[i].minorticks_on()
285-
x = np.unique(np.array(results_dict[param_name_list[0]]))
286-
y = np.unique(np.array(results_dict[param_name_list[1]]))
287-
Z = np.array(results_dict[var.lower()]).reshape(len(x), len(y)).T # shape: (len(y), len(x))
279+
x_raw = np.array(df_sorted[param_name_list[0]])
280+
y_raw = np.array(df_sorted[param_name_list[1]])
281+
z_raw = np.array(df_sorted[var.lower()])
282+
x = np.unique(x_raw)
283+
y = np.unique(y_raw)
284+
expected_size = len(x) * len(y)
285+
if z_raw.size != expected_size:
286+
print(f"Error with variable {var}: Expected {expected_size} data points but got {z_raw.size}.")
287+
print(f"Hint: This could be due to missing or failed simulations. Interpolating misssing points!")
288+
Z = np.full((len(x), len(y)), np.nan)
289+
for yi_index, yi in enumerate(y):
290+
mask = (y_raw == yi)
291+
xi_vals = x_raw[mask]
292+
zi_vals = z_raw[mask]
293+
if len(xi_vals) < 2:
294+
print(f"Skipping interpolation at y={yi:.3g}: Not enough points.")
295+
continue
296+
try:
297+
interp_fn = interp1d(xi_vals, zi_vals, kind='linear', bounds_error=False, fill_value=np.nan)
298+
zi_interp = interp_fn(x)
299+
except Exception as e:
300+
print(f"Interpolation failed at y={yi:.3g}: {e}")
301+
zi_interp = np.full_like(x, np.nan)
302+
Z[:, yi_index] = zi_interp
303+
else:
304+
Z = z_raw.reshape(len(x), len(y))
305+
Z_plot = Z.T # shape (len(y), len(x))
288306
if i in results_plot_contour_index:
289307
axs[i].set_ylabel(f"{param_name_list[1]}")
290308
X, Y = np.meshgrid(x,y)
291-
contour = axs[i].contourf(X, Y, Z, levels=10, cmap='viridis')
309+
contour = axs[i].contourf(X, Y, Z_plot, levels=10, cmap='viridis')
292310
cbar = fig.colorbar(contour, ax=axs[i])
293311
cbar.set_label(var)
294312
else:
295313
axs[i].set_ylabel(var)
296-
Z = Z.T
297314
if i in results_plot_logx_index and i in results_plot_logy_index:
298315
for l, yl in enumerate(y):
299316
axs[i].loglog(x, Z[:, l], label=f"{yl:.2g}")

0 commit comments

Comments
 (0)