Skip to content
Snippets Groups Projects
Commit 464c1ab3 authored by Jan David Mol's avatar Jan David Mol
Browse files

Merge branch 'L2SS-1010-dual-pol' into 'master'

L2SS-1070: Calibration values are actually per polarisation!

Closes L2SS-1070

See merge request !477
parents 388a6f8a 1b849ae3
Branches
Tags
1 merge request!477L2SS-1070: Calibration values are actually per polarisation!
......@@ -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,7 +471,8 @@ 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])
......@@ -479,9 +483,9 @@ class AntennaField(lofar_device):
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)
......
......@@ -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)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment