freepaths 2.3.4__tar.gz → 2.4.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.
- {freepaths-2.3.4 → freepaths-2.4.1}/PKG-INFO +1 -1
- {freepaths-2.3.4 → freepaths-2.4.1}/freepaths/__main__.py +1 -1
- {freepaths-2.3.4 → freepaths-2.4.1}/freepaths/config.py +16 -2
- {freepaths-2.3.4 → freepaths-2.4.1}/freepaths/data.py +14 -4
- freepaths-2.4.1/freepaths/default_config.py +164 -0
- freepaths-2.4.1/freepaths/electron.py +149 -0
- {freepaths-2.3.4 → freepaths-2.4.1}/freepaths/flight.py +34 -15
- {freepaths-2.3.4 → freepaths-2.4.1}/freepaths/main_mfp_sampling.py +57 -7
- {freepaths-2.3.4 → freepaths-2.4.1}/freepaths/main_tracing.py +16 -6
- {freepaths-2.3.4 → freepaths-2.4.1}/freepaths/maps.py +30 -11
- {freepaths-2.3.4 → freepaths-2.4.1}/freepaths/materials.py +206 -72
- {freepaths-2.3.4 → freepaths-2.4.1}/freepaths/output_electron_plots.py +13 -0
- {freepaths-2.3.4 → freepaths-2.4.1}/freepaths/output_info.py +32 -15
- {freepaths-2.3.4 → freepaths-2.4.1}/freepaths/output_plots.py +78 -41
- freepaths-2.4.1/freepaths/phonon.py +167 -0
- {freepaths-2.3.4 → freepaths-2.4.1}/freepaths/post_computations.py +14 -13
- {freepaths-2.3.4 → freepaths-2.4.1}/freepaths/run_particle.py +31 -6
- {freepaths-2.3.4 → freepaths-2.4.1}/freepaths/scatterers.py +60 -6
- {freepaths-2.3.4 → freepaths-2.4.1}/freepaths/scattering.py +24 -19
- {freepaths-2.3.4 → freepaths-2.4.1}/freepaths.egg-info/PKG-INFO +1 -1
- freepaths-2.3.4/freepaths/default_config.py +0 -97
- freepaths-2.3.4/freepaths/electron.py +0 -75
- freepaths-2.3.4/freepaths/phonon.py +0 -120
- {freepaths-2.3.4 → freepaths-2.4.1}/README.md +0 -0
- {freepaths-2.3.4 → freepaths-2.4.1}/freepaths/animation.py +0 -0
- {freepaths-2.3.4 → freepaths-2.4.1}/freepaths/interface_smmm.py +0 -0
- {freepaths-2.3.4 → freepaths-2.4.1}/freepaths/move.py +0 -0
- {freepaths-2.3.4 → freepaths-2.4.1}/freepaths/options.py +0 -0
- {freepaths-2.3.4 → freepaths-2.4.1}/freepaths/output_structure.py +0 -0
- {freepaths-2.3.4 → freepaths-2.4.1}/freepaths/particle.py +0 -0
- {freepaths-2.3.4 → freepaths-2.4.1}/freepaths/progress.py +0 -0
- {freepaths-2.3.4 → freepaths-2.4.1}/freepaths/scattering_interfaces.py +0 -0
- {freepaths-2.3.4 → freepaths-2.4.1}/freepaths/scattering_primitives.py +0 -0
- {freepaths-2.3.4 → freepaths-2.4.1}/freepaths/scattering_semicircle.py +0 -0
- {freepaths-2.3.4 → freepaths-2.4.1}/freepaths/scattering_types.py +0 -0
- {freepaths-2.3.4 → freepaths-2.4.1}/freepaths/sources.py +0 -0
- {freepaths-2.3.4 → freepaths-2.4.1}/freepaths.egg-info/SOURCES.txt +0 -0
- {freepaths-2.3.4 → freepaths-2.4.1}/freepaths.egg-info/dependency_links.txt +0 -0
- {freepaths-2.3.4 → freepaths-2.4.1}/freepaths.egg-info/entry_points.txt +0 -0
- {freepaths-2.3.4 → freepaths-2.4.1}/freepaths.egg-info/requires.txt +0 -0
- {freepaths-2.3.4 → freepaths-2.4.1}/freepaths.egg-info/top_level.txt +0 -0
- {freepaths-2.3.4 → freepaths-2.4.1}/pyproject.toml +0 -0
- {freepaths-2.3.4 → freepaths-2.4.1}/setup.cfg +0 -0
- {freepaths-2.3.4 → freepaths-2.4.1}/setup.py +0 -0
|
@@ -71,6 +71,11 @@ class Config:
|
|
|
71
71
|
self.energy_lower_bound = ENERGY_LOWER_BOUND
|
|
72
72
|
self.energy_step = ENERGY_STEP
|
|
73
73
|
self.electron_mfp = ELECTRON_MFP
|
|
74
|
+
self.doping_concentration = DOPING_CONCENTRATION
|
|
75
|
+
self.surface_potential = SURFACE_POTENTIAL
|
|
76
|
+
self.depletion_doping = DEPLETION_DOPING # doping for the dead-layer width only (falls back to DOPING_CONCENTRATION)
|
|
77
|
+
self.depletion_width = 0.0 # [m] carrier dead-layer width; set from the material at simulation start
|
|
78
|
+
self.electron_mfp_energy_exponent = ELECTRON_MFP_ENERGY_EXPONENT
|
|
74
79
|
self.mean_mapping_constant = MEAN_MAPPING_CONSTANT
|
|
75
80
|
self.is_carrier_electron = IS_CARRIER_ELECTRON
|
|
76
81
|
self.fermi_level_lower_bound = FERMI_LEVEL_LOWER_BOUND
|
|
@@ -85,6 +90,7 @@ class Config:
|
|
|
85
90
|
self.number_of_pixels_y = NUMBER_OF_PIXELS_Y
|
|
86
91
|
self.number_of_virtual_timesteps = NUMBER_OF_VIRTUAL_TIMESTEPS
|
|
87
92
|
self.ignore_faulty_particles = IGNORE_FAULTY_PARTICLES
|
|
93
|
+
self.gradient_fit_range = GRADIENT_FIT_RANGE
|
|
88
94
|
|
|
89
95
|
# Material parameters:
|
|
90
96
|
self.media = MEDIA
|
|
@@ -92,8 +98,11 @@ class Config:
|
|
|
92
98
|
|
|
93
99
|
# Internal scattering:
|
|
94
100
|
self.include_internal_scattering = INCLUDE_INTERNAL_SCATTERING
|
|
95
|
-
self.
|
|
96
|
-
self.
|
|
101
|
+
self.use_dispersion_heat_capacity = USE_DISPERSION_HEAT_CAPACITY
|
|
102
|
+
self.max_number_of_scattering_events = MAX_NUMBER_OF_SCATTERING_EVENTS
|
|
103
|
+
self.grain_size = GRAIN_SIZE
|
|
104
|
+
self.grain_size_std = GRAIN_SIZE_STD
|
|
105
|
+
self.grain_roughness = GRAIN_ROUGHNESS
|
|
97
106
|
|
|
98
107
|
# System dimensions:
|
|
99
108
|
self.thickness = THICKNESS
|
|
@@ -157,6 +166,11 @@ class Config:
|
|
|
157
166
|
if self.number_of_particles < self.output_trajectories_of_first:
|
|
158
167
|
self.output_trajectories_of_first = self.number_of_particles
|
|
159
168
|
|
|
169
|
+
if not (0.0 <= self.gradient_fit_range[0] < self.gradient_fit_range[1] <= 1.0):
|
|
170
|
+
logging.error("Parameter GRADIENT_FIT_RANGE must be a pair (start, end) with 0 <= start < end <= 1.\n" +
|
|
171
|
+
f"See the documentation at {WEBSITE}")
|
|
172
|
+
sys.exit()
|
|
173
|
+
|
|
160
174
|
if self.number_of_timeframes <= self.number_of_stabilization_timeframes:
|
|
161
175
|
logging.error("Parameter NUMBER_OF_STABILIZATION_TIMEFRAMES exceeds or equal to NUMBER_OF_TIMEFRAMES.\n" +
|
|
162
176
|
"Leave at least one timeframe after NUMBER_OF_STABILIZATION_TIMEFRAMES.\n" +
|
|
@@ -57,10 +57,14 @@ class GeneralData(Data):
|
|
|
57
57
|
"""Initialize arrays for writing various properties"""
|
|
58
58
|
self.initial_angles = []
|
|
59
59
|
self.exit_angles = []
|
|
60
|
+
self.launch_frequencies = []
|
|
61
|
+
self.exit_frequencies = []
|
|
60
62
|
self.hole_diff_scattering_angles = []
|
|
61
63
|
self.hole_spec_scattering_angles = []
|
|
62
64
|
self.free_paths = []
|
|
63
|
-
|
|
65
|
+
# Last-known frequency of each particle, paired with group_velocities below
|
|
66
|
+
# (used for the frequency-velocity/MFP correlation plots, not a true launch value):
|
|
67
|
+
self.final_frequencies = []
|
|
64
68
|
self.initial_energies = []
|
|
65
69
|
self.group_velocities = []
|
|
66
70
|
self.travel_times = []
|
|
@@ -72,7 +76,7 @@ class GeneralData(Data):
|
|
|
72
76
|
if mode is SimulationMode.ELECTRON:
|
|
73
77
|
self.initial_energies.append(pt.energy)
|
|
74
78
|
else:
|
|
75
|
-
self.
|
|
79
|
+
self.final_frequencies.append(pt.f)
|
|
76
80
|
self.group_velocities.append(pt.speed)
|
|
77
81
|
|
|
78
82
|
def save_flight_data(self, flight, mode=None):
|
|
@@ -83,6 +87,8 @@ class GeneralData(Data):
|
|
|
83
87
|
if mode is SimulationMode.PHONON_TRACING:
|
|
84
88
|
self.initial_angles.append(flight.initial_theta)
|
|
85
89
|
self.exit_angles.append(flight.exit_theta)
|
|
90
|
+
self.launch_frequencies.append(flight.initial_frequency)
|
|
91
|
+
self.exit_frequencies.append(flight.exit_frequency)
|
|
86
92
|
self.hole_diff_scattering_angles.extend(flight.hole_diff_scattering_angles)
|
|
87
93
|
self.hole_spec_scattering_angles.extend(flight.hole_spec_scattering_angles)
|
|
88
94
|
self.travel_times.append(flight.travel_time)
|
|
@@ -101,11 +107,13 @@ class GeneralData(Data):
|
|
|
101
107
|
if mode is SimulationMode.ELECTRON:
|
|
102
108
|
np.savetxt("Data/All initial energies.csv", self.initial_energies, fmt='%2.4e', header="Energy [J]", encoding='utf-8')
|
|
103
109
|
else:
|
|
104
|
-
np.savetxt("Data/All
|
|
110
|
+
np.savetxt("Data/All final frequencies.csv", self.final_frequencies, fmt='%2.4e', header="f [Hz]", encoding='utf-8')
|
|
105
111
|
np.savetxt("Data/All group velocities.csv", self.group_velocities, fmt='%.4f', header="Vg [m//s]", encoding='utf-8')
|
|
106
112
|
if mode is SimulationMode.PHONON_TRACING:
|
|
107
113
|
np.savetxt("Data/All exit angles.csv", self.exit_angles, fmt='%.4f', header="Angle [rad]", encoding='utf-8')
|
|
108
114
|
np.savetxt("Data/All initial angles.csv", self.initial_angles, fmt='%.4f', header="Angle [rad]", encoding='utf-8')
|
|
115
|
+
np.savetxt("Data/All launch frequencies.csv", self.launch_frequencies, fmt='%2.4e', header="f [Hz]", encoding='utf-8')
|
|
116
|
+
np.savetxt("Data/All exit frequencies.csv", self.exit_frequencies, fmt='%2.4e', header="f [Hz]", encoding='utf-8')
|
|
109
117
|
if cf.holes:
|
|
110
118
|
np.savetxt("Data/All hole diffuse scattering angles.csv", self.hole_diff_scattering_angles, fmt='%.4f', header="Angle [rad]", encoding='utf-8')
|
|
111
119
|
np.savetxt("Data/All hole specular scattering angles.csv", self.hole_spec_scattering_angles, fmt='%.4f', header="Angle [rad]", encoding='utf-8')
|
|
@@ -118,10 +126,12 @@ class GeneralData(Data):
|
|
|
118
126
|
return {
|
|
119
127
|
'initial_angles': self.initial_angles,
|
|
120
128
|
'exit_angles': self.exit_angles,
|
|
129
|
+
'launch_frequencies': self.launch_frequencies,
|
|
130
|
+
'exit_frequencies': self.exit_frequencies,
|
|
121
131
|
'hole_diff_scattering_angles': self.hole_diff_scattering_angles,
|
|
122
132
|
'hole_spec_scattering_angles': self.hole_spec_scattering_angles,
|
|
123
133
|
'free_paths': self.free_paths,
|
|
124
|
-
'
|
|
134
|
+
'final_frequencies': self.final_frequencies,
|
|
125
135
|
'group_velocities': self.group_velocities,
|
|
126
136
|
'travel_times': self.travel_times,
|
|
127
137
|
'mean_free_paths': self.mean_free_paths,
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""Default config file"""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from scipy.constants import k, electron_volt
|
|
5
|
+
from freepaths.sources import Source
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# General parameters:
|
|
9
|
+
OUTPUT_FOLDER_NAME = "Si nanowire at 300 K"
|
|
10
|
+
NUMBER_OF_PARTICLES = 10000
|
|
11
|
+
NUMBER_OF_NODES = 400
|
|
12
|
+
T = 300
|
|
13
|
+
OUTPUT_SCATTERING_MAP = False
|
|
14
|
+
OUTPUT_TRAJECTORIES_OF_FIRST = 20
|
|
15
|
+
OUTPUT_STRUCTURE_COLOR = "#F0F0F0"
|
|
16
|
+
NUMBER_OF_LENGTH_SEGMENTS = 10
|
|
17
|
+
LOW_MEMORY_USAGE = False
|
|
18
|
+
|
|
19
|
+
# Time parameters:
|
|
20
|
+
TIMESTEP = 2e-12
|
|
21
|
+
NUMBER_OF_TIMESTEPS = 200000
|
|
22
|
+
NUMBER_OF_VIRTUAL_TIMESTEPS = NUMBER_OF_TIMESTEPS * 3
|
|
23
|
+
NUMBER_OF_TIMEFRAMES = 8
|
|
24
|
+
NUMBER_OF_STABILIZATION_TIMEFRAMES = 5
|
|
25
|
+
|
|
26
|
+
# Electron parameters [eV]
|
|
27
|
+
IS_CARRIER_ELECTRON = True
|
|
28
|
+
ENERGY_UPPER_BOUND = 350e-3
|
|
29
|
+
ENERGY_LOWER_BOUND = 0
|
|
30
|
+
ENERGY_STEP = 5e-3
|
|
31
|
+
|
|
32
|
+
ELECTRON_MFP = 10e-9 # [m] crystal (acoustic-phonon-limited) MFP, energy-independent
|
|
33
|
+
# Ionized-impurity scattering: if DOPING_CONCENTRATION > 0, the MFP becomes energy-
|
|
34
|
+
# and doping-dependent via Matthiessen's rule, 1/Lambda_tot(E) = 1/ELECTRON_MFP +
|
|
35
|
+
# 1/Lambda_ii(E), where Lambda_ii is the Brooks-Herring contribution from N_I ionized
|
|
36
|
+
# dopants (screened Coulomb scattering). This shortens the MFP with doping and raises
|
|
37
|
+
# the Seebeck coefficient. 0 -> pure crystal MFP.
|
|
38
|
+
DOPING_CONCENTRATION = 0.0 # [m^-3] ionized-impurity (dopant) concentration N_I
|
|
39
|
+
# Surface depletion of carriers: with SURFACE_POTENTIAL > 0, a dead layer of width
|
|
40
|
+
# W = sqrt(2*eps_s*phi_s/(e*N_D)) (abrupt-junction depletion) is excluded around every
|
|
41
|
+
# free surface (walls, top/bottom, hole edges) for CARRIERS ONLY. phi_s is the surface
|
|
42
|
+
# band bending set by Fermi-level pinning at the native oxide / surface states. Needs
|
|
43
|
+
# a doping (DEPLETION_DOPING or DOPING_CONCENTRATION) and the material dielectric
|
|
44
|
+
# constant. 0 -> no depletion.
|
|
45
|
+
SURFACE_POTENTIAL = 0.0 # [eV] surface band bending phi_s (0 = off)
|
|
46
|
+
# Doping used ONLY for the depletion-width formula. If 0/None it falls back to
|
|
47
|
+
# DOPING_CONCENTRATION. Set this (and leave DOPING_CONCENTRATION = 0) to add a surface
|
|
48
|
+
# dead layer from the real doping WITHOUT engaging Brooks-Herring, i.e. to isolate the
|
|
49
|
+
# depletion effect on top of a constant-MFP transport calibration.
|
|
50
|
+
DEPLETION_DOPING = 0.0 # [m^-3] dopant concentration for the dead-layer width only
|
|
51
|
+
# Legacy phenomenological energy dependence (only used when DOPING_CONCENTRATION = 0):
|
|
52
|
+
# Lambda(E) = ELECTRON_MFP * (E/kT)^p. p=0 -> constant; p=2 -> ionized-impurity-like.
|
|
53
|
+
ELECTRON_MFP_ENERGY_EXPONENT = 0.0
|
|
54
|
+
MEAN_MAPPING_CONSTANT = 5e-6 # [m²]
|
|
55
|
+
|
|
56
|
+
FERMI_LEVEL_LOWER_BOUND = -0.2 # [eV] lower end of post-processing Fermi level sweep
|
|
57
|
+
FERMI_LEVEL_UPPER_BOUND = 0.1 # [eV] upper end of post-processing Fermi level sweep;
|
|
58
|
+
# far above the band edge the Fermi window leaves the sampled energy range and
|
|
59
|
+
# the MC results become noisy, so there is little point in sweeping further
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# Animation:
|
|
63
|
+
OUTPUT_PATH_ANIMATION = False
|
|
64
|
+
OUTPUT_ANIMATION_FPS = 24
|
|
65
|
+
|
|
66
|
+
# Map & profiles parameters:
|
|
67
|
+
NUMBER_OF_PIXELS_X = 7
|
|
68
|
+
NUMBER_OF_PIXELS_Y = 67
|
|
69
|
+
IGNORE_FAULTY_PARTICLES = False
|
|
70
|
+
GRADIENT_FIT_RANGE = (0.1, 0.9)
|
|
71
|
+
|
|
72
|
+
# Material parameters:
|
|
73
|
+
MEDIA = "Si"
|
|
74
|
+
MEDIA_FERMI_LEVEL = None
|
|
75
|
+
|
|
76
|
+
# Internal scattering:
|
|
77
|
+
INCLUDE_INTERNAL_SCATTERING = True
|
|
78
|
+
|
|
79
|
+
# Phonon frequencies and branches are always sampled from the tabulated dispersion
|
|
80
|
+
# (weight k^2 dk per bin, heat-capacity weighting, group-velocity weighting at the
|
|
81
|
+
# source), so particles are equal-energy deviational bundles and the thermal maps
|
|
82
|
+
# record a constant weight per phonon. At inelastic (anharmonic) internal scattering
|
|
83
|
+
# events the branch and frequency are re-drawn from the collision-rate-weighted
|
|
84
|
+
# distribution (Peraud & Hadjiconstantinou, PRB 84, 205331 (2011)), restoring local
|
|
85
|
+
# thermal equilibrium of the phonon population. Elastic internal events (impurity/alloy
|
|
86
|
+
# scattering) conserve the mode and only randomize the direction. This applies to the
|
|
87
|
+
# phonon tracing mode; MFP sampling and electron modes keep the particle identity by
|
|
88
|
+
# construction.
|
|
89
|
+
|
|
90
|
+
# Convert deposited particle energy into the temperature profile using the
|
|
91
|
+
# dispersion-only heat capacity (Material.dispersion_heat_capacity, summed only
|
|
92
|
+
# over the branches in the tabulated dispersion) instead of the experimental
|
|
93
|
+
# heat capacity fit (Material.assign_heat_capacity). On by default because it
|
|
94
|
+
# makes the reported temperature and kappa self-consistent with the
|
|
95
|
+
# dispersion-based sampling/rethermalization scheme and the model's own RTA
|
|
96
|
+
# integral. Set to False to use the real material heat capacity instead (e.g.
|
|
97
|
+
# including optical branches absent from the tabulated dispersion):
|
|
98
|
+
USE_DISPERSION_HEAT_CAPACITY = True
|
|
99
|
+
|
|
100
|
+
# MFP sampling mode only (no effect in phonon tracing, where particles must keep
|
|
101
|
+
# going for the full budget to build the flux map): end a phonon's flight early
|
|
102
|
+
# once it has accumulated this many free-path segments, instead of always running
|
|
103
|
+
# to NUMBER_OF_TIMESTEPS. A short-tau, low-velocity phonon takes tiny hops and
|
|
104
|
+
# diffuses so slowly that it essentially never reaches a domain boundary, but its
|
|
105
|
+
# mean free path already converges after a modest number of scattering events, so
|
|
106
|
+
# running it to the full timestep budget is wasted compute. 1000 is on by default,
|
|
107
|
+
# since it was validated (bulk Si MFP-sampling convergence study, Data/BulkSi_Phonon_MFP/)
|
|
108
|
+
# to give the same result as 5000 while being far cheaper. Set to None to disable
|
|
109
|
+
# (run to the timestep budget or a boundary, as before):
|
|
110
|
+
MAX_NUMBER_OF_SCATTERING_EVENTS = 1000
|
|
111
|
+
|
|
112
|
+
# Grain boundary scattering (polycrystalline materials):
|
|
113
|
+
# GRAIN_SIZE sets the mean grain diameter [m]. None disables grain boundary scattering entirely.
|
|
114
|
+
# GRAIN_SIZE_STD sets the standard deviation [m]; the grain size is drawn per phonon from a
|
|
115
|
+
# lognormal distribution with this mean and std. Set to 0 for monodisperse (single grain size).
|
|
116
|
+
# GRAIN_ROUGHNESS is the RMS disorder width of the grain boundary [m], used in a Soffer-type
|
|
117
|
+
# specularity factor: at low frequencies (long wavelengths) the boundary appears smooth and
|
|
118
|
+
# phonons pass through; at high frequencies it acts as a classical diffuse scatterer.
|
|
119
|
+
# Typical values: GRAIN_SIZE 100 nm – 10 µm, GRAIN_ROUGHNESS 0.5–2 nm.
|
|
120
|
+
GRAIN_SIZE = None
|
|
121
|
+
GRAIN_SIZE_STD = 0.0
|
|
122
|
+
GRAIN_ROUGHNESS = 1e-9
|
|
123
|
+
|
|
124
|
+
# System dimensions [m]:
|
|
125
|
+
THICKNESS = 150e-9
|
|
126
|
+
WIDTH = 200e-9
|
|
127
|
+
LENGTH = 2000e-9
|
|
128
|
+
IS_TWO_DIMENSIONAL_MATERIAL = False
|
|
129
|
+
INCLUDE_RIGHT_SIDEWALL = True
|
|
130
|
+
INCLUDE_LEFT_SIDEWALL = True
|
|
131
|
+
INCLUDE_TOP_SIDEWALL = False
|
|
132
|
+
INCLUDE_BOTTOM_SIDEWALL = False
|
|
133
|
+
|
|
134
|
+
# Hot and cold sides [m]:
|
|
135
|
+
COLD_SIDE_POSITION_TOP = True
|
|
136
|
+
COLD_SIDE_POSITION_BOTTOM = False
|
|
137
|
+
COLD_SIDE_POSITION_RIGHT = False
|
|
138
|
+
COLD_SIDE_POSITION_LEFT = False
|
|
139
|
+
HOT_SIDE_POSITION_TOP = False
|
|
140
|
+
HOT_SIDE_POSITION_BOTTOM = True
|
|
141
|
+
HOT_SIDE_POSITION_RIGHT = False
|
|
142
|
+
HOT_SIDE_POSITION_LEFT = False
|
|
143
|
+
RETHERMALIZATION_ON_HOT_SIDES = True
|
|
144
|
+
|
|
145
|
+
# Particle source:
|
|
146
|
+
PARTICLE_SOURCES = [Source(x=0, y=0, z=0, size_x=0, size_y=0, size_z=0, angle_distribution="random", angle=0)]
|
|
147
|
+
|
|
148
|
+
# Roughness [m]:
|
|
149
|
+
SIDE_WALL_ROUGHNESS = 2e-9
|
|
150
|
+
HOLE_ROUGHNESS = 2e-9
|
|
151
|
+
PILLAR_ROUGHNESS = 2e-9
|
|
152
|
+
TOP_ROUGHNESS = 0.2e-9
|
|
153
|
+
BOTTOM_ROUGHNESS = 0.2e-9
|
|
154
|
+
PILLAR_TOP_ROUGHNESS = 0.2e-9
|
|
155
|
+
INTERFACE_ROUGHNESS = 0.2e-9
|
|
156
|
+
|
|
157
|
+
# Holes and pillars:
|
|
158
|
+
HOLES = []
|
|
159
|
+
PILLARS = []
|
|
160
|
+
INTERFACES = []
|
|
161
|
+
BULKS = []
|
|
162
|
+
|
|
163
|
+
# Multiprocessing:
|
|
164
|
+
NUMBER_OF_PROCESSES = 10
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""This module provides electron class which generates and moves an electron"""
|
|
2
|
+
|
|
3
|
+
from random import choice, random
|
|
4
|
+
from scipy.constants import h, k, hbar, electron_volt, elementary_charge, epsilon_0
|
|
5
|
+
from freepaths.config import cf
|
|
6
|
+
from freepaths.particle import Particle
|
|
7
|
+
from freepaths.options import ParticleType
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _carrier_dos_mass(material):
|
|
13
|
+
"""DOS effective mass of the active carrier (electron or hole)."""
|
|
14
|
+
return material.effective_electron_dos_mass if cf.is_carrier_electron else material.effective_hole_dos_mass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def depletion_width(material):
|
|
18
|
+
"""Surface-depletion (dead-layer) width for carriers, from the abrupt-junction
|
|
19
|
+
depletion approximation W = sqrt(2*eps_s*phi_s/(e*N_D)), with phi_s = SURFACE_POTENTIAL
|
|
20
|
+
the surface band bending (eV, used as volts), N_D the doping, and eps_s the static
|
|
21
|
+
permittivity of the material. Carriers are excluded within W of every free surface
|
|
22
|
+
(Fermi-level pinning by surface states / native oxide). Returns 0 if there is no
|
|
23
|
+
doping or no surface potential. Reference: any semiconductor-device text (e.g. Sze),
|
|
24
|
+
surface-depletion / dead-layer model for nanostructure conductivity.
|
|
25
|
+
|
|
26
|
+
N_D is DEPLETION_DOPING if set, otherwise DOPING_CONCENTRATION. The separate
|
|
27
|
+
DEPLETION_DOPING lets the dead layer be driven by the real doping WITHOUT also
|
|
28
|
+
engaging the Brooks-Herring ionized-impurity MFP (which keys off DOPING_CONCENTRATION),
|
|
29
|
+
i.e. to isolate the depletion effect on top of a constant-MFP transport calibration."""
|
|
30
|
+
N_I = cf.depletion_doping if cf.depletion_doping else cf.doping_concentration
|
|
31
|
+
phi = cf.surface_potential
|
|
32
|
+
eps_r = getattr(material, 'dielectric_constant', None)
|
|
33
|
+
if not N_I or phi <= 0 or not eps_r:
|
|
34
|
+
return 0.0
|
|
35
|
+
eps = eps_r * epsilon_0
|
|
36
|
+
return np.sqrt(2.0 * eps * phi / (elementary_charge * N_I))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def electron_mfp(energy, material):
|
|
40
|
+
"""Energy- and doping-dependent carrier mean free path via Matthiessen's rule:
|
|
41
|
+
1/Lambda_tot(E) = 1/Lambda_ac + 1/Lambda_ii(E, N_I).
|
|
42
|
+
|
|
43
|
+
Lambda_ac = ELECTRON_MFP is the crystal (acoustic-phonon-limited) MFP, taken
|
|
44
|
+
energy-independent (for acoustic deformation-potential scattering tau ~ E^-1/2
|
|
45
|
+
cancels v ~ E^1/2, so Lambda = v*tau ~ E^0). Lambda_ii is the ionized-impurity
|
|
46
|
+
contribution from the Brooks-Herring model: screened Coulomb scattering off the
|
|
47
|
+
DOPING_CONCENTRATION ionized dopants, with tau_ii ~ E^{3/2} in the weak-screening
|
|
48
|
+
limit. Ionized impurities thus scatter LOW-energy carriers most, shortening the
|
|
49
|
+
MFP with doping and making scattering energy-selective, which raises the Seebeck
|
|
50
|
+
coefficient. DOPING_CONCENTRATION = 0 recovers the crystal MFP (optionally with
|
|
51
|
+
the legacy phenomenological exponent ELECTRON_MFP_ENERGY_EXPONENT).
|
|
52
|
+
|
|
53
|
+
Applies identically to electrons and holes: the carrier's own DOS effective mass
|
|
54
|
+
is used (chosen by IS_CARRIER_ELECTRON) and N_I is the density of ionized donors
|
|
55
|
+
(n-type) or acceptors (p-type). Reference: Brooks-Herring; Chattopadhyay &
|
|
56
|
+
Queisser, Rev. Mod. Phys. 53, 745 (1981). `energy` in J, scalar or array."""
|
|
57
|
+
lambda_ac = cf.electron_mfp
|
|
58
|
+
N_I = cf.doping_concentration
|
|
59
|
+
|
|
60
|
+
if not N_I:
|
|
61
|
+
# No ionized impurities: crystal MFP, with optional legacy power law.
|
|
62
|
+
p = cf.electron_mfp_energy_exponent
|
|
63
|
+
if p == 0:
|
|
64
|
+
return lambda_ac
|
|
65
|
+
return lambda_ac * (energy / (k * cf.temp)) ** p
|
|
66
|
+
|
|
67
|
+
# Brooks-Herring ionized-impurity MFP (Matthiessen-combined with the acoustic MFP):
|
|
68
|
+
energy = np.maximum(energy, 1e-4 * elementary_charge) # guard against E -> 0
|
|
69
|
+
m_star = _carrier_dos_mass(material)
|
|
70
|
+
eps = material.dielectric_constant * epsilon_0
|
|
71
|
+
T = cf.temp
|
|
72
|
+
# Debye screening from the free carriers (n = N_I, full activation): 1/L_D^2 = e^2 n / (eps kB T)
|
|
73
|
+
inv_screen_length_sq = elementary_charge**2 * N_I / (eps * k * T)
|
|
74
|
+
b = 8.0 * m_star * energy / (hbar**2 * inv_screen_length_sq) # b = (2k/beta)^2
|
|
75
|
+
screening = np.maximum(np.log1p(b) - b / (1.0 + b), 1e-12)
|
|
76
|
+
inv_tau_ii = (N_I * elementary_charge**4) / (16.0 * np.pi * np.sqrt(2.0 * m_star) * eps**2 * energy**1.5) * screening
|
|
77
|
+
v = np.sqrt(2.0 * energy / m_star)
|
|
78
|
+
lambda_ii = v / inv_tau_ii # = v * tau_ii
|
|
79
|
+
return 1.0 / (1.0 / lambda_ac + 1.0 / lambda_ii)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class Electron(Particle):
|
|
83
|
+
"""An electron particle"""
|
|
84
|
+
|
|
85
|
+
def __init__(self, material):
|
|
86
|
+
super().__init__()
|
|
87
|
+
|
|
88
|
+
# Assign particle type
|
|
89
|
+
self.type = ParticleType.ELECTRON
|
|
90
|
+
self.is_electron_carrier = cf.is_carrier_electron # assign electron or hole behavior
|
|
91
|
+
|
|
92
|
+
source = choice(cf.particles_sources)
|
|
93
|
+
while True:
|
|
94
|
+
self.x, self.y, self.z = source.generate_coordinates()
|
|
95
|
+
is_in_hole = any(hole.is_inside(self.x, self.y, None, cf) for hole in cf.holes)
|
|
96
|
+
if not is_in_hole:
|
|
97
|
+
break
|
|
98
|
+
|
|
99
|
+
# Assign initial angles
|
|
100
|
+
self.theta, self.phi = source.generate_angles()
|
|
101
|
+
self.correct_angle()
|
|
102
|
+
if cf.is_two_dimensional_material:
|
|
103
|
+
self.phi, self.z = 0.0, 0.0
|
|
104
|
+
|
|
105
|
+
self.assign_energy()
|
|
106
|
+
self.assign_frequency(material)
|
|
107
|
+
self.assign_speed(material)
|
|
108
|
+
self.assign_internal_scattering_time(material)
|
|
109
|
+
|
|
110
|
+
def assign_energy(self):
|
|
111
|
+
"""
|
|
112
|
+
Assign energy uniformly to an electron based on temperature.
|
|
113
|
+
Conduction minimum is considered at 0 energy by convention.
|
|
114
|
+
"""
|
|
115
|
+
num_energy_points = int((cf.energy_upper_bound-cf.energy_lower_bound)/cf.energy_step)
|
|
116
|
+
# Keep energy in J for other computations
|
|
117
|
+
self.energy = np.random.choice(np.linspace(cf.energy_lower_bound, cf.energy_upper_bound - cf.energy_step, num_energy_points)) * electron_volt + cf.energy_step * electron_volt
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def wavelength(self):
|
|
122
|
+
raise Exception("Electron wavelength undefined")
|
|
123
|
+
|
|
124
|
+
def assign_frequency(self, material):
|
|
125
|
+
"""
|
|
126
|
+
Conduction minimum is 0 by convention
|
|
127
|
+
"""
|
|
128
|
+
self.f = self.energy / h
|
|
129
|
+
|
|
130
|
+
def assign_speed(self, material):
|
|
131
|
+
"""
|
|
132
|
+
Assign speed to an electron using group velocity and considering parabolic energy-k relation and conduction minimum at 0
|
|
133
|
+
"""
|
|
134
|
+
if self.is_electron_carrier:
|
|
135
|
+
effective_mass = material.effective_electron_dos_mass
|
|
136
|
+
else:
|
|
137
|
+
effective_mass = material.effective_hole_dos_mass
|
|
138
|
+
self.speed = (2*self.energy/(effective_mass))**(0.5)
|
|
139
|
+
|
|
140
|
+
def assign_internal_scattering_time(self, material):
|
|
141
|
+
"""
|
|
142
|
+
Determine relaxation time after which this particle will undergo internal scattering.
|
|
143
|
+
Considering only elasctic scattering between electrons and phonons and thus a constant MFP.
|
|
144
|
+
With ELECTRON_MFP_ENERGY_EXPONENT != 0 the MFP becomes energy-dependent
|
|
145
|
+
(see electron_mfp_at_energy); scattering stays elastic so the electron's energy,
|
|
146
|
+
and hence its MFP, is fixed for the whole flight.
|
|
147
|
+
"""
|
|
148
|
+
mfp = electron_mfp(self.energy, material)
|
|
149
|
+
self.time_of_internal_scattering = -np.log(random()) * (mfp / self.speed)
|
|
@@ -3,21 +3,23 @@
|
|
|
3
3
|
from math import cos
|
|
4
4
|
import numpy as np
|
|
5
5
|
|
|
6
|
+
from freepaths.config import cf
|
|
7
|
+
|
|
6
8
|
|
|
7
9
|
class Path:
|
|
8
10
|
"""Particle path coordinates"""
|
|
9
11
|
|
|
10
12
|
def __init__(self, x_init, y_init, z_init):
|
|
11
13
|
"""Initialize a path"""
|
|
12
|
-
self.x =
|
|
13
|
-
self.y =
|
|
14
|
-
self.z =
|
|
14
|
+
self.x = [x_init]
|
|
15
|
+
self.y = [y_init]
|
|
16
|
+
self.z = [z_init]
|
|
15
17
|
|
|
16
18
|
def add_point(self, x_new, y_new, z_new):
|
|
17
19
|
"""Add a point to the particle path"""
|
|
18
|
-
self.x
|
|
19
|
-
self.y
|
|
20
|
-
self.z
|
|
20
|
+
self.x.append(x_new)
|
|
21
|
+
self.y.append(y_new)
|
|
22
|
+
self.z.append(z_new)
|
|
21
23
|
|
|
22
24
|
@property
|
|
23
25
|
def number_of_path_points(self):
|
|
@@ -36,21 +38,30 @@ class Flight:
|
|
|
36
38
|
self.initial_theta = self.particle.theta
|
|
37
39
|
self.path = Path(self.particle.x, self.particle.y, self.particle.z)
|
|
38
40
|
self.exit_theta = 0.0
|
|
41
|
+
self.exit_frequency = 0.0
|
|
39
42
|
self.free_path = 0.0
|
|
40
43
|
self.travel_time = 0.0
|
|
41
44
|
self.time_since_previous_scattering = 0.0
|
|
42
45
|
self.free_paths = []
|
|
46
|
+
self._mfp_sum = 0.0
|
|
47
|
+
self._mfp_count = 0
|
|
43
48
|
self.hole_diff_scattering_angles = []
|
|
44
49
|
self.hole_spec_scattering_angles = []
|
|
45
50
|
self.thermal_conductivity = 0.0
|
|
46
51
|
@property
|
|
47
52
|
def mean_free_path(self):
|
|
48
53
|
"""Mean value of all free flights"""
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
return self._mfp_sum / self._mfp_count if self._mfp_count else 0
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def mean_free_path_sem(self):
|
|
58
|
+
"""Standard error of the mean free path (MFP sampling mode only). Assumes
|
|
59
|
+
individual free-path segments are exponentially distributed (the natural
|
|
60
|
+
assumption for Poisson-process intrinsic scattering: coefficient of
|
|
61
|
+
variation = 1, i.e. std = mean), so SEM = mean_free_path / sqrt(n)."""
|
|
62
|
+
if self._mfp_count < 1:
|
|
63
|
+
return 0.0
|
|
64
|
+
return self.mean_free_path / (self._mfp_count ** 0.5)
|
|
54
65
|
|
|
55
66
|
def add_point_to_path(self):
|
|
56
67
|
"""Add a scattering point to the path"""
|
|
@@ -58,7 +69,10 @@ class Flight:
|
|
|
58
69
|
|
|
59
70
|
def save_free_paths(self):
|
|
60
71
|
"""Save current free path to the list of free paths"""
|
|
61
|
-
self.
|
|
72
|
+
self._mfp_sum += self.free_path
|
|
73
|
+
self._mfp_count += 1
|
|
74
|
+
if not cf.low_memory_usage:
|
|
75
|
+
self.free_paths.append(self.free_path)
|
|
62
76
|
|
|
63
77
|
def save_hole_diff_scattering_angle(self, angle):
|
|
64
78
|
"""Save angle of diffuse scattering from the hole"""
|
|
@@ -73,10 +87,15 @@ class Flight:
|
|
|
73
87
|
self.time_since_previous_scattering = 0.0
|
|
74
88
|
self.free_path = 0.0
|
|
75
89
|
|
|
76
|
-
def finish(self, step, timestep):
|
|
77
|
-
"""Finish the flight and record final state
|
|
90
|
+
def finish(self, step, timestep, reached_cold_side=False):
|
|
91
|
+
"""Finish the flight and record final state. travel_time is only set when
|
|
92
|
+
the particle genuinely reached the cold side - it means "time to cold side",
|
|
93
|
+
so leaving it at 0 for other exits (e.g. the MFP-sampling scattering-event
|
|
94
|
+
cap) keeps that meaning intact instead of recording a meaningless duration."""
|
|
78
95
|
self.exit_theta = self.particle.theta
|
|
79
|
-
self.
|
|
96
|
+
self.exit_frequency = self.particle.f
|
|
97
|
+
if reached_cold_side:
|
|
98
|
+
self.travel_time = step * timestep
|
|
80
99
|
|
|
81
100
|
def add_step(self, timestep):
|
|
82
101
|
"""Increase parameters of the flight by length of one step"""
|
|
@@ -99,6 +99,7 @@ def _run_branch(branch_number, shared_list, shared_progress):
|
|
|
99
99
|
scatter_maps = ScatteringMap()
|
|
100
100
|
|
|
101
101
|
total_thermal_conductivity = 0.0
|
|
102
|
+
total_kappa_variance = 0.0
|
|
102
103
|
last_pct = -1
|
|
103
104
|
|
|
104
105
|
for index in range(cf.number_of_particles):
|
|
@@ -112,9 +113,9 @@ def _run_branch(branch_number, shared_list, shared_progress):
|
|
|
112
113
|
d_k_vector = material.dispersion[index + 1, 0] - material.dispersion[index, 0]
|
|
113
114
|
|
|
114
115
|
# Initialise phonon at this k-point and run it through the geometry:
|
|
115
|
-
phonon = Phonon(material, branch_number, index)
|
|
116
|
+
phonon = Phonon(material, SimulationMode.PHONON_MFP_SAMPLING, branch_number, index)
|
|
116
117
|
flight = Flight(phonon)
|
|
117
|
-
run_particle(phonon, flight, scatter_stats, places_stats, segment_stats, None, scatter_maps, material)
|
|
118
|
+
run_particle(phonon, flight, scatter_stats, places_stats, segment_stats, None, scatter_maps, material, SimulationMode.PHONON_MFP_SAMPLING)
|
|
118
119
|
|
|
119
120
|
# Mode heat capacity (Ref. PRB 88 155318, 2013):
|
|
120
121
|
omega = 2 * math.pi * phonon.f
|
|
@@ -126,6 +127,12 @@ def _run_branch(branch_number, shared_list, shared_progress):
|
|
|
126
127
|
flight.thermal_conductivity = (1 / (6 * math.pi**2)) * c_p * phonon.speed**2 * mean_relax_time * k_vector**2 * d_k_vector
|
|
127
128
|
total_thermal_conductivity += flight.thermal_conductivity
|
|
128
129
|
|
|
130
|
+
# This mode's kappa is linear in its mean free path, so its variance
|
|
131
|
+
# propagates directly through the same proportionality (kappa_i / mfp_i):
|
|
132
|
+
if flight.mean_free_path > 0:
|
|
133
|
+
kappa_relative_sem = flight.mean_free_path_sem / flight.mean_free_path
|
|
134
|
+
total_kappa_variance += (flight.thermal_conductivity * kappa_relative_sem) ** 2
|
|
135
|
+
|
|
129
136
|
general_stats.save_particle_data(phonon, SimulationMode.PHONON_MFP_SAMPLING)
|
|
130
137
|
general_stats.save_flight_data(flight, SimulationMode.PHONON_MFP_SAMPLING)
|
|
131
138
|
|
|
@@ -135,6 +142,7 @@ def _run_branch(branch_number, shared_list, shared_progress):
|
|
|
135
142
|
# Push this branch's results to the shared list for the main process to collect:
|
|
136
143
|
shared_list.append({
|
|
137
144
|
'total_thermal_conductivity': total_thermal_conductivity,
|
|
145
|
+
'total_kappa_variance': total_kappa_variance,
|
|
138
146
|
'scatter_stats': scatter_stats.dump_data(),
|
|
139
147
|
'general_stats': general_stats.dump_data(),
|
|
140
148
|
'segment_stats': segment_stats.dump_data(),
|
|
@@ -144,6 +152,21 @@ def _run_branch(branch_number, shared_list, shared_progress):
|
|
|
144
152
|
})
|
|
145
153
|
|
|
146
154
|
|
|
155
|
+
def calculate_porosity(grid_points=300):
|
|
156
|
+
"""Calculate the porosity (hole area fraction) of the structure by sampling
|
|
157
|
+
the hole shapes on a regular grid. Holes partially outside the simulation
|
|
158
|
+
domain are accounted for with their inside part only."""
|
|
159
|
+
if not cf.holes:
|
|
160
|
+
return 0.0
|
|
161
|
+
xs = np.linspace(-cf.width / 2, cf.width / 2, grid_points)
|
|
162
|
+
ys = np.linspace(0, cf.length, grid_points)
|
|
163
|
+
inside = 0
|
|
164
|
+
for x in xs:
|
|
165
|
+
for y in ys:
|
|
166
|
+
inside += any(hole.is_inside(x, y, None, cf) for hole in cf.holes)
|
|
167
|
+
return inside / (grid_points * grid_points)
|
|
168
|
+
|
|
169
|
+
|
|
147
170
|
def main(input_file, mode: SimulationMode):
|
|
148
171
|
"""Integrate the phonon dispersion over all three branches in parallel to get
|
|
149
172
|
the bulk thermal conductivity via the MFP-sampling method."""
|
|
@@ -151,7 +174,7 @@ def main(input_file, mode: SimulationMode):
|
|
|
151
174
|
print(f'Mean free path sampling of {Fore.GREEN}{cf.output_folder_name}{Style.RESET_ALL}')
|
|
152
175
|
start_time = time.time()
|
|
153
176
|
|
|
154
|
-
branch_names = get_media_class(cf.media).
|
|
177
|
+
branch_names = get_media_class(cf.media).dispersion_branch_names
|
|
155
178
|
n_branches = len(branch_names)
|
|
156
179
|
|
|
157
180
|
# Spawn one worker process per polarization branch, all running concurrently:
|
|
@@ -189,9 +212,11 @@ def main(input_file, mode: SimulationMode):
|
|
|
189
212
|
path_stats = PathData()
|
|
190
213
|
scatter_maps = ScatteringMap()
|
|
191
214
|
total_thermal_conductivity = 0.0
|
|
215
|
+
total_kappa_variance = 0.0
|
|
192
216
|
|
|
193
217
|
for result in shared_list:
|
|
194
218
|
total_thermal_conductivity += result['total_thermal_conductivity']
|
|
219
|
+
total_kappa_variance += result['total_kappa_variance']
|
|
195
220
|
scatter_stats.read_data(result['scatter_stats'])
|
|
196
221
|
general_stats.read_data(result['general_stats'])
|
|
197
222
|
segment_stats.read_data(result['segment_stats'])
|
|
@@ -218,14 +243,39 @@ def main(input_file, mode: SimulationMode):
|
|
|
218
243
|
if cf.output_path_animation:
|
|
219
244
|
create_animation()
|
|
220
245
|
|
|
221
|
-
|
|
246
|
+
# The MFP-sampling integral uses the bulk DoS with boundary-limited free paths,
|
|
247
|
+
# so total_thermal_conductivity is the conductivity of the solid material
|
|
248
|
+
# (MFP suppression only, no volume removed by the holes). To obtain the
|
|
249
|
+
# effective conductivity of the porous structure, it is scaled by the Eucken
|
|
250
|
+
# factor F = (1 - phi) / (1 + phi/2), where phi is the porosity:
|
|
251
|
+
porosity = calculate_porosity()
|
|
252
|
+
eucken_factor = (1 - porosity) / (1 + porosity / 2)
|
|
253
|
+
effective_thermal_conductivity = total_thermal_conductivity * eucken_factor
|
|
254
|
+
|
|
255
|
+
header = "K_material [W/mK], Porosity, Eucken factor, K_effective [W/mK]"
|
|
256
|
+
np.savetxt("Data/Thermal conductivity from MFP.csv",
|
|
257
|
+
np.array([[total_thermal_conductivity, porosity, eucken_factor, effective_thermal_conductivity]]),
|
|
258
|
+
fmt='%2.4e', delimiter=',', header=header, encoding='utf-8')
|
|
259
|
+
|
|
260
|
+
# Kept in its own file rather than appended to "Thermal conductivity from MFP.csv",
|
|
261
|
+
# since several plotting scripts index into that file positionally (incl. by
|
|
262
|
+
# [-1] for the last column), which a trailing column would silently break:
|
|
263
|
+
kappa_sem = total_kappa_variance ** 0.5
|
|
264
|
+
effective_kappa_sem = kappa_sem * eucken_factor
|
|
265
|
+
np.savetxt("Data/Thermal conductivity from MFP uncertainty.csv",
|
|
266
|
+
np.array([[kappa_sem, effective_kappa_sem]]),
|
|
267
|
+
fmt='%2.4e', delimiter=',',
|
|
268
|
+
header="K_material SEM [W/mK], K_effective SEM [W/mK]", encoding='utf-8')
|
|
222
269
|
|
|
223
270
|
sys.stdout.write("\rAnalyzing the data...")
|
|
224
271
|
plot_data(mode, cf)
|
|
225
272
|
|
|
226
|
-
output_general_information(start_time)
|
|
273
|
+
output_general_information(start_time, general_stats, mode)
|
|
227
274
|
output_scattering_information(scatter_stats)
|
|
228
|
-
output_parameter_warnings(mode)
|
|
275
|
+
output_parameter_warnings(mode, general_stats)
|
|
229
276
|
sys.stdout.write(f'\rSee the results in {Fore.GREEN}Results/{cf.output_folder_name}{Style.RESET_ALL}\n')
|
|
230
|
-
sys.stdout.write(f"\
|
|
277
|
+
sys.stdout.write(f"\rMaterial thermal conductivity = {Fore.GREEN}{total_thermal_conductivity:.5f} ± {kappa_sem:.5f}{Style.RESET_ALL} W/m·K\n")
|
|
278
|
+
if cf.holes:
|
|
279
|
+
sys.stdout.write(f"\rEffective thermal conductivity = {Fore.GREEN}{effective_thermal_conductivity:.5f} ± {effective_kappa_sem:.5f}{Style.RESET_ALL} W/m·K "
|
|
280
|
+
f"(porosity {porosity:.1%}, Eucken factor {eucken_factor:.3f})\n")
|
|
231
281
|
sys.stdout.write(f"\r{Fore.BLUE}Thank you for using FreePATHS{Style.RESET_ALL}\n\n")
|