freepaths 2.3.2__tar.gz → 2.3.4__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 (44) hide show
  1. {freepaths-2.3.2 → freepaths-2.3.4}/PKG-INFO +1 -1
  2. {freepaths-2.3.2 → freepaths-2.3.4}/freepaths/__main__.py +5 -5
  3. {freepaths-2.3.2 → freepaths-2.3.4}/freepaths/config.py +7 -1
  4. {freepaths-2.3.2 → freepaths-2.3.4}/freepaths/data.py +35 -48
  5. {freepaths-2.3.2 → freepaths-2.3.4}/freepaths/default_config.py +4 -1
  6. {freepaths-2.3.2 → freepaths-2.3.4}/freepaths/electron.py +3 -5
  7. {freepaths-2.3.2 → freepaths-2.3.4}/freepaths/flight.py +0 -26
  8. freepaths-2.3.4/freepaths/main_mfp_sampling.py +231 -0
  9. {freepaths-2.3.2 → freepaths-2.3.4}/freepaths/main_tracing.py +52 -32
  10. {freepaths-2.3.2 → freepaths-2.3.4}/freepaths/maps.py +10 -45
  11. {freepaths-2.3.2 → freepaths-2.3.4}/freepaths/materials.py +73 -2
  12. freepaths-2.3.4/freepaths/options.py +10 -0
  13. freepaths-2.3.4/freepaths/output_electron_plots.py +239 -0
  14. {freepaths-2.3.2 → freepaths-2.3.4}/freepaths/output_info.py +41 -12
  15. {freepaths-2.3.2 → freepaths-2.3.4}/freepaths/output_plots.py +116 -558
  16. {freepaths-2.3.2 → freepaths-2.3.4}/freepaths/phonon.py +1 -1
  17. freepaths-2.3.4/freepaths/post_computations.py +199 -0
  18. {freepaths-2.3.2 → freepaths-2.3.4}/freepaths/run_particle.py +3 -2
  19. {freepaths-2.3.2 → freepaths-2.3.4}/freepaths/scatterers.py +0 -16
  20. {freepaths-2.3.2 → freepaths-2.3.4}/freepaths/scattering_interfaces.py +1 -1
  21. {freepaths-2.3.2 → freepaths-2.3.4}/freepaths/scattering_primitives.py +1 -1
  22. {freepaths-2.3.2 → freepaths-2.3.4}/freepaths.egg-info/PKG-INFO +1 -1
  23. {freepaths-2.3.2 → freepaths-2.3.4}/freepaths.egg-info/SOURCES.txt +2 -1
  24. freepaths-2.3.2/freepaths/main_mfp_sampling.py +0 -135
  25. freepaths-2.3.2/freepaths/particle_types.py +0 -5
  26. freepaths-2.3.2/freepaths/post_computations.py +0 -177
  27. {freepaths-2.3.2 → freepaths-2.3.4}/README.md +0 -0
  28. {freepaths-2.3.2 → freepaths-2.3.4}/freepaths/animation.py +0 -0
  29. {freepaths-2.3.2 → freepaths-2.3.4}/freepaths/interface_smmm.py +0 -0
  30. {freepaths-2.3.2 → freepaths-2.3.4}/freepaths/move.py +0 -0
  31. {freepaths-2.3.2 → freepaths-2.3.4}/freepaths/output_structure.py +0 -0
  32. {freepaths-2.3.2 → freepaths-2.3.4}/freepaths/particle.py +0 -0
  33. {freepaths-2.3.2 → freepaths-2.3.4}/freepaths/progress.py +0 -0
  34. {freepaths-2.3.2 → freepaths-2.3.4}/freepaths/scattering.py +0 -0
  35. {freepaths-2.3.2 → freepaths-2.3.4}/freepaths/scattering_semicircle.py +0 -0
  36. {freepaths-2.3.2 → freepaths-2.3.4}/freepaths/scattering_types.py +0 -0
  37. {freepaths-2.3.2 → freepaths-2.3.4}/freepaths/sources.py +0 -0
  38. {freepaths-2.3.2 → freepaths-2.3.4}/freepaths.egg-info/dependency_links.txt +0 -0
  39. {freepaths-2.3.2 → freepaths-2.3.4}/freepaths.egg-info/entry_points.txt +0 -0
  40. {freepaths-2.3.2 → freepaths-2.3.4}/freepaths.egg-info/requires.txt +0 -0
  41. {freepaths-2.3.2 → freepaths-2.3.4}/freepaths.egg-info/top_level.txt +0 -0
  42. {freepaths-2.3.2 → freepaths-2.3.4}/pyproject.toml +0 -0
  43. {freepaths-2.3.2 → freepaths-2.3.4}/setup.cfg +0 -0
  44. {freepaths-2.3.2 → freepaths-2.3.4}/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.4
4
4
  Summary: Phonon Monte Carlo simulator
5
5
  Home-page: https://github.com/anufrievroman/freepaths
6
6
  Author: Roman Anufriev
@@ -5,9 +5,9 @@ import argparse
5
5
  import colorama
6
6
  from colorama import Fore, Style
7
7
 
8
- from freepaths.particle_types import ParticleType
8
+ from freepaths.options import SimulationMode
9
9
 
10
- __version__ = "2.3.2"
10
+ __version__ = "2.3.4"
11
11
 
12
12
  colorama.init()
13
13
 
@@ -45,15 +45,15 @@ def run():
45
45
  try:
46
46
  if args.sampling:
47
47
  import freepaths.main_mfp_sampling
48
- freepaths.main_mfp_sampling.main(args.input_file, ParticleType.PHONON)
48
+ freepaths.main_mfp_sampling.main(args.input_file, SimulationMode.PHONON_MFP_SAMPLING)
49
49
 
50
50
  elif args.electron:
51
51
  import freepaths.main_tracing
52
- freepaths.main_tracing.main(args.input_file, ParticleType.ELECTRON)
52
+ freepaths.main_tracing.main(args.input_file, SimulationMode.ELECTRON)
53
53
 
54
54
  else:
55
55
  import freepaths.main_tracing
56
- freepaths.main_tracing.main(args.input_file, ParticleType.PHONON)
56
+ freepaths.main_tracing.main(args.input_file, SimulationMode.PHONON_TRACING)
57
57
 
58
58
  except KeyboardInterrupt:
59
59
  print(f"\n{Fore.RED}Simulation interrupted by user.{Style.RESET_ALL}")
@@ -3,6 +3,7 @@ Module that reads the user input file, provides default values,
3
3
  checks the validity of the parameters and converts the variables into enums
