freepaths 2.3.3__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 (42) hide show
  1. {freepaths-2.3.3 → freepaths-2.3.4}/PKG-INFO +1 -1
  2. {freepaths-2.3.3 → freepaths-2.3.4}/freepaths/__main__.py +5 -5
  3. {freepaths-2.3.3 → freepaths-2.3.4}/freepaths/config.py +5 -1
  4. {freepaths-2.3.3 → freepaths-2.3.4}/freepaths/data.py +32 -22
  5. {freepaths-2.3.3 → freepaths-2.3.4}/freepaths/electron.py +1 -1
  6. {freepaths-2.3.3 → freepaths-2.3.4}/freepaths/main_mfp_sampling.py +55 -19
  7. {freepaths-2.3.3 → freepaths-2.3.4}/freepaths/main_tracing.py +48 -30
  8. {freepaths-2.3.3 → freepaths-2.3.4}/freepaths/maps.py +8 -45
  9. {freepaths-2.3.3 → freepaths-2.3.4}/freepaths/materials.py +4 -0
  10. freepaths-2.3.4/freepaths/options.py +10 -0
  11. {freepaths-2.3.3 → freepaths-2.3.4}/freepaths/output_electron_plots.py +44 -23
  12. {freepaths-2.3.3 → freepaths-2.3.4}/freepaths/output_info.py +37 -10
  13. {freepaths-2.3.3 → freepaths-2.3.4}/freepaths/output_plots.py +55 -45
  14. {freepaths-2.3.3 → freepaths-2.3.4}/freepaths/phonon.py +1 -1
  15. {freepaths-2.3.3 → freepaths-2.3.4}/freepaths/post_computations.py +13 -4
  16. {freepaths-2.3.3 → freepaths-2.3.4}/freepaths/run_particle.py +1 -1
  17. {freepaths-2.3.3 → freepaths-2.3.4}/freepaths/scattering_interfaces.py +1 -1
  18. {freepaths-2.3.3 → freepaths-2.3.4}/freepaths/scattering_primitives.py +1 -1
  19. {freepaths-2.3.3 → freepaths-2.3.4}/freepaths.egg-info/PKG-INFO +1 -1
  20. {freepaths-2.3.3 → freepaths-2.3.4}/freepaths.egg-info/SOURCES.txt +1 -1
  21. freepaths-2.3.3/freepaths/particle_types.py +0 -5
  22. {freepaths-2.3.3 → freepaths-2.3.4}/README.md +0 -0
  23. {freepaths-2.3.3 → freepaths-2.3.4}/freepaths/animation.py +0 -0
  24. {freepaths-2.3.3 → freepaths-2.3.4}/freepaths/default_config.py +0 -0
  25. {freepaths-2.3.3 → freepaths-2.3.4}/freepaths/flight.py +0 -0
  26. {freepaths-2.3.3 → freepaths-2.3.4}/freepaths/interface_smmm.py +0 -0
  27. {freepaths-2.3.3 → freepaths-2.3.4}/freepaths/move.py +0 -0
  28. {freepaths-2.3.3 → freepaths-2.3.4}/freepaths/output_structure.py +0 -0
  29. {freepaths-2.3.3 → freepaths-2.3.4}/freepaths/particle.py +0 -0
  30. {freepaths-2.3.3 → freepaths-2.3.4}/freepaths/progress.py +0 -0
  31. {freepaths-2.3.3 → freepaths-2.3.4}/freepaths/scatterers.py +0 -0
  32. {freepaths-2.3.3 → freepaths-2.3.4}/freepaths/scattering.py +0 -0
  33. {freepaths-2.3.3 → freepaths-2.3.4}/freepaths/scattering_semicircle.py +0 -0
  34. {freepaths-2.3.3 → freepaths-2.3.4}/freepaths/scattering_types.py +0 -0
  35. {freepaths-2.3.3 → freepaths-2.3.4}/freepaths/sources.py +0 -0
  36. {freepaths-2.3.3 → freepaths-2.3.4}/freepaths.egg-info/dependency_links.txt +0 -0
  37. {freepaths-2.3.3 → freepaths-2.3.4}/freepaths.egg-info/entry_points.txt +0 -0
  38. {freepaths-2.3.3 → freepaths-2.3.4}/freepaths.egg-info/requires.txt +0 -0
  39. {freepaths-2.3.3 → freepaths-2.3.4}/freepaths.egg-info/top_level.txt +0 -0
  40. {freepaths-2.3.3 → freepaths-2.3.4}/pyproject.toml +0 -0
  41. {freepaths-2.3.3 → freepaths-2.3.4}/setup.cfg +0 -0
  42. {freepaths-2.3.3 → 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.3
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.3"
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()
@@ -66,43 +66,53 @@ class GeneralData(Data):
66
66
  self.travel_times = []
67
67
  self.mean_free_paths = []
68
68
  self.thermal_conductivity = []
69
- def save_particle_data(self, pt):
69
+ def save_particle_data(self, pt, mode=None):
70
70
  """Add information about the particle to the dataset"""
71
- self.frequencies.append(pt.f)
72
- self.group_velocities.append(pt.speed)
73
- self.initial_energies.append(pt.energy)
74
-
75
- 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):
76
79
  """Add information about the particle flight to the dataset"""
