def dynamicPressure(lat, lon, height, jd, velocity, gamma=1.0): """ Calculate dynamic pressure at the given point on meteor's trajectory. Either a single set of values can be given (i.e. every argument is a float number), or all arguments must be numpy arrays. Arguments: lat: [float] Latitude of the meteor (radians). lon: [float] Longitude of the meteor (radians). height: [float] Height of the meteor (meters). jd: [float] Julian date of the meteor. velocity: [float] Velocity of the meteor (m/s). Keyword arguments: gamma: [flot] Drag coefficient. 1 by defualt. Return: dyn_pressure: [float] Dynamic pressure in Pascals. """ # Get the atmospheric densities at every heights atm_dens = getAtmDensity_vect(lat, lon, height, jd) # Calculate the dynamic pressure dyn_pressure = atm_dens * gamma * velocity**2 return dyn_pressure
def dynamicMass(bulk_density, lat, lon, height, jd, velocity, decel, gamma=1.0, shape_factor=1.21): """ Calculate dynamic mass at the given point on meteor's trajectory. Either a single set of values can be given (i.e. every argument is a float number), or all arguments must be numpy arrays. Arguments: bulk_density: [float] Bulk density of the meteoroid in kg/m^3. lat: [float] Latitude of the meteor (radians). lon: [flaot] Longitude of the meteor (radians). height: [float] Height of the meteor (meters). jd: [float] Julian date of the meteor. velocity: [float] Velocity of the meteor (m/s). decel: [float] Deceleration in m/s^2. Keyword arguments: gamma: [flot] Drag coefficient. 1 by defualt. shape_factor: [float] Shape factory for the body. 1.21 (sphere) by default. Other values: - sphere = 1.21 - hemisphere = 1.92 - cube = 1.0 - brick 2:3:5 = 1.55 Return: dyn_mass: [float] Dynamic mass in kg. """ # Calculate the atmosphere density at the given point atm_dens = getAtmDensity_vect(lat, lon, height, jd) # Calculate the dynamic mass dyn_mass = (1.0 / (bulk_density**2)) * ((gamma * shape_factor * (velocity**2) * atm_dens) / decel)**3 return dyn_mass
def rescaleHeightToExponentialAtmosphere(lat, lon, ht_data, jd): """ Given observed heights, rescale them from the real NRLMSISE model to the a simplified exponential atmosphere model used by the Alpha-Beta procedure. Arguments: lat: [ndarray] Latitude in radians. lon: [ndarray] Longitude in radians. ht_data: [ndarray] Height in meters. jd: [float] Julian date. Return: rescaled_ht_data """ def _expAtmosphere(ht_data, rho_atm_0=1.0): """ Compute the atmosphere mass density using a simple exponential model and a scale height. Arguments: ht_data: [ndarray] Height in meters. Keyword arguments: rho_atm_0: [float] Sea-level atmospheric air density in kg/m^3. Return: [float] Atmospheric mass density in kg/m^3. """ return rho_atm_0 * (1 / np.e**(ht_data / HT_NORM_CONST)) def _expAtmosphereHeight(air_density, rho_atm_0=1.225): """ Compute the height given the air density and exponential atmosphere assumption. Arguments: air_density: [float] Air density in kg/m^3. Keyword arguments: rho_atm_0: [float] Sea-level atmospheric air density in kg/m^3. Return: [float] Height in meters. """ return HT_NORM_CONST * np.log(rho_atm_0 / air_density) # Get the atmosphere mass density from the NRLMSISE model for the observed heights atm_dens = getAtmDensity_vect(lat, lon, ht_data, jd) # Get the equivalent heights using the exponential atmosphre model ht_rescaled = _expAtmosphereHeight(atm_dens) # # Compare the models # plt.semilogy(ht_data/1000, atm_dens, label='NRLMSISE') # plt.semilogy(ht_data/1000, _expAtmosphere(ht_data), label='Exp') # plt.xlabel("Height (km)") # plt.ylabel("log air density kg/m3") # plt.legend() # plt.show() # # Compare the heights before and after rescaling # plt.scatter(ht_data/1000, ht_data - ht_rescaled) # plt.xlabel("Height (km)") # plt.ylabel("Height difference (m)") # plt.show() # sys.exit() return ht_rescaled
def projectNarrowPicks(dir_path, met, traj, traj_uncert, metal_mags, frag_info): """ Projects picks done in the narrow-field to the given trajectory. """ # Adjust initial velocity frag_v_init = traj.v_init + frag_info.v_init_adjust # List for computed values to be stored in a file computed_values = [] # Generate the file name prefix from the time (take from trajectory) file_name_prefix = traj.file_name # List that holds datetimes of fragmentations, used for the light curve plot fragmentations_datetime = [] # Go through picks from all sites for site_no in met.picks: # Extract site exact plate exact = met.exact_plates[site_no] # Extract site picks picks = np.array(met.picks[site_no]) # Skip the site if there are no picks if not len(picks): continue print() print('Processing site:', site_no) # Find unique fragments fragments = np.unique(picks[:, 1]) # If the fragmentation dictionary is empty, generate one if frag_info.frag_dict is None: frag_info.frag_dict = { float(i): i + 1 for i in range(len(fragments)) } # A list with results of finding the closest point on the trajectory cpa_list = [] # Go thorugh all fragments and calculate the coordinates of the closest points on the trajectory and # the line of sight for frag in fragments: # Take only those picks from current fragment frag_picks = picks[picks[:, 1] == frag] # Sort by frame frag_picks = frag_picks[np.argsort(frag_picks[:, 0])] # Extract Unix timestamp ts = frag_picks[:, 11] tu = frag_picks[:, 12] # Extract theta, phi theta = np.radians(frag_picks[:, 4]) phi = np.radians(frag_picks[:, 5]) # Calculate azimuth +E of N azim = (np.pi / 2.0 - phi) % (2 * np.pi) # Calculate elevation elev = np.pi / 2.0 - theta # Calculate Julian date from Unix timestamp jd_data = np.array([unixTime2JD(s, u) for s, u in zip(ts, tu)]) # Convert azim/elev to RA/Dec ra, dec = altAz2RADec_vect(azim, elev, jd_data, exact.lat, exact.lon) # Convert RA/Dec to ECI direction vector x_eci, y_eci, z_eci = raDec2ECI(ra, dec) # Convert station geocoords to ECEF coordinates x_stat_vect, y_stat_vect, z_stat_vect = geo2Cartesian_vect(exact.lat, exact.lon, exact.elev, \ jd_data) # Find closest points of aproach for all measurements for jd, x, y, z, x_stat, y_stat, z_stat in np.c_[jd_data, x_eci, y_eci, z_eci, x_stat_vect, \ y_stat_vect, z_stat_vect]: # Find the closest point of approach of every narrow LoS to the wide trajectory obs_cpa, rad_cpa, d = findClosestPoints(np.array([x_stat, y_stat, z_stat]), \ np.array([x, y, z]), traj.state_vect_mini, traj.radiant_eci_mini) # Calculate the height of each fragment for the given time rad_lat, rad_lon, height = cartesian2Geo(jd, *rad_cpa) cpa_list.append( [frag, jd, obs_cpa, rad_cpa, d, rad_lat, rad_lon, height]) # Find the coordinates of the first point in time on the trajectory and the first JD first_jd_indx = np.argmin([entry[1] for entry in cpa_list]) jd_ref = cpa_list[first_jd_indx][1] rad_cpa_ref = cpa_list[first_jd_indx][3] print(jd_ref) # Set the beginning time to the beginning of the widefield trajectory ref_beg_time = (traj.jdt_ref - jd_ref) * 86400 length_list = [] decel_list = [] # Go through all fragments and calculate the length from the reference point for frag in fragments: # Select only the data points of the current fragment cpa_data = [entry for entry in cpa_list if entry[0] == frag] # Lengths of the current fragment length_frag = [] # Go through all projected points on the trajectory for entry in cpa_data: jd = entry[1] rad_cpa = entry[3] rad_lat = entry[5] rad_lon = entry[6] height = entry[7] # Calculate the distance from the first point on the trajectory and the given point dist = vectMag(rad_cpa - rad_cpa_ref) # Calculate the time in seconds time_sec = (jd - jd_ref) * 24 * 3600 length_frag.append([time_sec, dist, rad_lat, rad_lon, height]) length_list.append( [frag, time_sec, dist, rad_lat, rad_lon, height]) ### Fit the deceleration model to the length ### ################################################################################################## length_frag = np.array(length_frag) # Extract JDs and lengths into individual arrays time_data, length_data, lat_data, lon_data, height_data = length_frag.T if frag_info.fit_full_exp_model: # Fit the full exp deceleration model # First guess of the lag parameters p0 = [ frag_v_init, 0, 0, traj.jacchia_fit[0], traj.jacchia_fit[1] ] # Length residuals function def _lenRes(params, time_data, length_data): return np.sum( (length_data - exponentialDeceleration(time_data, *params))**2) # Fit an exponential to the data res = scipy.optimize.basinhopping(_lenRes, p0, \ minimizer_kwargs={"method": "BFGS", 'args':(time_data, length_data)}, \ niter=1000) decel_fit = res.x else: # Fit only the deceleration parameters # First guess of the lag parameters p0 = [0, 0, traj.jacchia_fit[0], traj.jacchia_fit[1]] # Length residuals function def _lenRes(params, time_data, length_data, v_init): return np.sum((length_data - exponentialDeceleration( time_data, v_init, *params))**2) # Fit an exponential to the data res = scipy.optimize.basinhopping(_lenRes, p0, \ minimizer_kwargs={"method": "Nelder-Mead", 'args':(time_data, length_data, frag_v_init)}, \ niter=100) decel_fit = res.x # Add the velocity to the deceleration fit decel_fit = np.append(np.array([frag_v_init]), decel_fit) decel_list.append(decel_fit) print('---------------') print('Fragment', frag_info.frag_dict[frag], 'fit:') print(decel_fit) # plt.plot(time_data, length_data, label='Observed') # plt.plot(time_data, exponentialDeceleration(time_data, *decel_fit), label='fit') # plt.legend() # plt.xlabel('Time (s)') # plt.ylabel('Length (m)') # plt.title('Fragment {:d} fit'.format(frag_info.frag_dict[frag])) # plt.show() # # Plot the residuals # plt.plot(time_data, length_data - exponentialDeceleration(time_data, *decel_fit)) # plt.xlabel('Time (s)') # plt.ylabel('Length O - C (m)') # plt.title('Fragment {:d} fit residuals'.format(frag_info.frag_dict[frag])) # plt.show() ################################################################################################## # Generate a unique color for every fragment colors = plt.cm.rainbow(np.linspace(0, 1, len(fragments))) # Create a dictionary for every fragment-color pair colors_frags = {frag: color for frag, color in zip(fragments, colors)} # Make sure lags start at 0 offset_vel_max = 0 # Plot the positions of fragments from the beginning to the end # Calculate and plot the lag of all fragments for frag, decel_fit in zip(fragments, decel_list): # Select only the data points of the current fragment length_frag = [entry for entry in length_list if entry[0] == frag] # Find the last time of the fragment appearance last_time = max([entry[1] for entry in length_frag]) # Extract the observed data _, time_data, length_data, lat_data, lon_data, height_data = np.array( length_frag).T # Plot the positions of fragments from the first time to the end, using fitted parameters # The lag is calculated by subtracting an "average" velocity length from the observed length time_array = np.linspace(ref_beg_time, last_time, 1000) plt.plot(exponentialDeceleration(time_array, *decel_fit) - exponentialDeceleration(time_array, \ frag_v_init, 0, offset_vel_max, 0, 0), time_array, linestyle='--', color=colors_frags[frag], \ linewidth=0.75) # Plot the observed data fake_lag = length_data - exponentialDeceleration( time_data, frag_v_init, 0, offset_vel_max, 0, 0) plt.plot(fake_lag, time_data, color=colors_frags[frag], linewidth=0.75) # Plot the fragment number at the end of each lag plt.text(fake_lag[-1] - 10, time_data[-1] + 0.02, str(frag_info.frag_dict[frag]), color=colors_frags[frag], \ size=7, va='center', ha='right') # Check if the fragment has a fragmentation point and plot it if site_no in frag_info.fragmentation_points: if frag_info.frag_dict[frag] in frag_info.fragmentation_points[ site_no]: # Get the lag of the fragmentation point frag_point_time, fragments_list = frag_info.fragmentation_points[ site_no][frag_info.frag_dict[frag]] frag_point_lag = exponentialDeceleration(frag_point_time, *decel_fit) \ - exponentialDeceleration(frag_point_time, frag_v_init, 0, offset_vel_max, 0, 0) fragments_list = list(map(str, fragments_list)) # Save the fragmentation time in the list for light curve plot fragmentations_datetime.append([jd2Date(jd_ref + frag_point_time/86400, dt_obj=True), \ fragments_list]) # Plot the fragmentation point plt.scatter(frag_point_lag, frag_point_time, s=20, zorder=4, color=colors_frags[frag], \ edgecolor='k', linewidth=0.5, label='Fragmentation: ' + ",".join(fragments_list)) # Plot reference time plt.title('Reference time: ' + str(jd2Date(jd_ref, dt_obj=True))) plt.gca().invert_yaxis() plt.grid(color='0.9') plt.xlabel('Lag (m)') plt.ylabel('Time (s)') plt.ylim(ymax=ref_beg_time) plt.legend() plt.savefig(os.path.join(dir_path, file_name_prefix \ + '_fragments_deceleration_site_{:s}.png'.format(str(site_no))), dpi=300) plt.show() time_min = np.inf time_max = -np.inf ht_min = np.inf ht_max = -np.inf ### PLOT DYNAMIC PRESSURE FOR EVERY FRAGMENT for frag, decel_fit in zip(fragments, decel_list): # Select only the data points of the current fragment length_frag = [entry for entry in length_list if entry[0] == frag] # Extract the observed data _, time_data, length_data, lat_data, lon_data, height_data = np.array( length_frag).T # Fit a linear dependance of time vs. height line_fit, _ = scipy.optimize.curve_fit(lineFunc, time_data, height_data) # Get the time and height limits time_min = min(time_min, min(time_data)) time_max = max(time_max, max(time_data)) ht_min = min(ht_min, min(height_data)) ht_max = max(ht_max, max(height_data)) ### CALCULATE OBSERVED DYN PRESSURE # Get the velocity at every point in time velocities = exponentialDecelerationVel(time_data, *decel_fit) # Calculate the dynamic pressure dyn_pressure = dynamicPressure(lat_data, lon_data, height_data, jd_ref, velocities) ### # Plot Observed height vs. dynamic pressure plt.plot(dyn_pressure / 10**3, height_data / 1000, color=colors_frags[frag], zorder=3, linewidth=0.75) # Plot the fragment number at the end of each lag plt.text(dyn_pressure[-1]/10**3, height_data[-1]/1000 - 0.02, str(frag_info.frag_dict[frag]), \ color=colors_frags[frag], size=7, va='top', zorder=3) ### CALCULATE MODELLED DYN PRESSURE time_array = np.linspace(ref_beg_time, max(time_data), 1000) # Calculate the modelled height height_array = lineFunc(time_array, *line_fit) # Get the time and height limits time_min = min(time_min, min(time_array)) time_max = max(time_max, max(time_array)) ht_min = min(ht_min, min(height_array)) ht_max = max(ht_max, max(height_array)) # Get the atmospheric densities at every heights atm_dens_model = getAtmDensity_vect(np.zeros_like(time_array) + np.mean(lat_data), \ np.zeros_like(time_array) + np.mean(lon_data), height_array, jd_ref) # Get the velocity at every point in time velocities_model = exponentialDecelerationVel( time_array, *decel_fit) # Calculate the dynamic pressure dyn_pressure_model = atm_dens_model * DRAG_COEFF * velocities_model**2 ### # Plot Modelled height vs. dynamic pressure plt.plot(dyn_pressure_model/10**3, height_array/1000, color=colors_frags[frag], zorder=3, \ linewidth=0.75, linestyle='--') # Check if the fragment has a fragmentation point and plot it if site_no in frag_info.fragmentation_points: if frag_info.frag_dict[frag] in frag_info.fragmentation_points[ site_no]: # Get the lag of the fragmentation point frag_point_time, fragments_list = frag_info.fragmentation_points[ site_no][frag_info.frag_dict[frag]] # Get the fragmentation height frag_point_height = lineFunc(frag_point_time, *line_fit) # Calculate the velocity at fragmentation frag_point_velocity = exponentialDecelerationVel( frag_point_time, *decel_fit) # Calculate the atm. density at the fragmentation point frag_point_atm_dens = getAtmDensity(np.mean(lat_data), np.mean(lon_data), frag_point_height, \ jd_ref) # Calculate the dynamic pressure at fragmentation in kPa frag_point_dyn_pressure = frag_point_atm_dens * DRAG_COEFF * frag_point_velocity**2 frag_point_dyn_pressure /= 10**3 # Compute height in km frag_point_height_km = frag_point_height / 1000 fragments_list = map(str, fragments_list) # Plot the fragmentation point plt.scatter(frag_point_dyn_pressure, frag_point_height_km, s=20, zorder=5, \ color=colors_frags[frag], edgecolor='k', linewidth=0.5, \ label='Fragmentation: ' + ",".join(fragments_list)) ### Plot the errorbar # Compute the lower veloicty estimate stddev_multiplier = 2.0 # Check if the uncertainty exists if traj_uncert.v_init is None: v_init_uncert = 0 else: v_init_uncert = traj_uncert.v_init # Compute the range of velocities lower_vel = frag_point_velocity - stddev_multiplier * v_init_uncert higher_vel = frag_point_velocity + stddev_multiplier * v_init_uncert # Assume the atmosphere density can vary +/- 25% (Gunther's analysis) lower_atm_dens = 0.75 * frag_point_atm_dens higher_atm_dens = 1.25 * frag_point_atm_dens # Compute lower and higher range for dyn pressure in kPa lower_frag_point_dyn_pressure = ( lower_atm_dens * DRAG_COEFF * lower_vel**2) / 10**3 higher_frag_point_dyn_pressure = ( higher_atm_dens * DRAG_COEFF * higher_vel**2) / 10**3 # Compute errors lower_error = abs(frag_point_dyn_pressure - lower_frag_point_dyn_pressure) higher_error = abs(frag_point_dyn_pressure - higher_frag_point_dyn_pressure) print(frag_point_dyn_pressure, frag_point_height_km, [ lower_frag_point_dyn_pressure, higher_frag_point_dyn_pressure ]) # Plot the errorbar plt.errorbar(frag_point_dyn_pressure, frag_point_height_km, \ xerr=[[lower_error], [higher_error]], fmt='--', capsize=5, zorder=4, \ color=colors_frags[frag], label='+/- 25% $\\rho_{atm}$, 2$\\sigma_v$ ') # Save the computed fragmentation values to list # Site, Reference JD, Relative time, Fragment ID, Height, Dyn pressure, Dyn pressure lower \ # bound, Dyn pressure upper bound computed_values.append([site_no, jd_ref, frag_point_time, frag_info.frag_dict[frag], \ frag_point_height_km, frag_point_dyn_pressure, lower_frag_point_dyn_pressure, \ higher_frag_point_dyn_pressure]) ###### # Plot reference time plt.title('Reference time: ' + str(jd2Date(jd_ref, dt_obj=True))) plt.xlabel('Dynamic pressure (kPa)') plt.ylabel('Height (km)') plt.ylim([ht_min / 1000, ht_max / 1000]) # Remove repeating labels and plot the legend handles, labels = plt.gca().get_legend_handles_labels() by_label = OrderedDict(zip(labels, handles)) plt.legend(by_label.values(), by_label.keys()) plt.grid(color='0.9') # Create the label for seconds ax2 = plt.gca().twinx() ax2.set_ylim([time_max, time_min]) ax2.set_ylabel('Time (s)') plt.savefig(os.path.join(dir_path, file_name_prefix \ + '_fragments_dyn_pressures_site_{:s}.png'.format(str(site_no))), dpi=300) plt.show() ### PLOT DYNAMICS MASSES FOR ALL FRAGMENTS for frag, decel_fit in zip(fragments, decel_list): # Select only the data points of the current fragment length_frag = [entry for entry in length_list if entry[0] == frag] # Extract the observed data _, time_data, length_data, lat_data, lon_data, height_data = np.array( length_frag).T # Fit a linear dependance of time vs. height line_fit, _ = scipy.optimize.curve_fit(lineFunc, time_data, height_data) ### CALCULATE OBSERVED DYN MASS # Get the velocity at every point in time velocities = exponentialDecelerationVel(time_data, *decel_fit) decelerations = np.abs( exponentialDecelerationDecel(time_data, *decel_fit)) # Calculate the dynamic mass dyn_mass = dynamicMass(frag_info.bulk_density, lat_data, lon_data, height_data, jd_ref, \ velocities, decelerations) ### # Plot Observed height vs. dynamic pressure plt.plot(dyn_mass * 1000, height_data / 1000, color=colors_frags[frag], zorder=3, linewidth=0.75) # Plot the fragment number at the end of each lag plt.text(dyn_mass[-1]*1000, height_data[-1]/1000 - 0.02, str(frag_info.frag_dict[frag]), \ color=colors_frags[frag], size=7, va='top', zorder=3) ### CALCULATE MODELLED DYN MASS time_array = np.linspace(ref_beg_time, max(time_data), 1000) # Calculate the modelled height height_array = lineFunc(time_array, *line_fit) # Get the velocity at every point in time velocities_model = exponentialDecelerationVel( time_array, *decel_fit) # Get the deceleration decelerations_model = np.abs( exponentialDecelerationDecel(time_array, *decel_fit)) # Calculate the modelled dynamic mass dyn_mass_model = dynamicMass(frag_info.bulk_density, np.zeros_like(time_array) + np.mean(lat_data), np.zeros_like(time_array) + np.mean(lon_data), height_array, jd_ref, \ velocities_model, decelerations_model) ### # Plot Modelled height vs. dynamic mass plt.plot(dyn_mass_model*1000, height_array/1000, color=colors_frags[frag], zorder=3, \ linewidth=0.75, linestyle='--', \ label='Frag {:d} initial dyn mass = {:.1e} g'.format(frag_info.frag_dict[frag], \ 1000*dyn_mass_model[0])) # Plot reference time plt.title('Reference time: ' + str(jd2Date(jd_ref, dt_obj=True)) \ + ', $\\rho_m = ${:d} $kg/m^3$'.format(frag_info.bulk_density)) plt.xlabel('Dynamic mass (g)') plt.ylabel('Height (km)') plt.ylim([ht_min / 1000, ht_max / 1000]) # Remove repeating labels and plot the legend handles, labels = plt.gca().get_legend_handles_labels() by_label = OrderedDict(zip(labels, handles)) plt.legend(by_label.values(), by_label.keys()) plt.grid(color='0.9') # Create the label for seconds ax2 = plt.gca().twinx() ax2.set_ylim([time_max, time_min]) ax2.set_ylabel('Time (s)') plt.savefig(os.path.join(dir_path, file_name_prefix \ + '_fragments_dyn_mass_site_{:s}.png'.format(str(site_no))), dpi=300) plt.show() # Plot the light curve if the METAL .met file was given if (metal_mags is not None): # Make sure there are lightcurves in the data if len(metal_mags): lc_min = np.inf lc_max = -np.inf # Plot the lightcurves for site_entry in metal_mags: site_id, time, mags = site_entry # Track the minimum and maximum magnitude lc_min = np.min([lc_min, np.min(mags)]) lc_max = np.max([lc_max, np.max(mags)]) plt.plot(time, mags, marker='+', label='Site: ' + str(site_id), zorder=4, linewidth=1) # Plot times of fragmentation for frag_dt, fragments_list in fragmentations_datetime: # Plot the lines of fragmentation y_arr = np.linspace(lc_min, lc_max, 10) x_arr = [frag_dt] * len(y_arr) plt.plot(x_arr, y_arr, linestyle='--', zorder=4, \ label='Fragmentation: ' + ",".join(fragments_list)) plt.xlabel('Time (UTC)') plt.ylabel('Absolute magnitude (@100km)') plt.grid() plt.gca().invert_yaxis() plt.legend() ### Format the X axis datetimes import matplotlib def formatDT(x, pos=None): x = matplotlib.dates.num2date(x) # Add date to the first tick if pos == 0: fmt = '%D %H:%M:%S.%f' else: fmt = '%H:%M:%S.%f' label = x.strftime(fmt)[:-3] label = label.rstrip("0") label = label.rstrip(".") return label from matplotlib.ticker import FuncFormatter plt.gca().xaxis.set_major_formatter(FuncFormatter(formatDT)) plt.gca().xaxis.set_minor_formatter(FuncFormatter(formatDT)) ### plt.tight_layout() # Save the figure plt.savefig(os.path.join(dir_path, file_name_prefix + '_fragments_light_curve_comparison.png'), \ dpi=300) plt.show() # Save the computed values to file with open( os.path.join(dir_path, file_name_prefix + "_fragments_dyn_pressure_info.txt"), 'w') as f: # Site, Reference JD, Relative time, Fragment ID, Height, Dyn pressure, Dyn pressure lower \ # bound, Dyn pressure upper bound # Write the header f.write( "# Site, Ref JD, Rel time, Frag ID, Ht (km), DP (kPa), DP low, DP high\n" ) # Write computed values for every fragment for entry in computed_values: f.write( " {:>5s}, {:20.12f}, {:+8.6f}, {:7d}, {:7.3f}, {:9.2f}, {:8.2f}, {:8.2f}\n" .format(*entry))