4
4
  """
5
5
 
6
+ import os
6
7
  import sys
7
8
  import argparse
8
9
  import logging
@@ -32,8 +33,11 @@ args = parser.parse_args()
32
33
 
33
34
  # If a file is provided, overwrite the default values:
34
35
  if args.input_file:
36
+ input_path = args.input_file
37
+ if not os.path.isfile(input_path) and not input_path.endswith('.py') and os.path.isfile(input_path + '.py'):
38
+ input_path = input_path + '.py'
35
39
  try:
36
- exec(open(args.input_file, encoding='utf-8').read(), globals())
40
+ exec(open(input_path, encoding='utf-8').read(), globals())
37
41
  except FileNotFoundError:
38
42
  logging.error("Input file does not exist. Check if you are in the right folder and the file name is correct.")
39
43
  sys.exit()
@@ -69,6 +73,8 @@ class Config:
69
73
  self.electron_mfp = ELECTRON_MFP
70
74
  self.mean_mapping_constant = MEAN_MAPPING_CONSTANT
71
75
  self.is_carrier_electron = IS_CARRIER_ELECTRON
76
+ self.fermi_level_lower_bound = FERMI_LEVEL_LOWER_BOUND
77
+ self.fermi_level_upper_bound = FERMI_LEVEL_UPPER_BOUND
72
78
 
73
79
  # Animation:
74
80
  self.output_path_animation = OUTPUT_PATH_ANIMATION
@@ -66,59 +66,53 @@ 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
- def save_particle_data(self, pt):
69
+ def save_particle_data(self, pt, mode=None):
76
70
  """Add information about the particle to the dataset"""
77
- self.frequencies.append(pt.f)
78
- self.group_velocities.append(pt.speed)
79
- self.initial_energies.append(pt.energy)
80
-
81
- def save_flight_data(self, flight):
71
+ from freepaths.options import SimulationMode
72
+ if mode is SimulationMode.ELECTRON:
73
+ self.initial_energies.append(pt.energy)
74
+ else:
75
+ self.frequencies.append(pt.f)
76
+ self.group_velocities.append(pt.speed)
77
+
78
+ def save_flight_data(self, flight, mode=None):
82
79
  """Add information about the particle flight to the dataset"""
80
+ from freepaths.options import SimulationMode
83
81
  if not cf.low_memory_usage:
84
82
  self.free_paths.extend(flight.free_paths)
85
- self.initial_angles.append(flight.initial_theta)
86
- self.exit_angles.append(flight.exit_theta)
87
- self.hole_diff_scattering_angles.extend(flight.hole_diff_scattering_angles)
88
- self.hole_spec_scattering_angles.extend(flight.hole_spec_scattering_angles)
83
+ if mode is SimulationMode.PHONON_TRACING:
84
+ self.initial_angles.append(flight.initial_theta)
85
+ self.exit_angles.append(flight.exit_theta)
86
+ self.hole_diff_scattering_angles.extend(flight.hole_diff_scattering_angles)
87
+ self.hole_spec_scattering_angles.extend(flight.hole_spec_scattering_angles)
89
88
  self.travel_times.append(flight.travel_time)
90
89
  self.mean_free_paths.append(flight.mean_free_path)
91
- 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
-
90
+ if mode is SimulationMode.PHONON_MFP_SAMPLING:
91
+ self.thermal_conductivity.append(flight.thermal_conductivity)
98
92
 
99
- def write_into_files(self):
93
+ def write_into_files(self, mode=None):
100
94
  import os
95
+ from freepaths.options import SimulationMode
101
96
  if not os.path.exists("Data"):
102
97
  os.makedirs("Data")
103
98
  """Write all the data into files"""
104
99
  if not cf.low_memory_usage:
105
100
  np.savetxt("Data/All free paths.csv", self.free_paths, fmt='%2.4e', header="L [m]", encoding='utf-8')
106
- np.savetxt("Data/All initial frequencies.csv", self.frequencies, fmt='%2.4e', header="f [Hz]", encoding='utf-8')
107
- 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')
110
- np.savetxt("Data/All initial angles.csv", self.initial_angles, fmt='%.4f', header="Angle [rad]", encoding='utf-8')
111
- np.savetxt("Data/All group velocities.csv", self.group_velocities, fmt='%.4f', header="Vg [m//s]", encoding='utf-8')
101
+ if mode is SimulationMode.ELECTRON:
102
+ np.savetxt("Data/All initial energies.csv", self.initial_energies, fmt='%2.4e', header="Energy [J]", encoding='utf-8')
103
+ else:
104
+ np.savetxt("Data/All initial frequencies.csv", self.frequencies, fmt='%2.4e', header="f [Hz]", encoding='utf-8')
105
+ np.savetxt("Data/All group velocities.csv", self.group_velocities, fmt='%.4f', header="Vg [m//s]", encoding='utf-8')
106
+ if mode is SimulationMode.PHONON_TRACING:
107
+ np.savetxt("Data/All exit angles.csv", self.exit_angles, fmt='%.4f', header="Angle [rad]", encoding='utf-8')
108
+ np.savetxt("Data/All initial angles.csv", self.initial_angles, fmt='%.4f', header="Angle [rad]", encoding='utf-8')
109
+ if cf.holes:
110
+ np.savetxt("Data/All hole diffuse scattering angles.csv", self.hole_diff_scattering_angles, fmt='%.4f', header="Angle [rad]", encoding='utf-8')
111
+ np.savetxt("Data/All hole specular scattering angles.csv", self.hole_spec_scattering_angles, fmt='%.4f', header="Angle [rad]", encoding='utf-8')
112
112
  np.savetxt("Data/All travel times.csv", self.travel_times, fmt='%2.4e', header="Travel time [s]", encoding='utf-8')
113
113
  np.savetxt("Data/All mean free paths.csv", self.mean_free_paths, fmt='%2.4e', header="MFPs [m]", encoding='utf-8')
114
- np.savetxt("Data/All thermal conductivities.csv", self.thermal_conductivity, fmt='%2.4e', header="K [W/mK]", encoding='utf-8')
115
- 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
-
114
+ if mode is SimulationMode.PHONON_MFP_SAMPLING:
115
+ np.savetxt("Data/All thermal conductivities.csv", self.thermal_conductivity, fmt='%2.4e', header="K [W/mK]", encoding='utf-8')
122
116
  def dump_data(self):
123
117
  """Return data of a process in the form of a dictionary to be attached to the global data"""
124
118
  return {
@@ -133,11 +127,6 @@ class GeneralData(Data):
133
127
  'mean_free_paths': self.mean_free_paths,
134
128
  'thermal_conductivity': self.thermal_conductivity,
135
129
  '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
130
  }
142
131
 
143
132
 
@@ -298,11 +287,9 @@ class SegmentData(Data):
298
287
 
299
288
  def record_time_in_segment(self, coordinate):
300
289
  """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
