freepaths 2.3.2__tar.gz → 2.3.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.
Files changed (43) hide show
  1. {freepaths-2.3.2 → freepaths-2.3.3}/PKG-INFO +1 -1
  2. {freepaths-2.3.2 → freepaths-2.3.3}/freepaths/__main__.py +1 -1
  3. {freepaths-2.3.2 → freepaths-2.3.3}/freepaths/config.py +2 -0
  4. {freepaths-2.3.2 → freepaths-2.3.3}/freepaths/data.py +6 -29
  5. {freepaths-2.3.2 → freepaths-2.3.3}/freepaths/default_config.py +4 -1
  6. {freepaths-2.3.2 → freepaths-2.3.3}/freepaths/electron.py +2 -4
  7. {freepaths-2.3.2 → freepaths-2.3.3}/freepaths/flight.py +0 -26
  8. freepaths-2.3.3/freepaths/main_mfp_sampling.py +195 -0
  9. {freepaths-2.3.2 → freepaths-2.3.3}/freepaths/main_tracing.py +4 -2
  10. {freepaths-2.3.2 → freepaths-2.3.3}/freepaths/maps.py +2 -0
  11. {freepaths-2.3.2 → freepaths-2.3.3}/freepaths/materials.py +69 -2
  12. freepaths-2.3.3/freepaths/output_electron_plots.py +218 -0
  13. {freepaths-2.3.2 → freepaths-2.3.3}/freepaths/output_info.py +4 -2
  14. {freepaths-2.3.2 → freepaths-2.3.3}/freepaths/output_plots.py +72 -524
  15. freepaths-2.3.3/freepaths/post_computations.py +190 -0
  16. {freepaths-2.3.2 → freepaths-2.3.3}/freepaths/run_particle.py +2 -1
  17. {freepaths-2.3.2 → freepaths-2.3.3}/freepaths/scatterers.py +0 -16
  18. {freepaths-2.3.2 → freepaths-2.3.3}/freepaths.egg-info/PKG-INFO +1 -1
  19. {freepaths-2.3.2 → freepaths-2.3.3}/freepaths.egg-info/SOURCES.txt +1 -0
  20. freepaths-2.3.2/freepaths/main_mfp_sampling.py +0 -135
  21. freepaths-2.3.2/freepaths/post_computations.py +0 -177
  22. {freepaths-2.3.2 → freepaths-2.3.3}/README.md +0 -0
  23. {freepaths-2.3.2 → freepaths-2.3.3}/freepaths/animation.py +0 -0
  24. {freepaths-2.3.2 → freepaths-2.3.3}/freepaths/interface_smmm.py +0 -0
  25. {freepaths-2.3.2 → freepaths-2.3.3}/freepaths/move.py +0 -0
  26. {freepaths-2.3.2 → freepaths-2.3.3}/freepaths/output_structure.py +0 -0
  27. {freepaths-2.3.2 → freepaths-2.3.3}/freepaths/particle.py +0 -0
  28. {freepaths-2.3.2 → freepaths-2.3.3}/freepaths/particle_types.py +0 -0
  29. {freepaths-2.3.2 → freepaths-2.3.3}/freepaths/phonon.py +0 -0
  30. {freepaths-2.3.2 → freepaths-2.3.3}/freepaths/progress.py +0 -0
  31. {freepaths-2.3.2 → freepaths-2.3.3}/freepaths/scattering.py +0 -0
  32. {freepaths-2.3.2 → freepaths-2.3.3}/freepaths/scattering_interfaces.py +0 -0
  33. {freepaths-2.3.2 → freepaths-2.3.3}/freepaths/scattering_primitives.py +0 -0
  34. {freepaths-2.3.2 → freepaths-2.3.3}/freepaths/scattering_semicircle.py +0 -0
  35. {freepaths-2.3.2 → freepaths-2.3.3}/freepaths/scattering_types.py +0 -0
  36. {freepaths-2.3.2 → freepaths-2.3.3}/freepaths/sources.py +0 -0
  37. {freepaths-2.3.2 → freepaths-2.3.3}/freepaths.egg-info/dependency_links.txt +0 -0
  38. {freepaths-2.3.2 → freepaths-2.3.3}/freepaths.egg-info/entry_points.txt +0 -0
  39. {freepaths-2.3.2 → freepaths-2.3.3}/freepaths.egg-info/requires.txt +0 -0
  40. {freepaths-2.3.2 → freepaths-2.3.3}/freepaths.egg-info/top_level.txt +0 -0
  41. {freepaths-2.3.2 → freepaths-2.3.3}/pyproject.toml +0 -0
  42. {freepaths-2.3.2 → freepaths-2.3.3}/setup.cfg +0 -0
  43. {freepaths-2.3.2 → freepaths-2.3.3}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: freepaths
3
- Version: 2.3.2
3
+ Version: 2.3.3
4
4
  Summary: Phonon Monte Carlo simulator
5
5
  Home-page: https://github.com/anufrievroman/freepaths
6
6
  Author: Roman Anufriev
@@ -7,7 +7,7 @@ from colorama import Fore, Style
7
7
 
8
8
  from freepaths.particle_types import ParticleType
9
9
 
10
- __version__ = "2.3.2"
10
+ __version__ = "2.3.3"
11
11
 
12
12
  colorama.init()
13
13
 
@@ -69,6 +69,8 @@ class Config:
69
69
  self.electron_mfp = ELECTRON_MFP
70
70
  self.mean_mapping_constant = MEAN_MAPPING_CONSTANT
71
71
  self.is_carrier_electron = IS_CARRIER_ELECTRON
72
+ self.fermi_level_lower_bound = FERMI_LEVEL_LOWER_BOUND
73
+ self.fermi_level_upper_bound = FERMI_LEVEL_UPPER_BOUND
72
74
 
73
75
  # Animation:
74
76
  self.output_path_animation = OUTPUT_PATH_ANIMATION
@@ -66,12 +66,6 @@ class GeneralData(Data):
66
66
  self.travel_times = []
67
67
  self.mean_free_paths = []
68
68
  self.thermal_conductivity = []
69
- self.interfaces_angles = []
70
- self.interfaces_transmission_factor = []
71
- self.interfaces_wavelength = []
72
- self.interfaces_frequency = []
73
- self.interfaces_mode = []
74
-
75
69
  def save_particle_data(self, pt):
76
70
  """Add information about the particle to the dataset"""
77
71
  self.frequencies.append(pt.f)
@@ -89,11 +83,6 @@ class GeneralData(Data):
89
83
  self.travel_times.append(flight.travel_time)
90
84
  self.mean_free_paths.append(flight.mean_free_path)
91
85
  self.thermal_conductivity.append(flight.thermal_conductivity)
92
- self.interfaces_angles.extend(flight.interfaces_angles)
93
- self.interfaces_transmission_factor.extend(flight.interfaces_transmission_factor)
94
- self.interfaces_wavelength.extend(flight.interfaces_wavelength)
95
- self.interfaces_frequency.extend(flight.interfaces_frequency)
96
- self.interfaces_mode.extend(flight.interfaces_mode)
97
86
 
98
87
 
99
88
  def write_into_files(self):
@@ -105,20 +94,15 @@ class GeneralData(Data):
105
94
  np.savetxt("Data/All free paths.csv", self.free_paths, fmt='%2.4e', header="L [m]", encoding='utf-8')
106
95
  np.savetxt("Data/All initial frequencies.csv", self.frequencies, fmt='%2.4e', header="f [Hz]", encoding='utf-8')
107
96
  np.savetxt("Data/All exit angles.csv", self.exit_angles, fmt='%.4f', header="Angle [rad]", encoding='utf-8')
108
- np.savetxt("Data/All hole diffuse scattering angles.csv", self.hole_diff_scattering_angles, fmt='%.4f', header="Angle [rad]", encoding='utf-8')
109
- np.savetxt("Data/All hole specular scattering angles.csv", self.hole_spec_scattering_angles, fmt='%.4f', header="Angle [rad]", encoding='utf-8')
97
+ if cf.holes:
98
+ np.savetxt("Data/All hole diffuse scattering angles.csv", self.hole_diff_scattering_angles, fmt='%.4f', header="Angle [rad]", encoding='utf-8')
99
+ np.savetxt("Data/All hole specular scattering angles.csv", self.hole_spec_scattering_angles, fmt='%.4f', header="Angle [rad]", encoding='utf-8')
110
100
  np.savetxt("Data/All initial angles.csv", self.initial_angles, fmt='%.4f', header="Angle [rad]", encoding='utf-8')
