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 +30 -0
- freepaths/animation.py +64 -0
- freepaths/config.py +156 -0
- freepaths/data.py +157 -0
- freepaths/default_config.py +82 -0
- freepaths/flight.py +78 -0
- freepaths/main_mfp_sampling.py +111 -0
- freepaths/main_tracing.py +93 -0
- freepaths/maps.py +165 -0
- freepaths/materials.py +75 -0
- freepaths/move.py +23 -0
- freepaths/options.py +27 -0
- freepaths/output_info.py +98 -0
- freepaths/output_plots.py +315 -0
- freepaths/phonon.py +165 -0
- freepaths/progress.py +18 -0
- freepaths/run_phonon.py +55 -0
- freepaths/scattering.py +593 -0
- freepaths/scattering_types.py +55 -0
- freepaths-1.1.0.dist-info/METADATA +102 -0
- freepaths-1.1.0.dist-info/RECORD +24 -0
- freepaths-1.1.0.dist-info/WHEEL +5 -0
- freepaths-1.1.0.dist-info/entry_points.txt +2 -0
- freepaths-1.1.0.dist-info/top_level.txt +1 -0
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")
|