|
| 1 | +import numpy |
| 2 | +from astropy import constants |
| 3 | +import astropy.units as u |
| 4 | +from astropy.coordinates import SkyCoord |
| 5 | + |
| 6 | +from processing_library.util.array_functions import average_chunks2 |
| 7 | +from processing_library.util.coordinate_support import xyz_to_uvw, skycoord_to_lmn |
| 8 | + |
| 9 | +def simulate_DTV(frequency, times, power=50e3, timevariable=False, frequency_variable=False): |
| 10 | + """ Calculate DTV sqrt(power) as a function of time and frequency |
| 11 | +
|
| 12 | + :param frequency: (sample frequencies) |
| 13 | + :param times: sample times (s) |
| 14 | + :param power: DTV emitted power W |
| 15 | + :return: Complex array [ntimes, nchan] |
| 16 | + """ |
| 17 | + nchan = len(frequency) |
| 18 | + ntimes = len(times) |
| 19 | + shape = [ntimes, nchan] |
| 20 | + bchan = nchan // 4 |
| 21 | + echan = 3 * nchan // 4 |
| 22 | + amp = power / (max(frequency) - min(frequency)) |
| 23 | + signal = numpy.zeros(shape, dtype='complex') |
| 24 | + if timevariable: |
| 25 | + if frequency_variable: |
| 26 | + sshape = [ntimes, nchan // 2] |
| 27 | + signal[:, bchan:echan] += numpy.random.normal(0.0, numpy.sqrt(amp/2.0), sshape) \ |
| 28 | + + 1j * numpy.random.normal(0.0, numpy.sqrt(amp/2.0), sshape) |
| 29 | + else: |
| 30 | + sshape = [ntimes] |
| 31 | + signal[:, bchan:echan] += numpy.random.normal(0.0, numpy.sqrt(amp/2.0), sshape) \ |
| 32 | + + 1j * numpy.random.normal(0.0, numpy.sqrt(amp/2.0), sshape) |
| 33 | + else: |
| 34 | + if frequency_variable: |
| 35 | + sshape = [nchan // 2] |
| 36 | + signal[:, bchan:echan] += (numpy.random.normal(0.0, numpy.sqrt(amp/2.0), sshape) |
| 37 | + + 1j * numpy.random.normal(0.0, numpy.sqrt(amp/2.0), sshape))[numpy.newaxis, ...] |
| 38 | + else: |
| 39 | + signal[:, bchan:echan] = amp |
| 40 | + |
| 41 | + return signal |
| 42 | + |
| 43 | + |
| 44 | +def add_noise(visibility, bandwidth, int_time): |
| 45 | + """Determine noise rms per visibility |
| 46 | +
|
| 47 | + :returns: visibility with noise added |
| 48 | + """ |
| 49 | + # The specified sensitivity (effective area / T_sys) is roughly 610 m ^ 2 / K in the range 160 - 200MHz |
| 50 | + # sigma_vis = 2 k T_sys / (area * sqrt(tb)) = 2 k 512 / (610 * sqrt(tb) |
| 51 | + sens = 610 |
| 52 | + k_b = 1.38064852e-23 |
| 53 | + bt = bandwidth * int_time |
| 54 | + sigma = 2 * 1e26 * k_b / ((sens / 512) * (numpy.sqrt(bt))) |
| 55 | + print("RMS noise per sample = %g Jy" % sigma) |
| 56 | + sshape = visibility.shape |
| 57 | + visibility += numpy.random.normal(0.0, sigma, sshape) + 1j * numpy.random.normal(0.0, sigma, sshape) |
| 58 | + return visibility |
| 59 | + |
| 60 | + |
| 61 | +def create_propagators(config, interferer, frequency, attenuation=1e-9): |
| 62 | + """ Create a set of propagators |
| 63 | +
|
| 64 | + :return: Complex array [nants, ntimes] |
| 65 | + """ |
| 66 | + nchannels = len(frequency) |
| 67 | + nants = len(config.data['names']) |
| 68 | + interferer_xyz = [interferer.geocentric[0].value, interferer.geocentric[1].value, interferer.geocentric[2].value] |
| 69 | + propagators = numpy.zeros([nants, nchannels], dtype='complex') |
| 70 | + for iant, ant_xyz in enumerate(config.xyz): |
| 71 | + vec = ant_xyz - interferer_xyz |
| 72 | + # This ignores the Earth! |
| 73 | + r = numpy.sqrt(vec[0] ** 2 + vec[1] ** 2 + vec[2] ** 2) |
| 74 | + k = 2.0 * numpy.pi * frequency / constants.c.value |
| 75 | + propagators[iant, :] = numpy.exp(- 1.0j * k * r) / r |
| 76 | + return propagators * attenuation |
| 77 | + |
| 78 | + |
| 79 | +def calculate_rfi_at_station(propagators, emitter): |
| 80 | + """ Calculate the rfi at each station |
| 81 | +
|
| 82 | + :param propagators: |
| 83 | + :param emitter: |
| 84 | + :return: Complex array [nants, ntimes, nchannels] |
| 85 | + """ |
| 86 | + rfi_at_station = emitter[numpy.newaxis, :, :] * propagators[:, numpy.newaxis, :] |
| 87 | + rfi_at_station[numpy.abs(rfi_at_station)<1e-15] = 0. |
| 88 | + return rfi_at_station |
| 89 | + |
| 90 | + |
| 91 | +def calculate_station_fringe_rotation(ants_xyz, times, frequency, phasecentre, pole): |
| 92 | + """Calculate fringe rotation vectors |
| 93 | + |
| 94 | + :param ants_xyz: |
| 95 | + :param times: |
| 96 | + :param frequency: |
| 97 | + :param phasecentre: |
| 98 | + :param pole: |
| 99 | + :return: phasor, uvw |
| 100 | + """ |
| 101 | + |
| 102 | + uvw = xyz_to_uvw(ants_xyz, times, phasecentre.dec.rad) |
| 103 | + nants, nuvw = uvw.shape |
| 104 | + ntimes = len(times) |
| 105 | + uvw = uvw.reshape([nants, ntimes, 3]) |
| 106 | + lmn = skycoord_to_lmn(phasecentre, pole) |
| 107 | + delay = numpy.dot(uvw, lmn) |
| 108 | + nchan = len(frequency) |
| 109 | + phase = numpy.zeros([nants, ntimes, nchan]) |
| 110 | + for ant in range(nants): |
| 111 | + for chan in range(nchan): |
| 112 | + phase[ant, :, chan] = delay[ant] * frequency[chan] / constants.c.value |
| 113 | + phase[...] -= phase[0, :, :][numpy.newaxis,...] |
| 114 | + return numpy.exp(2.0 * numpy.pi * 1j * phase), uvw |
| 115 | + |
| 116 | + |
| 117 | +def calculate_station_correlation_rfi(fringe_rotation, rfi_at_station): |
| 118 | + """ Form the correlation from the rfi at the station and the fringe rotation |
| 119 | + |
| 120 | + :param fringe_rotation: |
| 121 | + :param rfi_at_station: |
| 122 | + :return: Correlation(nant, nants, ntimes, nchan] in Jy |
| 123 | + """ |
| 124 | + phased_rotated_rfi_at_station = fringe_rotation * rfi_at_station |
| 125 | + nants, ntimes, nchan = fringe_rotation.shape |
| 126 | + correlation = numpy.zeros([nants, nants, ntimes, nchan], dtype='complex') |
| 127 | + |
| 128 | + for time in range(ntimes): |
| 129 | + for chan in range(nchan): |
| 130 | + correlation[..., time, chan] = numpy.outer(phased_rotated_rfi_at_station[..., time, chan], |
| 131 | + numpy.conjugate(phased_rotated_rfi_at_station[..., time, chan])) |
| 132 | + return correlation * 1e26 |
| 133 | + |
| 134 | + |
| 135 | +def calculate_averaged_correlation(correlation, channel_width, time_width): |
| 136 | + """ Average the correlation in time and frequency |
| 137 | + |
| 138 | + :param correlation: Correlation(nant, nants, ntimes, nchan] |
| 139 | + :param channel_width: Number of channels to average |
| 140 | + :param time_width: Number of integrations to average |
| 141 | + :return: |
| 142 | + """ |
| 143 | + wts = numpy.ones(correlation.shape, dtype='float') |
| 144 | + return average_chunks2(correlation, wts, (channel_width, time_width))[0] |
| 145 | + |
| 146 | + |
| 147 | +def simulate_rfi_block(config, times, frequency, phasecentre, emitter_location, emitter_power=5e4, |
| 148 | + attenuation=1.0): |
| 149 | + """ Simulate RFI block |
| 150 | +
|
| 151 | + :param config: ARL telescope Configuration |
| 152 | + :param times: observation times (hour angles) |
| 153 | + :param frequency: frequencies |
| 154 | + :param phasecentre: |
| 155 | + :param emitter_location: EarthLocation of emitter |
| 156 | + :param emitter_power: Power of emitter |
| 157 | + :param attenuation: Attenuation to be applied to signal |
| 158 | + :return: |
| 159 | + """ |
| 160 | + # Calculate the power spectral density of the DTV station: Watts/Hz |
| 161 | + emitter = simulate_DTV(frequency, times, power=emitter_power, timevariable=False) |
| 162 | + |
| 163 | + # Calculate the propagators for signals from Perth to the stations in low |
| 164 | + # These are fixed in time but vary with frequency. The ad hoc attenuation |
| 165 | + # is set to produce signal roughly equal to noise at LOW |
| 166 | + propagators = create_propagators(config, emitter_location, frequency=frequency, |
| 167 | + attenuation=attenuation) |
| 168 | + # Now calculate the RFI at the stations, based on the emitter and the propagators |
| 169 | + rfi_at_station = calculate_rfi_at_station(propagators, emitter) |
| 170 | + |
| 171 | + # Station fringe rotation: shape [nants, ntimes, nchan] complex phasor to be applied to |
| 172 | + # reference to the pole. |
| 173 | + pole = SkyCoord(ra=+0.0 * u.deg, dec=-90.0 * u.deg, frame='icrs', equinox='J2000') |
| 174 | + fringe_rotation, uvw = calculate_station_fringe_rotation(config.xyz, times, frequency, phasecentre, pole) |
| 175 | + |
| 176 | + # Calculate the rfi correlationm using the fringe rotation and the rfi at the station |
| 177 | + # [nants, nants, ntimes, nchan] |
| 178 | + correlation = calculate_station_correlation_rfi(fringe_rotation, rfi_at_station) |
| 179 | + |
| 180 | + return correlation, uvw |
0 commit comments