111
101
  np.savetxt("Data/All group velocities.csv", self.group_velocities, fmt='%.4f', header="Vg [m//s]", encoding='utf-8')
112
102
  np.savetxt("Data/All travel times.csv", self.travel_times, fmt='%2.4e', header="Travel time [s]", encoding='utf-8')
113
103
  np.savetxt("Data/All mean free paths.csv", self.mean_free_paths, fmt='%2.4e', header="MFPs [m]", encoding='utf-8')
114
104
  np.savetxt("Data/All thermal conductivities.csv", self.thermal_conductivity, fmt='%2.4e', header="K [W/mK]", encoding='utf-8')
115
105
  np.savetxt("Data/All initial energies.csv", self.initial_energies, fmt='%2.4e', header="Energy [J]", encoding='utf-8')
116
- np.savetxt("Data/All interfaces angles.csv", self.interfaces_angles, fmt='%.4f', header="Angle [rad]", encoding='utf-8')
117
- np.savetxt("Data/All interfaces transmission factor.csv", self.interfaces_transmission_factor, fmt='%2.4e', header="Transmission factor [%]", encoding='utf-8')
118
- np.savetxt("Data/All interfaces wavelength.csv", self.interfaces_wavelength, fmt='%.18f', header="Interfaces wavelength [m]", encoding='utf-8')
119
- np.savetxt("Data/All interfaces frequency.csv", self.interfaces_frequency, fmt='%.18f', header="Interfaces frequency [THz]", encoding='utf-8')
120
- np.savetxt("Data/All interfaces mode.csv", self.interfaces_mode, fmt='%d', header="Interfaces mode", encoding='utf-8')
121
-
122
106
  def dump_data(self):
123
107
  """Return data of a process in the form of a dictionary to be attached to the global data"""
124
108
  return {
@@ -133,11 +117,6 @@ class GeneralData(Data):
133
117
  'mean_free_paths': self.mean_free_paths,
134
118
  'thermal_conductivity': self.thermal_conductivity,
135
119
  'initial_energies': self.initial_energies,
136
- 'interfaces_angles': self.interfaces_angles,
137
- 'interfaces_transmission_factor': self.interfaces_transmission_factor,
138
- 'interfaces_wavelength': self.interfaces_wavelength,
139
- 'interfaces_frequency': self.interfaces_frequency,
140
- 'interfaces_mode': self.interfaces_mode,
141
120
  }
142
121
 
143
122
 
@@ -298,11 +277,9 @@ class SegmentData(Data):
298
277
 
299
278
  def record_time_in_segment(self, coordinate):
300
279
  """Record how long particle stays in different segments"""
301
- for segment_number in range(cf.number_of_length_segments):
302
- segment_beginning = segment_number * (cf.length / cf.number_of_length_segments)
303
- segment_end = (segment_number + 1)*(cf.length / cf.number_of_length_segments)
304
- if segment_beginning <= coordinate < segment_end:
305
- self.time_spent[segment_number] += cf.timestep * 1e6
280
+ segment = int(coordinate // (cf.length / cf.number_of_length_segments))
281
+ if 0 <= segment < cf.number_of_length_segments:
282
+ self.time_spent[segment] += cf.timestep * 1e9
306
283
 
307
284
  def write_into_files(self):
308
285
  """Write data into files"""
@@ -25,13 +25,16 @@ NUMBER_OF_STABILIZATION_TIMEFRAMES = 5
25
25
 
26
26
  # Electron parameters [eV]
27
27
  IS_CARRIER_ELECTRON = True
28
- ENERGY_UPPER_BOUND = 6*k*T / electron_volt
28
+ ENERGY_UPPER_BOUND = 350e-3
29
29
  ENERGY_LOWER_BOUND = 0
30
30
  ENERGY_STEP = 5e-3
31
31
 
32
32
  ELECTRON_MFP = 10e-9 # [m]
33
33
  MEAN_MAPPING_CONSTANT = 5e-6 # [m²]
34
34
 
35
+ FERMI_LEVEL_LOWER_BOUND = -0.2 # [eV] lower end of post-processing Fermi level sweep
36
+ FERMI_LEVEL_UPPER_BOUND = 0.2 # [eV] upper end of post-processing Fermi level sweep
37
+
35
38
 
36
39
  # Animation:
37
40
  OUTPUT_PATH_ANIMATION = False
@@ -1,6 +1,6 @@
1
1
  """This module provides electron class which generates and moves an electron"""
2
2
 
3
- from random import choice
3
+ from random import choice, random
4
4
  from scipy.constants import h, electron_volt
5
5
  from freepaths.config import cf
6
6
  from freepaths.particle import Particle
@@ -72,6 +72,4 @@ class Electron(Particle):
72
72
  Considering only elasctic scattering between electrons and phonons and thus a constant MFP.
73
73
  """
74
74
  mfp = cf.electron_mfp
75
- self.time_of_internal_scattering = mfp / self.speed
76
-
77
- # self.time_of_internal_scattering = -np.log(random()) * (mfp/self.speed)
75
+ self.time_of_internal_scattering = -np.log(random()) * (mfp / self.speed)
@@ -43,12 +43,6 @@ class Flight:
43
43
  self.hole_diff_scattering_angles = []
44
44
  self.hole_spec_scattering_angles = []
45
45
  self.thermal_conductivity = 0.0
46
- self.interfaces_angles = []
47
- self.interfaces_transmission_factor = []
48
- self.interfaces_wavelength = []
49
- self.interfaces_frequency = []
50
- self.interfaces_mode = []
51
-
52
46
  @property
53
47
  def mean_free_path(self):
54
48
  """Mean value of all free flights"""
@@ -74,26 +68,6 @@ class Flight:
74
68
  """Save angle of specular scattering from the hole"""
75
69
  self.hole_spec_scattering_angles.append(angle)
76
70
 
77
- def save_interfaces_angles(self, angle):
78
- """Save a transmission event angle"""
79
- self.interfaces_angles.append(angle)
80
-
81
- def save_interfaces_transmission_factor(self, alpha_total):
82
- """Save a transmission event"""
83
- self.interfaces_transmission_factor.append(alpha_total)
84
-
85
- def save_interfaces_wavelength(self):
86
- """Save a wavelength event"""
87
- self.interfaces_wavelength.append(self.particle.wavelength)
88
-
89
- def save_interfaces_frequency(self):
90
- """Save a frequency event"""
91
- self.interfaces_frequency.append(self.particle.f)
92
-
93
- def save_interfaces_mode (self):
94
- """Save mode"""
95
- self.interfaces_mode.append(self.particle.branch_number)
96
-
97
71
  def restart(self):
98
72
  """Restart the flight after a scattering event"""
99
73
  self.time_since_previous_scattering = 0.0
@@ -0,0 +1,195 @@
1
+ """Module to run thermal conductivity calculations by integrating the phonon dispersion"""
2
+
3
+ import os
4
+ import numpy as np
5
+ import sys
6
+ import time
7
+ import shutil
8
+ import scipy
9
+ import math
10
+ import logging
11
+ import signal
12
+ import traceback
13
+ import multiprocessing
14
+ from colorama import Fore, Style
15
+
16
+ # Modules:
17
+ from freepaths.animation import create_animation
18
+ from freepaths.config import cf
19
+ from freepaths.run_particle import run_particle
20
+ from freepaths.phonon import Phonon
21
+ from freepaths.flight import Flight
22
+ from freepaths.particle_types import ParticleType
23
+ from freepaths.data import ScatteringData, GeneralData, SegmentData, PathData, TriangleScatteringData
24
+ from freepaths.materials import Si, SiC, Graphite, SiGe
25
+ from freepaths.maps import ScatteringMap
26
+ from freepaths.progress import Progress
27
+ from freepaths.output_info import output_general_information, output_scattering_information, output_parameter_warnings
28
+ from freepaths.output_plots import plot_data
29
+
30
+
31
+ def _branch_worker(branch_number, shared_list):
32
+ """Entry point for each worker process. Catches exceptions so they are logged
33
+ rather than silently swallowed by the multiprocessing machinery."""
34
+ signal.signal(signal.SIGINT, signal.SIG_IGN)
35
+ try:
36
+ _run_branch(branch_number, shared_list)
37
+ except Exception:
38
+ logging.exception("Branch worker %d crashed", branch_number)
39
+
40
+
41
+ def _run_branch(branch_number, shared_list):
42
+ """Run all phonons for one polarization branch and push results to shared_list.
43
+
44
+ Each call processes NUMBER_OF_PARTICLES phonons sampled uniformly across the
45
+ dispersion of the given branch. The k-vector integration weight (d_k_vector)
46
+ is the width of the k-space interval assigned to each phonon, so the sum of
47
+ flight.thermal_conductivity over all phonons approximates the branch integral
48
+ in Phys. Rev. 132 2461 (1963).
49
+
50
+ Branch 0 also drives the shared progress bar; the other two run silently.
51
+ Results are appended to shared_list as a dict of dump_data() payloads so that
52
+ the main process can merge them with the standard read_data() mechanism.
53
+ """
54
+
55
+ # Each worker builds its own material instance so the dispersion is
56
+ # sampled with the correct number of points (number_of_particles + 1
57
+ # intervals → number_of_particles midpoints):
58
+ if cf.media == "Si":
59
+ material = Si(cf.temp, num_points=cf.number_of_particles + 1)
60
+ elif cf.media == "SiGe":
61
+ material = SiGe(cf.temp, num_points=cf.number_of_particles + 1)
62
+ elif cf.media == "SiC":
63
+ material = SiC(cf.temp, num_points=cf.number_of_particles + 1)
64
+ elif cf.media == "Graphite":
65
+ material = Graphite(cf.temp, num_points=cf.number_of_particles + 1)
66
+ else:
67
+ logging.error(f"Material {cf.media} is not supported")
68
+ return
69
+
70
+ # Local data structures — merged into the global ones after all workers finish:
71
+ scatter_stats = ScatteringData()
72
+ general_stats = GeneralData()
73
+ segment_stats = SegmentData()
74
+ places_stats = TriangleScatteringData()
75
+ path_stats = PathData()
76
+ scatter_maps = ScatteringMap()
77
+
78
+ total_thermal_conductivity = 0.0
79
+ progress = Progress() if branch_number == 0 else None
80
+
81
+ for index in range(cf.number_of_particles):
82
+ if progress:
83
+ progress.render(index, cf.number_of_particles)
84
+
85
+ # Mid-point k-vector and integration weight for this phonon:
86
+ k_vector = (material.dispersion[index + 1, 0] + material.dispersion[index, 0]) / 2
87
+ d_k_vector = material.dispersion[index + 1, 0] - material.dispersion[index, 0]
88
+
89
+ # Initialise phonon at this k-point and run it through the geometry:
90
+ phonon = Phonon(material, branch_number, index)
91
+ flight = Flight(phonon)
92
+ run_particle(phonon, flight, scatter_stats, places_stats, segment_stats, None, scatter_maps, material)
93
+
94
+ # Mode heat capacity (Ref. PRB 88 155318, 2013):
95
+ omega = 2 * math.pi * phonon.f
96
+ part = scipy.constants.hbar * omega / (scipy.constants.k * cf.temp)
97
+ c_p = scipy.constants.k * part**2 * math.exp(part) / (math.exp(part) - 1)**2
98
+
99
+ # Thermal conductivity contribution from this k-point (Ref. Phys. Rev. 132 2461, 1963):
100
+ mean_relax_time = flight.mean_free_path / phonon.speed
101
+ flight.thermal_conductivity = (1 / (6 * math.pi**2)) * c_p * phonon.speed**2 * mean_relax_time * k_vector**2 * d_k_vector
102
+ total_thermal_conductivity += flight.thermal_conductivity
103
+
104
+ general_stats.save_particle_data(phonon)
105
+ general_stats.save_flight_data(flight)
106
+
107
+ if index < cf.output_trajectories_of_first:
108
+ path_stats.save_particle_path(flight)
109
+
110
+ # Push this branch's results to the shared list for the main process to collect:
111
+ shared_list.append({
112
+ 'total_thermal_conductivity': total_thermal_conductivity,
113
+ 'scatter_stats': scatter_stats.dump_data(),
114
+ 'general_stats': general_stats.dump_data(),
115
+ 'segment_stats': segment_stats.dump_data(),
116
+ 'places_stats': places_stats.dump_data(),
117
+ 'path_stats': path_stats.dump_data(),
118
+ 'scatter_maps': scatter_maps.dump_data(),
119
+ })
120
+
121
+
122
+ def main(input_file, particle_type):
123
+ """Integrate the phonon dispersion over all three branches in parallel to get
124
+ the bulk thermal conductivity via the MFP-sampling method."""
125
+
126
+ print(f'Mean free path sampling of {Fore.GREEN}{cf.output_folder_name}{Style.RESET_ALL}')
127
+ start_time = time.time()
128
+
129
+ # Spawn one worker process per polarization branch (LA + 2×TA).
130
+ # All three run concurrently; branch 0 reports progress to stdout:
131
+ manager = multiprocessing.Manager()
132
+ shared_list = manager.list()
133
+
134
+ processes = [
135
+ multiprocessing.Process(target=_branch_worker, args=(branch_number, shared_list))
136
+ for branch_number in range(3)
137
+ ]
138
+ for process in processes:
139
+ process.start()
140
+ try:
141
+ for process in processes:
142
+ process.join()
143
+ except KeyboardInterrupt:
144
+ for process in processes:
145
+ process.terminate()
146
+ raise
147
+
148
+ # Merge per-branch results into single data structures:
149
+ scatter_stats = ScatteringData()
150
+ general_stats = GeneralData()
151
+ segment_stats = SegmentData()
152
+ places_stats = TriangleScatteringData()
153
+ path_stats = PathData()
154
+ scatter_maps = ScatteringMap()
155
+ total_thermal_conductivity = 0.0
156
+
157
+ for result in shared_list:
158
+ total_thermal_conductivity += result['total_thermal_conductivity']
159
+ scatter_stats.read_data(result['scatter_stats'])
160
+ general_stats.read_data(result['general_stats'])
161
+ segment_stats.read_data(result['segment_stats'])
162
+ places_stats.read_data(result['places_stats'])
163
+ path_stats.read_data(result['path_stats'])
164
+ scatter_maps.read_data(result['scatter_maps'])
165
+
166
+ # Create the output folder and copy input file:
167
+ if not os.path.exists("Results/" + cf.output_folder_name):
168
+ os.makedirs("Results/" + cf.output_folder_name)
169
+ os.makedirs("Results/" + cf.output_folder_name + '/Data')
170
+ if input_file:
171
+ shutil.copy(input_file, "Results/" + cf.output_folder_name)
172
+ os.chdir("Results/" + cf.output_folder_name)
173
+
174
+ # Save data and generate plots:
175
+ general_stats.write_into_files()
176
+ scatter_stats.write_into_files()
177
+ segment_stats.write_into_files()
178
+ if cf.output_scattering_map:
179
+ scatter_maps.write_into_files()
180
+ path_stats.write_into_files()
181
+
182
+ if cf.output_path_animation:
183
+ create_animation()
184
+
185
+ np.savetxt("Data/Thermal conductivity from MFP.csv", np.array([total_thermal_conductivity]), fmt='%2.4e', header="K [W/mK]", encoding='utf-8')
186
+
187
+ sys.stdout.write("\rAnalyzing the data...")
188
+ plot_data(particle_type, cf, mfp_sampling=True)
189
+
190
+ output_general_information(start_time)
191
+ output_scattering_information(scatter_stats)
192
+ output_parameter_warnings(ParticleType.PHONON)
193
+ sys.stdout.write(f'\rSee the results in {Fore.GREEN}Results/{cf.output_folder_name}{Style.RESET_ALL}\n')
194
+ sys.stdout.write(f"\rThermal conductivity = {Fore.GREEN}{total_thermal_conductivity:.5f}{Style.RESET_ALL} W/m·K\n")
195
+ sys.stdout.write(f"\r{Fore.BLUE}Thank you for using FreePATHS{Style.RESET_ALL}\n\n")
@@ -280,10 +280,12 @@ def main(input_file, particle_type):
280
280
  sys.stdout.write("\rSaving raw data...")
281
281
  general_stats.write_into_files()
282
282
  scatter_stats.write_into_files()
283
- places_stats.write_into_files()
283
+ if cf.holes:
284
+ places_stats.write_into_files()
284
285
  segment_stats.write_into_files()
285
286
  thermal_maps.write_into_files()
286
- scatter_maps.write_into_files()
287
+ if cf.output_scattering_map:
288
+ scatter_maps.write_into_files()
287
289
  path_stats.write_into_files()
288
290
  if particle_type is ParticleType.ELECTRON:
289
291
  electron_computations.write_into_file()
@@ -119,6 +119,8 @@ class ThermalMaps(Maps):
119
119
 
120
120
  def calculate_pixel_volumes(self, number_of_pixels_x, number_of_pixels_y):
121
121
  """Calculate a map showing if the pixel contains material (1) or a hole (0)"""
122
+ if not cf.holes:
123
+ return np.ones((number_of_pixels_y, number_of_pixels_x))
122
124
  pixel_volume_ratios = np.zeros((number_of_pixels_y, number_of_pixels_x))
123
125
  for x_index in range(number_of_pixels_x):
124
126
  for y_index in range(number_of_pixels_y):
@@ -2,7 +2,7 @@
2
2
 
3
3
  from abc import ABC, abstractmethod
4
4
  import numpy as np
5
- from scipy.constants import electron_volt, electron_mass
5
+ from scipy.constants import electron_volt, electron_mass, k as k_B, h as h_planck
6
6
 
7
7
 
8
8
  class Material(ABC):
@@ -74,6 +74,16 @@ class Si(Material):
74
74
  coeffs = above_50K_coeffs
75
75
  self.heat_capacity = np.polyval(coeffs, self.temp)
76
76
 
77
+ # --- Impurity scattering via Matthiessen's rule (disabled; re-enable when ready) ---
78
+ # Phonon-limited electron MFP at 300K, calibrated to match lightly doped Si literature
79
+ # phonon_limited_electron_mfp = 10e-9 # [m]
80
+ # Caughey-Thomas ionized-impurity parameters for electrons, Masetti et al. (1983)
81
+ # _ct_mu_max = 1417e-4 # [m²/V·s]
82
+ # _ct_mu_min = 52.2e-4 # [m²/V·s]
83
+ # _ct_n_ref = 9.68e22 # [m⁻³] (= 9.68e16 cm⁻³)
84
+ # _ct_alpha = 0.680
85
+ # ---------------------------------------------------------------------------------
86
+
77
87
  def assign_electrical_properties(self, fermi_level):
78
88
  """Assign differents electrical properties to the material."""
79
89
  self.effective_electron_dos_mass = 1.18 * electron_mass # [kg] at 300K for pure Si, supposed constant for all temperatures (~1-5% error)
@@ -87,6 +97,17 @@ class Si(Material):
87
97
  else:
88
98
  self.fermi_level = -0.037 * electron_volt # [J]
89
99
 
100
+ # def carrier_density(self):
101
+ # """Electron carrier density from Fermi level using Boltzmann approximation [m⁻³]"""
102
+ # nc = 2 * (2 * np.pi * self.effective_electron_dos_mass * k_B * self.temp / h_planck**2) ** 1.5
103
+ # return nc * np.exp(self.fermi_level / (k_B * self.temp))
104
+
105
+ # def effective_electron_mfp(self):
106
+ # """Effective electron MFP combining phonon and impurity scattering via Matthiessen's rule"""
107
+ # n = self.carrier_density()
108
+ # mu = self._ct_mu_min + (self._ct_mu_max - self._ct_mu_min) / (1 + (n / self._ct_n_ref) ** self._ct_alpha)
109
+ # return self.phonon_limited_electron_mfp * mu / self._ct_mu_max
110
+
90
111
 
91
112
  class Vacuum:
92
113
  def __init__(self, temp=300):
@@ -110,7 +131,18 @@ class SiC:
110
131
  Heat capacity - Collins et al. Journal of Applied Physics 68, 6510 (1990)
111
132
  """
112
133
 
113
- def __init__(self, temp, num_points=1000):
134
+ # --- Impurity scattering via Matthiessen's rule (disabled; re-enable when ready) ---
135
+ # 4H-SiC electrical properties:
136
+ # Electron effective masses - Ioffe database / Persson & Lindefelt, PRB 54, 10257 (1996)
137
+ # CT parameters - Roschke & Schwierz, IEEE Trans. Electron Devices 48, 1442 (2001)
138
+ # phonon_limited_electron_mfp = 7e-9 # [m] estimate, needs experimental calibration
139
+ # _ct_mu_max = 947e-4 # [m²/V·s]
140
+ # _ct_mu_min = 40e-4 # [m²/V·s]
141
+ # _ct_n_ref = 2.0e23 # [m⁻³] (= 2.0e17 cm⁻³)
142
+ # _ct_alpha = 0.61
143
+ # ---------------------------------------------------------------------------------
144
+
145
+ def __init__(self, temp, num_points=1000, fermi_level=None):
114
146
  self.name = "SiC"
115
147
  self.density = 3215 # [kg/m^3]
116
148
  self.default_speed = 6500 # [m/s] Need to change this probably...
@@ -118,6 +150,27 @@ class SiC:
118
150
  self.assign_phonon_dispersion(num_points)
119
151
  self.assign_heat_capacity()
120
152
 
153
+ # def assign_electrical_properties(self, fermi_level):
154
+ # """Assign electrical properties for 4H-SiC"""
155
+ # self.effective_electron_dos_mass = 1.0 * electron_mass # [kg] 4H-SiC, Ioffe
156
+ # self.effective_electron_mass = 0.36 * electron_mass # [kg] conductivity mass
157
+ # self.effective_hole_dos_mass = 0.6 * electron_mass
158
+ # self.effective_hole_mass = 0.6 * electron_mass
159
+ # self.fermi_level = fermi_level # None = no CT correction applied
160
+
161
+ # def carrier_density(self):
162
+ # """Electron carrier density from Fermi level using Boltzmann approximation [m⁻³]"""
163
+ # nc = 2 * (2 * np.pi * self.effective_electron_dos_mass * k_B * self.temp / h_planck**2) ** 1.5
164
+ # return nc * np.exp(self.fermi_level / (k_B * self.temp))
165
+
166
+ # def effective_electron_mfp(self):
167
+ # """Effective electron MFP combining phonon and impurity scattering via Matthiessen's rule"""
168
+ # if self.fermi_level is None:
169
+ # return self.phonon_limited_electron_mfp
170
+ # n = self.carrier_density()
171
+ # mu = self._ct_mu_min + (self._ct_mu_max - self._ct_mu_min) / (1 + (n / self._ct_n_ref) ** self._ct_alpha)
172
+ # return self.phonon_limited_electron_mfp * mu / self._ct_mu_max
173
+
121
174
  def assign_phonon_dispersion(self, num_points):
122
175
  """Assign phonon dispersion"""
123
176
 
@@ -220,6 +273,13 @@ class SiGe(Material):
220
273
  Effective masses – linearly interpolated between Si and Ge at x=0.2 (Schaffler, 2001)
221
274
  """
222
275
 
276
+ # --- Impurity scattering via Matthiessen's rule (disabled; re-enable when ready) ---
277
+ # phonon_limited_electron_mfp = 7e-9 # [m] estimate; alloy scattering reduces from pure Si
278
+ # def effective_electron_mfp(self):
279
+ # """Phonon-limited only — CT params not established for SiGe alloy"""
280
+ # return self.phonon_limited_electron_mfp
281
+ # ---------------------------------------------------------------------------------
282
+
223
283
  def __init__(self, temp, num_points=1000):
224
284
  self.name = "SiGe"
225
285
  self.default_speed = 3700 # [m/s] – average LA/TA
@@ -275,6 +335,13 @@ class Diamond(Material):
275
335
  Dispersion - Ref. PRB 58 12899 (1998)
276
336
  """
277
337
 
338
+ # --- Impurity scattering via Matthiessen's rule (disabled; re-enable when ready) ---
339
+ # phonon_limited_electron_mfp = 20e-9 # [m] estimate; CT params for n-type diamond not established
340
+ # def effective_electron_mfp(self):
341
+ # """Phonon-limited only — CT params for n-type diamond not established"""
342
+ # return self.phonon_limited_electron_mfp
343
+ # ---------------------------------------------------------------------------------
344
+
278
345
  def __init__(self, temp, num_points=1000):
279
346
  self.name = "Diamond"
280
347
  self.density = 3500 # [kg/m^3]