freepaths 1.1__0-py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
freepaths/__main__.py ADDED
@@ -0,0 +1,30 @@
1
+ """FreePATHS - Free Phonon and THermal Simulator"""
2
+
3
+ import argparse
4
+
5
+ import freepaths.main_tracing
6
+ import freepaths.main_mfp_sampling
7
+
8
+ __version__ = "1.1.0"
9
+
10
+ # Parse user arguments:
11
+ parser = argparse.ArgumentParser(
12
+ prog = 'FreePATHS',
13
+ description = 'Monte Carlo simulator',
14
+ epilog = 'For more information, visit: https://github.com/anufrievroman/freepaths'
15
+ )
16
+ parser.add_argument('input_file', nargs='?', default=None, help='The input file')
17
+ parser.add_argument("-s", "--sampling", help="Run in MFP sampling mode", action="store_true")
18
+ args = parser.parse_args()
19
+
20
+
21
+ def run():
22
+ """Run the program depending on the mode"""
23
+ if args.sampling:
24
+ freepaths.main_mfp_sampling.main(args.input_file)
25
+ else:
26
+ freepaths.main_tracing.main(args.input_file)
27
+
28
+
29
+ if __name__ == "__main__":
30
+ run()
freepaths/animation.py ADDED
@@ -0,0 +1,64 @@
1
+ """Module that creates animations from recorded phonon paths"""
2
+
3
+ import sys
4
+ import os
5
+ import shutil
6
+ import imageio
7
+ import numpy as np
8
+ import matplotlib.pyplot as plt
9
+
10
+ from freepaths.config import cf
11
+
12
+
13
+ def generate_frames_xy():
14
+ """Generate animation frames with phonon paths"""
15
+
16
+ data = np.genfromtxt("Data/Phonon paths.csv",
17
+ unpack=False,
18
+ delimiter=',',
19
+ skip_header=1,
20
+ encoding='utf-8')
21
+
22
+ plt.rcParams['xtick.direction'] = "out"
23
+ plt.rcParams['ytick.direction'] = "out"
24
+ cmap = plt.get_cmap("tab10")
25
+
26
+ # Create XY plots for each step of each phonon:
27
+ frame_number = 0
28
+ for phonon_num in range(cf.phonons_in_animation):
29
+ x_coords = np.trim_zeros(data[:, 3 * phonon_num], trim='b')
30
+ y_coords = np.trim_zeros(data[:, 3 * phonon_num + 1], trim='b')
31
+ steps = np.shape(x_coords)[0]
32
+ for step in range(1, steps):
33
+ fig, ax = plt.subplots()
34
+ ax.plot(x_coords[:step], y_coords[:step], color=cmap(phonon_num), linewidth=0.5)
35
+ ax.set_xlabel('X (μm)', fontsize=12)
36
+ ax.set_ylabel('Y (μm)', fontsize=12)
37
+ ax.set_xlim([-0.55*cf.width*1e6, 0.55*cf.width*1e6])
38
+ ax.set_ylim([0, cf.length*1e6])
39
+ ax.set_aspect('equal')
40
+ fig.savefig(f"Frames/frame_{frame_number:0>6}.png", dpi=600, bbox_inches="tight")
41
+ plt.close(fig)
42
+ frame_number += 1
43
+
44
+
45
+ def generate_animation_xy():
46
+ """Generate animation of phonon path in XY plane"""
47
+ images = []
48
+ filenames = os.listdir("Frames/")
49
+ for filename in filenames:
50
+ images.append(imageio.imread(f"Frames/{filename}"))
51
+ imageio.mimsave("Animated paths XY.gif", images)
52
+
53
+
54
+ def delete_frames():
55
+ """Delete frames after creating the animation"""
56
+ folder_path = "Frames/"
57
+ shutil.rmtree(folder_path)
58
+
59
+
60
+ def create_animation():
61
+ """Main function that creates the animation"""
62
+ generate_frames_xy()
63
+ generate_animation_xy()
64
+ delete_frames()
freepaths/config.py ADDED
@@ -0,0 +1,156 @@
1
+ """Module that reads the user input file, privides default values, and converts the variables into enums"""
2
+
3
+ import sys
4
+ import argparse
5
+
6
+ from freepaths.options import Materials, Distributions, Positions
7
+
8
+ # Import a default input file:
9
+ from freepaths.default_config import *
10
+
11
+
12
+ # Parse user arguments:
13
+ WEBSITE = 'https://github.com/anufrievroman/freepaths'
14
+ parser = argparse.ArgumentParser(prog='FreePATHS', description='Monte Carlo simulator',
15
+ epilog=f'For more information, visit: {WEBSITE}')
16
+ parser.add_argument('input_file', nargs='?', default=None, help='The input file')
17
+ parser.add_argument("-s", "--sampling", help="Run in MFP sampling mode", action="store_true")
18
+ args = parser.parse_args()
19
+
20
+
21
+ # If a file is provided, overwrite the default values:
22
+ if args.input_file:
23
+ exec(open(args.input_file).read(), globals())
24
+ else:
25
+ print("You didn't provide any input file, so we run a demo simulation:\n")
26
+
27
+
28
+ class Config:
29
+ """Class that contains all the settings for the simulation"""
30
+
31
+ def __init__(self):
32
+ """Initiate all the parameters from global variables"""
33
+
34
+ # General parameters:
35
+ self.output_folder_name = OUTPUT_FOLDER_NAME
36
+ self.number_of_phonons = NUMBER_OF_PHONONS
37
+ self.number_of_timesteps = NUMBER_OF_TIMESTEPS
38
+ self.number_of_nodes = NUMBER_OF_NODES
39
+ self.timestep = TIMESTEP
40
+ self.temp = T
41
+ self.plots_in_terminal = PLOTS_IN_TERMINAL
42
+ self.output_scattering_map = OUTPUT_SCATTERING_MAP
43
+ self.output_raw_thermal_map = OUTPUT_RAW_THERMAL_MAP
44
+ self.output_trajectories_of_first = OUTPUT_TRAJECTORIES_OF_FIRST
45
+ self.output_path_animation = OUTPUT_PATH_ANIMATION
46
+ self.phonons_in_animation = PHONONS_IN_ANIMATION
47
+ self.number_of_length_segments = NUMBER_OF_LENGTH_SEGMENTS
48
+ self.hot_side_angle_distribution = HOT_SIDE_ANGLE_DISTRIBUTION
49
+
50
+ # Map & profiles parameters:
51
+ self.number_of_pixels_x = NUMBER_OF_PIXELS_X
52
+ self.number_of_pixels_y = NUMBER_OF_PIXELS_Y
53
+ self.number_of_timeframes = NUMBER_OF_TIMEFRAMES
54
+
55
+ # Material parameters:
56
+ self.media = MEDIA
57
+ self.specific_heat_capacity = SPECIFIC_HEAT_CAPACITY
58
+
59
+ # Internal scattering:
60
+ self.include_internal_scattering = INCLUDE_INTERNAL_SCATTERING
61
+ self.use_gray_approximation_mfp = USE_GRAY_APPROXIMATION_MFP
62
+ self.gray_approximation_mfp = GRAY_APPROXIMATION_MFP
63
+
64
+ # System dimensions:
65
+ self.thickness = THICKNESS
66
+ self.width = WIDTH
67
+ self.length = LENGTH
68
+
69
+ # Hot and cold sides:
70
+ self.frequency_detector_size = FREQUENCY_DETECTOR_SIZE
71
+ self.cold_side_position = COLD_SIDE_POSITION
72
+ self.hot_side_x = HOT_SIDE_X
73
+ self.hot_side_y = HOT_SIDE_Y
74
+ self.hot_side_width_x = HOT_SIDE_WIDTH_X
75
+ self.hot_side_width_y = HOT_SIDE_WIDTH_Y
76
+
77
+ # Roughness:
78
+ self.side_wall_roughness = SIDE_WALL_ROUGHNESS
79
+ self.hole_roughness = HOLE_ROUGHNESS
80
+ self.pillar_roughness = PILLAR_ROUGHNESS
81
+ self.top_roughness = TOP_ROUGHNESS
82
+ self.bottom_roughness = BOTTOM_ROUGHNESS
83
+ self.pillar_top_roughness = PILLAR_TOP_ROUGHNESS
84
+
85
+ # Parabolic boundary:
86
+ self.include_top_parabola = INCLUDE_TOP_PARABOLA
87
+ self.top_parabola_tip = TOP_PARABOLA_TIP
88
+ self.top_parabola_focus = TOP_PARABOLA_FOCUS
89
+ self.include_bottom_parabola = INCLUDE_BOTTOM_PARABOLA
90
+ self.bottom_parabola_tip = BOTTOM_PARABOLA_TIP
91
+ self.bottom_parabola_focus = BOTTOM_PARABOLA_FOCUS
92
+
93
+ # Hole array parameters:
94
+ self.include_holes = INCLUDE_HOLES
95
+ self.circular_hole_diameter = CIRCULAR_HOLE_DIAMETER
96
+ self.rectangular_hole_side_x = RECTANGULAR_HOLE_SIDE_X
97
+ self.rectangular_hole_side_y = RECTANGULAR_HOLE_SIDE_Y
98
+ self.period_x = PERIOD_X
99
+ self.period_y = PERIOD_Y
100
+
101
+ # Lattice of holes:
102
+ self.hole_coordinates = HOLE_COORDINATES
103
+ self.hole_shapes = HOLE_SHAPES
104
+
105
+ # Pillar array parameters [m]
106
+ self.include_pillars = INCLUDE_PILLARS
107
+ self.pillar_coordinates = PILLAR_COORDINATES
108
+ self.pillar_height = PILLAR_HEIGHT
109
+ self.pillar_wall_angle = PILLAR_WALL_ANGLE
110
+
111
+ def convert_to_enums(self):
112
+ """Convert some user generated parameters into enums"""
113
+ if self.hot_side_angle_distribution in ["random", "lambert", "directional", "uniform"]:
114
+ self.hot_side_angle_distribution = Distributions[self.hot_side_angle_distribution.upper()]
115
+ else:
116
+ print("ERROR: Parameter HOT_SIDE_ANGLE_DISTRIBUTION is not set correctly.\n")
117
+ sys.exit()
118
+
119
+ if self.media in ["Si", "SiC", "Diamond", "AlN"]:
120
+ self.media = Materials[self.media]
121
+ else:
122
+ print(f"ERROR: Material {self.media} is not in the database.\n")
123
+ sys.exit()
124
+
125
+ if self.cold_side_position in ["top", "top_and_right", "top_and_bottom", "right"]:
126
+ self.cold_side_position = Positions[self.cold_side_position.upper()]
127
+ else:
128
+ print("ERROR: Parameter COLD_SIDE_POSITION is not set correctly.\n")
129
+ sys.exit()
130
+
131
+ def check_parameter_validity(self):
132
+ """Check if some parameteres are valid"""
133
+ if self.number_of_phonons < self.output_trajectories_of_first:
134
+ print("ERROR: Parameter OUTPUT_TRAJECTORIES_OF_FIRST exceeds NUMBER_OF_PHONONS.\n")
135
+ sys.exit()
136
+
137
+ if self.hot_side_y > self.length:
138
+ print("ERROR: Parameter HOT_SIDE_Y exceeds LENGHT.\n")
139
+ sys.exit()
140
+
141
+ if self.hot_side_y < 0:
142
+ print("ERROR: Parameter HOT_SIDE_Y is negative.\n")
143
+ sys.exit()
144
+
145
+ if self.hot_side_y - self.hot_side_width_y / 2 < 0:
146
+ print("ERROR: Parameter HOT_SIDE_WIDTH_Y is too large.\n")
147
+ sys.exit()
148
+
149
+ if self.hot_side_width_x > self.width:
150
+ print("ERROR: Parameter HOT_SIDE_WIDTH_X exceeds WIDTH.\n")
151
+ sys.exit()
152
+
153
+
154
+ cf = Config()
155
+ cf.convert_to_enums()
156
+ cf.check_parameter_validity()
freepaths/data.py ADDED
@@ -0,0 +1,157 @@
1
+ """Module that controles recording of various data"""
2
+
3
+ import numpy as np
4
+
5
+ from freepaths.config import cf
6
+ from freepaths.scattering_types import Scattering
7
+
8
+ class PathData:
9
+ """Paths of phonons in space"""
10
+
11
+ def __init__(self):
12
+ """Initialize arrays to save phonon paths"""
13
+ self.phonon_paths = []
14
+
15
+ def save_phonon_path(self, flight):
16
+ """Save the path to list of all paths"""
17
+ self.phonon_paths.append(flight.path)
18
+
19
+ @property
20
+ def length_of_longest_path(self):
21
+ """Calculate the number of points in the longest path"""
22
+ return max([path.number_of_path_points for path in self.phonon_paths])
23
+
24
+ def write_into_files(self):
25
+ """Write all the path coordinates into a file"""
26
+ filename = "Data/Phonon paths.csv"
27
+ data = np.zeros((self.length_of_longest_path, 3*len(self.phonon_paths)))
28
+ for index, path in enumerate(self.phonon_paths):
29
+ for point_n, (x, y, z) in enumerate(zip(path.x, path.y, path.z)):
30
+ data[point_n, 0 + index*3] = x*1e6
31
+ data[point_n, 1 + index*3] = y*1e6
32
+ data[point_n, 2 + index*3] = z*1e6
33
+ np.savetxt(filename, data, fmt='%2.4f', delimiter=",", header="X (μm), Y (μm), Z (μm)", encoding='utf-8')
34
+
35
+
36
+ class GeneralData:
37
+ """General statistics of various phonon properties"""
38
+
39
+ def __init__(self):
40
+ """Initialize arrays for writing various properties"""
41
+ self.initial_angles = []
42
+ self.exit_angles = []
43
+ self.free_paths = []
44
+ self.free_paths_along_y = []
45
+ self.frequencies = []
46
+ self.detected_frequencies = []
47
+ self.group_velocities = []
48
+ self.travel_times = []
49
+
50
+ def save_phonon_data(self, ph):
51
+ """Add information about the phonon to the dataset"""
52
+ self.frequencies.append(ph.f)
53
+ self.group_velocities.append(ph.speed)
54
+
55
+ def save_flight_data(self, flight):
56
+ """Add information about the phonon flight to the dataset"""
57
+ self.initial_angles.append(flight.initial_theta)
58
+ self.exit_angles.append(flight.exit_theta)
59
+ self.free_paths.extend(flight.free_paths)
60
+ self.free_paths_along_y.extend(flight.free_paths_along_y)
61
+ self.travel_times.append(flight.travel_time)
62
+ self.detected_frequencies.append(flight.detected_frequency)
63
+
64
+ def write_into_files(self):
65
+ """Write all the data into files"""
66
+ np.savetxt("Data/All free paths.csv", self.free_paths, fmt='%2.4e', delimiter=",", header="L [m]", encoding='utf-8')
67
+ np.savetxt("Data/All free paths in plane.csv", self.free_paths_along_y, fmt='%2.4e', delimiter=",", header="Ly [m]", encoding='utf-8')
68
+ np.savetxt("Data/All initial frequencies.csv", self.frequencies, fmt='%2.4e', delimiter=",", header="f [Hz]", encoding='utf-8')
69
+ np.savetxt("Data/All detected frequencies.csv", self.detected_frequencies, fmt='%2.4e', delimiter=",", header="f [Hz]", encoding='utf-8')
70
+ np.savetxt("Data/All exit angles.csv", self.exit_angles, fmt='%2.4e', delimiter=",", header="Angle [rad]", encoding='utf-8')
71
+ np.savetxt("Data/All initial angles.csv", self.initial_angles, fmt='%2.4e', delimiter=",", header="Angle [rad]", encoding='utf-8')
72
+ np.savetxt("Data/All group velocities.csv", self.group_velocities, fmt='%2.4e', delimiter=",", header="Vg [rad]", encoding='utf-8')
73
+ np.savetxt("Data/All travel times.csv", self.travel_times, fmt='%2.4e', delimiter=",", header="Travel time [s]", encoding='utf-8')
74
+
75
+
76
+ class ScatteringData:
77
+ """Statistics of phonon scattering events"""
78
+
79
+ def __init__(self):
80
+ """Initialize arrays according to the number of segments"""
81
+ self.wall_diffuse = np.zeros(cf.number_of_length_segments)
82
+ self.wall_specular = np.zeros(cf.number_of_length_segments)
83
+ self.top_diffuse = np.zeros(cf.number_of_length_segments)
84
+ self.top_specular = np.zeros(cf.number_of_length_segments)
85
+ self.hole_diffuse = np.zeros(cf.number_of_length_segments)
86
+ self.hole_specular = np.zeros(cf.number_of_length_segments)
87
+ self.pillar_diffuse = np.zeros(cf.number_of_length_segments)
88
+ self.pillar_specular = np.zeros(cf.number_of_length_segments)
89
+ self.hot_side = np.zeros(cf.number_of_length_segments)
90
+ self.internal = np.zeros(cf.number_of_length_segments)
91
+ self.total = np.zeros(cf.number_of_length_segments)
92
+
93
+ def save_scattering_events(self, y, scattering_types):
94
+ """Analyze types of scattering at the current timestep and add it to the statistics"""
95
+
96
+ # Calculate in which length segment (starting from zero) we are:
97
+ segment = int(y // (cf.length / cf.number_of_length_segments))
98
+ self.total[segment] += 1
99
+
100
+ # Scattering on side walls:
101
+ self.wall_diffuse[segment] += 1 if scattering_types.walls == Scattering.DIFFUSE else 0
102
+ self.wall_specular[segment] += 1 if scattering_types.walls == Scattering.SPECULAR else 0
103
+
104
+ # Scattering on top and bottom:
105
+ self.top_diffuse[segment] += 1 if scattering_types.top_bottom == Scattering.DIFFUSE else 0
106
+ self.top_specular[segment] += 1 if scattering_types.top_bottom == Scattering.SPECULAR else 0
107
+
108
+ # Scattering on holes:
109
+ self.hole_diffuse[segment] += 1 if scattering_types.holes == Scattering.DIFFUSE else 0
110
+ self.hole_specular[segment] += 1 if scattering_types.holes == Scattering.SPECULAR else 0
111
+
112
+ # Scattering on pillars:
113
+ self.pillar_diffuse[segment] += 1 if scattering_types.pillars == Scattering.DIFFUSE else 0
114
+ self.pillar_specular[segment] += 1 if scattering_types.pillars == Scattering.SPECULAR else 0
115
+
116
+ # Internal scattering and rethermalization on hot side:
117
+ self.hot_side[segment] += 1 if scattering_types.hot_side == Scattering.DIFFUSE else 0
118
+ self.internal[segment] += 1 if scattering_types.internal == Scattering.DIFFUSE else 0
119
+
120
+ def write_into_files(self):
121
+ """Write data into a file"""
122
+ filename = "Data/Scattering events statistics.csv"
123
+ data = np.vstack((self.wall_diffuse, self.wall_specular, self.top_diffuse, self.top_specular, self.hole_diffuse,
124
+ self.hole_specular, self.hot_side, self.internal, self.pillar_diffuse, self.pillar_specular)).T
125
+ header1 = "Sidewalls diffuse, Sidewalls specular, Top & bottom diffuse, Top & bottom specular, "
126
+ header2 = "Holes diffuse, Holes specular, Hot side, Internal, Pillars diffuse, Pillars specular"
127
+ header = header1 + header2
128
+ np.savetxt(filename, data, fmt='%1.3e', delimiter=",", header=header, encoding='utf-8')
129
+
130
+
131
+ class SegmentData:
132
+ """Statistics of events happening in different segments"""
133
+
134
+ def __init__(self):
135
+ """Initialize arrays according to the number of segments"""
136
+ self.time_spent = np.zeros(cf.number_of_length_segments)
137
+
138
+ @property
139
+ def segment_coordinates(self):
140
+ """Calculate coordinates of the centers of each segment"""
141
+ segment_length = cf.length * 1e6 / cf.number_of_length_segments
142
+ segments = [(segment_length/2 + i*segment_length) for i in range(cf.number_of_length_segments)]
143
+ return segments
144
+
145
+ def record_time_in_segment(self, coordinate):
146
+ """Record how long phonon stays in different segments"""
147
+ for segment_number in range(cf.number_of_length_segments):
148
+ segment_beginning = segment_number * (cf.length / cf.number_of_length_segments)
149
+ segment_end = (segment_number + 1)*(cf.length / cf.number_of_length_segments)
150
+ if segment_beginning <= coordinate < segment_end:
151
+ self.time_spent[segment_number] += cf.timestep * 1e6
152
+
153
+ def write_into_files(self):
154
+ """Write data into files"""
155
+ filename = "Data/Time spent in segments.csv"
156
+ data = np.vstack((self.segment_coordinates, self.time_spent)).T
157
+ np.savetxt(filename, data, fmt='%1.3e', delimiter=",", header="Y [um], Time [ns]", encoding='utf-8')
@@ -0,0 +1,82 @@
1
+ """Default config file"""
2
+
3
+ import numpy as np
4
+
5
+ # General parameters:
6
+ OUTPUT_FOLDER_NAME = "Si nanowire at 300 K"
7
+ NUMBER_OF_PHONONS = 500
8
+ NUMBER_OF_TIMESTEPS = 60000
9
+ NUMBER_OF_NODES = 400
10
+ TIMESTEP = 2e-12
11
+ T = 300
12
+ PLOTS_IN_TERMINAL = False
13
+ OUTPUT_SCATTERING_MAP = False
14
+ OUTPUT_RAW_THERMAL_MAP = True
15
+ OUTPUT_TRAJECTORIES_OF_FIRST = 10
16
+ NUMBER_OF_LENGTH_SEGMENTS = 10
17
+ HOT_SIDE_ANGLE_DISTRIBUTION = "random"
18
+
19
+ # Animation:
20
+ OUTPUT_PATH_ANIMATION = False
21
+ PHONONS_IN_ANIMATION = 3
22
+
23
+ # Map & profiles parameters:
24
+ NUMBER_OF_PIXELS_X = 100
25
+ NUMBER_OF_PIXELS_Y = 100
26
+ NUMBER_OF_TIMEFRAMES = 5
27
+
28
+ # Material parameters:
29
+ MEDIA = "Si"
30
+ SPECIFIC_HEAT_CAPACITY = 714 # [J/kg/K] for Si at 300 K
31
+
32
+ # Internal scattering:
33
+ INCLUDE_INTERNAL_SCATTERING = True
34
+ USE_GRAY_APPROXIMATION_MFP = False
35
+ GRAY_APPROXIMATION_MFP = None
36
+
37
+ # System dimensions [m]:
38
+ THICKNESS = 150e-9
39
+ WIDTH = 500e-9
40
+ LENGTH = 2200e-9
41
+
42
+ # Hot and cold sides [m]:
43
+ FREQUENCY_DETECTOR_SIZE = WIDTH
44
+ COLD_SIDE_POSITION = 'top'
45
+ HOT_SIDE_X = 0
46
+ HOT_SIDE_WIDTH_X = WIDTH
47
+ HOT_SIDE_Y = 0
48
+ HOT_SIDE_WIDTH_Y = 0
49
+
50
+ # Roughness [m]:
51
+ SIDE_WALL_ROUGHNESS = 2e-9
52
+ HOLE_ROUGHNESS = 2e-9
53
+ PILLAR_ROUGHNESS = 2e-9
54
+ TOP_ROUGHNESS = 0.2e-9
55
+ BOTTOM_ROUGHNESS = 0.2e-9
56
+ PILLAR_TOP_ROUGHNESS = 0.2e-9
57
+
58
+ # Parabolic boundaries:
59
+ INCLUDE_TOP_PARABOLA = False
60
+ TOP_PARABOLA_TIP = 1000e-9
61
+ TOP_PARABOLA_FOCUS = 100e-9
62
+ INCLUDE_BOTTOM_PARABOLA = False
63
+ BOTTOM_PARABOLA_TIP = 1000e-9
64
+ BOTTOM_PARABOLA_FOCUS = 100e-9
65
+
66
+ # Hole array parameters [m]:
67
+ INCLUDE_HOLES = False
68
+ CIRCULAR_HOLE_DIAMETER = 200e-9
69
+ RECTANGULAR_HOLE_SIDE_X = 200e-9
70
+ RECTANGULAR_HOLE_SIDE_Y = 200e-9
71
+ PERIOD_X = 300e-9
72
+ PERIOD_Y = 300e-9
73
+
74
+ # Lattice of holes:
75
+ HOLE_COORDINATES = np.zeros((1, 3))
76
+ HOLE_SHAPES = ["circle" for x in range(1)]
77
+
78
+ # Pillar array parameters [m]
79
+ INCLUDE_PILLARS = False
80
+ PILLAR_HEIGHT = 100e-9
81
+ PILLAR_WALL_ANGLE = 0.0
82
+ PILLAR_COORDINATES = np.zeros((1, 3))
freepaths/flight.py ADDED
@@ -0,0 +1,78 @@
1
+ """Module that provides phonon flight characteristics"""
2
+
3
+ from math import cos
4
+ import numpy as np
5
+
6
+
7
+ class Path:
8
+ """Phonon path coordinates"""
9
+
10
+ def __init__(self, x_init, y_init, z_init):
11
+ """Initialize a path"""
12
+ self.x = np.array([x_init])
13
+ self.y = np.array([y_init])
14
+ self.z = np.array([z_init])
15
+
16
+ def add_point(self, x_new, y_new, z_new):
17
+ """Add a point to the phonon path"""
18
+ self.x = np.append(self.x, x_new)
19
+ self.y = np.append(self.y, y_new)
20
+ self.z = np.append(self.z, z_new)
21
+
22
+ @property
23
+ def number_of_path_points(self):
24
+ """Calculate number of points in the path"""
25
+ return len(self.x)
26
+
27
+
28
+ class Flight:
29
+ """Phonon flight characteristics"""
30
+
31
+ def __init__(self, phonon):
32
+ """Initialize a phonon flight"""
33
+ self.phonon = phonon
34
+ self.initial_frequency = self.phonon.f
35
+ self.initial_theta = self.phonon.theta
36
+ self.path = Path(self.phonon.x, self.phonon.y, self.phonon.z)
37
+ self.exit_theta = 0.0
38
+ self.detected_frequency = 0.0
39
+ self.free_path = 0.0
40
+ self.free_path_along_y = 0.0
41
+ self.travel_time = 0.0
42
+ self.time_since_previous_scattering = 0.0
43
+ self.free_paths = []
44
+ self.free_paths_along_y = []
45
+
46
+ @property
47
+ def mean_free_path(self):
48
+ """Mean value of all free flights"""
49
+ return sum(self.free_paths)/len(self.free_paths)
50
+
51
+ def add_point_to_path(self):
52
+ """Add a scattering point to the path"""
53
+ self.path.add_point(self.phonon.x, self.phonon.y, self.phonon.z)
54
+
55
+ def save_free_paths(self):
56
+ """Save current free path to the list of free paths"""
57
+ self.free_paths.append(self.free_path)
58
+ self.free_paths_along_y.append(self.free_path_along_y)
59
+
60
+ def restart(self):
61
+ """Restart the flight after a scattering event"""
62
+ self.time_since_previous_scattering = 0.0
63
+ self.free_path = 0.0
64
+ self.free_path_along_y = 0.0
65
+
66
+ def finish(self, step, timestep, detector_size):
67
+ """Finish the flight and record final state"""
68
+ self.exit_theta = self.phonon.theta
69
+ self.travel_time = step * timestep
70
+ if abs(self.phonon.x) < detector_size / 2.0:
71
+ self.detected_frequency = self.phonon.f
72
+
73
+ def add_step(self, timestep):
74
+ """Increase parameters of the flight by length of one step"""
75
+ step_length = self.phonon.speed * timestep
76
+ self.free_path += step_length
77
+ self.free_path_along_y += step_length * abs(cos(self.phonon.phi)) * abs(cos(self.phonon.theta))
78
+ self.time_since_previous_scattering += timestep
@@ -0,0 +1,111 @@
1
+ """Module to run thermal conductivity calculations by integrating the phonon dispersion"""
2
+
3
+ import os
4
+ import sys
5
+ import time
6
+ import shutil
7
+ import scipy
8
+ import math
9
+
10
+ # Modules:
11
+ from freepaths.animation import create_animation
12
+ from freepaths.config import cf
13
+ from freepaths.run_phonon import run_phonon
14
+ from freepaths.phonon import Phonon, Polarization
15
+ from freepaths.flight import Flight
16
+ from freepaths.data import ScatteringData, GeneralData, SegmentData, PathData
17
+ from freepaths.progress import Progress
18
+ from freepaths.materials import Material
19
+ from freepaths.maps import ScatteringMap, ThermalMaps
20
+ from freepaths.output_info import output_general_information, output_scattering_information
21
+ from freepaths.output_plots import plot_data
22
+
23
+
24
+ def main(input_file):
25
+ """This is the main function, which integrates phonon dispersion to get thermal conductivity"""
26
+
27
+ print(f'Mean free path sampling of {cf.output_folder_name}')
28
+ start_time = time.time()
29
+ progress = Progress()
30
+
31
+ # Initiate data structures:
32
+ material = Material(cf.media, num_points=cf.number_of_phonons+1)
33
+ scatter_stats = ScatteringData()
34
+ general_stats = GeneralData()
35
+ segment_stats = SegmentData()
36
+ path_stats = PathData()
37
+ scatter_maps = ScatteringMap()
38
+ thermal_maps = ThermalMaps()
39
+
40
+ thermal_conductivity = 0
41
+
42
+ # For each polarization branch:
43
+ for polarization in [Polarization.LA, Polarization.TA, Polarization.TA]:
44
+ sys.stdout.write(f"\rIntegrating {polarization.name} branch.\n")
45
+
46
+ # For each phonon:
47
+ for index in range(cf.number_of_phonons):
48
+
49
+ # Wave vector:
50
+ k_vector = (material.dispersion[index+1, 0] + material.dispersion[index, 0]) / 2
51
+ d_k_vector = (material.dispersion[index+1, 0] - material.dispersion[index, 0])
52
+
53
+ # Initiate a phonon and its flight:
54
+ phonon = Phonon(material, polarization, index)
55
+ flight = Flight(phonon)
56
+
57
+ # Run this phonon through the structure:
58
+ run_phonon(phonon, flight, scatter_stats, segment_stats, thermal_maps, scatter_maps, material)
59
+
60
+ # Record the properties returned for this phonon:
61
+ general_stats.save_phonon_data(phonon)
62
+ general_stats.save_flight_data(flight)
63
+
64
+ # Record trajectories of the first N phonons:
65
+ if index < cf.output_trajectories_of_first:
66
+ path_stats.save_phonon_path(flight)
67
+
68
+ # Heat capacity, Ref. PRB 88 155318 (2013):
69
+ omega = 2 * math.pi * phonon.f
70
+ part = scipy.constants.hbar * omega / (scipy.constants.k * cf.temp)
71
+ c_p = scipy.constants.k * part**2 * math.exp(part) / (math.exp(part) - 1)**2
72
+
73
+ # Thermal conductivity, Ref. Phys. Rev. 132 2461 (1963):
74
+ relax_time = flight.mean_free_path/phonon.speed
75
+ thermal_conductivity += (1/(6*(math.pi**2)))*c_p*(phonon.speed**2)*relax_time*(k_vector**2)*d_k_vector
76
+
77
+ # Run additional calculations:
78
+ thermal_maps.calculate_thermal_conductivity()
79
+
80
+ # Create the folder if it does not exist and copy input file there:
81
+ if not os.path.exists("Results/" + cf.output_folder_name):
82
+ os.makedirs("Results/" + cf.output_folder_name)
83
+ os.makedirs("Results/" + cf.output_folder_name + '/Data')
84
+ if input_file:
85
+ shutil.copy(input_file, "Results/" + cf.output_folder_name)
86
+ os.chdir("Results/" + cf.output_folder_name)
87
+
88
+ # Save data into files:
89
+ general_stats.write_into_files()
90
+ scatter_stats.write_into_files()
91
+ segment_stats.write_into_files()
92
+ thermal_maps.write_into_files()
93
+ scatter_maps.write_into_files()
94
+ path_stats.write_into_files()
95
+
96
+ # Output general information:
97
+ output_general_information(start_time)
98
+ output_scattering_information(scatter_stats)
99
+
100
+ # Analyze and plot the data:
101
+ sys.stdout.write("\rAnalyzing the data...")
102
+ plot_data()
103
+
104
+ # Generate animation of phonon paths:
105
+ if cf.output_path_animation:
106
+ sys.stdout.write("\rGenerating path animation...")
107
+ create_animation()
108
+
109
+ sys.stdout.write(f'\rSee the results in "Results/{cf.output_folder_name}" folder.\n')
110
+ sys.stdout.write(f"\rThermal conductivity = {thermal_conductivity}\n")
111
+ sys.stdout.write("\rThank you for using FreePATHS.\n")