80
+ from freepaths.options import SimulationMode
77
81
  if not cf.low_memory_usage:
78
82
  self.free_paths.extend(flight.free_paths)
79
- self.initial_angles.append(flight.initial_theta)
80
- self.exit_angles.append(flight.exit_theta)
81
- self.hole_diff_scattering_angles.extend(flight.hole_diff_scattering_angles)
82
- 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)
83
88
  self.travel_times.append(flight.travel_time)
84
89
  self.mean_free_paths.append(flight.mean_free_path)
85
- self.thermal_conductivity.append(flight.thermal_conductivity)
86
-
90
+ if mode is SimulationMode.PHONON_MFP_SAMPLING:
91
+ self.thermal_conductivity.append(flight.thermal_conductivity)
87
92
 
88
- def write_into_files(self):
93
+ def write_into_files(self, mode=None):
89
94
  import os
95
+ from freepaths.options import SimulationMode
90
96
  if not os.path.exists("Data"):
91
97
  os.makedirs("Data")
92
98
  """Write all the data into files"""
93
99
  if not cf.low_memory_usage:
94
100
  np.savetxt("Data/All free paths.csv", self.free_paths, fmt='%2.4e', header="L [m]", encoding='utf-8')
95
- np.savetxt("Data/All initial frequencies.csv", self.frequencies, fmt='%2.4e', header="f [Hz]", encoding='utf-8')
96
- np.savetxt("Data/All exit angles.csv", self.exit_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')
100
- np.savetxt("Data/All initial angles.csv", self.initial_angles, fmt='%.4f', header="Angle [rad]", encoding='utf-8')
101
- 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')
102
112
  np.savetxt("Data/All travel times.csv", self.travel_times, fmt='%2.4e', header="Travel time [s]", encoding='utf-8')
103
113
  np.savetxt("Data/All mean free paths.csv", self.mean_free_paths, fmt='%2.4e', header="MFPs [m]", encoding='utf-8')
104
- np.savetxt("Data/All thermal conductivities.csv", self.thermal_conductivity, fmt='%2.4e', header="K [W/mK]", encoding='utf-8')
105
- np.savetxt("Data/All initial energies.csv", self.initial_energies, fmt='%2.4e', header="Energy [J]", encoding='utf-8')
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')
106
116
  def dump_data(self):
107
117
  """Return data of a process in the form of a dictionary to be attached to the global data"""
108
118
  return {
@@ -4,7 +4,7 @@ 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
 
@@ -19,26 +19,49 @@ from freepaths.config import cf
19
19
  from freepaths.run_particle import run_particle
20
20
  from freepaths.phonon import Phonon
21
21
  from freepaths.flight import Flight
22
- from freepaths.particle_types import ParticleType
22
+ from freepaths.options import SimulationMode
23
23
  from freepaths.data import ScatteringData, GeneralData, SegmentData, PathData, TriangleScatteringData
24
24
  from freepaths.materials import Si, SiC, Graphite, SiGe
25
25
  from freepaths.maps import ScatteringMap
26
- from freepaths.progress import Progress
27
26
  from freepaths.output_info import output_general_information, output_scattering_information, output_parameter_warnings
27
+ from freepaths.materials import get_media_class
28
28
  from freepaths.output_plots import plot_data
29
29
 
30
30
 
31
- def _branch_worker(branch_number, shared_list):
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):
32
53
  """Entry point for each worker process. Catches exceptions so they are logged
33
54
  rather than silently swallowed by the multiprocessing machinery."""
34
55
  signal.signal(signal.SIGINT, signal.SIG_IGN)
35
56
  try:
36
- _run_branch(branch_number, shared_list)
57
+ _run_branch(branch_number, shared_list, shared_progress)
58
+ shared_progress[branch_number] = 100
59
+ finished_branches.value += 1
37
60
  except Exception:
38
61
  logging.exception("Branch worker %d crashed", branch_number)
39
62
 
40
63
 
41
- def _run_branch(branch_number, shared_list):
64
+ def _run_branch(branch_number, shared_list, shared_progress):
42
65
  """Run all phonons for one polarization branch and push results to shared_list.
43
66
 
44
67
  Each call processes NUMBER_OF_PARTICLES phonons sampled uniformly across the
@@ -47,7 +70,7 @@ def _run_branch(branch_number, shared_list):
47
70
  flight.thermal_conductivity over all phonons approximates the branch integral
48
71
  in Phys. Rev. 132 2461 (1963).
49
72
 
50
- Branch 0 also drives the shared progress bar; the other two run silently.
73
+ Branch 2 (TA) drives the shared progress bar; the other two run silently.
51
74
  Results are appended to shared_list as a dict of dump_data() payloads so that
52
75
  the main process can merge them with the standard read_data() mechanism.
53
76
  """
@@ -76,11 +99,13 @@ def _run_branch(branch_number, shared_list):
76
99
  scatter_maps = ScatteringMap()
77
100
 
78
101
  total_thermal_conductivity = 0.0
79
- progress = Progress() if branch_number == 0 else None
102
+ last_pct = -1
80
103
 
81
104
  for index in range(cf.number_of_particles):
82
- if progress:
83
- progress.render(index, 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
84
109
 
85
110
  # Mid-point k-vector and integration weight for this phonon:
86
111
  k_vector = (material.dispersion[index + 1, 0] + material.dispersion[index, 0]) / 2
@@ -101,8 +126,8 @@ def _run_branch(branch_number, shared_list):
101
126
  flight.thermal_conductivity = (1 / (6 * math.pi**2)) * c_p * phonon.speed**2 * mean_relax_time * k_vector**2 * d_k_vector
102
127
  total_thermal_conductivity += flight.thermal_conductivity
103
128
 
104
- general_stats.save_particle_data(phonon)
105
- general_stats.save_flight_data(flight)
129
+ general_stats.save_particle_data(phonon, SimulationMode.PHONON_MFP_SAMPLING)
130
+ general_stats.save_flight_data(flight, SimulationMode.PHONON_MFP_SAMPLING)
106
131
 
107
132
  if index < cf.output_trajectories_of_first:
108
133
  path_stats.save_particle_path(flight)
@@ -119,24 +144,32 @@ def _run_branch(branch_number, shared_list):
119
144
  })
120
145
 
121
146
 
122
- def main(input_file, particle_type):
147
+ def main(input_file, mode: SimulationMode):
123
148
  """Integrate the phonon dispersion over all three branches in parallel to get
124
149
  the bulk thermal conductivity via the MFP-sampling method."""
125
150
 
126
151
  print(f'Mean free path sampling of {Fore.GREEN}{cf.output_folder_name}{Style.RESET_ALL}')
127
152
  start_time = time.time()
128
153
 
129
- # Spawn one worker process per polarization branch (LA + 2×TA).
130
- # All three run concurrently; branch 0 reports progress to stdout:
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:
131
158
  manager = multiprocessing.Manager()
132
159
  shared_list = manager.list()
160
+ finished_branches = manager.Value('i', 0)
161
+ shared_progress = manager.list([0] * n_branches)
133
162
 
134
163
  processes = [
135
- multiprocessing.Process(target=_branch_worker, args=(branch_number, shared_list))
136
- for branch_number in range(3)
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)
137
166
  ]
138
167
  for process in processes:
139
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
+
140
173
  try:
141
174
  for process in processes:
142
175
  process.join()
@@ -145,6 +178,9 @@ def main(input_file, particle_type):
145
178
  process.terminate()
146
179
  raise
147
180
 
181
+ display_process.join(timeout=3)
182
+ display_process.terminate()
183
+
148
184
  # Merge per-branch results into single data structures:
149
185
  scatter_stats = ScatteringData()
150
186
  general_stats = GeneralData()
@@ -172,7 +208,7 @@ def main(input_file, particle_type):
172
208
  os.chdir("Results/" + cf.output_folder_name)
173
209
 
174
210
  # Save data and generate plots:
175
- general_stats.write_into_files()
211
+ general_stats.write_into_files(mode)
176
212
  scatter_stats.write_into_files()
177
213
  segment_stats.write_into_files()
178
214
  if cf.output_scattering_map:
@@ -185,11 +221,11 @@ def main(input_file, particle_type):
185
221
  np.savetxt("Data/Thermal conductivity from MFP.csv", np.array([total_thermal_conductivity]), fmt='%2.4e', header="K [W/mK]", encoding='utf-8')
186
222
 
187
223
  sys.stdout.write("\rAnalyzing the data...")
188
- plot_data(particle_type, cf, mfp_sampling=True)
224
+ plot_data(mode, cf)
189
225
 
190
226
  output_general_information(start_time)
191
227
  output_scattering_information(scatter_stats)
192
- output_parameter_warnings(ParticleType.PHONON)
228
+ output_parameter_warnings(mode)
193
229
  sys.stdout.write(f'\rSee the results in {Fore.GREEN}Results/{cf.output_folder_name}{Style.RESET_ALL}\n')
194
230
  sys.stdout.write(f"\rThermal conductivity = {Fore.GREEN}{total_thermal_conductivity:.5f}{Style.RESET_ALL} W/m·K\n")
195
231
  sys.stdout.write(f"\r{Fore.BLUE}Thank you for using FreePATHS{Style.RESET_ALL}\n\n")
@@ -8,6 +8,7 @@ import signal
8
8
  import multiprocessing
9
9
  import traceback
10
10
  import logging
11
+ import numpy as np
11
12
  from colorama import Fore, Style
12
13
 
13
14
  # Modules:
@@ -16,13 +17,13 @@ from freepaths.run_particle import run_particle
16
17
  from freepaths.electron import Electron
17
18
  from freepaths.phonon import Phonon
18
19
  from freepaths.flight import Flight
19
- from freepaths.particle_types import ParticleType
20
+ from freepaths.options import SimulationMode
20
21
  from freepaths.data import ScatteringData, GeneralData, SegmentData, PathData, TriangleScatteringData
21
22
  from freepaths.post_computations import ElectronPostComputation
22
23
  from freepaths.progress import Progress
23
24
  from freepaths.materials import Si, SiC, Graphite, SiGe
24
25
  from freepaths.maps import ScatteringMap, ThermalMaps
25
- from freepaths.output_info import output_general_information, output_scattering_information, output_parameter_warnings
26
+ from freepaths.output_info import output_general_information, output_scattering_information, output_parameter_warnings, output_electron_information
26
27
  from freepaths.animation import create_animation
27
28
  from freepaths.output_plots import plot_data
28
29
  from freepaths.scattering import surface_scattering
@@ -34,7 +35,7 @@ class ParticleSimulator:
34
35
  It is meant to be used as a worker for multiprocessing
35
36
  """
36
37
 
37
- def __init__(self, worker_id, particle_type: ParticleType, total_particles, shared_list, output_trajectories_of):
38
+ def __init__(self, worker_id, mode: SimulationMode, total_particles, shared_list, output_trajectories_of):
38
39
 
39
40
  # Initialize the material:
40
41
  if cf.media == "Si":
@@ -52,7 +53,7 @@ class ParticleSimulator:
52
53
  # Save some general information about the process:
53
54
  self.worker_id = worker_id
54
55
  self.total_particles = total_particles
55
- self.particle_type = particle_type
56
+ self.mode = mode
56
57
  self.result_queue = shared_list
57
58
  self.creation_time = time.time()
58
59
  self.output_trajectories_of = output_trajectories_of
@@ -65,7 +66,7 @@ class ParticleSimulator:
65
66
  self.path_stats = PathData()
66
67
  self.places_stats = TriangleScatteringData()
67
68
  self.scatter_maps = ScatteringMap()
68
- self.thermal_maps = ThermalMaps()
69
+ self.thermal_maps = ThermalMaps() if mode is SimulationMode.PHONON_TRACING else None
69
70
 
70
71
  self.total_thermal_conductivity = 0.0
71
72
  self.interfaces_transmission_specular = 0
@@ -74,10 +75,10 @@ class ParticleSimulator:
74
75
 
75
76
  def simulate_particle(self, index):
76
77
  # Initiate a particle and its flight:
77
- if self.particle_type is ParticleType.PHONON:
78
- particle = Phonon(self.material)
79
- else:
78
+ if self.mode is SimulationMode.ELECTRON:
80
79
  particle = Electron(self.material)
80
+ else:
81
+ particle = Phonon(self.material)
81
82
  flight = Flight(particle)
82
83
  particle.flight = flight
83
84
 
@@ -85,8 +86,8 @@ class ParticleSimulator:
85
86
  run_particle(particle, flight, self.scatter_stats, self.places_stats, self.segment_stats, self.thermal_maps, self.scatter_maps, self.material)
86
87
 
87
88
  # Record the properties returned for this particle:
88
- self.general_stats.save_particle_data(particle) # FIXME: change method's name
89
- self.general_stats.save_flight_data(flight)
89
+ self.general_stats.save_particle_data(particle, self.mode)
90
+ self.general_stats.save_flight_data(flight, self.mode)
90
91
 
91
92
  # Record trajectories of the first N particles:
92
93
  if index < self.output_trajectories_of:
@@ -118,7 +119,7 @@ class ParticleSimulator:
118
119
  'segment_stats': self.segment_stats.dump_data(),
119
120
  'path_stats': self.path_stats.dump_data(),
120
121
  'scatter_maps': self.scatter_maps.dump_data(),
121
- 'thermal_maps': self.thermal_maps.dump_data(),
122
+ 'thermal_maps': self.thermal_maps.dump_data() if self.thermal_maps is not None else {},
122
123
  'execution_time': time.time() - self.creation_time,
123
124
  }
124
125
 
@@ -126,11 +127,11 @@ class ParticleSimulator:
126
127
  self.result_queue.append(collected_data)
127
128
 
128
129
 
129
- def worker_process(worker_id, particle_type: ParticleType,total_particles, shared_list, output_trajectories_of, finished_workers):
130
+ def worker_process(worker_id, mode: SimulationMode, total_particles, shared_list, output_trajectories_of, finished_workers):
130
131
  signal.signal(signal.SIGINT, signal.SIG_IGN)
131
132
  try:
132
133
  # Create a particle simulator and run the simulation:
133
- simulator = ParticleSimulator(worker_id, particle_type, total_particles, shared_list, output_trajectories_of)
134
+ simulator = ParticleSimulator(worker_id, mode, total_particles, shared_list, output_trajectories_of)
134
135
  simulator.simulate_particles(render_progress=1 if worker_id == 0 else 0)
135
136
 
136
137
  # Declare that the calculation is finished:
@@ -161,7 +162,7 @@ def display_workers_finished(finished_workers):
161
162
  pass
162
163
 
163
164
 
164
- def main(input_file, particle_type):
165
+ def main(input_file, mode: SimulationMode):
165
166
  """
166
167
  This is the main function.
167
168
  It should be used to simulate electron paths or phonon paths at low temperatures.
@@ -192,7 +193,7 @@ def main(input_file, particle_type):
192
193
  for i in range(cf.num_workers):
193
194
  worker_particles = workload_per_worker + (1 if i < remaining_particles else 0)
194
195
  output_trajectory_of = output_trajectories_per_worker + (1 if i < remaining_output_trajectories else 0)
195
- process = multiprocessing.Process(target=worker_process, args=(i, particle_type, worker_particles, shared_list, output_trajectory_of, finished_workers))
196
+ process = multiprocessing.Process(target=worker_process, args=(i, mode, worker_particles, shared_list, output_trajectory_of, finished_workers))
196
197
  processes.append(process)
197
198
  process.start()
198
199
 
@@ -222,7 +223,7 @@ def main(input_file, particle_type):
222
223
  segment_stats = SegmentData()
223
224
  path_stats = PathData()
224
225
  scatter_maps = ScatteringMap()
225
- thermal_maps = ThermalMaps()
226
+ thermal_maps = ThermalMaps() if mode is SimulationMode.PHONON_TRACING else None
226
227
 
227
228
  # Collect the results:
228
229
  sys.stdout.write('\nCollecting data from workers...\r')
@@ -243,7 +244,8 @@ def main(input_file, particle_type):
243
244
  segment_stats.read_data(collected_data['segment_stats'])
244
245
  path_stats.read_data(collected_data['path_stats'])
245
246
  scatter_maps.read_data(collected_data['scatter_maps'])
246
- thermal_maps.read_data(collected_data['thermal_maps'])
247
+ if thermal_maps is not None:
248
+ thermal_maps.read_data(collected_data['thermal_maps'])
247
249
  execution_time_list.append(collected_data['execution_time'])
248
250
 
249
251
  # Give some info about the variability in the worker calculation time:
@@ -251,17 +253,13 @@ def main(input_file, particle_type):
251
253
  sys.stdout.write(f'Shortest process execution time: {round(min(execution_time_list))}s\n')
252
254
  sys.stdout.write(f'Longest process execution time: {round(max(execution_time_list))}s\n')
253
255
 
254
- # Check if the total number of returned particles from the workers corresponds with the number of particles to be simulated:
255
- if len(general_stats.initial_angles) != cf.number_of_particles:
256
- sys.stdout.write(f'WARNING: {cf.number_of_particles} were meant to be simulated but only {len(general_stats.initial_angles)} particles were collected from the workers\n')
257
-
258
256
  # Run thermal calculations:
259
- thermal_maps.calculate_thermal_conductivity()
260
- thermal_maps.calculate_weighted_flux()
261
- thermal_maps.calculate_heat_flux_modulus()
257
+ if thermal_maps is not None:
258
+ thermal_maps.calculate_thermal_conductivity()
259
+ thermal_maps.calculate_heat_flux_modulus()
262
260
 
263
261
  # Run calculations on electrons if needed:
264
- if particle_type is ParticleType.ELECTRON:
262
+ if mode is SimulationMode.ELECTRON:
265
263
 
266
264
  electron_computations = ElectronPostComputation(general_stats)
267
265
  electron_computations.compute()
@@ -276,18 +274,36 @@ def main(input_file, particle_type):
276
274
  shutil.copy(input_file, "Results/" + cf.output_folder_name)
277
275
  os.chdir("Results/" + cf.output_folder_name)
278
276
 
277
+ # Try to load pre-computed phonon thermal conductivity to enable ZT calculation:
278
+ if mode is SimulationMode.ELECTRON:
279
+ kappa_ph = None
280
+ if os.path.isfile("Data/Thermal conductivity from MFP.csv"):
281
+ try:
282
+ kappa_ph = float(np.genfromtxt("Data/Thermal conductivity from MFP.csv", skip_header=1))
283
+ except Exception:
284
+ pass
285
+ elif os.path.isfile("Data/Average thermal conductivity.csv"):
286
+ try:
287
+ data = np.genfromtxt("Data/Average thermal conductivity.csv", delimiter=',', skip_header=1)
288
+ kappa_ph = float(np.atleast_1d(data)[1]) # column 1 = av_mat_tc
289
+ except Exception:
290
+ pass
291
+ if kappa_ph is not None:
292
+ electron_computations.compute_zt(kappa_ph)
293
+
279
294
  # Save data into files:
280
295
  sys.stdout.write("\rSaving raw data...")
281
- general_stats.write_into_files()
296
+ general_stats.write_into_files(mode)
282
297
  scatter_stats.write_into_files()
283
298
  if cf.holes:
284
299
  places_stats.write_into_files()
285
300
  segment_stats.write_into_files()
286
- thermal_maps.write_into_files()
301
+ if thermal_maps is not None:
302
+ thermal_maps.write_into_files(mode)
287
303
  if cf.output_scattering_map:
288
304
  scatter_maps.write_into_files()
289
305
  path_stats.write_into_files()
290
- if particle_type is ParticleType.ELECTRON:
306
+ if mode is SimulationMode.ELECTRON:
291
307
  electron_computations.write_into_file()
292
308
 
293
309
  # Generate animation of particle paths:
@@ -296,12 +312,14 @@ def main(input_file, particle_type):
296
312
 
297
313
  # Analyze and plot the data:
298
314
  sys.stdout.write("\rAnalyzing the data...")
299
- plot_data(particle_type, cf)
315
+ plot_data(mode, cf)
300
316
 
301
317
  # Output general information:
302
318
  output_general_information(start_time)
303
319
  output_scattering_information(scatter_stats)
304
- output_parameter_warnings(particle_type)
320
+ if mode is SimulationMode.ELECTRON:
321
+ output_electron_information(electron_computations)
322
+ output_parameter_warnings(mode)
305
323
 
306
324
  sys.stdout.write(f'\rSee the results in {Fore.GREEN}Results/{cf.output_folder_name}{Style.RESET_ALL}\n')
307
325
  sys.stdout.write(f"\r{Fore.BLUE}Thank you for using FreePATHS{Style.RESET_ALL}\n\n")