freepaths 2.4.1__tar.gz → 2.4.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {freepaths-2.4.1 → freepaths-2.4.3}/PKG-INFO +1 -1
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths/__main__.py +1 -1
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths/data.py +17 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths/flight.py +31 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths/output_plots.py +83 -14
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths/run_particle.py +1 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths.egg-info/PKG-INFO +1 -1
- {freepaths-2.4.1 → freepaths-2.4.3}/README.md +0 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths/animation.py +0 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths/config.py +0 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths/default_config.py +0 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths/electron.py +0 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths/interface_smmm.py +0 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths/main_mfp_sampling.py +0 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths/main_tracing.py +0 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths/maps.py +0 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths/materials.py +0 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths/move.py +0 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths/options.py +0 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths/output_electron_plots.py +0 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths/output_info.py +0 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths/output_structure.py +0 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths/particle.py +0 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths/phonon.py +0 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths/post_computations.py +0 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths/progress.py +0 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths/scatterers.py +0 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths/scattering.py +0 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths/scattering_interfaces.py +0 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths/scattering_primitives.py +0 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths/scattering_semicircle.py +0 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths/scattering_types.py +0 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths/sources.py +0 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths.egg-info/SOURCES.txt +0 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths.egg-info/dependency_links.txt +0 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths.egg-info/entry_points.txt +0 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths.egg-info/requires.txt +0 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/freepaths.egg-info/top_level.txt +0 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/pyproject.toml +0 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/setup.cfg +0 -0
- {freepaths-2.4.1 → freepaths-2.4.3}/setup.py +0 -0
|
@@ -70,6 +70,14 @@ class GeneralData(Data):
|
|
|
70
70
|
self.travel_times = []
|
|
71
71
|
self.mean_free_paths = []
|
|
72
72
|
self.thermal_conductivity = []
|
|
73
|
+
# Per-phonon internal and boundary scattering rates (MFP-sampling mode only),
|
|
74
|
+
# the two channels of the total rate v/MFP resolved as a function of frequency:
|
|
75
|
+
self.internal_scattering_rates = []
|
|
76
|
+
self.boundary_scattering_rates = []
|
|
77
|
+
# Per-phonon dispersion branch index (0=LA, 1=TA1, 2=TA2, ...). In MFP-sampling
|
|
78
|
+
# mode each phonon keeps its launch branch (no rethermalization), so this lets the
|
|
79
|
+
# scattering-rate spectra be resolved per branch. Row-aligned with the arrays above.
|
|
80
|
+
self.branches = []
|
|
73
81
|
def save_particle_data(self, pt, mode=None):
|
|
74
82
|
"""Add information about the particle to the dataset"""
|
|
75
83
|
from freepaths.options import SimulationMode
|
|
@@ -78,6 +86,7 @@ class GeneralData(Data):
|
|
|
78
86
|
else:
|
|
79
87
|
self.final_frequencies.append(pt.f)
|
|
80
88
|
self.group_velocities.append(pt.speed)
|
|
89
|
+
self.branches.append(pt.branch_number)
|
|
81
90
|
|
|
82
91
|
def save_flight_data(self, flight, mode=None):
|
|
83
92
|
"""Add information about the particle flight to the dataset"""
|
|
@@ -95,6 +104,8 @@ class GeneralData(Data):
|
|
|
95
104
|
self.mean_free_paths.append(flight.mean_free_path)
|
|
96
105
|
if mode is SimulationMode.PHONON_MFP_SAMPLING:
|
|
97
106
|
self.thermal_conductivity.append(flight.thermal_conductivity)
|
|
107
|
+
self.internal_scattering_rates.append(flight.internal_scattering_rate)
|
|
108
|
+
self.boundary_scattering_rates.append(flight.boundary_scattering_rate)
|
|
98
109
|
|
|
99
110
|
def write_into_files(self, mode=None):
|
|
100
111
|
import os
|
|
@@ -121,6 +132,9 @@ class GeneralData(Data):
|
|
|
121
132
|
np.savetxt("Data/All mean free paths.csv", self.mean_free_paths, fmt='%2.4e', header="MFPs [m]", encoding='utf-8')
|
|
122
133
|
if mode is SimulationMode.PHONON_MFP_SAMPLING:
|
|
123
134
|
np.savetxt("Data/All thermal conductivities.csv", self.thermal_conductivity, fmt='%2.4e', header="K [W/mK]", encoding='utf-8')
|
|
135
|
+
np.savetxt("Data/All internal scattering rates.csv", self.internal_scattering_rates, fmt='%2.4e', header="1/tau_internal [1/s]", encoding='utf-8')
|
|
136
|
+
np.savetxt("Data/All boundary scattering rates.csv", self.boundary_scattering_rates, fmt='%2.4e', header="1/tau_boundary [1/s]", encoding='utf-8')
|
|
137
|
+
np.savetxt("Data/All branches.csv", self.branches, fmt='%d', header="Branch index (0=LA, 1=TA1, 2=TA2)", encoding='utf-8')
|
|
124
138
|
def dump_data(self):
|
|
125
139
|
"""Return data of a process in the form of a dictionary to be attached to the global data"""
|
|
126
140
|
return {
|
|
@@ -136,6 +150,9 @@ class GeneralData(Data):
|
|
|
136
150
|
'travel_times': self.travel_times,
|
|
137
151
|
'mean_free_paths': self.mean_free_paths,
|
|
138
152
|
'thermal_conductivity': self.thermal_conductivity,
|
|
153
|
+
'internal_scattering_rates': self.internal_scattering_rates,
|
|
154
|
+
'boundary_scattering_rates': self.boundary_scattering_rates,
|
|
155
|
+
'branches': self.branches,
|
|
139
156
|
'initial_energies': self.initial_energies,
|
|
140
157
|
}
|
|
141
158
|
|
|
@@ -45,6 +45,11 @@ class Flight:
|
|
|
45
45
|
self.free_paths = []
|
|
46
46
|
self._mfp_sum = 0.0
|
|
47
47
|
self._mfp_count = 0
|
|
48
|
+
# Per-channel counts of the diffuse events that terminate a free-path segment,
|
|
49
|
+
# used to split the total scattering rate v/MFP into internal (phonon-phonon +
|
|
50
|
+
# impurity) and boundary (diffuse surface) spectra in the MFP-sampling mode:
|
|
51
|
+
self._internal_count = 0
|
|
52
|
+
self._boundary_count = 0
|
|
48
53
|
self.hole_diff_scattering_angles = []
|
|
49
54
|
self.hole_spec_scattering_angles = []
|
|
50
55
|
self.thermal_conductivity = 0.0
|
|
@@ -53,6 +58,24 @@ class Flight:
|
|
|
53
58
|
"""Mean value of all free flights"""
|
|
54
59
|
return self._mfp_sum / self._mfp_count if self._mfp_count else 0
|
|
55
60
|
|
|
61
|
+
@property
|
|
62
|
+
def internal_scattering_rate(self):
|
|
63
|
+
"""Per-phonon internal (phonon-phonon + impurity) scattering rate [1/s]:
|
|
64
|
+
number of internal events divided by the total flight time (mfp_sum / speed).
|
|
65
|
+
Summed with boundary_scattering_rate this recovers the total rate v/MFP, so the
|
|
66
|
+
two split the total scattering-rate spectrum into physical channels."""
|
|
67
|
+
if self._mfp_sum <= 0:
|
|
68
|
+
return 0.0
|
|
69
|
+
return self.particle.speed * self._internal_count / self._mfp_sum
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def boundary_scattering_rate(self):
|
|
73
|
+
"""Per-phonon boundary (diffuse surface) scattering rate [1/s]: number of
|
|
74
|
+
diffuse boundary events divided by the total flight time (mfp_sum / speed)."""
|
|
75
|
+
if self._mfp_sum <= 0:
|
|
76
|
+
return 0.0
|
|
77
|
+
return self.particle.speed * self._boundary_count / self._mfp_sum
|
|
78
|
+
|
|
56
79
|
@property
|
|
57
80
|
def mean_free_path_sem(self):
|
|
58
81
|
"""Standard error of the mean free path (MFP sampling mode only). Assumes
|
|
@@ -74,6 +97,14 @@ class Flight:
|
|
|
74
97
|
if not cf.low_memory_usage:
|
|
75
98
|
self.free_paths.append(self.free_path)
|
|
76
99
|
|
|
100
|
+
def record_scattering_channel(self, is_internal, is_boundary):
|
|
101
|
+
"""Tally the channel of a diffuse event that ends a free-path segment, so the
|
|
102
|
+
total scattering rate can later be split into internal and boundary spectra."""
|
|
103
|
+
if is_internal:
|
|
104
|
+
self._internal_count += 1
|
|
105
|
+
if is_boundary:
|
|
106
|
+
self._boundary_count += 1
|
|
107
|
+
|
|
77
108
|
def save_hole_diff_scattering_angle(self, angle):
|
|
78
109
|
"""Save angle of diffuse scattering from the hole"""
|
|
79
110
|
self.hole_diff_scattering_angles.append(angle)
|
|
@@ -266,37 +266,106 @@ def plot_velocity_distribution():
|
|
|
266
266
|
|
|
267
267
|
|
|
268
268
|
def plot_scattering_rate_vs_frequency():
|
|
269
|
-
"""Plot phonon scattering rate (1/τ = v/MFP) vs frequency
|
|
269
|
+
"""Plot phonon scattering rate (1/τ = v/MFP) vs frequency, resolved per dispersion
|
|
270
|
+
branch (color + marker shape: LA / TA1 / TA2) and per channel (marker fill:
|
|
271
|
+
filled = Total, open = Boundary, small dot = Internal), with the theoretical
|
|
272
|
+
internal rate overlaid. MFP-sampling mode."""
|
|
273
|
+
from matplotlib.lines import Line2D
|
|
274
|
+
|
|
270
275
|
mfps = np.loadtxt("Data/All mean free paths.csv")
|
|
271
276
|
frequencies = np.loadtxt("Data/All final frequencies.csv")
|
|
272
277
|
speeds = np.loadtxt("Data/All group velocities.csv")
|
|
273
278
|
mask = mfps > 0
|
|
274
279
|
scattering_rates = speeds[mask] / mfps[mask]
|
|
280
|
+
f_masked = frequencies[mask]
|
|
281
|
+
|
|
282
|
+
# Per-channel MC rates and per-phonon branch index written by GeneralData in
|
|
283
|
+
# MFP-sampling mode (internal + boundary ≈ total). Optional so the plot still
|
|
284
|
+
# works on older result folders:
|
|
285
|
+
internal_rates = boundary_rates = branches = None
|
|
286
|
+
try:
|
|
287
|
+
internal_rates = np.loadtxt("Data/All internal scattering rates.csv")
|
|
288
|
+
boundary_rates = np.loadtxt("Data/All boundary scattering rates.csv")
|
|
289
|
+
except OSError:
|
|
290
|
+
pass
|
|
291
|
+
try:
|
|
292
|
+
branches = np.atleast_1d(np.loadtxt("Data/All branches.csv")).astype(int)
|
|
293
|
+
except OSError:
|
|
294
|
+
pass
|
|
275
295
|
|
|
276
296
|
# Theoretical internal scattering rate from material model:
|
|
277
297
|
material = get_media_class(cf.media)(cf.temp)
|
|
278
|
-
f_range = np.linspace(
|
|
279
|
-
|
|
280
|
-
tau_internal = np.array([material.phonon_relaxation_time(w) for w in omega_range])
|
|
281
|
-
|
|
298
|
+
f_range = np.linspace(f_masked.min(), f_masked.max(), 500)
|
|
299
|
+
tau_internal = np.array([material.phonon_relaxation_time(w) for w in 2 * np.pi * f_range])
|
|
282
300
|
step_rate_ns = 1e-9 / cf.timestep # 1/TIMESTEP in ns⁻¹
|
|
283
301
|
|
|
302
|
+
branch_names = material.dispersion_branch_names
|
|
303
|
+
branch_colors = ['royalblue', '#5ec962', 'crimson', 'darkorange'] # one per branch
|
|
304
|
+
# Channel -> marker shape (+ a slightly different alpha to help separate overlaps):
|
|
305
|
+
channel_names = ['Total', 'Boundary', 'Internal']
|
|
306
|
+
channel_markers = ['o', 's', '^']
|
|
307
|
+
channel_alphas = [0.9, 0.6, 0.5]
|
|
308
|
+
|
|
284
309
|
fig, ax = plt.subplots()
|
|
285
|
-
|
|
286
|
-
|
|
310
|
+
have_channels = internal_rates is not None and boundary_rates is not None
|
|
311
|
+
|
|
312
|
+
if branches is not None and have_channels:
|
|
313
|
+
# Branch-resolved: branch -> color, channel -> marker shape + alpha.
|
|
314
|
+
br = branches[mask]
|
|
315
|
+
intr, bnd = internal_rates[mask], boundary_rates[mask]
|
|
316
|
+
present = [b for b in range(len(branch_names)) if np.any(br == b)]
|
|
317
|
+
for b in present:
|
|
318
|
+
sel = br == b
|
|
319
|
+
color = branch_colors[b % 4]
|
|
320
|
+
fx = f_masked[sel] * 1e-12
|
|
321
|
+
channel_data = [scattering_rates[sel], bnd[sel], intr[sel]]
|
|
322
|
+
for cdata, marker, alpha in zip(channel_data, channel_markers, channel_alphas):
|
|
323
|
+
ax.scatter(fx, cdata * 1e-9, s=8, marker=marker, color=color,
|
|
324
|
+
alpha=alpha, linewidths=0)
|
|
325
|
+
|
|
326
|
+
# Two proxy legends: branch (color), channel (marker shape):
|
|
327
|
+
branch_handles = [Line2D([], [], linestyle='None', marker='o', markersize=4,
|
|
328
|
+
markerfacecolor=branch_colors[b % 4],
|
|
329
|
+
markeredgecolor=branch_colors[b % 4], label=branch_names[b]) for b in present]
|
|
330
|
+
branch_handles.append(Line2D([], [], color='gray', linestyle='--', label='Internal (Theory)'))
|
|
331
|
+
channel_handles = [Line2D([], [], linestyle='None', marker=m, markersize=4,
|
|
332
|
+
markerfacecolor='gray', markeredgecolor='gray', label=n)
|
|
333
|
+
for m, n in zip(channel_markers, channel_names)]
|
|
334
|
+
# Two horizontal legends side by side above the axes (branch on the left,
|
|
335
|
+
# channel on the right), clear of the data:
|
|
336
|
+
leg1 = ax.legend(handles=branch_handles, loc='lower left', bbox_to_anchor=(0, 1.02),
|
|
337
|
+
ncol=len(branch_handles), fontsize=5.5, title='Branch', title_fontsize=6,
|
|
338
|
+
columnspacing=0.8, handletextpad=0.3)
|
|
339
|
+
ax.add_artist(leg1)
|
|
340
|
+
ax.legend(handles=channel_handles, loc='lower right', bbox_to_anchor=(1, 1.02),
|
|
341
|
+
ncol=len(channel_handles), fontsize=5.5, title='Channel', title_fontsize=6,
|
|
342
|
+
columnspacing=0.8, handletextpad=0.3)
|
|
343
|
+
|
|
344
|
+
# Consolidated CSV in long format, sorted by (branch, frequency):
|
|
345
|
+
order = np.lexsort((f_masked, br))
|
|
346
|
+
legend = ", ".join(f"{i}={name}" for i, name in enumerate(branch_names))
|
|
347
|
+
spectra = np.column_stack((br, f_masked, scattering_rates, intr, bnd))[order]
|
|
348
|
+
np.savetxt("Data/Distribution of scattering rates.csv", spectra,
|
|
349
|
+
fmt=['%d', '%2.4e', '%2.4e', '%2.4e', '%2.4e'], delimiter=',',
|
|
350
|
+
header=f"branch ({legend}), f [Hz], Total (MC) [1/s], "
|
|
351
|
+
"Internal (MC) [1/s], Boundary (MC) [1/s]", encoding='utf-8')
|
|
352
|
+
else:
|
|
353
|
+
# Fallback for older result folders without branch/channel data: total only.
|
|
354
|
+
ax.plot(f_masked * 1e-12, scattering_rates * 1e-9, '.', markersize=2, c='black', label='Total (MC)')
|
|
355
|
+
ax.legend(fontsize=7)
|
|
356
|
+
order = np.argsort(f_masked)
|
|
357
|
+
np.savetxt("Data/Distribution of scattering rates.csv",
|
|
358
|
+
np.column_stack((f_masked, scattering_rates))[order],
|
|
359
|
+
fmt='%2.4e', delimiter=',', header="f [Hz], Total (MC) [1/s]", encoding='utf-8')
|
|
360
|
+
|
|
361
|
+
ax.plot(f_range * 1e-12, 1e-9 / tau_internal, '--', linewidth=0.8, c='gray')
|
|
287
362
|
ax.set_xlabel('Frequency (THz)')
|
|
288
363
|
ax.set_ylabel(r'Scattering rate (ns$^{-1}$)')
|
|
289
|
-
ax.legend()
|
|
290
364
|
ax.text(0.98, 0.04, f'Step rate = {step_rate_ns:.1f} ns$^{{-1}}$',
|
|
291
|
-
transform=ax.transAxes, ha='right', va='bottom',
|
|
292
|
-
fontsize=7, color='gray')
|
|
365
|
+
transform=ax.transAxes, ha='right', va='bottom', fontsize=7, color='gray')
|
|
293
366
|
fig.savefig('Distribution of phonon scattering rates.pdf', format='pdf', bbox_inches="tight")
|
|
294
367
|
plt.close(fig)
|
|
295
368
|
|
|
296
|
-
mc_data = np.vstack((frequencies[mask] * 1e-12, scattering_rates * 1e-9)).T
|
|
297
|
-
np.savetxt('Data/Distribution of phonon scattering rates.csv', mc_data, fmt='%1.3e', delimiter=",",
|
|
298
|
-
header="Frequency (THz),MC total scattering rate (1/ns)")
|
|
299
|
-
|
|
300
369
|
|
|
301
370
|
def plot_energy_distribution():
|
|
302
371
|
"""Plot distribution of energy"""
|
|
@@ -56,6 +56,7 @@ def run_particle(particle, flight, scatter_stats, places_stats, segment_stats, t
|
|
|
56
56
|
|
|
57
57
|
# If diffuse scattering has occurred, reset particle free path:
|
|
58
58
|
if scattering_types.is_diffuse or scattering_types.is_internal:
|
|
59
|
+
flight.record_scattering_channel(scattering_types.is_internal, scattering_types.is_diffuse)
|
|
59
60
|
flight.save_free_paths()
|
|
60
61
|
flight.restart()
|
|
61
62
|
# An inelastic (anharmonic) internal event rethermalizes the phonon: new
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|