freepaths 2.2.4__tar.gz → 2.3.1__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 (41) hide show
  1. {freepaths-2.2.4 → freepaths-2.3.1}/PKG-INFO +7 -3
  2. {freepaths-2.2.4 → freepaths-2.3.1}/README.md +6 -2
  3. freepaths-2.3.1/freepaths/__main__.py +64 -0
  4. {freepaths-2.2.4 → freepaths-2.3.1}/freepaths/config.py +1 -2
  5. {freepaths-2.2.4 → freepaths-2.3.1}/freepaths/data.py +6 -4
  6. {freepaths-2.2.4 → freepaths-2.3.1}/freepaths/default_config.py +7 -7
  7. {freepaths-2.2.4 → freepaths-2.3.1}/freepaths/main_tracing.py +21 -10
  8. {freepaths-2.2.4 → freepaths-2.3.1}/freepaths/maps.py +4 -3
  9. {freepaths-2.2.4 → freepaths-2.3.1}/freepaths/output_info.py +43 -34
  10. {freepaths-2.2.4 → freepaths-2.3.1}/freepaths/output_plots.py +35 -25
  11. {freepaths-2.2.4 → freepaths-2.3.1}/freepaths.egg-info/PKG-INFO +7 -3
  12. freepaths-2.2.4/freepaths/__main__.py +0 -42
  13. {freepaths-2.2.4 → freepaths-2.3.1}/freepaths/animation.py +0 -0
  14. {freepaths-2.2.4 → freepaths-2.3.1}/freepaths/electron.py +0 -0
  15. {freepaths-2.2.4 → freepaths-2.3.1}/freepaths/flight.py +0 -0
  16. {freepaths-2.2.4 → freepaths-2.3.1}/freepaths/interface_smmm.py +0 -0
  17. {freepaths-2.2.4 → freepaths-2.3.1}/freepaths/main_mfp_sampling.py +0 -0
  18. {freepaths-2.2.4 → freepaths-2.3.1}/freepaths/materials.py +0 -0
  19. {freepaths-2.2.4 → freepaths-2.3.1}/freepaths/move.py +0 -0
  20. {freepaths-2.2.4 → freepaths-2.3.1}/freepaths/output_structure.py +0 -0
  21. {freepaths-2.2.4 → freepaths-2.3.1}/freepaths/particle.py +0 -0
  22. {freepaths-2.2.4 → freepaths-2.3.1}/freepaths/particle_types.py +0 -0
  23. {freepaths-2.2.4 → freepaths-2.3.1}/freepaths/phonon.py +0 -0
  24. {freepaths-2.2.4 → freepaths-2.3.1}/freepaths/post_computations.py +0 -0
  25. {freepaths-2.2.4 → freepaths-2.3.1}/freepaths/progress.py +0 -0
  26. {freepaths-2.2.4 → freepaths-2.3.1}/freepaths/run_particle.py +0 -0
  27. {freepaths-2.2.4 → freepaths-2.3.1}/freepaths/scatterers.py +0 -0
  28. {freepaths-2.2.4 → freepaths-2.3.1}/freepaths/scattering.py +0 -0
  29. {freepaths-2.2.4 → freepaths-2.3.1}/freepaths/scattering_interfaces.py +0 -0
  30. {freepaths-2.2.4 → freepaths-2.3.1}/freepaths/scattering_primitives.py +0 -0
  31. {freepaths-2.2.4 → freepaths-2.3.1}/freepaths/scattering_semicircle.py +0 -0
  32. {freepaths-2.2.4 → freepaths-2.3.1}/freepaths/scattering_types.py +0 -0
  33. {freepaths-2.2.4 → freepaths-2.3.1}/freepaths/sources.py +0 -0
  34. {freepaths-2.2.4 → freepaths-2.3.1}/freepaths.egg-info/SOURCES.txt +0 -0
  35. {freepaths-2.2.4 → freepaths-2.3.1}/freepaths.egg-info/dependency_links.txt +0 -0
  36. {freepaths-2.2.4 → freepaths-2.3.1}/freepaths.egg-info/entry_points.txt +0 -0
  37. {freepaths-2.2.4 → freepaths-2.3.1}/freepaths.egg-info/requires.txt +0 -0
  38. {freepaths-2.2.4 → freepaths-2.3.1}/freepaths.egg-info/top_level.txt +0 -0
  39. {freepaths-2.2.4 → freepaths-2.3.1}/pyproject.toml +0 -0
  40. {freepaths-2.2.4 → freepaths-2.3.1}/setup.cfg +0 -0
  41. {freepaths-2.2.4 → freepaths-2.3.1}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: freepaths
3
- Version: 2.2.4
3
+ Version: 2.3.1
4
4
  Summary: Phonon Monte Carlo simulator
5
5
  Home-page: https://github.com/anufrievroman/freepaths
6
6
  Author: Roman Anufriev
@@ -54,9 +54,13 @@ FreePATHS is a command line application, so it runs inside Linux, MacOS, or Wind
54
54
 
55
55
  There are two modes of using the program. Main mode traces a large number of phonons through a structure and collects statistics about their paths. The MFP sampling mode measures phonon mean free paths using a small number of phonons and calculates the thermal conductivity by integrating phonon dispersion.
56
56
 
57
- ### Demo
57
+ ### Getting started
58
58
 
59
- If you simply run `freepaths` without specifying an input file, the program will run a demo simulation.
59
+ If you run `freepaths` without any arguments, the program will print a usage summary. To run a demo simulation with default parameters, use the `--demo` flag:
60
+
61
+ `freepaths --demo`
62
+
63
+ Example input files are available in the [examples](https://github.com/anufrievroman/freepaths/tree/master/examples) folder.
60
64
 
61
65
  ### Main mode
62
66
 
@@ -18,9 +18,13 @@ FreePATHS is a command line application, so it runs inside Linux, MacOS, or Wind
18
18
 
19
19
  There are two modes of using the program. Main mode traces a large number of phonons through a structure and collects statistics about their paths. The MFP sampling mode measures phonon mean free paths using a small number of phonons and calculates the thermal conductivity by integrating phonon dispersion.
20
20
 
21
- ### Demo
21
+ ### Getting started
22
22
 
23
- If you simply run `freepaths` without specifying an input file, the program will run a demo simulation.
23
+ If you run `freepaths` without any arguments, the program will print a usage summary. To run a demo simulation with default parameters, use the `--demo` flag:
24
+
25
+ `freepaths --demo`
26
+
27
+ Example input files are available in the [examples](https://github.com/anufrievroman/freepaths/tree/master/examples) folder.
24
28
 
25
29
  ### Main mode
26
30
 
@@ -0,0 +1,64 @@
1
+ """FreePATHS - Free Phonon and THermal Simulator"""
2
+
3
+ import sys
4
+ import argparse
5
+ import colorama
6
+ from colorama import Fore, Style
7
+
8
+ from freepaths.particle_types import ParticleType
9
+
10
+ __version__ = "2.3.1"
11
+
12
+ colorama.init()
13
+
14
+ # Parse user arguments:
15
+ parser = argparse.ArgumentParser(
16
+ prog = 'FreePATHS',
17
+ description = 'Phonon Monte Carlo simulator',
18
+ epilog = 'For more information, examples, and tutorials, visit: https://anufrievroman.gitbook.io/freepaths'
19
+ )
20
+ parser.add_argument('input_file', nargs='?', default=None, help='The input file')
21
+ parser.add_argument("-v", "--version", action="version", version=f"FreePATHS {__version__}")
22
+ parser.add_argument("-s", "--sampling", help="Run in phonon MFP sampling mode", action="store_true")
23
+ parser.add_argument("-e", "--electron", help="Run simulation for electrons", action="store_true")
24
+ parser.add_argument("--demo", help="Run a demo simulation with default parameters", action="store_true")
25
+ args = parser.parse_args()
26
+
27
+
28
+ def run():
29
+ """Run the program depending on the mode"""
30
+ print(f"\n{Fore.BLUE}FreePATHS v{__version__}{Style.RESET_ALL}")
31
+
32
+ if not args.input_file and not args.demo:
33
+ print("\nUsage:")
34
+ print(f" freepaths <input_file> run phonon simulation")
35
+ print(f" freepaths <input_file> -e run electron simulation")
36
+ print(f" freepaths <input_file> -s run in MFP sampling mode")
37
+ print(f" freepaths --demo run a demo with default parameters")
38
+ print(f" freepaths --version show version")
39
+ print(f"\nExample input files:")
40
+ print(f" https://github.com/anufrievroman/freepaths/tree/master/examples")
41
+ print(f"\nDocumentation:")
42
+ print(f" https://anufrievroman.gitbook.io/freepaths")
43
+ sys.exit(0)
44
+
45
+ try:
46
+ if args.sampling:
47
+ import freepaths.main_mfp_sampling
48
+ freepaths.main_mfp_sampling.main(args.input_file, ParticleType.PHONON)
49
+
50
+ elif args.electron:
51
+ import freepaths.main_tracing
52
+ freepaths.main_tracing.main(args.input_file, ParticleType.ELECTRON)
53
+
54
+ else:
55
+ import freepaths.main_tracing
56
+ freepaths.main_tracing.main(args.input_file, ParticleType.PHONON)
57
+
58
+ except KeyboardInterrupt:
59
+ print(f"\n{Fore.RED}Simulation interrupted by user.{Style.RESET_ALL}")
60
+ sys.exit(0)
61
+
62
+
63
+ if __name__ == "__main__":
64
+ run()
@@ -26,6 +26,7 @@ parser = argparse.ArgumentParser(prog='FreePATHS', description='Monte Carlo simu
26
26
  parser.add_argument('input_file', nargs='?', default=None, help='The input file')
27
27
  parser.add_argument("-s", "--sampling", help="Run in MFP sampling mode", action="store_true")
28
28
  parser.add_argument("-e", "--electron", help="Run with electrons", action="store_true")
29
+ parser.add_argument("--demo", help="Run a demo simulation with default parameters", action="store_true")
29
30
  args = parser.parse_args()
30
31
 
31
32
 
@@ -36,8 +37,6 @@ if args.input_file:
36
37
  except FileNotFoundError:
37
38
  logging.error("Input file does not exist. Check if you are in the right folder and the file name is correct.")
38
39
  sys.exit()
39
- else:
40
- logging.warning("You provided no input file, so we will run a demo simulation:")
41
40
 
42
41
 
43
42
  class Config:
@@ -204,10 +204,12 @@ class ScatteringData(Data):
204
204
  def write_into_files(self):
205
205
  """Write data into a file"""
206
206
  filename = "Data/Scattering events statistics.csv"
207
- data = np.vstack((self.wall_diffuse, self.wall_specular, self.top_diffuse, self.top_specular, self.hole_diffuse,
208
- self.hole_specular, self.hot_side, self.internal, self.pillar_diffuse, self.pillar_specular,
209
- self.interfaces_diffuse, self.interfaces_specular,
210
- self.interfaces_transmission_diffuse, self.interfaces_transmission_specular)).T
207
+ n = cf.number_of_length_segments
208
+ data = np.vstack((self.wall_diffuse[:n], self.wall_specular[:n], self.top_diffuse[:n], self.top_specular[:n],
209
+ self.hole_diffuse[:n], self.hole_specular[:n], self.hot_side[:n], self.internal[:n],
210
+ self.pillar_diffuse[:n], self.pillar_specular[:n],
211
+ self.interfaces_diffuse[:n], self.interfaces_specular[:n],
212
+ self.interfaces_transmission_diffuse[:n], self.interfaces_transmission_specular[:n])).T
211
213
  header = ("Sidewalls diffuse, Sidewalls specular, Top & bottom diffuse, Top & bottom specular, "
212
214
  "Holes diffuse, Holes specular, Hot side, Internal, Pillars diffuse, Pillars specular, "
213
215
  "Interfaces diffuse, Interfaces specular, Interfaces transmission diffuse, Interfaces transmission specular")
@@ -7,18 +7,18 @@ from freepaths.sources import Source
7
7
 
8
8
  # General parameters:
9
9
  OUTPUT_FOLDER_NAME = "Si nanowire at 300 K"
10
- NUMBER_OF_PARTICLES = 5000
10
+ NUMBER_OF_PARTICLES = 10000
11
11
  NUMBER_OF_NODES = 400
12
12
  T = 300
13
13
  OUTPUT_SCATTERING_MAP = False
14
- OUTPUT_TRAJECTORIES_OF_FIRST = 50
14
+ OUTPUT_TRAJECTORIES_OF_FIRST = 20
15
15
  OUTPUT_STRUCTURE_COLOR = "#F0F0F0"
16
16
  NUMBER_OF_LENGTH_SEGMENTS = 10
17
17
  LOW_MEMORY_USAGE = False
18
18
 
19
19
  # Time parameters:
20
- TIMESTEP = 1e-12
21
- NUMBER_OF_TIMESTEPS = 300000
20
+ TIMESTEP = 2e-12
21
+ NUMBER_OF_TIMESTEPS = 200000
22
22
  NUMBER_OF_VIRTUAL_TIMESTEPS = NUMBER_OF_TIMESTEPS * 3
23
23
  NUMBER_OF_TIMEFRAMES = 8
24
24
  NUMBER_OF_STABILIZATION_TIMEFRAMES = 5
@@ -38,8 +38,8 @@ OUTPUT_PATH_ANIMATION = False
38
38
  OUTPUT_ANIMATION_FPS = 24
39
39
 
40
40
  # Map & profiles parameters:
41
- NUMBER_OF_PIXELS_X = 25
42
- NUMBER_OF_PIXELS_Y = 100
41
+ NUMBER_OF_PIXELS_X = 7
42
+ NUMBER_OF_PIXELS_Y = 67
43
43
  IGNORE_FAULTY_PARTICLES = False
44
44
 
45
45
  # Material parameters:
@@ -53,7 +53,7 @@ GRAY_APPROXIMATION_MFP = None
53
53
 
54
54
  # System dimensions [m]:
55
55
  THICKNESS = 150e-9
56
- WIDTH = 500e-9
56
+ WIDTH = 200e-9
57
57
  LENGTH = 2000e-9
58
58
  IS_TWO_DIMENSIONAL_MATERIAL = False
59
59
  INCLUDE_RIGHT_SIDEWALL = True
@@ -4,6 +4,7 @@ import os
4
4
  import sys
5
5
  import time
6
6
  import shutil
7
+ import signal
7
8
  import multiprocessing
8
9
  import traceback
9
10
  import logging
@@ -126,6 +127,7 @@ class ParticleSimulator:
126
127
 
127
128
 
128
129
  def worker_process(worker_id, particle_type: ParticleType,total_particles, shared_list, output_trajectories_of, finished_workers):
130
+ signal.signal(signal.SIGINT, signal.SIG_IGN)
129
131
  try:
130
132
  # Create a particle simulator and run the simulation:
131
133
  simulator = ParticleSimulator(worker_id, particle_type, total_particles, shared_list, output_trajectories_of)
@@ -146,13 +148,17 @@ def worker_process(worker_id, particle_type: ParticleType,total_particles, share
146
148
 
147
149
  def display_workers_finished(finished_workers):
148
150
  """ Print out the number of active workers"""
149
- while True:
150
- text_to_display = f' Processes finished: {finished_workers.value}/{cf.num_workers}'
151
- sys.stdout.write(text_to_display)
152
- sys.stdout.write(f'\033[{len(text_to_display)}D') # move cursor back
153
- sys.stdout.flush()
154
- if finished_workers.value == cf.num_workers: break
155
- time.sleep(0.3)
151
+ signal.signal(signal.SIGINT, signal.SIG_IGN)
152
+ try:
153
+ while True:
154
+ text_to_display = f' Processes finished: {finished_workers.value}/{cf.num_workers}'
155
+ sys.stdout.write(text_to_display)
156
+ sys.stdout.write(f'\033[{len(text_to_display)}D') # move cursor back
157
+ sys.stdout.flush()
158
+ if finished_workers.value == cf.num_workers: break
159
+ time.sleep(0.3)
160
+ except Exception:
161
+ pass
156
162
 
157
163
 
158
164
  def main(input_file, particle_type):
@@ -196,8 +202,13 @@ def main(input_file, particle_type):
196
202
 
197
203
  # Wait for all processes to finish:
198
204
  # Note that join is not called on worker_count_process because we do not want to wait for it to finish
199
- for process in processes:
200
- process.join()
205
+ try:
206
+ for process in processes:
207
+ process.join()
208
+ except KeyboardInterrupt:
209
+ for process in processes:
210
+ process.terminate()
211
+ raise
201
212
 
202
213
  # Wait for the worker count to finish but continue after 3 seconds:
203
214
  worker_count_process.join(timeout=3)
@@ -288,7 +299,7 @@ def main(input_file, particle_type):
288
299
  # Output general information:
289
300
  output_general_information(start_time)
290
301
  output_scattering_information(scatter_stats)
291
- output_parameter_warnings()
302
+ output_parameter_warnings(particle_type)
292
303
 
293
304
  sys.stdout.write(f'\rSee the results in {Fore.GREEN}Results/{cf.output_folder_name}{Style.RESET_ALL}\n')
294
305
  sys.stdout.write(f"\r{Fore.BLUE}Thank you for using FreePATHS{Style.RESET_ALL}\n\n")
@@ -208,9 +208,10 @@ class ThermalMaps(Maps):
208
208
  slope, _ = np.polyfit(coordinates_y, self.temperature_profile_y[:, timeframe_number], 1)
209
209
  grad_T = -slope
210
210
 
211
- # Average heat flux:
212
- J_effective = np.mean(self.effective_heat_flux_profile_y[:, timeframe_number])
213
- J_material = np.mean(self.material_heat_flux_profile_y[:, timeframe_number])
211
+ # Average heat flux (skip first pixel — it has a spurious spike due to
212
+ # rethermalization being applied before map recording in the time-step loop):
213
+ J_effective = np.mean(self.effective_heat_flux_profile_y[1:, timeframe_number])
214
+ J_material = np.mean(self.material_heat_flux_profile_y[1:, timeframe_number])
214
215
 
215
216
  # By definition, J = -K * grad(T), so:
216
217
  if grad_T != 0:
@@ -39,13 +39,18 @@ def output_scattering_information(scatter_stats):
39
39
  """Calculate and output general statistics on scattering events"""
40
40
 
41
41
  # Calculate the percentage of different scattering events:
42
- total = np.sum(scatter_stats.total)
43
42
  total_wall = np.sum(scatter_stats.wall_diffuse) + np.sum(scatter_stats.wall_specular)
44
43
  total_topbot = np.sum(scatter_stats.top_diffuse) + np.sum(scatter_stats.top_specular)
45
44
  total_hole = np.sum(scatter_stats.hole_diffuse) + np.sum(scatter_stats.hole_specular)
46
45
  total_pill = np.sum(scatter_stats.pillar_diffuse) + np.sum(scatter_stats.pillar_specular)
46
+ # total_interf counts ALL interface events (both reflection and transmission),
47
+ # because scattering_types.interfaces is set for both. total_transmission is
48
+ # a subset of total_interf, so must NOT be added separately to avoid double-counting.
47
49
  total_interf = np.sum(scatter_stats.interfaces_diffuse) + np.sum(scatter_stats.interfaces_specular)
48
50
  total_transmission = np.sum(scatter_stats.interfaces_transmission_specular) + np.sum(scatter_stats.interfaces_transmission_diffuse)
51
+ total_retherm = np.sum(scatter_stats.hot_side)
52
+ total_internal = np.sum(scatter_stats.internal)
53
+ total = total_wall + total_topbot + total_hole + total_pill + total_interf + total_retherm + total_internal
49
54
 
50
55
  sc_on_walls = 100*(np.sum(scatter_stats.wall_diffuse) +
51
56
  np.sum(scatter_stats.wall_specular)) / total
@@ -62,8 +67,8 @@ def output_scattering_information(scatter_stats):
62
67
  sc_on_topbot_diff = 0
63
68
  sc_on_topbot_spec = 0
64
69
 
65
- retherm = 100*np.sum(scatter_stats.hot_side) / total
66
- internal = 100*np.sum(scatter_stats.internal) / total
70
+ retherm = 100 * total_retherm / total
71
+ internal = 100 * total_internal / total
67
72
 
68
73
  info = [
69
74
  f'\n{sc_on_walls:.2f}% - scattering on side walls ',
@@ -100,45 +105,54 @@ def output_scattering_information(scatter_stats):
100
105
  )
101
106
 
102
107
  if cf.interfaces:
103
- sc_on_interf = 100*(np.sum(scatter_stats.interfaces_diffuse) +
104
- np.sum(scatter_stats.interfaces_specular)) / total
105
- sc_on_interf_diff = 100*np.sum(scatter_stats.interfaces_diffuse) / total_interf
106
- sc_on_interf_spec = 100*np.sum(scatter_stats.interfaces_specular) / total_interf
108
+ sc_on_interf = 100 * total_interf / total
109
+ sc_on_interf_diff = 100 * np.sum(scatter_stats.interfaces_diffuse) / total_interf
110
+ sc_on_interf_spec = 100 * np.sum(scatter_stats.interfaces_specular) / total_interf
111
+ trans_on_interf = 100 * total_transmission / total_interf
112
+ refl_on_interf = 100 * (total_interf - total_transmission) / total_interf
107
113
  info.extend([
108
114
  f'\n{sc_on_interf:.2f}% - scattering on interfaces ',
109
115
  f'({sc_on_interf_diff:.2f}% - diffuse, ',
110
- f'{sc_on_interf_spec:.2f}% - specular)']
111
- )
112
-
113
- trans_on_interf = 100 * (np.sum(scatter_stats.interfaces_transmission_diffuse) +
114
- np.sum(scatter_stats.interfaces_transmission_specular)) / total_interf
115
- trans_on_interf_diff = 100*np.sum(scatter_stats.interfaces_transmission_diffuse)/ total_transmission
116
- trans_on_interf_spec = 100*np.sum(scatter_stats.interfaces_transmission_specular)/ total_transmission
117
-
118
-
119
- info.extend([
120
- f'\n{trans_on_interf:.2f}% - transmission through interfaces ',
121
- f'({trans_on_interf_diff:.2f}% - diffuse, ',
122
- f'{trans_on_interf_spec:.2f}% - specular)'
116
+ f'{sc_on_interf_spec:.2f}% - specular), ',
117
+ f'of which {trans_on_interf:.2f}% - transmission, ',
118
+ f'{refl_on_interf:.2f}% - reflection',
123
119
  ])
124
120
 
125
121
  # Write the file:
126
122
  with open("Information.txt", "a", encoding="utf-8") as file:
127
123
  file.writelines(info)
128
124
 
125
+ # Warn if multiple scattering events co-occur in a single timestep:
126
+ total_timesteps_with_scattering = np.sum(scatter_stats.total)
127
+ if total_timesteps_with_scattering > 0:
128
+ multi_event_percentage = 100 * (total - total_timesteps_with_scattering) / total_timesteps_with_scattering
129
+ if multi_event_percentage > 5:
130
+ logging.warning(f"In {multi_event_percentage:.1f}% of scattering timesteps, more than one scattering "
131
+ f"mechanism fired simultaneously. Consider reducing TIMESTEP.")
129
132
 
130
- def output_parameter_warnings():
133
+
134
+ def output_parameter_warnings(particle_type):
131
135
  """Check if parameters used for this simulation made sense considering the simulation results"""
132
136
 
133
- # Check if some particles had longer travel times than stabilization period:
137
+ from freepaths.particle_types import ParticleType
138
+
139
+ # These checks are only relevant for phonon simulations:
140
+ if particle_type is not ParticleType.ELECTRON:
141
+ travel_times = np.loadtxt("Data/All travel times.csv", encoding='utf-8')
142
+
143
+ total_time = cf.timestep * cf.number_of_timesteps
144
+ time_of_stabilization = cf.number_of_stabilization_timeframes * total_time / cf.number_of_timeframes
145
+ long_travel_times = travel_times[travel_times > time_of_stabilization]
146
+ percentage = (len(long_travel_times) / len(travel_times)) * 100
147
+ if percentage > 10:
148
+ logging.warning(f"Travel time of {percentage}% of particles was longer than the stabilization period.\n" +
149
+ "Increase stabilization period as the thermal conductivity might be incorrect.")
150
+
151
+ # Check how many particles reached the cold side during simulation:
134
152
  travel_times = np.loadtxt("Data/All travel times.csv", encoding='utf-8')
135
- total_time = cf.timestep * cf.number_of_timesteps
136
- time_of_stabilization = cf.number_of_stabilization_timeframes * total_time / cf. number_of_timeframes
137
- long_travel_times = travel_times[travel_times > time_of_stabilization]
138
- percentage = (len(long_travel_times) / len(travel_times)) * 100
139
- if percentage > 10:
140
- logging.warning(f"Travel time of {percentage}% of particles was longer than the stabilization period.\n" +
141
- "Increase stabilization period as the thermal conductivity might be incorrect.")
153
+ percentage = int(100 * np.count_nonzero(travel_times) / cf.number_of_particles)
154
+ if percentage < 95:
155
+ logging.warning(f"Only {percentage}% of particles reached the cold side. Increase the number of timesteps.")
142
156
 
143
157
  # Check if pixel size is too small:
144
158
  speeds = np.loadtxt("Data/All group velocities.csv", encoding='utf-8')
@@ -146,8 +160,3 @@ def output_parameter_warnings():
146
160
  logging.warning("Pixels in y direction are smaller than length of one step")
147
161
  if max(speeds) * cf.timestep > cf.width / cf.number_of_pixels_x:
148
162
  logging.warning("Pixels in x direction are smaller than length of one step")
149
-
150
- # Check how many particles reached the cold side during simulation:
151
- percentage = int(100 * np.count_nonzero(travel_times) / cf.number_of_particles)
152
- if percentage < 95:
153
- logging.warning(f"Only {percentage}% of particles reached the cold side. Increase the number of timesteps.")
@@ -271,19 +271,6 @@ def plot_interfaces_angles_distribution():
271
271
  np.savetxt('Data/Distribution of interfaces angles.csv', angle_distributions, fmt='%1.3e', delimiter=",")
272
272
 
273
273
 
274
- import subprocess
275
- import os
276
-
277
- def open_pdf_with_mupdf(pdf_path):
278
- try:
279
- # path of mu pdf
280
- mupdf_path = r"C:\Users\victo\Downloads\mupdf-1.26.2-windows\mupdf-1.26.2-windows\mupdf-gl.exe"
281
-
282
- # Lancer mupdf avec ton fichier
283
- subprocess.Popen([mupdf_path, os.path.abspath(pdf_path)])
284
- except Exception as e:
285
- print(f"[ERROR] Impossible d'ouvrir le PDF avec MuPDF : {e}")
286
-
287
274
  def plot_transmission_vs_angle():
288
275
  try:
289
276
  angles = np.loadtxt("Data/All interfaces angles.csv", dtype='float', encoding='utf-8')
@@ -808,8 +795,13 @@ def plot_temperature_profile():
808
795
  """Plot profile of temperature for each time segment"""
809
796
  fig, ax = plt.subplots()
810
797
  data = np.genfromtxt("Data/Temperature profiles y.csv", unpack=True, delimiter=',', skip_header=1, encoding='utf-8')
811
- for timeframe_num in range(len(data) - 1):
812
- ax.plot(data[0][1:], data[timeframe_num + 1][1:], linewidth=1, label=f'Time frame {timeframe_num+1}')
798
+ coords = data[0]
799
+ n_timeframes = len(data) - 1
800
+ for timeframe_num in range(n_timeframes):
801
+ ax.plot(coords[1:], data[timeframe_num + 1][1:], linewidth=1, label=f'Time frame {timeframe_num+1}')
802
+ # Linear fit of the last timeframe (same algorithm as thermal conductivity calculation)
803
+ slope, intercept = np.polyfit(coords, data[n_timeframes], 1)
804
+ ax.plot(coords[1:], slope * coords[1:] + intercept, '--', color='gray', linewidth=1, label='Linear fit (last frame)')
813
805
  ax.set_xlabel('Y (μm)')
814
806
  ax.set_ylabel('Temperature (K)')
815
807
  ax.legend()
@@ -819,11 +811,16 @@ def plot_temperature_profile():
819
811
  def plot_heat_flux_profile():
820
812
  """Plot profile of heat flux for each time segment"""
821
813
 
814
+ data = np.genfromtxt("Data/Heat flux profiles y.csv", unpack=True, delimiter=',', skip_header=1, encoding='utf-8')
815
+ n_timeframes = (len(data) - 1) // 2
816
+
822
817
  # Effective heat flux:
823
818
  fig, ax = plt.subplots()
824
- data = np.genfromtxt("Data/Heat flux profiles y.csv", unpack=True, delimiter=',', skip_header=1, encoding='utf-8')
825
- for timeframe_num in range((len(data) - 1) // 2):
826
- ax.plot(data[0], data[timeframe_num + 1], linewidth=1, label=f'Time frame {timeframe_num+1}')
819
+ for timeframe_num in range(n_timeframes):
820
+ ax.plot(data[0][1:], data[timeframe_num + 1][1:], linewidth=1, label=f'Time frame {timeframe_num+1}')
821
+ # Mean of the last timeframe, skipping first pixel (same as thermal conductivity calculation)
822
+ mean_flux = np.mean(data[n_timeframes][1:])
823
+ ax.axhline(mean_flux, color='gray', linestyle='--', linewidth=1, label='Mean (last frame)')
827
824
  ax.set_xlabel('Y (μm)')
828
825
  ax.set_ylabel('Heat flux (W/m²)')
829
826
  ax.legend()
@@ -832,8 +829,10 @@ def plot_heat_flux_profile():
832
829
 
833
830
  # Material heat flux:
834
831
  fig, ax = plt.subplots()
835
- for timeframe_num in range((len(data) - 1) // 2):
836
- ax.plot(data[0], data[2*timeframe_num + 1], linewidth=1, label=f'Time frame {timeframe_num+1}')
832
+ for timeframe_num in range(n_timeframes):
833
+ ax.plot(data[0][1:], data[2*timeframe_num + 1][1:], linewidth=1, label=f'Time frame {timeframe_num+1}')
834
+ mean_flux_mat = np.mean(data[2*n_timeframes - 1][1:])
835
+ ax.axhline(mean_flux_mat, color='gray', linestyle='--', linewidth=1, label='Mean (last frame)')
837
836
  ax.set_xlabel('Y (μm)')
838
837
  ax.set_ylabel('Heat flux (W/m²)')
839
838
  ax.legend()
@@ -872,7 +871,7 @@ def plot_heat_flux_map(file, label, units="a.u."):
872
871
  """Plot heat flux map as color map"""
873
872
  fig = plt.figure()
874
873
  heat_flux_map = np.genfromtxt(file, unpack=False, delimiter=',', skip_header=0, encoding='utf-8')
875
- heat_flux_map = np.flipud(heat_flux_map)
874
+ heat_flux_map = np.abs(np.flipud(heat_flux_map))
876
875
  minimum_of_colorbar = 1e5
877
876
  boundaries = [(-cf.width / 2) * 1e6, (cf.width / 2) * 1e6, 0, cf.length * 1e6]
878
877
  plt.imshow(heat_flux_map, cmap='jet', interpolation='none', extent=boundaries,
@@ -970,7 +969,8 @@ def plot_scattering_statistics():
970
969
  if scattering_type == 6: # Skip hot side scattering
971
970
  continue
972
971
  # Calculate and plot scattering events per second:
973
- scattering_rate = [events / time for events, time in zip(scattering_data[scattering_type, :], time_spent)]
972
+ scattering_rate = np.divide(scattering_data[scattering_type, :], time_spent,
973
+ out=np.zeros_like(time_spent), where=time_spent != 0)
974
974
  all_scattering_rates.append(scattering_rate)
975
975
  ax.plot(segments, scattering_rate, '-o', markersize=2)
976
976
 
@@ -995,6 +995,15 @@ def plot_scattering_statistics():
995
995
  data = np.vstack((segments, all_scattering_rates)).T
996
996
  np.savetxt(filename, data, fmt='%1.2e', delimiter=",", header=header)
997
997
 
998
+ # Save average scattering rates (one value per type, averaged over all segments):
999
+ avg_header = ("Sidewalls diffuse [1/ns], Sidewalls specular [1/ns], Top & bottom diffuse [1/ns], "
1000
+ "Top & bottom specular [1/ns], Holes diffuse [1/ns], Holes specular [1/ns], "
1001
+ "Internal [1/ns], Pillars diffuse [1/ns], Pillars specular [1/ns], "
1002
+ "Interfaces diffuse [1/ns], Interfaces specular [1/ns], "
1003
+ "Interfaces transmission diffuse [1/ns], Interfaces transmission specular [1/ns]")
1004
+ avg_data = np.array([np.mean(rate) for rate in all_scattering_rates])
1005
+ np.savetxt("Data/Scattering rates averaged.csv", avg_data.reshape(1, -1), fmt='%1.2e', delimiter=",", header=avg_header)
1006
+
998
1007
  def plot_scattering_rate_vs_energy():
999
1008
  fig, ax = plt.subplots()
1000
1009
  energies, scattering_rate = np.genfromtxt("Data/Scattering rate vs energy.csv", unpack=True, delimiter=',', usecols=(0,1), skip_header=1)
@@ -1099,11 +1108,8 @@ def plot_data(particle_type: ParticleType, cf, mfp_sampling=False):
1099
1108
  plot_trajectories,
1100
1109
  plot_angle_distribution,
1101
1110
  plot_free_path_distribution,
1102
- plot_frequency_distribution,
1103
- plot_wavelength_distribution,
1104
1111
  plot_travel_time_distribution,
1105
1112
  plot_mean_free_path_distribution,
1106
- plot_velocity_distribution,
1107
1113
  plot_energy_distribution,
1108
1114
  plot_travel_time_vs_energy,
1109
1115
  plot_transport_function,
@@ -1134,6 +1140,10 @@ def plot_data(particle_type: ParticleType, cf, mfp_sampling=False):
1134
1140
  else:
1135
1141
  function_list = electron_function_list
1136
1142
 
1143
+ # These plots require data files that are not saved in low memory mode:
1144
+ if cf.low_memory_usage:
1145
+ function_list = [f for f in function_list if f not in (plot_trajectories, plot_free_path_distribution)]
1146
+
1137
1147
  # Run main functions and handle exceptions:
1138
1148
  for func in function_list:
1139
1149
  try:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: freepaths
3
- Version: 2.2.4
3
+ Version: 2.3.1
4
4
  Summary: Phonon Monte Carlo simulator
5
5
  Home-page: https://github.com/anufrievroman/freepaths
6
6
  Author: Roman Anufriev
@@ -54,9 +54,13 @@ FreePATHS is a command line application, so it runs inside Linux, MacOS, or Wind
54
54
 
55
55
  There are two modes of using the program. Main mode traces a large number of phonons through a structure and collects statistics about their paths. The MFP sampling mode measures phonon mean free paths using a small number of phonons and calculates the thermal conductivity by integrating phonon dispersion.
56
56
 
57
- ### Demo
57
+ ### Getting started
58
58
 
59
- If you simply run `freepaths` without specifying an input file, the program will run a demo simulation.
59
+ If you run `freepaths` without any arguments, the program will print a usage summary. To run a demo simulation with default parameters, use the `--demo` flag:
60
+
61
+ `freepaths --demo`
62
+
63
+ Example input files are available in the [examples](https://github.com/anufrievroman/freepaths/tree/master/examples) folder.
60
64
 
61
65
  ### Main mode
62
66
 
@@ -1,42 +0,0 @@
1
- """FreePATHS - Free Phonon and THermal Simulator"""
2
-
3
- import argparse
4
- import colorama
5
- from colorama import Fore, Style
6
-
7
- from freepaths.particle_types import ParticleType
8
-
9
- __version__ = "2.2.4"
10
-
11
- colorama.init()
12
-
13
- # Parse user arguments:
14
- parser = argparse.ArgumentParser(
15
- prog = 'FreePATHS',
16
- description = 'Phonon Monte Carlo simulator',
17
- epilog = 'For more information, examples, and tutorials, visit: https://anufrievroman.gitbook.io/freepaths'
18
- )
19
- parser.add_argument('input_file', nargs='?', default=None, help='The input file')
20
- parser.add_argument("-s", "--sampling", help="Run in phonon MFP sampling mode", action="store_true")
21
- parser.add_argument("-e", "--electron", help="Run simulation for electrons", action="store_true")
22
- args = parser.parse_args()
23
-
24
-
25
- def run():
26
- """Run the program depending on the mode"""
27
- print(f"\n{Fore.BLUE}FreePATHS v{__version__}{Style.RESET_ALL}")
28
- if args.sampling:
29
- import freepaths.main_mfp_sampling
30
- freepaths.main_mfp_sampling.main(args.input_file, ParticleType.PHONON)
31
-
32
- elif args.electron:
33
- import freepaths.main_tracing
34
- freepaths.main_tracing.main(args.input_file, ParticleType.ELECTRON)
35
-
36
- else:
37
- import freepaths.main_tracing
38
- freepaths.main_tracing.main(args.input_file, ParticleType.PHONON)
39
-
40
-
41
- if __name__ == "__main__":
42
- run()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes