def makePropLine(ref_pos, D, alpha=255): lats = [] lons = [] for line in D: temp = Position(0, 0, 0) temp.x = line[0] temp.y = line[1] temp.z = line[2] temp.pos_geo(ref_pos) if not np.isnan(temp.lat) and not np.isnan(temp.lon): lats.append(temp.lat) lons.append(temp.lon) lats.sort() lons.sort() return lats, lons
def makePropLine(self, D, alpha=255): ref_pos = Position(self.bam.setup.lat_centre, self.bam.setup.lon_centre, 0) lats = [] lons = [] for line in D: temp = Position(0, 0, 0) temp.x = line[0] temp.y = line[1] temp.z = line[2] temp.pos_geo(ref_pos) if not np.isnan(temp.lat) and not np.isnan(temp.lon): lats.append(temp.lat) lons.append(temp.lon) lats.sort() lons.sort() start_pt = pg.PlotCurveItem() start_pt.setData(x=lons, y=lats) start_pt.setPen((255, 85, 0, alpha)) return start_pt
def psoSearch(stns, w, s_name, bam, prefs, ref_pos, manual=False, pert_num=0, override_supra=[], theo=False): """ Optimizes the paths between the detector stations and a supracenter to find the best fit for the position of the supracenter, within the given search area. The supracenter is found with an initial guess, in a given grid, and is moved closer to points of better fit through particle swarm optimization. Produces a graph with the stations and residual results, and prints out the optimal supracenter location """ print('Data converted. Searching...') setup = bam.setup atmos = bam.atmos search_min = Position(setup.lat_min, setup.lon_min, setup.elev_min) search_max = Position(setup.lat_max, setup.lon_max, setup.elev_max) if not manual: try: search_min.pos_loc(ref_pos) search_max.pos_loc(ref_pos) except (AttributeError, TypeError) as e: errorMessage('Search min and search max have not been defined! Aborting search!', 2, info='Please define a search area in the "Sources" tab on the left side of the screen!', detail='{:}'.format(e)) return None output_name = prefs.workdir if not isinstance(override_supra, list): single_point = override_supra else: single_point = setup.manual_fragmentation_search[0] if single_point.toList()[0] is None and manual: errorMessage('Manual Fragmentation Point undefined', 2, info='Unable to parse: Lat: {:} Lon: {:} Elev: {:} Time: {:}'.format(*single_point.toList())) return None ref_time = setup.fireball_datetime if setup.enable_restricted_time: kotc = setup.restricted_time else: kotc = None # check if user defined occurrence time is used if kotc != None: kotc = (kotc - ref_time).total_seconds() # number of stations total n_stations = len(stns) # Station Location xstn = stns[0:n_stations, 0:3] # Initialize arrays # Travel time to each station time3D = np.zeros(n_stations) # Initial azimuths angles of each station az = np.zeros(n_stations) # Initial takeoff angles of each station tf = np.zeros(n_stations) # difference in theoretical and simulated travel times sotc = np.zeros_like(n_stations) # Initialize variables # combined weights nwn = sum(w) if prefs.ballistic_en: try: v = -setup.trajectory.vector.xyz setup.ref_pos = setup.trajectory.pos_f if prefs.debug: print("Constraining Trajectory") except: v = [None] if prefs.debug: print("Free Search") else: v = [None] if prefs.debug: print("Free Search") # If automatic search if not manual: # Prevent search below stations if search_min.elev < max(xstn[:, 2]): # Must be just above the stations search_min.elev = max(xstn[:, 2]) + 0.0001 # Boundaries of search volume # [x, y, z] local coordinates # arguments to be passed to timeFunction() args = (stns, w, kotc, setup, ref_pos, atmos, prefs, v, pert_num, theo) # Particle Swarm Optimization # x_opt - optimal supracenter location # f_opt - optimal supracenter error # if setup.restrict_to_trajectory: # #cons = [lineConstraintx, lineConstrainty, lineConstraintz] # x_opt, f_opt = pso(timeFunction, lb, ub, f_ieqcons=lineConstraint, args=args, swarmsize=int(setup.swarmsize), maxiter=int(setup.maxiter), \ # phip=setup.phip, phig=setup.phig, debug=False, omega=setup.omega, minfunc=setup.minfunc, minstep=setup.minstep) # else: # # Restricted to trajectory # if v[0] != None: # x_opt_temp, f_opt, sup, errors = pso(timeFunction, search_min.xyz, search_max.xyz, \ # ieqcons=[trajConstraints, timeConstraints], args=args,\ # swarmsize=int(prefs.pso_swarm_size), maxiter=int(prefs.pso_max_iter), \ # phip=prefs.pso_phi_p, phig=prefs.pso_phi_g, debug=False, omega=prefs.pso_omega, \ # minfunc=prefs.pso_min_error, minstep=prefs.pso_min_step, processes=1, particle_output=True) x_opt_temp, f_opt, sup, errors = pso(timeFunction, search_min.xyz, search_max.xyz, \ args=args, swarmsize=int(prefs.pso_swarm_size), maxiter=int(prefs.pso_max_iter), \ phip=prefs.pso_phi_p, phig=prefs.pso_phi_g, debug=False, omega=prefs.pso_omega,\ minfunc=prefs.pso_min_error, minstep=prefs.pso_min_step, processes=1, particle_output=True) print('Done Searching') x_opt = Position(0, 0, 0) x_opt.x = x_opt_temp[0] x_opt.y = x_opt_temp[1] x_opt.z = x_opt_temp[2] x_opt.pos_geo(ref_pos) # If manual search else: single_point.position.pos_loc(ref_pos) x_opt = single_point.position sup = single_point.position.xyz errors=0 # Get results for current Supracenter time3D, az, tf, r, motc, sotc, trace = outputWeather(n_stations, x_opt, stns, setup, \ ref_pos, atmos, output_name, s_name, kotc, w, prefs, theo) for ii, element in enumerate(time3D): if np.isnan(element): w[ii] = 0 sotc[ii] = 0 # Find error for manual searches if manual: f_opt = np.dot(w, np.absolute(sotc - np.dot(w, sotc)/nwn))/nwn # x, y distance from Supracenter to each station horz_dist = np.zeros(n_stations) for i in range(n_stations): horz_dist[i] = np.sqrt((x_opt.x - xstn[i, 0])**2 + (x_opt.y - xstn[i, 1])**2)/1000 # Calculate and Set the Occurrence Time into HH:MM:SS time_diff = motc + ref_time.microsecond/1e6 + ref_time.second + ref_time.minute*60 + ref_time.hour*3600 try: otc = (datetime.datetime.min + datetime.timedelta(seconds=time_diff)).time() except (ValueError, OverflowError): print('STATUS: Unable to parse otc') otc = None try: #while max(errors) > setup.max_error/100: a = [] std_error = np.std(errors) lim = np.mean(errors) + 0*std_error for i in range(len(errors)): if errors[i] >= lim: a.append(i) errors = np.delete(errors, (a), axis=0) sup = np.delete(sup, (a), axis=0) except: print("WARNING: Unable to filter errors") class Results: def __init__(self): pass results = Results() results.r = r results.w = w results.x_opt = x_opt results.f_opt = f_opt results.sup = sup results.errors = errors results.horz_dist = horz_dist results.time3D = time3D results.az = az results.tf = tf results.motc = motc results.sotc = sotc results.kotc = kotc results.otc = otc results.trace = trace return results # # scatter plot(s) # min_search, max_search = scatterPlot(single_point, n_stations, xstn, s_name, r, x_opt, \ # reported_points, search, output_name, ref_pos, sup, errors, tweaks, dataset) # # residual plot # residPlot(x_opt, s_name, xstn, r, output_name, n_stations) # # output results # outputText(min_search, max_search, single_point, ref_time, otc, kotc, x_opt, f_opt, n_stations, tweaks, s_name, xstn, \ # r, w, az, tf, time3D, horz_dist, output_name, tstn)
def psoTrajectory(station_list, bam, prefs): point_on_traj = None ref_pos = Position(bam.setup.lat_centre, bam.setup.lon_centre, 0) # ref_pos = bam.setup.trajectory.pos_f if bam.setup.pos_min.isNone() or bam.setup.pos_max.isNone(): errorMessage( 'Search boundaries are not defined!', 2, info= 'Please define the minimum and maximum parameters in the "Sources" tab on the left side of the screen!' ) return None bam.setup.pos_min.pos_loc(ref_pos) bam.setup.pos_max.pos_loc(ref_pos) if point_on_traj is None: bounds = [ (bam.setup.pos_min.x, bam.setup.pos_max.x), # X0 (bam.setup.pos_min.y, bam.setup.pos_max.y), # Y0 (bam.setup.t_min, bam.setup.t_max), # t0 (bam.setup.v_min, bam.setup.v_max), # Velocity (m/s) (bam.setup.azimuth_min.deg, bam.setup.azimuth_max.deg), # Azimuth (bam.setup.zenith_min.deg, bam.setup.zenith_max.deg ) # Zenith angle ] else: bounds = [ (bam.setup.v_min, bam.setup.v_max), # Velocity (m/s) (bam.setup.azimuth_min.deg, bam.setup.azimuth_max.deg), # Azimuth (bam.setup.zenith_min.deg, bam.setup.zenith_max.deg ) # Zenith angle ] lower_bounds = [bound[0] for bound in bounds] upper_bounds = [bound[1] for bound in bounds] if prefs.debug: print('Free Search') import matplotlib.pyplot as plt plt.ion() fig, ax = plt.subplots(2, 3, sharey='row') ax[0, 0].set_ylabel("Total Error") ax[1, 0].set_ylabel("Total Error") ax[0, 0].set_xlabel("Latitude") ax[0, 1].set_xlabel("Longitude") ax[0, 2].set_xlabel("Time") ax[1, 0].set_xlabel("Velocity") ax[1, 1].set_xlabel("Azimuth") ax[1, 2].set_xlabel("Zenith") plot = [] for i in range(2): for j in range(3): plot.append(ax[i, j].scatter([], [])) x, fopt = pso(trajSearch, lower_bounds, upper_bounds, args=(station_list, ref_pos, bam, prefs, plot, ax, fig, point_on_traj), \ maxiter=prefs.pso_max_iter, swarmsize=prefs.pso_swarm_size, \ phip=prefs.pso_phi_p, phig=prefs.pso_phi_g, debug=False, omega=prefs.pso_omega, \ particle_output=False) # if point_on_traj is None: print('Results:') print('X: {:.4f}'.format(x[0])) print('Y: {:.4f}'.format(x[1])) print('Time: {:.4f}'.format(x[2])) print('Velocity: {:.4f}'.format(x[3])) print('Azimuth: {:.4f}'.format(x[4])) print('Zenith: {:.4f}'.format(x[5])) print('Adjusted Error: {:.4f}'.format(fopt)) # else: # print('Results:') # print('Velocity: {:.4f}'.format(x[0])) # print('Azimuth: {:.4f}'.format(x[1])) # print('Zenith: {:.4f}'.format(x[2])) # print('Adjusted Error: {:.4f}'.format(fopt)) # if point_on_traj is None: geo = Position(0, 0, 0) geo.x = x[0] geo.y = x[1] geo.z = 0 geo.pos_geo(ref_pos) print('Geometric Landing Point:') print(geo) stat_names = [] stat_pick = [] final_traj = Trajectory(x[2], x[3], zenith=Angle(x[5]), azimuth=Angle(x[4]), pos_f=geo) points = final_traj.findPoints(gridspace=100, min_p=17000, max_p=50000) for stn in station_list: stat_names.append("{:}-{:}".format(stn[1], stn[2])) t_nom, t_pert = timeOfArrival(np.array([stn[3], stn[4], stn[5]]), final_traj, bam, prefs, points, ref_loc=ref_pos) stat_pick.append(t_nom - stn[6]) return [x, fopt, geo, stat_names, stat_pick]
def trajSearch(params, station_list, ref_pos, bam, prefs, plot, ax, fig, point_on_traj): if point_on_traj is None: x0, y0, t0, v, azim, zangle = params pos_f = Position(0, 0, 0) pos_f.x = x0 pos_f.y = y0 pos_f.z = 0 pos_f.pos_geo(ref_pos) temp_traj = Trajectory(t0, v, zenith=Angle(zangle), azimuth=Angle(azim), pos_f=pos_f) else: v, azim, zangle = params temp_traj = Trajectory(point_on_traj.time, v, zenith=Angle(zangle), azimuth=Angle(azim), pos_i=point_on_traj.position) pos_f = temp_traj.pos_f temp_traj = Trajectory(point_on_traj.time, v, zenith=Angle(zangle), azimuth=Angle(azim), pos_f=pos_f) #points = temp_traj.findPoints(gridspace=1000, min_p=pos_f.elev, max_p=100000) points = temp_traj.trajInterp2(div=50, min_p=17000, max_p=60000, xyz=False) if prefs.debug: dif = points[1] - points[0] dis = (dif[0]**2 + dif[1]**2 + dif[2]**2)**0.5 tim = dis / 330 u = temp_traj.vector.xyz cost_value = 0 failed_stats = 0 error_list = [] N = len(station_list) for stn in station_list: t_theo, t_pert = timeOfArrival(np.array([stn[3], stn[4], stn[5]]), temp_traj, bam, prefs, points, ref_loc=ref_pos) t_obs = stn[6] if not np.isnan(t_theo): cost_value = ((1 + (t_theo - t_obs)**2)**0.5 - 1) error_list.append(cost_value) else: failed_stats += 1 # if np.isnan(t_theo): # if prefs.debug: # print(np.inf) # return np.inf perc_fail = 100 - failed_stats / len(station_list) * 100 # temporary adjustment to try and get the most stations if N - failed_stats >= 3: total_error = sum(error_list) / ( N - failed_stats) # + 2*max(error_list)*(failed_stats) else: total_error = np.inf if prefs.debug: print( "Error {:10.4f} | Failed Stats {:3} {:} | Error between points: {:.2f} km ({:.2f} s)" .format(total_error, failed_stats, printPercent(perc_fail, N - failed_stats), dis / 1000, tim)) # Quick adjustment to try and better include stations for i in range(6): array = np.array(plot[i].get_offsets()) if not np.isinf(total_error): if i == 0: point = np.array([pos_f.lat, total_error]) elif i == 1: point = np.array([pos_f.lon, total_error]) elif i == 2: if point_on_traj is not None: t0 = 0 point = np.array([t0, total_error]) elif i == 3: point = np.array([v, total_error]) elif i == 4: point = np.array([azim, total_error]) elif i == 5: point = np.array([zangle, total_error]) # add the points to the plot array = np.append(array, [point], axis=0) plot[i].set_offsets(array) # # update x and ylim to show all points: ax[i // 3, i % 3].set_xlim(array[:, 0].min() - 0.01, array[:, 0].max() + 0.01) ax[i // 3, i % 3].set_ylim(array[:, 1].min() - 0.01, array[:, 1].max() + 0.01) # update the figure fig.canvas.draw() plt.pause(0.05) return total_error
def waveReleasePointWindsContour(bam, traj, ref_loc, points, div=37, mode='ballistic'): setup = bam.setup atmos = bam.atmos steps = 90 alpha = np.linspace(0, 360 * ((steps - 1) / steps), steps) alpha = np.radians(alpha) beta = np.linspace(0, 90 * ((steps - 1) / steps), steps) beta = np.radians(beta) theta = setup.trajectory.azimuth.rad phi = setup.trajectory.zenith.rad tol = 25 #deg # tol_fact = np.radians(np.linspace(-tol, tol, 10)) WIND = True u = traj.getTrajVect() v_list = [] for i in range(steps): for j in range(steps): v = np.array([np.sin(alpha[i])*np.sin(beta[j]),\ np.cos(alpha[i])*np.sin(beta[j]),\ -np.cos(beta[j])]) if np.abs(90 - np.degrees(np.arccos(np.dot(u, v)))) <= tol: v_list.append([alpha[i], beta[j]]) v_list = np.array(v_list) results = [] # temp hotfix if mode == 'ballistic_old': grid_space = 25 p1 = Position(43.8, 13.1, 0) p2 = Position(47.8, 17.1, 0) p1.pos_loc(ref_loc) p2.pos_loc(ref_loc) xs = np.linspace(p1.x, p2.x, grid_space) ys = np.linspace(p1.y, p2.y, grid_space) n_steps = grid_space**2 * len(points) for xnum, xx in enumerate(xs): for ynum, yy in enumerate(ys): angle_list = [] time_list = [] D = [xx, yy, 0] for pnum, pp in enumerate(points): step = pnum + ynum * len(points) + xnum * grid_space * len( points) loadingBar("Contour Calculation", step, n_steps) P = Position(pp[0], pp[1], pp[2]) P.pos_loc(ref_loc) S = P.xyz R = Position(0, 0, 0) R.x = D[0] R.y = D[1] R.z = D[2] R.pos_geo(ref_loc) lats = [P.lat, R.lat] lons = [P.lon, R.lon] elev = [P.elev, R.elev] z_profile, _ = atmos.getSounding( lats, lons, elev, ref_time=setup.fireball_datetime, spline=100) res = cyscan(np.array(S), np.array(D), z_profile, wind=True, h_tol=330, v_tol=3000, debug=False) alpha = np.radians(res[1]) beta = np.radians(180 - res[2]) res_vector = np.array([np.sin(alpha)*np.sin(beta),\ np.cos(alpha)*np.sin(beta),\ -np.cos(beta)]) angle_list.append( np.abs(90 - np.degrees( np.arccos( np.dot( u / np.sqrt(u.dot(u)), res_vector / np.sqrt(res_vector.dot(res_vector))))))) time_list.append(res[0]) if np.nanmin(angle_list) <= tol: best_angle_index = np.nanargmin(angle_list) best_time = time_list[best_angle_index] if not np.isnan(best_time): res = [xx, yy, 0, best_time] results.append(res) # u = np.array([bam.setup.trajectory.vector.x, # bam.setup.trajectory.vector.y, # bam.setup.trajectory.vector.z]) # angle_off = [] # X = [] # for i in range(len(bam.setup.fragmentation_point)): # az = stn.times.fragmentation[i][0][1] # tf = stn.times.fragmentation[i][0][2] # az = np.radians(az) # tf = np.radians(180 - tf) # v = np.array([np.sin(az)*np.sin(tf), np.cos(az)*np.sin(tf), -np.cos(tf)]) # angle_off.append(np.degrees(np.arccos(np.dot(u/np.sqrt(u.dot(u)), v/np.sqrt(v.dot(v)))))) # X.append(bam.setup.fragmentation_point[i].position.elev) # angle_off = np.array(angle_off) # try: # best_indx = np.nanargmin(abs(angle_off - 90)) # except ValueError: # best_indx = None # a.append(np.array([np.nan, np.nan, np.nan, np.nan])) # for pert in perturbations: # a.append(np.array([np.nan, np.nan, np.nan, np.nan])) # stn.times.ballistic.append(a) # continue # np.array([t_arrival, azimuth, takeoff, E[k, l]]) elif mode == 'ballistic': n_steps = len(v_list) * len(points) WIND = True for pp, p in enumerate(points): for vv, v in enumerate(v_list): step = vv + pp * len(v_list) loadingBar("Contour Calculation", step, n_steps) # v[i] = np.array([np.sin(alpha[i])*np.sin(beta[i]),\ # np.cos(alpha[i])*np.sin(beta[i]),\ # -np.cos(beta[i])]) P = Position(p[0], p[1], p[2]) S = Position(p[0], p[1], 0) P.pos_loc(ref_loc) pt = P.xyz # s = p + p[2]/np.cos(beta[i])*v[i] # S = Position(0, 0, 0) # P = Position(0, 0, 0) # S.x, S.y, S.z = s[0], s[1], s[2] # P.x, P.y, P.z = p[0], p[1], p[2] # S.pos_geo(ref_loc) # P.pos_geo(ref_loc) lats = [P.lat, S.lat] lons = [P.lon, S.lon] elev = [P.elev, S.elev] # z_profile, _ = supra.Supracenter.cyweatherInterp.getWeather(p, s, setup.weather_type, \ # ref_loc, copy.copy(sounding), convert=True) if WIND: z_profile, _ = atmos.getSounding( lats, lons, elev, ref_time=setup.fireball_datetime, spline=200) res = anglescan(pt, np.degrees(v[0]), np.degrees(v[1]) + 90, z_profile, wind=True, debug=False) else: # if no wind, just draw a straight line vect = np.array([np.sin(v[0])*np.sin(v[1]),\ np.cos(v[0])*np.sin(v[1]),\ -np.cos(v[1])]) s = -pt[2] / vect[2] ground_point = pt + s * vect ground_time = s / 330 res = [ ground_point[0], ground_point[1], ground_point[2], ground_time ] # This is the limit in distance from the trajectory (hardcoded) if res[-1] <= 1000: # if np.sqrt(res[0]**2 + res[1]**2) <= 150000: results.append(res) else: # n_steps = len(tol_fact)*len(points)*steps beta = np.linspace(90 + 0.01, 180, steps) beta = np.radians(beta) p = points for ii, i in enumerate(range(steps)): for jj, j in enumerate(range(steps)): # print(np.degrees(beta[i])) step = jj + ii * steps loadingBar("Contour Calculation", step, n_steps) v[i*steps + j] = np.array([np.sin(alpha[i])*np.sin(beta[j]),\ np.cos(alpha[i])*np.sin(beta[j]),\ -np.cos(beta[j])]) s = p + p[2] / np.cos(beta[j]) * v[i * steps + j] S = Position(0, 0, 0) P = Position(0, 0, 0) S.x, S.y, S.z = s[0], s[1], s[2] P.x, P.y, P.z = p[0], p[1], p[2] S.pos_geo(ref_loc) P.pos_geo(ref_loc) lats = [P.lat, S.lat] lons = [P.lon, S.lon] elev = [P.elev, S.elev] z_profile, _ = atmos.getSounding( lats, lons, elev, ref_time=setup.fireball_datetime, spline=100) res = anglescan(p, np.degrees(alpha[i]), np.degrees(beta[j]), z_profile, wind=True, debug=False) # if np.sqrt(res[0]**2 + res[1]**2) <= 200000: results.append(res) return results
def propegateBackwards(ref_pos, stn, bam, offset=0): S = stn.metadata.position S.pos_loc(ref_pos) # Initial guess for sounding sounding, _ = bam.atmos.getSounding(lat=[S.lat, S.lat], lon=[S.lon, S.lon], heights=[S.elev, 50000]) D = [] offset = 0 for pol in range(len(stn.polarization.azimuth)): min_az = stn.polarization.azimuth[ pol] - SIGMA * stn.polarization.azimuth_error[pol] max_az = stn.polarization.azimuth[ pol] + SIGMA * stn.polarization.azimuth_error[pol] for azimuth in np.linspace(min_az, max_az, 5): for zenith in np.linspace(1, 89, 100): # T - expected final arrival, with bad sounding # Recalculate winds # D - real, expected final arrival # This is overkill, atmosphere won't change that much T_pos = anglescanrev(S.xyz, (azimuth + offset) % 360, zenith, sounding, wind=True) T = Position(0, 0, 0) T.x = T_pos[0] T.y = T_pos[1] T.z = T_pos[2] T.pos_geo(ref_pos) try: sounding_plus, perts = bam.atmos.getSounding( lat=[S.lat, T.lat], lon=[S.lon, T.lon], heights=[S.elev, 50000]) except ValueError: sounding_plus, perts = bam.atmos.getSounding( lat=[S.lat, S.lat], lon=[S.lon, S.lon], heights=[S.elev, 50000]) nom_data = [None] * len(perts + 1) nom_data[0] = anglescanrev(S.xyz, (azimuth + offset) % 360, zenith, sounding_plus, wind=True, trace=True) for pp, pert in enumerate(perts): nom_data[pp] = anglescanrev(S.xyz, (azimuth + offset) % 360, zenith, pert, wind=True, trace=True) # Repeat 180 deg away T_pos = anglescanrev(S.xyz, (azimuth + offset + 180) % 360, zenith, sounding, wind=True) T = Position(0, 0, 0) T.x = T_pos[0] T.y = T_pos[1] T.z = T_pos[2] T.pos_geo(ref_pos) try: sounding_plus, perts = bam.atmos.getSounding( lat=[S.lat, T.lat], lon=[S.lon, T.lon], heights=[S.elev, 50000]) except ValueError: sounding_plus, perts = bam.atmos.getSounding( lat=[S.lat, S.lat], lon=[S.lon, S.lon], heights=[S.elev, 50000]) nom_data_rev = [None] * len(perts + 1) nom_data_rev[0] = anglescanrev(S.xyz, (azimuth + offset + 180) % 360, zenith, sounding_plus, wind=True, trace=True) for pp, pert in enumerate(perts): nom_data_rev[pp] = anglescanrev( S.xyz, (azimuth + offset + 180) % 360, zenith, pert, wind=True, trace=True) for ii in range(len(nom_data)): for line in nom_data[ii]: line[3] -= stn.polarization.time[pol] D.append(line) for line in nom_data_rev[ii]: line[3] -= stn.polarization.time[pol] D.append(line) return D