290
+ segment = int(coordinate // (cf.length / cf.number_of_length_segments))
291
+ if 0 <= segment < cf.number_of_length_segments:
292
+ self.time_spent[segment] += cf.timestep * 1e9
306
293
 
307
294
  def write_into_files(self):
308
295
  """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,10 +1,10 @@
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
7
- from freepaths.particle_types import ParticleType
7
+ from freepaths.options import ParticleType
8
8
 
9
9
  import numpy as np
10
10
 
@@ -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,231 @@
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.options import SimulationMode
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.output_info import output_general_information, output_scattering_information, output_parameter_warnings
27
+ from freepaths.materials import get_media_class
28
+ from freepaths.output_plots import plot_data
29
+
30
+
31
+ def display_all_progress(shared_progress, finished_branches, branch_names):
32
+ """Render a live progress percentage for each branch on its own line."""
33
+ signal.signal(signal.SIGINT, signal.SIG_IGN)
34
+ n = len(branch_names)
35
+ width = max(len(name) for name in branch_names)
36
+ try:
37
+ for name in branch_names:
38
+ sys.stdout.write(f' {name.ljust(width)}: 0%\n')
39
+ sys.stdout.flush()
40
+ while True:
41
+ sys.stdout.write(f'\033[{n}A')
42
+ for i, name in enumerate(branch_names):
43
+ sys.stdout.write(f'\r {name.ljust(width)}: {shared_progress[i]:3d}%\n')
44
+ sys.stdout.flush()
45
+ if finished_branches.value == n:
46
+ break
47
+ time.sleep(0.1)
48
+ except Exception:
49
+ pass
50
+
51
+
52
+ def _branch_worker(branch_number, shared_list, finished_branches, shared_progress):
53
+ """Entry point for each worker process. Catches exceptions so they are logged
54
+ rather than silently swallowed by the multiprocessing machinery."""
55
+ signal.signal(signal.SIGINT, signal.SIG_IGN)
56
+ try:
57
+ _run_branch(branch_number, shared_list, shared_progress)
58
+ shared_progress[branch_number] = 100
59
+ finished_branches.value += 1
60
+ except Exception:
61
+ logging.exception("Branch worker %d crashed", branch_number)
62
+
63
+
64
+ def _run_branch(branch_number, shared_list, shared_progress):
65
+ """Run all phonons for one polarization branch and push results to shared_list.
66
+
67
+ Each call processes NUMBER_OF_PARTICLES phonons sampled uniformly across the
68
+ dispersion of the given branch. The k-vector integration weight (d_k_vector)
69
+ is the width of the k-space interval assigned to each phonon, so the sum of
70
+ flight.thermal_conductivity over all phonons approximates the branch integral
71
+ in Phys. Rev. 132 2461 (1963).
72
+
73
+ Branch 2 (TA) drives the shared progress bar; the other two run silently.
74
+ Results are appended to shared_list as a dict of dump_data() payloads so that
75
+ the main process can merge them with the standard read_data() mechanism.
76
+ """
77
+
78
+ # Each worker builds its own material instance so the dispersion is
79
+ # sampled with the correct number of points (number_of_particles + 1
80
+ # intervals → number_of_particles midpoints):
81
+ if cf.media == "Si":
82
+ material = Si(cf.temp, num_points=cf.number_of_particles + 1)
83
+ elif cf.media == "SiGe":
84
+ material = SiGe(cf.temp, num_points=cf.number_of_particles + 1)
85
+ elif cf.media == "SiC":
86
+ material = SiC(cf.temp, num_points=cf.number_of_particles + 1)
87
+ elif cf.media == "Graphite":
88
+ material = Graphite(cf.temp, num_points=cf.number_of_particles + 1)
89
+ else:
90
+ logging.error(f"Material {cf.media} is not supported")
91
+ return
92
+
93
+ # Local data structures — merged into the global ones after all workers finish:
94
+ scatter_stats = ScatteringData()
95
+ general_stats = GeneralData()
96
+ segment_stats = SegmentData()
97
+ places_stats = TriangleScatteringData()
98
+ path_stats = PathData()
99
+ scatter_maps = ScatteringMap()
100
+
101
+ total_thermal_conductivity = 0.0
102
+ last_pct = -1
103
+
104
+ for index in range(cf.number_of_particles):
105
+ pct = 100 * index // cf.number_of_particles
106
+ if pct > last_pct:
107
+ shared_progress[branch_number] = pct
108
+ last_pct = pct
109
+
110
+ # Mid-point k-vector and integration weight for this phonon:
111
+ k_vector = (material.dispersion[index + 1, 0] + material.dispersion[index, 0]) / 2
112
+ d_k_vector = material.dispersion[index + 1, 0] - material.dispersion[index, 0]
113
+
114
+ # Initialise phonon at this k-point and run it through the geometry:
115
+ phonon = Phonon(material, branch_number, index)
116
+ flight = Flight(phonon)
117
+ run_particle(phonon, flight, scatter_stats, places_stats, segment_stats, None, scatter_maps, material)
118
+
119
+ # Mode heat capacity (Ref. PRB 88 155318, 2013):
120
+ omega = 2 * math.pi * phonon.f
121
+ part = scipy.constants.hbar * omega / (scipy.constants.k * cf.temp)
122
+ c_p = scipy.constants.k * part**2 * math.exp(part) / (math.exp(part) - 1)**2
123
+
124
+ # Thermal conductivity contribution from this k-point (Ref. Phys. Rev. 132 2461, 1963):
125
+ mean_relax_time = flight.mean_free_path / phonon.speed
126
+ flight.thermal_conductivity = (1 / (6 * math.pi**2)) * c_p * phonon.speed**2 * mean_relax_time * k_vector**2 * d_k_vector
127
+ total_thermal_conductivity += flight.thermal_conductivity
128
+
129
+ general_stats.save_particle_data(phonon, SimulationMode.PHONON_MFP_SAMPLING)
130
+ general_stats.save_flight_data(flight, SimulationMode.PHONON_MFP_SAMPLING)
131
+
132
+ if index < cf.output_trajectories_of_first:
133
+ path_stats.save_particle_path(flight)
134
+
135
+ # Push this branch's results to the shared list for the main process to collect:
136
+ shared_list.append({
137
+ 'total_thermal_conductivity': total_thermal_conductivity,
138
+ 'scatter_stats': scatter_stats.dump_data(),
139
+ 'general_stats': general_stats.dump_data(),
140
+ 'segment_stats': segment_stats.dump_data(),
141
+ 'places_stats': places_stats.dump_data(),
142
+ 'path_stats': path_stats.dump_data(),
143
+ 'scatter_maps': scatter_maps.dump_data(),
144
+ })
145
+
146
+
147
+ def main(input_file, mode: SimulationMode):
148
+ """Integrate the phonon dispersion over all three branches in parallel to get
149
+ the bulk thermal conductivity via the MFP-sampling method."""
150
+
151
+ print(f'Mean free path sampling of {Fore.GREEN}{cf.output_folder_name}{Style.RESET_ALL}')
152
+ start_time = time.time()
153
+
154
+ branch_names = get_media_class(cf.media).branch_names
155
+ n_branches = len(branch_names)
156
+
157
+ # Spawn one worker process per polarization branch, all running concurrently:
158
+ manager = multiprocessing.Manager()
159
+ shared_list = manager.list()
160
+ finished_branches = manager.Value('i', 0)
161
+ shared_progress = manager.list([0] * n_branches)
162
+
163
+ processes = [
164
+ multiprocessing.Process(target=_branch_worker, args=(branch_number, shared_list, finished_branches, shared_progress))
165
+ for branch_number in range(n_branches - 1, -1, -1)
166
+ ]
167
+ for process in processes:
168
+ process.start()
169
+
170
+ display_process = multiprocessing.Process(target=display_all_progress, args=(shared_progress, finished_branches, branch_names))
171
+ display_process.start()
172
+
173
+ try:
174
+ for process in processes:
175
+ process.join()
176
+ except KeyboardInterrupt:
177
+ for process in processes:
178
+ process.terminate()
179
+ raise
180
+
181
+ display_process.join(timeout=3)
182
+ display_process.terminate()
183
+
184
+ # Merge per-branch results into single data structures:
185
+ scatter_stats = ScatteringData()
186
+ general_stats = GeneralData()
187
+ segment_stats = SegmentData()
188
+ places_stats = TriangleScatteringData()
189
+ path_stats = PathData()
190
+ scatter_maps = ScatteringMap()
191
+ total_thermal_conductivity = 0.0
192
+
193
+ for result in shared_list:
194
+ total_thermal_conductivity += result['total_thermal_conductivity']
195
+ scatter_stats.read_data(result['scatter_stats'])
196
+ general_stats.read_data(result['general_stats'])
197
+ segment_stats.read_data(result['segment_stats'])
198
+ places_stats.read_data(result['places_stats'])
199
+ path_stats.read_data(result['path_stats'])
200
+ scatter_maps.read_data(result['scatter_maps'])
201
+
202
+ # Create the output folder and copy input file:
203
+ if not os.path.exists("Results/" + cf.output_folder_name):
204
+ os.makedirs("Results/" + cf.output_folder_name)
205
+ os.makedirs("Results/" + cf.output_folder_name + '/Data')
206
+ if input_file:
207
+ shutil.copy(input_file, "Results/" + cf.output_folder_name)
208
+ os.chdir("Results/" + cf.output_folder_name)
209
+
210
+ # Save data and generate plots:
211
+ general_stats.write_into_files(mode)
212
+ scatter_stats.write_into_files()
213
+ segment_stats.write_into_files()
214
+ if cf.output_scattering_map:
215
+ scatter_maps.write_into_files()
216
+ path_stats.write_into_files()
217
+
218
+ if cf.output_path_animation:
219
+ create_animation()
220
+
221
+ np.savetxt("Data/Thermal conductivity from MFP.csv", np.array([total_thermal_conductivity]), fmt='%2.4e', header="K [W/mK]", encoding='utf-8')
222
+
223
+ sys.stdout.write("\rAnalyzing the data...")
224
+ plot_data(mode, cf)
225
+
226
+ output_general_information(start_time)
227
+ output_scattering_information(scatter_stats)
228
+ output_parameter_warnings(mode)
229
+ sys.stdout.write(f'\rSee the results in {Fore.GREEN}Results/{cf.output_folder_name}{Style.RESET_ALL}\n')
230
+ sys.stdout.write(f"\rThermal conductivity = {Fore.GREEN}{total_thermal_conductivity:.5f}{Style.RESET_ALL} W/m·K\n")
231
+ sys.stdout.write(f"\r{Fore.BLUE}Thank you for using FreePATHS{Style.RESET_ALL}\n\n")