Commit bd6d7e3b authored by DavidWalz's avatar DavidWalz
Browse files

refactor and tuning

parent c74b907f
...@@ -433,12 +433,10 @@ class Atmosphere(): ...@@ -433,12 +433,10 @@ class Atmosphere():
mask_flat, mask_taylor, mask_numeric = self.__get_method_mask(zenith) mask_flat, mask_taylor, mask_numeric = self.__get_method_mask(zenith)
tmp = np.zeros_like(zenith) tmp = np.zeros_like(zenith)
if np.sum(mask_numeric): if np.sum(mask_numeric):
print("get vertical height numeric")
tmp[mask_numeric] = self._get_vertical_height_numeric(*self.__get_arguments(mask_numeric, zenith, X)) tmp[mask_numeric] = self._get_vertical_height_numeric(*self.__get_arguments(mask_numeric, zenith, X))
if np.sum(mask_taylor): if np.sum(mask_taylor):
tmp[mask_taylor] = self._get_vertical_height_numeric_taylor(*self.__get_arguments(mask_taylor, zenith, X)) tmp[mask_taylor] = self._get_vertical_height_numeric_taylor(*self.__get_arguments(mask_taylor, zenith, X))
if np.sum(mask_flat): if np.sum(mask_flat):
print("get vertical height flat")
tmp[mask_flat] = self._get_vertical_height_flat(*self.__get_arguments(mask_flat, zenith, X)) tmp[mask_flat] = self._get_vertical_height_flat(*self.__get_arguments(mask_flat, zenith, X))
return tmp return tmp
......
...@@ -8,6 +8,7 @@ from matplotlib.colors import Normalize ...@@ -8,6 +8,7 @@ from matplotlib.colors import Normalize
from utils import distance2showeraxis from utils import distance2showeraxis
from pylab import * from pylab import *
def plot_array(v_stations, values, label='', vmin=None, vmax=None): def plot_array(v_stations, values, label='', vmin=None, vmax=None):
"""Plot a map *values* for an detector array specified by *v_stations*. """Plot a map *values* for an detector array specified by *v_stations*.
""" """
...@@ -29,37 +30,32 @@ def plot_array(v_stations, values, label='', vmin=None, vmax=None): ...@@ -29,37 +30,32 @@ def plot_array(v_stations, values, label='', vmin=None, vmax=None):
ax.set_ylabel('y [km]') ax.set_ylabel('y [km]')
def plot_traces_of_array_for_one_event(Smu, Sem, v_axis, v_stations, arraysize = 5): def plot_traces_of_array_for_one_event(Smu, Sem, v_axis, v_stations, n=5):
'''plot time traces of tanks around the tank with the smallest distance to shower core """ Plot time traces of the n^2 central tanks
Sem = EM-SigmalTrace, Smu = Muon-SignalTrace, arraysize = length of array''' Sem = EM-SigmalTrace, Smu = Muon-SignalTrace
t = np.arange(0, 2001, 25) """
fig, axes = subplots(arraysize, arraysize, sharex=True, figsize=(29, 16), dpi=80, facecolor='w', edgecolor='k') n0 = int(len(v_stations)**.5)
i0 = (n0 - n) // 2
S1 = Smu.reshape(n0, n0, -1)
S2 = Sem.reshape(n0, n0, -1)
fig, axes = subplots(n, n, sharex=True, figsize=(29, 16), facecolor='w')
plt.tight_layout()
t = np.arange(12.5, 2001, 25) t = np.arange(12.5, 2001, 25)
tight_layout() for ix in range(n):
coordVcore = np.argmin(distance2showeraxis(v_stations, v_axis)) for iy in range(n):
coordX = int(coordVcore/11) ax = axes[ix, iy]
coordY = coordVcore%11 h1 = S1[ix + i0, iy + i0]
coords = np.array(0) h2 = S2[ix + i0, iy + i0]
for j in range(arraysize): ax.step(t, h1 + h2, c='k', where='mid')
for k in range(arraysize): ax.step(t, h1, label='$\mu$', where='mid')
coords = np.append(coords, (coordX-2+j)*11 + (coordY-2+k)) ax.step(t, h2, label='$e\gamma$', where='mid')
coords = coords[1:arraysize*arraysize+1] ax.legend(title='%i, %i' % (ix + i0, iy + i0), fontsize='x-small')
for i, ax in enumerate(axes.flat): ax.set_xlim(0, 1500)
try: ax.grid(True)
h1, h2 = Smu[coords[i]], Sem[coords[i]] ax.set_xlabel('$t$ / ns', fontsize='x-small')
except TypeError: ax.set_ylabel('$S$ / VEM', fontsize='x-small')
h2 = np.histogram(0, bins=t)[0].astype(float)
ax.step(t, h1 + h2, c='k', where='mid')
ax.step(t, h1, label='$\mu$', where='mid')
ax.step(t, h2, label='$e\gamma$', where='mid')
ax.legend(title='r=%i' % coords[i], fontsize='x-small')
ax.set_xlim(0, 1500)
ax.grid(True)
for k in range(arraysize):
for l in range(arraysize):
axes[k, l].set_xlabel('$t$ / ns')
axes[k, l].set_ylabel('$S$ / VEM')
savefig('trace_VEM.png')#, bbox_inches='tight')
if __name__ == '__main__': if __name__ == '__main__':
......
from __future__ import division from __future__ import division
import numpy as np import numpy as np
from utils import * import utils
import atmosphere import atmosphere
import gumbel import gumbel
from pylab import *
atm = atmosphere.Atmosphere()
import gumbel
import plotting import plotting
atm = atmosphere.Atmosphere()
HEIGHT = 1400
SPACING = 1500
def station_response(r, dX, logE=19, A=1): def station_response(r, dX, logE=19, A=1):
""" Simulate time trace f(t) for a given SD station and shower. """ Simulate time trace f(t) for a given SD station and shower.
Args: Args:
...@@ -20,12 +24,11 @@ def station_response(r, dX, logE=19, A=1): ...@@ -20,12 +24,11 @@ def station_response(r, dX, logE=19, A=1):
""" """
# strength of muon and em signal # strength of muon and em signal
# scaling with energy # scaling with energy
S0 = 1200 * 10**(logE - 19) S0 = 2000 * 10**(logE - 19)
# scaling with mass number # scaling with mass number and relative scaling mu/em
a = A**0.15 a = A**0.15
b = 2 ## EM scaling S1 = S0 * a / (a + 1) * 1
S1 = S0 * a / (a + 1) * 1/(b+1) S2 = S0 * 1 / (a + 1) * 3
S2 = S0 * 1 / (a + 1) * b/(b+1)
# TODO: add scaling with zenith angle (CIC) # TODO: add scaling with zenith angle (CIC)
# ... # ...
# scaling with distance of station to shower axis # scaling with distance of station to shower axis
...@@ -33,11 +36,11 @@ def station_response(r, dX, logE=19, A=1): ...@@ -33,11 +36,11 @@ def station_response(r, dX, logE=19, A=1):
S2 *= np.minimum((r / 1000)**-5.5, 1000) S2 *= np.minimum((r / 1000)**-5.5, 1000)
# scaling with traversed atmosphere to station # scaling with traversed atmosphere to station
S1 *= np.minimum((dX / 100)**-0.1, 10) S1 *= np.minimum((dX / 100)**-0.1, 10)
S2 *= np.minimum((dX / 100)**-0.8, 10) S2 *= np.minimum((dX / 100)**-0.4, 10)
# limit total signal, otherwise we get memory problems when drawing that many samples from distribution # limit total signal, otherwise we get memory problems when drawing that many samples from distribution
Stot = S1 + S2 Stot = S1 + S2
Smax = 1E6 Smax = 1E7
if Stot > Smax: if Stot > Smax:
S1 *= Smax / Stot S1 *= Smax / Stot
S2 *= Smax / Stot S2 *= Smax / Stot
...@@ -47,26 +50,26 @@ def station_response(r, dX, logE=19, A=1): ...@@ -47,26 +50,26 @@ def station_response(r, dX, logE=19, A=1):
N2 = np.maximum(int(S2 + S2**.5 * np.random.randn()), 0) # number of em particles N2 = np.maximum(int(S2 + S2**.5 * np.random.randn()), 0) # number of em particles
# parameters of log-normal distributions # parameters of log-normal distributions
mean1 = np.log(200 * (r / 750)**1.2 * (1 - 0.2 * dX / 1000.)) mean1 = np.log(50 + 140 * (r / 750)**1.4 * (1 - 0.2 * dX / 1000.))
mean2 = np.log(320 * (r / 750)**1.2 * (1 - 0.1 * dX / 1000.)) mean2 = np.log(80 + 200 * (r / 750)**1.4 * (1 - 0.1 * dX / 1000.))
sigma1 = 0.7 sigma1 = 0.7
sigma2 = 0.7 sigma2 = 0.7
# draw samples from distributions and create histograms # draw samples from distributions and create histograms
shift = np.exp(mean2) - np.exp(mean1) shift = (np.exp(mean2) - np.exp(mean1)) / 1.5
bins = np.arange(0, 2001, 25) # time bins [ns] bins = np.arange(0, 2001, 25) # time bins [ns]
h1 = np.histogram(np.random.lognormal(mean1, sigma1, size=N1), bins=bins)[0] h1 = np.histogram(np.random.lognormal(mean1, sigma1, size=N1), bins=bins)[0]
h2 = np.histogram(np.random.lognormal(mean2, sigma2, size=N2) + 0.5*shift, bins=bins)[0] h2 = np.histogram(np.random.lognormal(mean2, sigma2, size=N2) + shift, bins=bins)[0]
# total signal (simplify: 1 particle = 1 VEM) # total signal (simplify: 1 particle = 1 VEM)
return h1, h2 return h1, h2
def detector_response(logE, mass, v_axis, v_max, v_stations): def detector_response(logE, mass, v_axis, v_core, v_max, v_stations):
""" Simulate the detector response for all SD stations and one event """ Simulate the detector response for all SD stations and one event
""" """
r = distance2showeraxis(v_stations, v_axis) # radial distance to shower axis r = utils.distance2showeraxis(v_stations, v_core, v_axis) # radial distance to shower axis
phi, zen = vec2ang(v_max - v_stations) # direction of shower maximum relative to stations phi, zen = utils.vec2ang(v_max - v_stations) # direction of shower maximum relative to stations
# distance to shower maximum in [g/cm^2] # distance to shower maximum in [g/cm^2]
dX = atm.get_atmosphere( dX = atm.get_atmosphere(
zen, # zenith angle of shower maximum seen from each station zen, # zenith angle of shower maximum seen from each station
...@@ -90,30 +93,31 @@ def rand_shower_geometry(N, logE, mass): ...@@ -90,30 +93,31 @@ def rand_shower_geometry(N, logE, mass):
""" Generate random shower geometries: Xmax, axis, core, maximum. """ """ Generate random shower geometries: Xmax, axis, core, maximum. """
N = len(logE) N = len(logE)
# shower axis # 1) shower axis
phi = 2 * np.pi * (np.random.rand(N) - 0.5) phi = 2 * np.pi * (np.random.rand(N) - 0.5)
zenith = rand_zenith(N) zenith = utils.rand_zenith(N)
v_axis = ang2vec(phi, zenith) # phi = 0 * np.ones(N)
#### TESTIN ###################################################### # zenith = np.deg2rad(60) * np.ones(N)
phi = 2 *0* np.pi * (np.random.rand(N) - 0.5) v_axis = utils.ang2vec(phi, zenith)
zenith = 60*np.pi/180*rand_zenith(N)
# 2) shower core on ground (random offset w.r.t. grid origin)
# shower core on ground (random offset w.r.t. grid origin) v_core = SPACING * (np.random.rand(N, 3) - 0.5)
v_core = 0*SPACING * (np.random.rand(N, 3) - 0.5)
v_core[:, 2] = HEIGHT # core is always at ground level v_core[:, 2] = HEIGHT # core is always at ground level
# shower maximum, require h > hmin # 3) shower maximum, require h > hmin
Xmax = np.empty(N) Xmax = np.empty(N)
hmax = np.empty(N) hmax = np.empty(N)
i = 0 i = 0
while i < N: while i < N:
Xmax[i] = gumbel.rand_gumbel(logE[i], mass[i]) Xmax[i] = gumbel.rand_gumbel(logE[i], mass[i])
hmax[i] = atm.get_vertical_height(zenith[i], Xmax[i]) try:
hmax[i] = atm.get_vertical_height(zenith[i], Xmax[i])
except:
continue
if hmax[i] < HEIGHT + 200: if hmax[i] < HEIGHT + 200:
continue # shower maximum too low, repeat continue # shower maximum too low, repeat
i += 1 i += 1
print(i)
# position of shower maximum
dmax = (hmax - HEIGHT) / np.cos(zenith) # distance to Xmax dmax = (hmax - HEIGHT) / np.cos(zenith) # distance to Xmax
v_max = v_core + v_axis * dmax[:, np.newaxis] v_max = v_core + v_axis * dmax[:, np.newaxis]
...@@ -122,26 +126,29 @@ def rand_shower_geometry(N, logE, mass): ...@@ -122,26 +126,29 @@ def rand_shower_geometry(N, logE, mass):
if __name__ == '__main__': if __name__ == '__main__':
# detector array # detector array
nb_stations = 11**2 # number of stations n = 11
v_stations = triangular_array(nb_stations**.5) # x,y,z coordinates of SD stations v_stations = utils.station_coordinates(n, layout='cartesian') # x,y,z coordinates of SD stations
nb_stations = n**2 # number of stations
# showers # showers
nb_events = 2 print('simulating showers')
nb_events = 100
logE = 18.5 + 1.5 * np.random.rand(nb_events) logE = 18.5 + 1.5 * np.random.rand(nb_events)
logE = 20 *np.ones(nb_events) # logE = 20 * np.ones(nb_events)
mass = 1 * np.ones(nb_events) mass = 1 * np.ones(nb_events)
Xmax, v_axis, v_core, v_max = rand_shower_geometry(nb_events, logE, mass) Xmax, v_axis, v_core, v_max = rand_shower_geometry(nb_events, logE, mass)
# detector response for each event # detector response for each event
print('simulating detector response')
T = np.zeros((nb_events, nb_stations)) T = np.zeros((nb_events, nb_stations))
S1 = np.zeros((nb_events, nb_stations, 80)) S1 = np.zeros((nb_events, nb_stations, 80))
S2 = np.zeros((nb_events, nb_stations, 80)) S2 = np.zeros((nb_events, nb_stations, 80))
for i in range(nb_events): for i in range(nb_events):
print(i) print(i)
s1, s2 = detector_response(logE[i], mass[i], v_axis[i], v_max[i], v_stations) s1, s2 = detector_response(logE[i], mass[i], v_axis[i], v_core[i], v_max[i], v_stations)
S1[i] = s1 S1[i] = s1
S2[i] = s2 S2[i] = s2
T[i] = arrival_time_planar(v_stations, v_core[i], v_axis[i]) T[i] = utils.arrival_time_planar(v_stations, v_core[i], v_axis[i])
# add per ton noise on arrival time # add per ton noise on arrival time
T += 20E-9 * np.random.randn(*T.shape) T += 20E-9 * np.random.randn(*T.shape)
...@@ -156,7 +163,6 @@ if __name__ == '__main__': ...@@ -156,7 +163,6 @@ if __name__ == '__main__':
# S1 += 0.5 + 0.2 * np.random.randn(*S1.shape) # S1 += 0.5 + 0.2 * np.random.randn(*S1.shape)
# S2 += 0.5 + 0.2 * np.random.randn(*S2.shape) # S2 += 0.5 + 0.2 * np.random.randn(*S2.shape)
plotting.plot_traces_of_array_for_one_event(Smu=S1[0], Sem=S2[0], v_axis=v_axis[0], v_stations=v_stations, arraysize = 5)
# trigger threshold: use only stations with sufficient signal-to-noise # trigger threshold: use only stations with sufficient signal-to-noise
## c = S.sum(axis=-1) < 80 * 0.55 ## c = S.sum(axis=-1) < 80 * 0.55
## T[c] = np.NaN ## T[c] = np.NaN
...@@ -176,3 +182,12 @@ if __name__ == '__main__': ...@@ -176,3 +182,12 @@ if __name__ == '__main__':
# showeraxis=v_axis, # showeraxis=v_axis,
# showermax=v_max, # showermax=v_max,
# detector=v_stations) # detector=v_stations)
plotting.plot_traces_of_array_for_one_event(
Smu=S1[0], Sem=S2[0], v_axis=v_axis[0], v_stations=v_stations, n=5)
# r = utils.distance2showeraxis(v_stations, v_core[0], v_axis[0])
# plotting.plot_array(v_stations, r)
import matplotlib.pyplot as plt
plt.show()
from pylab import * from pylab import *
close('all') close('all')
# r = linspace(100, 3000)
# figure()
# plot(r, 220 * (r / 750)**1.2) def mean1(r, dx):
# plot(r, 420 * (r / 750)**1.2) return 30 + 180 * (r / 750)**1.4 * (1 - 0.2 * dX / 1000.)
# plot([750, 850, 1250], [220, 260, 500], 'C0+')
# plot([750, 850, 1250], [420, 500, 1000], 'C1+') def mean2(r, dx):
# xlabel('$r$ [m]') return 50 + 300 * (r / 750)**1.4 * (1 - 0.1 * dX / 1000.)
# ylabel('$\Delta t$ [ns]')
# grid()
figure()
r = linspace(100, 3000)
# for r in [750, 850, 1250]: dX = 600
# mean1 = np.log(200 * (r / 750)**1.2) # * (1 - 0.2 * dX / 1000.) plot(r, mean1(r, dX))
# mean2 = np.log(320 * (r / 750)**1.2) # * (1 - 0.1 * dX / 1000.) plot(r, mean2(r, dX))
plot([750, 850, 1250], [220, 260, 500], 'C0+')
plot([750, 850, 1250], [420, 500, 1000], 'C1+')
xlabel('$r$ [m]')
ylabel('$\Delta t$ [ns]')
grid()
for r in [750, 850, 1250]:
m1 = np.log(mean1(r, dX))
m2 = np.log(mean2(r, dX))
figure()
bins = np.arange(0, 2001, 25) # time bins [ns]
axvline(exp(m1), color='C0')
axvline(exp(m2), color='C1')
hist(np.random.lognormal(m1, 0.7, size=10000), bins=bins, alpha=0.7)
hist(np.random.lognormal(m2, 0.7, size=10000) + exp(m2) - exp(m1), bins=bins, alpha=0.7)
xlim(0, 1500)
show()
# r = 750
# for dX in [300, 600, 900]:
# mean1 = np.log(200 * (r / 750)**1.2 * (1 - 0.2 * dX / 1000.))
# mean2 = np.log(320 * (r / 750)**1.2 * (1 - 0.1 * dX / 1000.))
# sigma1 = 0.7 # * (r / 1000.)**0.15 * (1 - 0.2 * dX / 1000.) # sigma1 = 0.7 # * (r / 1000.)**0.15 * (1 - 0.2 * dX / 1000.)
# sigma2 = 0.7 # * (r / 1000.)**0.15 # sigma2 = 0.7 # * (r / 1000.)**0.15
...@@ -23,25 +47,8 @@ close('all') ...@@ -23,25 +47,8 @@ close('all')
# bins = np.arange(0, 2001, 25) # time bins [ns] # bins = np.arange(0, 2001, 25) # time bins [ns]
# axvline(exp(mean1), color='C0') # axvline(exp(mean1), color='C0')
# axvline(exp(mean2), color='C1') # axvline(exp(mean2), color='C1')
# shift = exp(mean2)-exp(mean1)
# hist(np.random.lognormal(mean1, sigma1, size=10000), bins=bins, alpha=0.7) # hist(np.random.lognormal(mean1, sigma1, size=10000), bins=bins, alpha=0.7)
# hist(np.random.lognormal(mean2, sigma2, size=10000) + exp(mean2)-exp(mean1), bins=bins, alpha=0.7) # hist(np.random.lognormal(mean2, sigma2, size=10000) + shift, bins=bins, alpha=0.7)
# xlim(0, 1500) # xlim(0, 1500)
# show() # show()
r = 750
for dX in [300, 600, 900]:
mean1 = np.log(200 * (r / 750)**1.2 * (1 - 0.2 * dX / 1000.))
mean2 = np.log(320 * (r / 750)**1.2 * (1 - 0.1 * dX / 1000.))
sigma1 = 0.7 # * (r / 1000.)**0.15 * (1 - 0.2 * dX / 1000.)
sigma2 = 0.7 # * (r / 1000.)**0.15
figure()
bins = np.arange(0, 2001, 25) # time bins [ns]
axvline(exp(mean1), color='C0')
axvline(exp(mean2), color='C1')
shift = exp(mean2)-exp(mean1)
hist(np.random.lognormal(mean1, sigma1, size=10000), bins=bins, alpha=0.7)
hist(np.random.lognormal(mean2, sigma2, size=10000) + shift, bins=bins, alpha=0.7)
xlim(0, 1500)
show()
...@@ -3,34 +3,38 @@ from __future__ import division ...@@ -3,34 +3,38 @@ from __future__ import division
import numpy as np import numpy as np
HEIGHT = 1400 # detector height in [m]
SPACING = 1500 # detector spacing in [m]
def rectangular_array(n=11): def rectangular_array(n=11):
""" Coordinates for rectangular array with n^2 stations and given spacing. """ Return x,y coordinates for rectangular array with n^2 stations. """
Returns: n^2 x 3 array of x,y,z coordinates for each station.
"""
n0 = (n - 1) / 2 n0 = (n - 1) / 2
x, y = (np.mgrid[0:n, 0:n].astype(float) - n0) * SPACING return (np.mgrid[0:n, 0:n].astype(float) - n0)
z = np.ones_like(x) * HEIGHT # z-position
return np.dstack([x, y, z]).reshape(-1, 3)
def triangular_array(n=11, offset=True): def triangular_array(n=11, offset=True):
""" Coordinates for triangular array with n^2 stations and given spacing. """ Return x,y coordinates for triangular array with n^2 stations. """
Returns: n^2 x 3 array of x,y,z coordinates for each station.
"""
n0 = (n - 1) / 2 n0 = (n - 1) / 2
x, y = np.mgrid[0:n, 0:n].astype(float) - n0 x, y = np.mgrid[0:n, 0:n].astype(float) - n0
if offset: # offset coordinates if offset: # offset coordinates
x += 0.5 * (y % 2) x += 0.5 * (y % 2)
else: # axial coordinates else: # axial coordinates
x += 0.5 * y x += 0.5 * y
x *= SPACING y *= np.sin(np.pi / 3)
y *= np.sin(np.pi / 3) * SPACING return x, y
z = np.ones_like(x) * HEIGHT # z-position
return np.dstack([x, y, z]).reshape(-1, 3)
def station_coordinates(n=11, layout='axial', spacing=1500, height=1400):
""" Return array of n^2*(x,y,z) coordinates of SD stations for given layout. """
if layout == 'axial':
x, y = triangular_array(n, offset=False)
elif layout == 'offset':
x, y = triangular_array(n, offset=True)
elif layout == 'cartesian':
x, y = rectangular_array(n)
else:
raise ValueError('layout must be one of axial, offset, cartesian')
x = x.reshape(n**2) * spacing
y = y.reshape(n**2) * spacing
z = np.ones_like(x) * height
return np.c_[x, y, z]
def rand_zenith(N=1, zmax=np.pi / 3): def rand_zenith(N=1, zmax=np.pi / 3):
...@@ -71,23 +75,25 @@ def vec2ang(v): ...@@ -71,23 +75,25 @@ def vec2ang(v):
return phi, zenith return phi, zenith
def distance2showerplane(v, va): def distance2showerplane(v, vc, va):
""" Get shortest distance to shower plane """ Get shortest distance to shower plane
Args: Args:
v (Nx3 array): array of positions v (Nx3 array): array of positions
va (3 array): shower axis = normal vector of the shower plane va (3 array): shower axis = normal vector of the shower plane
vc (3 array): shower core
""" """
return np.dot(v, va) return np.dot(v - vc, va)
def distance2showeraxis(v, va): def distance2showeraxis(v, vc, va):
""" Shortest distance to shower axis. """ Shortest distance to shower axis.
Args: Args:
v (Nx3 array): array of positions v (Nx3 array): array of positions
va (3 array): shower axis va (3 array): shower axis
vc (3 array): shower core
""" """
d = distance2showerplane(v, va) d = distance2showerplane(v, vc, va)
vp = v - np.outer(d, va) vp = v - vc - np.outer(d, va)
return np.linalg.norm(vp, axis=-1) return np.linalg.norm(vp, axis=-1)
...@@ -110,5 +116,5 @@ def arrival_time_planar(v, vc, va): ...@@ -110,5 +116,5 @@ def arrival_time_planar(v, vc, va):
Return: Return:
array: arrival times [s] array: arrival times [s]
""" """
d = distance2showerplane(v - vc, -va) d = distance2showerplane(v, vc, -va)
return d / 3E8 return d / 3E8
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment