diff --git a/tangostationcontrol/tangostationcontrol/devices/antennafield.py b/tangostationcontrol/tangostationcontrol/devices/antennafield.py index 0363b5af38f6706a4a3334ed12b2a50eebbea1b0..2c89283f88c123042c1b8bc531b1623b374d62c8 100644 --- a/tangostationcontrol/tangostationcontrol/devices/antennafield.py +++ b/tangostationcontrol/tangostationcontrol/devices/antennafield.py @@ -152,25 +152,25 @@ class AntennaField(lofar_device): ) Calibration_SDP_Subband_Weights_50MHz = device_property( - doc=f"Measured calibration values for the sdp.FPGA_subband_weights_RW columns of each antenna, at 50 MHz. Each antenna is represented by a (real, imag) pair for every subband.", + doc=f"Measured calibration values for the sdp.FPGA_subband_weights_RW columns of each polarisation of each antenna, at 50 MHz. Each polarisation is represented by a (real, imag) pair for every subband.", dtype='DevVarFloatArray', mandatory=False ) Calibration_SDP_Subband_Weights_150MHz = device_property( - doc=f"Measured calibration values for the sdp.FPGA_subband_weights_RW columns of each antenna, at 150 MHz. Each antenna is represented by a (real, imag) pair for every subband.", + doc=f"Measured calibration values for the sdp.FPGA_subband_weights_RW columns of each polarisation of each antenna, at 150 MHz. Each polarisation is represented by a (real, imag) pair for every subband.", dtype='DevVarFloatArray', mandatory=False ) Calibration_SDP_Subband_Weights_200MHz = device_property( - doc=f"Measured calibration values for the sdp.FPGA_subband_weights_RW columns of each antenna, at 200 MHz. Each antenna is represented by a (real, imag) pair for every subband.", + doc=f"Measured calibration values for the sdp.FPGA_subband_weights_RW columns of each polarisation of each antenna, at 200 MHz. Each polarisation is represented by a (real, imag) pair for every subband.", dtype='DevVarFloatArray', mandatory=False ) Calibration_SDP_Subband_Weights_250MHz = device_property( - doc=f"Measured calibration values for the sdp.FPGA_subband_weights_RW columns of each antenna, at 250 MHz. Each antenna is represented by a (real, imag) pair for every subband.", + doc=f"Measured calibration values for the sdp.FPGA_subband_weights_RW columns of each polarisation of each antenna, at 250 MHz. Each polarisation is represented by a (real, imag) pair for every subband.", dtype='DevVarFloatArray', mandatory=False ) @@ -303,13 +303,13 @@ class AntennaField(lofar_device): dtype=(numpy.uint32,), max_dim_x=MAX_ANTENNA, unit="dB") Calibration_SDP_Fine_Calibration_Default_R = attribute( doc=f"Computed calibration values for the fine calibration of each antenna. Each antenna is represented by a (delay, phase_offset, amplitude_scaling) triplet.", - dtype=((numpy.float64,),), max_dim_y=MAX_ANTENNA, max_dim_x=3) + dtype=((numpy.float64,),), max_dim_y=MAX_ANTENNA * N_pol, max_dim_x=3) Calibration_SDP_Subband_Weights_Default_R = attribute( - doc=f"Calibration values for the rows in sdp.FPGA_subband_weights_RW relevant for our antennas, as computed. Each subband of each antenna is represented by a real_imag number (real, imag).", - dtype=((numpy.float64,),), max_dim_y=MAX_ANTENNA, max_dim_x=N_subbands * VALUES_PER_COMPLEX) + doc=f"Calibration values for the rows in sdp.FPGA_subband_weights_RW relevant for our antennas, as computed. Each subband of each polarisation of each antenna is represented by a real_imag number (real, imag).", + dtype=((numpy.float64,),), max_dim_y=MAX_ANTENNA * N_pol, max_dim_x=N_subbands * VALUES_PER_COMPLEX) Calibration_SDP_Subband_Weights_R = attribute( - doc=f"Calibration values for the rows in sdp.FPGA_subband_weights_RW relevant for our antennas. Each subband of each antenna is represented by a real_imag number (real, imag). Returns the measured values from Calibration_SDP_Subband_Weights_XXXMHz if available, and values computed from Calibration_SDP_Fine_Calibration_Default_R otherwise.", - dtype=((numpy.float64,),), max_dim_y=MAX_ANTENNA, max_dim_x=N_subbands * VALUES_PER_COMPLEX) + doc=f"Calibration values for the rows in sdp.FPGA_subband_weights_RW relevant for our antennas. Each subband of each polarisation of each antenna is represented by a real_imag number (real, imag). Returns the measured values from Calibration_SDP_Subband_Weights_XXXMHz if available, and values computed from Calibration_SDP_Fine_Calibration_Default_R otherwise.", + dtype=((numpy.float64,),), max_dim_y=MAX_ANTENNA * N_pol, max_dim_x=N_subbands * VALUES_PER_COMPLEX) # ----- Quality and usage information @@ -426,10 +426,14 @@ class AntennaField(lofar_device): return input_delay_samples def read_Calibration_SDP_Fine_Calibration_Default_R(self): + def repeat_per_pol(arr): + # repeat values twice, and restore the shape (with the inner dimension being twice the size now) + return numpy.dstack((arr,arr)).reshape(arr.shape[0] * N_pol, *arr.shape[1:]) + # ----- Delay - # correct for signal delays in the cables - signal_delay_seconds = self.read_attribute("Antenna_Cables_Delay_R") + # correct for signal delays in the cables (equal for both polarisations) + signal_delay_seconds = repeat_per_pol(self.read_attribute("Antenna_Cables_Delay_R")) # compute the required compensation clock = self.sdp_proxy.clock_RW @@ -438,17 +442,17 @@ class AntennaField(lofar_device): # ----- Phase offsets # we don't have any - phase_offsets = numpy.zeros((self.read_attribute("nr_antennas_R"),),dtype=numpy.float64) + phase_offsets = repeat_per_pol(numpy.zeros((self.read_attribute("nr_antennas_R"),),dtype=numpy.float64)) # ----- Amplitude # correct for signal loss in the cables - signal_delay_loss = self.read_attribute("Antenna_Cables_Loss_R") - self.Field_Attenuation + signal_delay_loss = repeat_per_pol(self.read_attribute("Antenna_Cables_Loss_R") - self.Field_Attenuation) # return fine scaling to apply _, input_attenuation_remaining_factor = loss_compensation(signal_delay_loss) - # Return as (delay, phase_offset, amplitude) triplet per antenna + # Return as (delay, phase_offset, amplitude) triplet per polarisation return numpy.stack((input_delay_subsample_seconds, phase_offsets, input_attenuation_remaining_factor), axis=1) def read_Calibration_SDP_Subband_Weights_Default_R(self): @@ -459,8 +463,7 @@ class AntennaField(lofar_device): nr_antennas = self.read_attribute("nr_antennas_R") antenna_to_sdp_mapping = self.read_attribute("Antenna_to_SDP_Mapping_R") - - subband_weights = numpy.zeros((nr_antennas, N_subbands, VALUES_PER_COMPLEX), dtype=numpy.float64) + subband_weights = numpy.zeros((nr_antennas, N_pol, N_subbands, VALUES_PER_COMPLEX), dtype=numpy.float64) # compute real_imag weight for each subband for antenna_nr in range(nr_antennas): @@ -468,20 +471,21 @@ class AntennaField(lofar_device): if input_nr == -1: continue - delay, phase_offset, amplitude = delay_phase_amplitude[antenna_nr, :] + for pol_nr in range(N_pol): + delay, phase_offset, amplitude = delay_phase_amplitude[antenna_nr * N_pol + pol_nr, :] - for subband_nr in range(N_subbands): - frequency = subband_frequency(subband_nr, clock, nyquist_zone[fpga_nr, input_nr]) + for subband_nr in range(N_subbands): + frequency = subband_frequency(subband_nr, clock, nyquist_zone[fpga_nr, input_nr]) - # turn signal backwards to compensate for the provided delay and offset - phase_shift = -(2 * numpy.pi * frequency * delay + phase_offset) + # turn signal backwards to compensate for the provided delay and offset + phase_shift = -(2 * numpy.pi * frequency * delay + phase_offset) - real = numpy.cos(phase_shift) * amplitude - imag = numpy.sin(phase_shift) * amplitude + real = numpy.cos(phase_shift) * amplitude + imag = numpy.sin(phase_shift) * amplitude - subband_weights[antenna_nr, subband_nr, :] = (real, imag) + subband_weights[antenna_nr, pol_nr, subband_nr, :] = (real, imag) - return subband_weights.reshape(nr_antennas, N_subbands * VALUES_PER_COMPLEX) + return subband_weights.reshape(nr_antennas * N_pol, N_subbands * VALUES_PER_COMPLEX) def _rcu_band_to_calibration_table(self) -> dict: """ @@ -507,7 +511,7 @@ class AntennaField(lofar_device): # reshape them into their actual form for band, caltable in rcu_band_to_caltable.items(): - rcu_band_to_caltable[band] = numpy.array(caltable).reshape(nr_antennas, N_subbands, 2) + rcu_band_to_caltable[band] = numpy.array(caltable).reshape(nr_antennas, N_pol, N_subbands, 2) return rcu_band_to_caltable @@ -526,7 +530,7 @@ class AntennaField(lofar_device): # construct the subband weights based on the rcu_band of each antenna, # combining the relevant tables. nr_antennas = self.read_attribute("nr_antennas_R") - subband_weights = numpy.zeros((nr_antennas, N_subbands, VALUES_PER_COMPLEX), dtype=numpy.float64) + subband_weights = numpy.zeros((nr_antennas, N_pol, N_subbands, VALUES_PER_COMPLEX), dtype=numpy.float64) for antenna_nr, rcu_band in enumerate(rcu_bands): # Skip antennas not connected to RECV. These do not have a valid RCU band selected. if recvs[antenna_nr] == 0: @@ -536,9 +540,9 @@ class AntennaField(lofar_device): if antenna_to_sdp_mapping[antenna_nr, 1] == -1: continue - subband_weights[antenna_nr, :, :] = rcu_band_to_caltable[rcu_band][antenna_nr, :, :] + subband_weights[antenna_nr, :, :, :] = rcu_band_to_caltable[rcu_band][antenna_nr, :, :, :] - return subband_weights.reshape(nr_antennas, N_subbands * VALUES_PER_COMPLEX) + return subband_weights.reshape(nr_antennas * N_pol, N_subbands * VALUES_PER_COMPLEX) def read_Calibration_RCU_Attenuation_dB_R(self): # Correct for signal loss in the cables @@ -815,7 +819,8 @@ class AntennaField(lofar_device): continue # set weights - fpga_subband_weights[fpga_nr, input_nr, :] = real_imag_to_weights(caltable[antenna_nr, :], SDP.SUBBAND_UNIT_WEIGHT) + fpga_subband_weights[fpga_nr, input_nr * N_pol + 0, :] = real_imag_to_weights(caltable[antenna_nr * N_pol + 0, :], SDP.SUBBAND_UNIT_WEIGHT) + fpga_subband_weights[fpga_nr, input_nr * N_pol + 1, :] = real_imag_to_weights(caltable[antenna_nr * N_pol + 1, :], SDP.SUBBAND_UNIT_WEIGHT) self.sdp_proxy.FPGA_subband_weights_RW = fpga_subband_weights.reshape(N_pn, S_pn * N_subbands) diff --git a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_antennafield.py b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_antennafield.py index 14002bca5b81ccfec845bf3291b986c6c8fd34da..1fdb815c93a8744ed791f0d9cfaa3cd8b3861a61 100644 --- a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_antennafield.py +++ b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_antennafield.py @@ -15,7 +15,7 @@ from tangostationcontrol.devices.antennafield import AntennaQuality, AntennaUse from tangostationcontrol.devices.sdp.common import weight_to_complex from tangostationcontrol.devices.sdp.sdp import SDP from .base import AbstractTestBases -from tangostationcontrol.common.constants import N_elements, MAX_ANTENNA, N_pol, N_rcu, N_rcu_inp, DEFAULT_N_HBA_TILES, CLK_200_MHZ, N_pn, S_pn, N_subbands +from tangostationcontrol.common.constants import N_elements, MAX_ANTENNA, N_pol, N_rcu, N_rcu_inp, DEFAULT_N_HBA_TILES, CLK_200_MHZ, N_pn, A_pn, N_subbands class TestAntennaFieldDevice(AbstractTestBases.TestDeviceBase): @@ -352,12 +352,17 @@ class TestAntennaFieldDevice(AbstractTestBases.TestDeviceBase): # and on the exact delay and loss differences between the cables. # rather than repeating the computations from the code, # we implement this as a regression test. - subband_weights = self.sdp_proxy.FPGA_subband_weights_RW.reshape(N_pn, S_pn, N_subbands) + subband_weights = self.sdp_proxy.FPGA_subband_weights_RW.reshape(N_pn, A_pn, N_pol, N_subbands) def to_complex(weight): return weight_to_complex(weight, SDP.SUBBAND_UNIT_WEIGHT) - self.assertAlmostEqual(0.929 + 0j, to_complex(subband_weights[0, 0, 0]), places=3) - self.assertAlmostEqual(0.309 + 0.876j, to_complex(subband_weights[0, 0, 511]), places=3) - self.assertAlmostEqual(0.989 + 0j, to_complex(subband_weights[0, 1, 0]), places=3) - self.assertAlmostEqual(0.883 - 0.444j, to_complex(subband_weights[0, 1, 511]), places=3) + # weight should be equal for both polarisations, different per antenna + self.assertAlmostEqual(0.929 + 0j, to_complex(subband_weights[0, 0, 0, 0]), places=3) + self.assertAlmostEqual(0.309 + 0.876j, to_complex(subband_weights[0, 0, 0, 511]), places=3) + self.assertAlmostEqual(0.929 + 0j, to_complex(subband_weights[0, 0, 1, 0]), places=3) + self.assertAlmostEqual(0.309 + 0.876j, to_complex(subband_weights[0, 0, 1, 511]), places=3) + self.assertAlmostEqual(0.989 + 0j, to_complex(subband_weights[0, 1, 0, 0]), places=3) + self.assertAlmostEqual(0.883 - 0.444j, to_complex(subband_weights[0, 1, 0, 511]), places=3) + self.assertAlmostEqual(0.989 + 0j, to_complex(subband_weights[0, 1, 1, 0]), places=3) + self.assertAlmostEqual(0.883 - 0.444j, to_complex(subband_weights[0, 1, 1, 511]), places=3)