"""
Used for checking CSP configuration strings for conformance
"""
from inspect import cleandoc
from itertools import repeat
from schema import And, Optional, Or, Regex, Schema
from .._common import (
MID_MKT,
MID_SKA,
TMSchema,
mk_if,
split_interface_version,
)
from ..pss.schema import get_pss_config_schema
from ..pss.version import (
PSS_CONFIG_VER0_0,
PSS_CONFIG_VER0_1,
PSS_CONFIG_VER1_0,
)
from ..pst.schema import _get_pst_config_schema
from . import validators as validators
from . import version as csp_version
from .common_schema import (
CHANNEL_WIDTH_VALUES,
IPV4_REGEX_PATTERN,
MAX_CHANNELS_PER_FSP,
MAX_CORR_CHANNELS,
MAX_PORT_VALUE,
MAX_SIGNED_32BIT_INT,
_get_common_config_schema_without_band,
add_mid_frequency_band,
use_camel_case,
)
[docs]
def get_vlbi_config_schema(version: str, strict: bool):
"""VLBI specific items
:param version: Interface Version URI
:param strict: Schema strictness
:return: the JSON schema for the Mid.CBF VLBI configuration.
"""
return TMSchema.new(
"VLBI config",
version,
strict,
schema={Optional("dummy_param"): str},
description=cleandoc(
"""
Very Long Baseline Interferometry specific parameters. To
be borrowed from IICD This section contains the parameters
relevant only for VLBI. This section is forwarded only to
CSP subelement.
"""
),
as_reference=True,
)
[docs]
def get_fsp_config_schema(version: str, strict: bool):
"""Frequency slice processor configuration schema
:param version: Interface Version URI
:param strict: Schema strictness
:return: the JSON schema for the MID.CBF FSP configuration.
"""
if_strict = mk_if(strict)
camel_case = use_camel_case(version)
(major, minor) = split_interface_version(version)
elems = TMSchema.new("FSP config", version, strict, as_reference=True)
elems.add_field(
("fsp_id" if camel_case else "fspID"),
int,
check_strict=lambda n: n >= 1 and n <= 27,
)
elems.add_field(
("function_mode" if camel_case else "functionMode"),
str,
check_strict=Or("CORR", "PSS-BF", "PST-BF", "VLBI"),
)
elems.add_opt_field(
"receptors",
[
Regex(
r"""^(SKA(00[1-9]|0[1-9][0-9]|1[0-2][0-9]|13[0-3]))|
(MKT(0[0-5][0-9]|06[0-3]))$"""
)
if strict
else str
],
description=cleandoc(
"""
Optionally a subset of receptors to be correlated can be
specified. If not specified, all receptors that belong to
the subarray are cross-correlated (i.e. visibilities for
all the baselines in the subarray are generated and
transmitted to SDP).
Valid receptor IDs include:
SKA dishes: "SKAnnn", where nnn is a zero padded integer in the
range of 001 to 133.
MeerKAT dishes: "MKTnnn", where nnn is a zero padded integer in the
range of 000 to 063.
"""
),
)
elems.add_field(
("frequency_slice_id" if camel_case else "frequencySliceID"),
int,
check_strict=lambda n: n >= 1 and n <= 26,
description=cleandoc(
"""
Frequency Slice to be processed on this FSP (valid range
depends on the Frequency Band).
"""
),
)
if (major, minor) < (3, 0):
elems.add_field(
("zoom_factor" if camel_case else "corrBandwidth"),
int,
check_strict=lambda n: n >= 0 and n <= 6,
description=cleandoc(
"""
Bandwidth to be correlated calculated as FSBW/2n, where n is in
range [0..6].
When n=0 the full Frequency Slice bandwidth is correlated.
BW > 0 implies ‘Zoom Window’ configuration; the spectral
Zoom Window tuning must be specified.
"""
),
)
elems.add_opt_field(
("zoom_window_tuning" if camel_case else "zoomWindowTuning"),
int,
description=cleandoc(
"""
The Zoom Window tuning provided in absolute terms as RF
center frequency. Based on that, CSP_Mid calculates
tuning within the data stream received from the receptor.
Must be selected so that the entire Zoom Window is within
the Frequency Slice. If partially out of the FS a warning
is generated. If completely outside of the FS an
exception is generated.
Step size <= 0.01MHz.
The Frequency Band Offset can be used to shift the entire
observed band in order to accommodate a Zoom Window that
spans across a Frequency Slice boundary.
"""
),
)
if (major, minor) < (3, 0):
elems.add_field(
("integration_factor" if camel_case else "integrationTime"),
(
And(int, if_strict(validators.validate_integration_factor))
if camel_case
else 1400
),
description="Integration time for the "
"correlation products, defines multiple of 140 milliseconds.",
)
else:
elems.add_field(
("integration_factor" if camel_case else "integrationTime"),
(
And(int, if_strict(validators.validate_integration_factor))
if camel_case
else 1400
),
description=cleandoc(
"""
Integration time for the correlation products, defines
multiple of 140 milliseconds.
Range: Integer from 1-10 inclusive
"""
),
)
if (major, minor) < (3, 0):
elems.add_opt_field(
("output_mac" if camel_case else "outputMac"),
[And([int, str], if_strict(lambda lst: len(lst) == 2))],
description=cleandoc(
"""
Output MAC address to send visibilities to for every channel,
given as a list of start channel ID to IEEE 802 MAC
addresses. Where no value is given for a concrete channel,
the previous value should be used.
"""
),
)
if (major, minor) < (3, 0):
elems.add_opt_field(
("channel_averaging_map" if camel_case else "channelAveragingMap"),
[And([int], if_strict(lambda lst: len(lst) == 2))],
description=cleandoc(
"""
Table of up to 20 x 2 integers. Each of entries contains:
* Start channel ID, and
* averaging factor.
Explanation: Each FSP produces 14880 (TBC) fine channels
across the correlated bandwidth (Frequency Slice or Zoom
Window). Channels are evenly spaced in frequency.
TM shall provide the table that for each FSP and each
group of 744 channels (there are 20 groups per FSP)
indicates the channel averaging factor. More precisely,
for each group the TMC provided table specifies:
* the channel ID (integer) of the first channel, and
* the averaging factor, as follows:
* 0 means do not send channels to SDP,
* 1 means no averaging,
* 2 means average two adjacent channels,
* 3 means average three adjacent channels,
and so on.
If no entry is present for an FSP, the averaging settings
of the previous FSP are still applicable.
"""
),
)
else:
elems.add_opt_field(
("channel_averaging_map" if camel_case else "channelAveragingMap"),
[And([int], if_strict(lambda lst: len(lst) == 2))],
description=cleandoc(
"""
Table of up to 20 x 2 integers. Each of entries contains:
* Start channel ID, and
* averaging factor.
Explanation: Each FSP produces 14880 (TBC) fine channels
across the correlated bandwidth (Frequency Slice or Zoom
Window). Channels are evenly spaced in frequency.
TM shall provide the table that for each FSP and each
group of 744 channels (there are 20 groups per FSP)
indicates the channel averaging factor. More precisely,
for each group the TMC provided table specifies:
The start channel ID will be shifted internally
according to the channel_offset value if specified.
* the channel ID (integer) of the first channel, and
* the averaging factor, as follows:
* 0 means do not send channels to SDP,
* 1 means no averaging,
* 2 means average two adjacent channels,
* 3 means average three adjacent channels,
and so on.
If no entry is present for an FSP, the averaging settings
of the previous FSP are still applicable.
"""
),
)
if (major, minor) < (3, 0):
elems.add_opt_field(
("channel_offset" if camel_case else "fspChannelOffset"),
int,
description=cleandoc(
"""
Channel ID to use for visibilities of the first channel
produced by this FSP. For example, if the channel offset is
5000 the first channel group would span IDs 5000-5743.
Note that this offset does not apply to channel maps in
this structure (such as `channelAveragingMap` or
`outputHost`).
"""
),
)
else:
elems.add_opt_field(
("channel_offset" if camel_case else "fspChannelOffset"),
int,
description=cleandoc(
"""
Channel ID to use for visibilities of the first channel
produced by this FSP. For example, if the channel offset is
5000 the first channel group would span IDs 5000-5743.
Note that this offset does not apply to channel maps in
this structure:
* `channel_averaging_map`
* `output_link_map`
* `output_host`
* `output_port`
"""
),
default=0,
)
if (major, minor) < (3, 0):
elems.add_opt_field(
("output_link_map" if camel_case else "outputLinkMap"),
[And([int, str], if_strict(lambda lst: len(lst) == 2))],
description=cleandoc(
"""
Output links to emit visibilities on for every channel, given
as a list of start channel ID to link ID. Where no value is
given for concrete channel, the previous value should be
used.
"""
),
)
else:
elems.add_field(
("output_link_map" if camel_case else "outputLinkMap"),
[[int]],
check_strict=lambda table: len(table) == 1
and False
not in list(
map(
validators.validate_output_link_map,
table,
repeat(MAX_CHANNELS_PER_FSP - 1),
)
),
description=cleandoc(
"""
Output links to emit visibilities on for every channel, given
as a list of start channel ID to link ID. Where no value is
given for concrete channel, the previous value should be
used.
For AA0.5 and AA1, the link map will only allow a single entry
with a link ID of 1.
The start channel ID will be shifted internally
according to the channel_offset value if specified.
Ranges:
Start Channel ID: Integer from 0 to 14879 inclusive
Link ID: Integer of value 1
"""
),
)
if (major, minor) < (3, 0):
elems.add_opt_field(
("output_host" if camel_case else "outputHost"),
[And([int, str], if_strict(lambda lst: len(lst) == 2))],
description=cleandoc(
"""
Output host to send visibilities to for every channel, given
as a list of start channel ID to host IP addresses in
dot-decimal notation. Where no value is given for a concrete
channel, the previous value should be used.
"""
),
)
elems.add_opt_field(
("output_port" if camel_case else "outputPort"),
[
And(
[int],
if_strict(lambda lst: len(lst) >= 2 and len(lst) <= 3),
)
],
description=cleandoc(
"""
Output port to send visibilities to for every channel, given
as a list of start channel ID to port number. Where no value
is given for a concrete channel, the previous value should be
used.
"""
),
)
else:
elems.add_opt_field(
("output_host" if camel_case else "outputHost"),
[[int, str]],
check_strict=lambda table: False
not in list(
map(
validators.validate_output_host,
table,
repeat(MAX_CHANNELS_PER_FSP - 1),
)
),
description=cleandoc(
"""
Output host to send visibilities to for every channel, given
as a list of start channel ID to host IP addresses in
dot-decimal notation. Where no value is given for a concrete
channel, the previous value should be used.
If `output_host` is not specified, then the Mid.CBF will not
send any output products to SDP.
The start channel ID will be shifted internally
according to the channel_offset value if specified.
Ranges:
Start Channel ID: Integer from channel_offset to
(channel_offset + 14879) inclusive
Host IP: String w/ any valid IPv4 in dot-decimal notation
[0.0.0.0-255.255.255.255]
"""
),
)
elems.add_opt_field(
("output_port" if camel_case else "outputPort"),
[[int]],
check_strict=lambda table: validators.validate_output_port(
table, major, minor
),
description=cleandoc(
"""
Output port to send visibilities to for every channel, given
as a list of start channel ID to port number. When no port
number is given for a concrete channel, the previous value
will be used. The format is a restricted
:ref:`ska-telmodel-channel-map` (see
restrictions below)
If `output_port` is not specified, then the Mid.CBF will not
send any output products to SDP.
The start channel ID will be shifted internally
according to the channel_offset value if specified.
Example::
channel_offset: 111
output_port: [
[0, 9000],
[20, 9001],
[40, 9002],
...
[14840, 9742],
[14860, 9743]
]
Which will unpack to::
Channel 111 to port 9000
Channel 112 to port 9000
...
Channel 130 to port 9000
Channel 131 to port 9001
...
Channel 150 to port 9001
Channel 151 to port 9002
...
Channel 14950 to port 9741
Channel 14951 to port 9742
...
Channel 14970 to port 9742
Channel 14971 to port 9743
...
Channel 14990 to port 9743
Format Restrictions
At most 744 ports can be defined for an FSP
The first start channel ID in the list must be 0
Start channel IDs must be in ascending order
For AA0.5 and AA1, the start channel IDs must be in increments
of 20
At most 20 channels can be sent to the same port per host
Start Channel ID: Integer from 0 to 14879 inclusive
Port Number: Integer from 0 to 65535 inclusive
"""
),
)
return elems
[docs]
def get_cbf_config_schema(
version: str, strict: bool, tmc_schema_uri: str = ""
) -> Schema:
"""Correlator and Beamformer configuration schema
:param version: Interface Version URI
:param strict: Schema strictness
:param tmc_schema_uri: tmc URI version. Used to differentiate
the OSO-TMC-CSP schema according
to the TMC interface version
:return: the JSON Schema for the Mid.CBF configuration.
"""
# Sub-schemas
searchWindow = get_search_window_config_schema(version, strict)
# cbf specific items
camel_case = use_camel_case(version)
(major, minor) = split_interface_version(version)
if (major, minor) < (4, 0):
elems = TMSchema.new(
"CBF config",
version,
strict,
description=cleandoc(
"""
Correlator and Beamformer specific parameters. This section
contains the parameters relevant only for CBF
sub-element. This section is forwarded only to CBF
subelement. Most of it to be borrowed from IICD
"""
),
as_reference=True,
)
else:
elems = TMSchema.new(
"Mid.CBF config",
version,
strict,
description=cleandoc(
"""
Correlator and Beamformer specific parameters. This section
contains the parameters relevant only for Mid.CBF
sub-element. This section is forwarded only to Mid.CBF
subelement.
"""
),
as_reference=True,
)
if (major, minor) < (3, 0):
elems.add_opt_field(
(
"frequency_band_offset_stream1"
if camel_case
else "frequencyBandOffsetStream1"
),
int,
description=cleandoc(
"""
Optionally, an offset can be specified so that the entire
observed band is shifted (to accommodate a Zoom Window that
crosses a ‘natural’ Frequency Slice boundary). If specified,
applies for all the receptors in the sub-array.
Bands 1 and 2: input from the receptor consists of a
single data stream; the Frequency Band Offset (FBO) should
be specified for Stream 1 only.
Bands 5a and 5b: input from the receptor consists of two
data streams; the FBO can be specified for each stream
independently. Note: For Band 5a and 5b the frequency
shift is performed by the receptor (DISH).
Note: This is optional and does not need to be implemented
in PI3, but would be great for demo; if Team Buttons is
looking for opportunities to showcase interesting GUIs,
Zoom Windows are perfect opportunity (would require TMC
and CSP to support these two parameters, corrBandwidth
values > 0 and zoom window tuning.)
"""
),
)
elems.add_opt_field(
(
"frequency_band_offset_stream2"
if camel_case
else "frequencyBandOffsetStream2"
),
int,
description="See `frequencyBandOffsetStream1`",
)
else:
elems.add_opt_field(
(
"frequency_band_offset_stream1"
if camel_case
else "frequencyBandOffsetStream1"
),
int,
check_strict=lambda n: n >= -100000000 and n <= 100000000,
description=cleandoc(
"""
Optionally, an offset can be specified so that the entire
observed band is shifted in Hz (to accommodate a Zoom Window
that crosses a ‘natural’ Frequency Slice boundary). If
specified, applies for all the receptors in the sub-array.
Bands 1 and 2: input from the receptor consists of a
single data stream; the Frequency Band Offset (FBO) should
be specified for Stream 1 only.
Bands 5a and 5b: input from the receptor consists of two
data streams; the FBO can be specified for each stream
independently. Note: For Band 5a and 5b the frequency
shift is performed by the receptor (DISH).
Range: Integer from -100000000 to 100000000 inclusive (+/- 0.5
* Frequency Slice BW)
"""
),
)
elems.add_opt_field(
(
"frequency_band_offset_stream2"
if camel_case
else "frequencyBandOffsetStream2"
),
int,
check_strict=lambda n: n >= -100000000 and n <= 100000000,
description=cleandoc(
"""
See `frequencyBandOffsetStream1`
Range: Integer from -100000000 to 100000000 inclusive (+/- 0.5
* Frequency Slice BW)
"""
),
)
if (major, minor) < (3, 0):
elems.add_opt_field(
(
"delay_model_subscription_point"
if camel_case
else "delayModelSubscriptionPoint"
),
str,
description=cleandoc(
"""
FQDN of TMC.DelayModel TANGO attribute which exposes
delay values for all the dishes assigned to a Subarray
in JSON format. Delay values are updated every 10 seconds.
"""
),
)
else:
elems.add_field(
(
"delay_model_subscription_point"
if camel_case
else "delayModelSubscriptionPoint"
),
str,
description=cleandoc(
"""
FQDN of TMC.DelayModel TANGO attribute which exposes
delay values for all the dishes assigned to a Subarray
in JSON format. Delay values are updated every 10 seconds.
"""
),
)
if (major, minor) < (3, 0):
elems.add_opt_field(
(
"doppler_phase_corr_subscription_point"
if camel_case
else "dopplerPhaseCorrSubscriptionPoint"
),
str,
description=cleandoc(
"""
The same model applies for all receptors that belong to
the subarray. Delivered by TMC using publish-subscribe
mechanism (see ICD Section 3.8.8.5.3). The Doppler phase
correction, by default, applies only to the CSP_Mid
Processing Mode Correlation; optionally may apply to other
Processing Modes as well.
"""
),
)
elems.add_opt_field(
("rfi_flagging_mask" if camel_case else "rfiFlaggingMask"),
{},
description=cleandoc(
"""
Specified as needed in advance of the scan start and/or
during the scan. Delivered using publish-subscribe
mechanism (see ICD Section 3.8.8.5.7).
"""
),
)
if (major, minor) < (4, 0):
elems.add_field("fsp", [get_fsp_config_schema(version, strict)])
elif (major, minor) < (5, 0):
elems.add_field(
"correlation",
get_correlation_config_schema(
version, strict, tmc_schema_uri=tmc_schema_uri
),
)
else:
elems.add_opt_field(
"correlation",
get_correlation_config_schema(
version, strict, tmc_schema_uri=tmc_schema_uri
),
)
# field did not exist prior to this version
if (major, minor) >= (5, 0):
elems.add_opt_field(
"pst_bf",
get_pst_bf_config_schema(version, strict),
)
elems.add_opt_field("vlbi", get_vlbi_config_schema(version, strict))
elems.add_opt_field("search_window", [searchWindow])
return elems
[docs]
def get_cbf_config_schema_oso_tmc(
version: str, strict: bool, tmc_schema_uri: str
) -> Schema:
"""Correlator and Beamformer configuration schema received under TMC-OSO
:param version: Interface Version URI
:param strict: Schema strictness
:param tmc_schema_uri: tmc URI version. Used to differentiate the
OSO-TMC-CSP schema according
to the TMC interface version
:return: the JSON Schema for the MID.CBF configuration.
"""
# mid cbf specific items
camel_case = use_camel_case(version)
(major, minor) = split_interface_version(version)
elems = TMSchema.new(
"Mid CBF config",
version,
strict,
description=cleandoc(
"""
Correlator and Beamformer specific parameters. This section
contains the parameters relevant only for CBF
sub-element. This section is forwarded only to CBF
subelement. Most of it to be borrowed from IICD
"""
),
as_reference=True,
)
elems.add_opt_field(
(
"frequency_band_offset_stream1"
if camel_case
else "frequencyBandOffsetStream1"
),
int,
check_strict=lambda n: n >= -100000000 and n <= 100000000,
description=cleandoc(
"""
Optionally, an offset can be specified so that the entire
observed band is shifted in Hz (to accommodate a Zoom Window
that crosses a ‘natural’ Frequency Slice boundary). If
specified, applies for all the receptors in the sub-array.
Bands 1 and 2: input from the receptor consists of a
single data stream; the Frequency Band Offset (FBO) should
be specified for Stream 1 only.
Bands 5a and 5b: input from the receptor consists of two
data streams; the FBO can be specified for each stream
independently. Note: For Band 5a and 5b the frequency
shift is performed by the receptor (DISH).
Range: Integer from -100000000 to 100000000 inclusive (+/- 0.5
* Frequency Slice BW)
"""
),
)
elems.add_opt_field(
(
"frequency_band_offset_stream2"
if camel_case
else "frequencyBandOffsetStream2"
),
int,
check_strict=lambda n: n >= -100000000 and n <= 100000000,
description=cleandoc(
"""
See `frequencyBandOffsetStream1`
Range: Integer from -100000000 to 100000000 inclusive (+/- 0.5
* Frequency Slice BW)
"""
),
)
if (major, minor) < (5, 0):
elems.add_field(
"correlation",
get_correlation_config_schema(
version, strict, tmc_schema_uri=tmc_schema_uri
),
)
else:
elems.add_opt_field(
"correlation",
get_correlation_config_schema(
version, strict, tmc_schema_uri=tmc_schema_uri
),
)
# field did not exist prior to this version
if (major, minor) >= (5, 0):
elems.add_opt_field(
"pst_bf",
get_pst_bf_config_schema(version, strict),
)
elems.add_opt_field("vlbi", get_vlbi_config_schema(version, strict))
return elems
[docs]
def get_search_window_config_schema(version: str, strict: bool) -> Schema:
"""SearchWindow configuration schema
:param version: Interface Version URI
:param strict: Schema strictness
:return: the JSON Schema for the MID.CBF SearchWindow configuration.
"""
# Search window schema
camel_case = use_camel_case(version)
(major, minor) = split_interface_version(version)
elems = TMSchema.new(
"Search window config",
version,
strict,
description=cleandoc(
"""
Up to two 300 MHz Search Windows can be optionally configured and
used as input for Transient Data Capture and/or Pulsar Search
beam-forming.
"""
),
as_reference=True,
)
elems.add_field(
("search_window_id" if camel_case else "searchWindowID"),
int,
description="Identifier of the 300MHz Search Window. "
"Unique within a sub-array.",
)
elems.add_field(
("search_window_tuning" if camel_case else "searchWindowTuning"),
int,
description=cleandoc(
"""
The Search Window tuning is provided in absolute terms as RF
center frequency. The Search Window must be placed
within the observed band. If partially out of the
observed Band a warning is generated. If completely
outside of the observed Band an exception is
generated.
"""
),
)
if (major, minor) < (3, 0):
elems.add_field(
("tdc_enable" if camel_case else "tdcEnable"),
bool,
description="Enable / disable Transient Data Capture"
"for the Search Window.",
)
elems.add_opt_field(
("tdc_num_bits" if camel_case else "tdcNumBits"),
int,
description=cleandoc(
"""
Number of bits per sample (for the Transient Data Capture).
Required if TDC is enabled, otherwise not specified.
"""
),
)
elems.add_opt_field(
(
"tdc_period_before_epoch"
if camel_case
else "tdcPeriodBeforeEpoch"
),
int,
description=cleandoc(
"""
Users can trade the period of time for which data are saved and
transmitted for the sample bit-width and/or the number of
Search Windows. The exact information regarding the
memory capacity per receptor and supported range will be
provided in construction.
The epoch is specified in the command that triggers TDC
off-loading (transmission of data).
"""
),
)
elems.add_opt_field(
(
"tdc_period_after_epoch"
if camel_case
else "tdcPeriodAfterEpoch"
),
int,
description="see `tdcPeriodBeforeEpoch`",
)
elems.add_opt_field(
(
"tdc_destination_address"
if camel_case
else "tdcDestinationAddress"
),
[int, str, str, str],
description=cleandoc(
"""
Destination addresses (MAC, IP, port) for off-loading of the
content of the Transient Data Capture Buffer, specified
per receptor. The destination addresses for the content of
the Transient Data Capture can be provided either as a
part of the scan configuration or by the command that
triggers transmission of the captured data. The latter, if
provided, overrides previously set addresses.
Required if TDC is enabled, otherwise not specified.
"""
),
)
return elems
[docs]
def get_correlation_config_schema(
version: str, strict: bool, tmc_schema_uri: str
) -> Schema:
"""CSP Correlation configuration schema
:param version: Interface Version URI
:param strict: Schema strictness
:param tmc_schema_uri: tmc URI version. Used to differentiate
the OSO-TMC-CSP schema according
to the TMC interface version
:return: the JSON Schema for the CSP subarray specific configuraiton.
"""
elems = TMSchema.new(
"Correlation config",
version,
strict,
description="Correlation specific parameters",
as_reference=True,
)
elems.add_field(
"processing_regions",
[
get_corr_processing_region_config_schema(
version, strict, tmc_schema_uri=tmc_schema_uri
)
],
)
return elems
[docs]
def get_corr_processing_region_config_schema(
version: str, strict: bool, tmc_schema_uri: str
) -> Schema:
"""Correlation Processing Region configuration schema
:param version: Interface Version URI
:param strict: Schema strictness
:param tmc_schema_uri: tmc URI version. Used to differentiate the
OSO-TMC-CSP schema according
to the TMC interface version
:return: the JSON Schema for the Correlation Processing Region specific
configuration.
"""
(major, minor) = split_interface_version(version)
elems = TMSchema.new(
"Correlation Processing Region config",
version,
strict,
description="Parameters that define a processing region of spectrum",
as_reference=True,
)
elems.add_field(
"fsp_ids",
[int],
check_strict=lambda table: len(table) > 0
and len(table) <= 26
and False not in list(map(lambda n: n >= 1 and n <= 27, table)),
description=cleandoc(
"""
List of FSPs to be used for the processing region
For non-zoom, the limit is 13 because that is the number of FSPs
needed to process data from one 2.5 GHz sub-band, which is the
maximum bandwidth that can be stitched together. Band 5 sub-bands
(a and b) cannot be combined in a single processing region.
Ranges:
Array Length: Array of 1 to 26 Integers inclusive
Array Element: Integer from 1 to 27 inclusive
Notes: AA0.5 supports an array of 1 to 4 integers AA1 supports an
array of 1 to 8 integers
"""
),
)
elems.add_opt_field(
"receptors",
[Or(MID_SKA, MID_MKT) if strict else str],
description=cleandoc(
"""
Optionally a subset of receptors to be correlated can be
specified. If not specified, all receptors that belong to
the subarray are cross-correlated (i.e. visibilities for
all the baselines in the subarray are generated and
transmitted to SDP).
Valid receptor IDs include:
SKA dishes: "SKAnnn", where nnn is a zero padded integer in the
range of 001 to 133.
MeerKAT dishes: "MKTnnn", where nnn is a zero padded integer in the
range of 000 to 063.
"""
),
)
if (major, minor) >= (4, 1):
elems.add_field(
"start_freq",
int,
check_strict=lambda n: n >= 0 and n <= 15400000000,
description=cleandoc(
"""
Center frequency of the first fine channel in the output
product in Hz.
Range: Integer from 0 to 15400000000 inclusive
Notes:
AA0.5 Supports start_freq of 0Hz to 1980MHz
AA0.5 Mid.CBF will produce a warning when the bandwidth
[(start_freq - channel_width/2) to (start_freq +
(channel_count - 1) * channel_width + channel_width/2)] of the
region is outside the range of bands 1 & 2 (350MHz - 1760MHz)
"""
),
)
else:
elems.add_field(
"start_freq",
int,
check_strict=lambda n: n >= 350000000 and n <= 15400000000,
description=cleandoc(
"""
Center frequency of the first fine channel in the output
product in Hz.
Note: Range for start_freq has been specified to cover the
frequency range of all bands, however the entire processing
region ((start_freq - channel_width/2) + channel_width *
channel_count) must fall within the frequency range for the
band specified for the scan configuration
Range: Integer from 350000000 to 15400000000 inclusive
"""
),
)
elems.add_field(
"channel_width",
int,
check_strict=lambda n: n in CHANNEL_WIDTH_VALUES,
description=cleandoc(
"""
The width of each fine channel in Hz.
Allowed channel widths are a fraction/multiple of the default
fine channel bandwidth: 13440 Hz.
Zoom mode will use channel width less than 13440 Hz.
Channel averaging will use channel width more than 13440 Hz.
Range: Enum value that is one of::
210
420
840
1680
3360
6720
13440
26880
40320
53760
80640
107520
161280
215040
322560
416640
430080
645120
Note: AA0.5/1 only supports 13440 channel width value
"""
),
)
elems.add_field(
"channel_count",
int,
check_strict=lambda value: value >= 1 and value <= MAX_CORR_CHANNELS,
description=cleandoc(
"""
Number of fine channels in the output product.
Range: Integer from 1 to 2147483647 inclusive
Note: AA0.5/1: Integer from 1 to 58982 inclusive and must be a
multiple of 20
"""
),
)
elems.add_field(
"sdp_start_channel_id",
int,
check_strict=lambda n: n >= 0 and n <= MAX_CORR_CHANNELS,
description=cleandoc(
"""
In the output products (visibility packets) sent to SDP, Mid.CBF
will number channels sequentially in increments of 1 starting at
the sdp_start_channel_id.
sdp_start_channel_id allows TMC to configure Mid.CBF to provide
SDP with unique channel IDs for every channel in a sub-array when
multiple processing regions are used. Mid.CBF does not require or
validate that each channel in a sub-array will have a unique
channel ID. The channel ID provided to SDP will be the zero based
channel number within the processing region plus the
sdp_start_channel_id specified for the processing region. This
means the channel with the center frequency of start_freq will
have a channel ID of sdp_start_channel_id.
Range: Integer from 0 to 2147483647 inclusive
"""
),
)
if (major, minor) < (5, 0):
elems.add_field(
"integration_factor",
int,
check_strict=lambda n: validators.validate_integration_factor(n),
description=cleandoc(
"""
Integration time for the correlation products, defines
multiple of 140 milliseconds.
Range: Integer from 1-10 inclusive
"""
),
)
else:
elems.add_field(
"integration_factor",
int,
check_strict=lambda n: validators.validate_integration_factor(n),
description=cleandoc(
"""
Integration time for the correlation products, defines
multiple of 140 milliseconds.
.. list-table::
:widths: 50 50
:header-rows: 1
* - integration_factor value
- Interval (s)
* - 1
- 0.14
* - 2
- 0.28
* - 3
- 0.42
* - 4
- 0.56
* - 5
- 0.70
* - 6
- 0.84
* - 7
- 0.98
* - 8
- 1.12
* - 9
- 1.26
* - 10
- 1.40
Range: Integer from 1-10 inclusive
"""
),
)
if tmc_schema_uri:
# this is for OSO-TMC-CSP schema generation
# to avoid method duplication
pass
else:
elems.add_opt_field(
"output_host",
[[int, str]],
check_strict=lambda table: len(table) > 0
and False
not in list(
map(
validators.validate_output_host,
table,
repeat(MAX_CORR_CHANNELS),
)
),
description=cleandoc(
"""
Output host to send visibilities to for every channel, given
as a list of start channel ID to host IP addresses in
dot-decimal notation. Where no value is given for a concrete
channel, the previous value should be used.
The first channel id in the list needs to be the same as
sdp_start_channel_id and then all the rest of the numbers are
relative to the sdp_start_channel_id.
If `output_host` is not specified, then the Mid.CBF will not
send any output products to SDP.
Ranges:
Start Channel ID: Integer from 0 to 2147483647 inclusive
Host IP: String w/ any valid IPv4 in dot-decimal notation
[0.0.0.0-255.255.255.255]
"""
),
)
elems.add_opt_field(
"output_port",
[[int]],
check_strict=lambda table: validators.validate_output_port(
table, major, minor
),
description=cleandoc(
"""
Output port to send visibilities to for every channel, given
as a list of start channel ID to port number. When no port
number is given for a concrete channel, the previous value
will be used. The format is a restricted
:ref:`ska-telmodel-channel-map` (see
restrictions below)
The first channel id in the list needs to be the same as
sdp_start_channel_id and then all the rest of the numbers are
relative to the sdp_start_channel_id.
If `output_port` is not specified, then the Mid.CBF will not
send any output products to SDP.
Example::
sdp_start_channel_id: 120
output_port: [
[120, 9000],
[140, 9001],
[160, 9002],
...
[14840, 9742],
[14860, 9743]
]
Which will unpack to::
Channel 120 to port 9000
Channel 121 to port 9000
...
Channel 139 to port 9000
Channel 140 to port 9001
...
Channel 159 to port 9001
Channel 160 to port 9002
...
Channel 14839 to port 9741
Channel 14940 to port 9742
...
Channel 14859 to port 9742
Channel 14860 to port 9743
...
Channel 14879 to port 9743
Format Restrictions
The first start channel id must match sdp_start_channel_id
Start channel IDs must be in ascending order
For AA0.5 and AA1, the start channel IDs must be in increments
of 20
At most 20 channels can be sent to the same port per host
Start Channel ID: Integer from 0 to 2147483647 inclusive
Port Number: Integer from 0 to 65535 inclusive
"""
),
)
elems.add_field(
"output_link_map",
[[int]],
check_strict=lambda table: len(table) == 1
and False
not in list(
map(
validators.validate_output_link_map,
table,
repeat(MAX_CORR_CHANNELS),
)
),
description=cleandoc(
"""
Output links to emit visibilities on for every channel, given
as a list of start channel ID to link ID. Where no value is
given for concrete channel, the previous value should be
used.
The first channel id in the list needs to be the same as
sdp_start_channel_id and then all the rest of the numbers are
relative to the sdp_start_channel_id.
For AA0.5 and AA1, the link map will only allow a single entry
with a link ID of 1.
Ranges:
Start Channel ID: Integer from 0 to 2147483647 inclusive
Link ID: Integer of value 1
"""
),
)
return elems
[docs]
def get_pst_bf_config_schema(version: str, strict: bool) -> Schema:
"""CSP Pulsar Search Timing Beam-Former configuration schema
:param version: Interface Version URI
:param strict: Schema strictness
:return: the JSON Schema for the CSP subarray specific configuraiton.
"""
elems = TMSchema.new(
"PST-BF config",
version,
strict,
description="PST Beam-Former specific parameters",
as_reference=True,
)
processing_region_schema = get_pst_bf_processing_region_config_schema(
version, strict
)
elems.add_field(
"processing_regions",
[processing_region_schema],
check_strict=And(
Schema(
name="At least one processing region",
schema=lambda value: len(value) >= 1,
),
Schema(
name="Processing regions have unique FSP IDs",
schema=lambda value: (
validators.processing_regions_have_unique_fsp_ids(value)
),
),
Schema(
name="first output_host mapping start channel matches "
+ "pst_start_channel_id",
schema=lambda value: (
validators.timing_beams_start_with_start_value(
map_property="output_host",
start_property="pst_start_channel_id",
processing_regions=value,
)
),
),
Schema(
name="first output_port mapping start channel matches "
+ "pst_start_channel_id",
schema=lambda value: (
validators.timing_beams_start_with_start_value(
map_property="output_port",
start_property="pst_start_channel_id",
processing_regions=value,
)
),
),
Schema(
name="first output_link_map mapping start channel matches "
+ "pst_start_channel_id",
schema=lambda value: (
validators.timing_beams_start_with_start_value(
map_property="output_link_map",
start_property="pst_start_channel_id",
processing_regions=value,
)
),
),
),
)
return elems
[docs]
def get_pst_bf_processing_region_config_schema(
version: str,
strict: bool,
) -> Schema:
"""PST Beam-Former Processing Region configuration schema
:param version: Interface Version URI
:param strict: Schema strictness
:return: the JSON Schema for the PST Beam-Former Processing Region specific
configuration.
"""
elems = TMSchema.new(
"PST Processing Region config",
version,
strict,
description="Parameters that define a processing region of spectrum",
as_reference=True,
)
elems.add_field(
"fsp_ids",
check=[int],
check_strict=And(
Schema(
name="Array length within the valid of range of 1 to 26 "
+ "inclusive",
schema=lambda value: len(value) >= 1 and len(value) <= 26,
),
Schema(
name="Array values within the valid range of 1 to 27 "
+ "inclusive",
schema=lambda value: False
not in list(map(lambda n: n >= 1 and n <= 27, value)),
),
Schema(
name="Array values are unique",
schema=lambda value: len(value) == len(set(value)),
),
),
description=cleandoc(
"""
List of FSPs to be used for the processing region
Ranges:
* Array Length: Array of 1 to 26 Integers inclusive
* Array Element: Integer from 1 to 27 inclusive
Notes:
For AA0.5/1:
* Array Length of 1 integer
* Array values of range 1-8 inclusive
"""
),
)
max_pst_bf_freq_range_hz = 15399982080
pst_bf_channel_width_hz = 53760
elems.add_field(
"start_freq",
int,
check_strict=And(
Schema(
name="Is within the valid range of 0 to "
+ f"{max_pst_bf_freq_range_hz} inclusive",
schema=lambda value: value >= 0
and value <= max_pst_bf_freq_range_hz,
),
Schema(
name=f"is a multiple of {pst_bf_channel_width_hz} Hz",
schema=lambda value: value % pst_bf_channel_width_hz == 0,
),
),
description=cleandoc(
f"""
Center frequency of the first fine channel in the output
product in Hz.
Range: Integer from 0 Hz to {max_pst_bf_freq_range_hz} Hz
inclusive
Checks:
* Must be a multiple of {pst_bf_channel_width_hz} Hz
Notes:
For AA0.5/1:
``start_freq`` is a Enum value that is one of:
Band 1::
296862720
495075840
693235200
891448320
Band 2::
891448320
1089607680
1287767040
1485980160
1684139520
"""
),
)
max_channel_count = 47923
elems.add_field(
"channel_count",
int,
check_strict=Schema(
name=f"Is within the valid range of 1 to {max_channel_count} "
+ "inclusive",
schema=lambda value: value >= 1 and value <= max_channel_count,
),
description=cleandoc(
f"""
Number of fine channels in the output product.
Max channel_count calculated as:
``198,180,164 Hz per FS * 13 FS / 53760 Hz per channel = 47,923``
Range:
* Integer from 1 to {max_channel_count} inclusive
Notes:
For AA0.5/1:
* The only valid value is 3700
"""
),
)
elems.add_field(
"pst_start_channel_id",
int,
check_strict=Schema(
name=f"Is within the valid range of 0 to {MAX_SIGNED_32BIT_INT} "
+ "inclusive",
schema=lambda value: value >= 0 and value <= MAX_SIGNED_32BIT_INT,
),
description=cleandoc(
f"""
Starting value for the channel numbering in the processing region.
Used only to number channels in the output product (timing beam).
Channels in the output product are numbered sequentially starting
with the ``pst_start_channel_id``. The channel with the centre
frequency of start_freq will have a channel ID of
``pst_start_channel_id``. The next channel will have the channel
ID of ``pst_start_channel_id`` + 1.
Range:
* Integer from 0 to {MAX_SIGNED_32BIT_INT} inclusive
"""
),
)
elems.add_field(
"timing_beams",
[get_pst_timing_beam_config_schema(version, strict)],
check_strict=Schema(
name="Is array length within the valid range of 1 to 16 inclusive",
schema=lambda value: len(value) > 0 and len(value) <= 16,
description=cleandoc(
"""
Parameters that define a timing beam
Range:
* Array length between 1 to 16 inclusive.
Notes:
For AA0.5/1:
* Array length of 1
"""
),
),
)
return elems
[docs]
def get_pst_timing_beam_config_schema(version: str, strict: bool) -> Schema:
"""Timing beam configuration schema
:param version: Interface Version URI
:param strict: Schema strictness
:param tmc_schema_uri: tmc URI version. Used to differentiate the
OSO-TMC-CSP schema according
to the TMC interface version
:return: the JSON Schema for the Timing beam specific
configuration.
"""
elems = TMSchema.new(
"Timing beam config",
version,
strict,
description="Parameters that define a timing beam",
as_reference=True,
)
elems.add_field(
"timing_beam_id",
int,
check_strict=Schema(
name="Is within the valid range of 1 to 16 inclusive",
schema=lambda value: value >= 1 and value <= 16,
),
description=cleandoc(
"""
Identifier for a beam timing
Each ``timing_beam_id`` can only be used by one subarray at a time.
Range:
* 1 to 16 inclusive
"""
),
)
elems.add_opt_field(
"receptors",
[Or(MID_SKA, MID_MKT) if strict else str],
description=cleandoc(
"""
Optionally a subset of receptors to be included in the timing beam
can be specified. If not specified, all receptors that belong to
the subarray are included in the timing beam.
Valid receptor IDs include:
* SKA dishes: "SKAnnn", where nnn is a zero padded integer in the
range of 001 to 133.
* MeerKAT dishes: "MKTnnn", where nnn is a zero padded integer in
the range of 000 to 063.
Note:
For AA0.5/1:
* If receptors are specified for one FSP, the same receptors need
to be specified for all the FSPs.
"""
),
)
elems.add_field(
"output_link_map",
[[int]],
check_strict=And(
Schema(
name="At least one mapping is provided",
schema=lambda value: len(value) != 0,
),
Schema(
name="Each mapping is of length 2",
schema=lambda value: False
not in list(map(lambda n: len(n) == 2, value)),
),
Schema(
name="Start channel ID is within the valid range of 0 to "
+ f"{MAX_SIGNED_32BIT_INT} inclusive",
schema=lambda value: False
not in list(
map(
lambda n: n[0] >= 0 and n[0] <= MAX_SIGNED_32BIT_INT,
value,
)
),
),
Schema(
name="Start channel IDs are in ascending order",
schema=lambda value: validators.channel_ids_in_asc_order(
value
),
),
Schema(
name="Link value greater than 0",
schema=lambda value: False
not in list(map(lambda n: n[1] > 0, value)),
),
),
description=cleandoc(
"""
Output links to emit timing beam on for every channel, given
as a list of start channel ID to link ID. Where no value is
given for concrete channel, the previous value should be
used.
Ranges:
Start Channel ID: Integer from 0 to 14879 inclusive
Link ID: Positve Integer of value
Checks:
* At least one ``output_link_map`` mapping required
* Start Channel IDs are in ascending order
* In the first mapping entry, the Start Channel ID must match
``pst_start_channel_id``
Note:
For AA0.5/1:
* ``output_link_map`` only allows a single entry with a link ID of
1.
"""
),
)
packet_size = 185
elems.add_field(
"output_host",
[[int, str]],
check_strict=And(
Schema(
name="At least one mapping is provided",
schema=lambda value: len(value) != 0,
),
Schema(
name="Start channel IDs are within the valid range of 0 "
+ f"to {MAX_SIGNED_32BIT_INT} inclusive",
schema=lambda value: False
not in list(
map(
lambda n: n[0] >= 0 and n[0] <= MAX_SIGNED_32BIT_INT,
value,
)
),
),
Schema(
name="Start channel IDs are in ascending order",
schema=lambda value: validators.channel_ids_in_asc_order(
value
),
),
Schema(
name="Host must be a valid IPv4 address",
schema=lambda value: False
not in list(
map(
lambda n: IPV4_REGEX_PATTERN.fullmatch(n[1])
is not None,
value,
)
),
),
),
description=cleandoc(
f"""
Output host to send timing beam to for every channel, given
as a list of start channel ID to host IP addresses in
dot-decimal notation. Where no value is given for a concrete
channel, the previous value should be used.
The first start channel ID will be the `pst_start_channel_id`
Ranges:
* Start Channel ID: Integer from 0 to {MAX_SIGNED_32BIT_INT}
inclusive
* Host IP: String w/ any valid IPv4 in dot-decimal notation
[0.0.0.0-255.255.255.255]
Checks:
* At least one `output_host` mapping required
* Start channel IDs are in ascending order
* In the first mapping entry, the Start Channel ID must match
``pst_start_channel_id``
Note:
For AA0.5/1 :
* Only one `output_host` mapping allowed
* Difference in Start chanenel IDs are a multiple of {packet_size}
* Start Channel ID Range is between ``pst_start_channel_id`` and
``(pst_start_channel_id + (channel_count - 185))`` inclusive
"""
),
)
elems.add_field(
"output_port",
check=[[int]],
check_strict=And(
Schema(
name="At least one mapping is provided",
schema=lambda value: len(value) != 0,
),
Schema(
name="Each mapping is of length 2",
schema=lambda value: False
not in list(map(lambda n: len(n) == 2, value)),
),
Schema(
name="Start channel IDs are within the valid range of 0 "
+ f"to {MAX_SIGNED_32BIT_INT} inclusive",
schema=lambda value: False
not in list(
map(
lambda n: n[0] >= 0 and n[0] <= MAX_SIGNED_32BIT_INT,
value,
)
),
),
Schema(
name="Start channel IDs are in ascending order",
schema=lambda value: validators.channel_ids_in_asc_order(
value
),
),
Schema(
name="Port numbers are with the valid range of 0 to "
+ f"{MAX_PORT_VALUE} inclusive",
schema=lambda value: False
not in list(
map(lambda n: n[1] >= 0 and n[1] <= MAX_PORT_VALUE, value)
),
),
),
description=cleandoc(
"""
Output port to send beam timing to for every channel, given
as a list of start channel ID to port number. When no port
number is given for a concrete channel, the previous value
will be used. The format is a restricted
:ref:`ska-telmodel-channel-map` (see
restrictions below)
Ranges:
* Start Channel ID: Integer from 0 to 2147483647 inclusive
* Port Number: Integer from 0 to 65535 inclusive
Format Restrictions:
* At least one ``output_port`` mapping is required
* The first start channel ID in the list must be
``pst_start_channel_id``
* Start channel IDs must be in ascending order
Note:
For AA0.5/1:
* Only one ``output_port`` entry allowed
* Start channel IDs must be in increments of 185
* At most 3700 channels can be sent to the same port per host
"""
),
)
return elems
[docs]
def get_subarray_config_schema(version: str, strict: bool) -> Schema:
"""CSP Subarray configuration schema
:param version: Interface Version URI
:param strict: Schema strictness
:return: the JSON Schema for the CSP subarray specific configuraiton.
"""
# # subarray specific items
camel_case = use_camel_case(version)
elems = TMSchema.new(
"subarray",
version,
strict,
description=cleandoc(
"""
subarray section, containing the parameters relevant only for
the current sub-array device. This section is not forwarded
to any subelement.
"""
),
)
elems.add_field(
("subarray_name" if camel_case else "subarrayName"),
str,
description=cleandoc(
"""
Name and scope of current subarray
the sub-array.
"""
),
)
return elems
[docs]
def get_common_config_schema(version: str, strict: bool) -> Schema:
"""CSP Subarray common configuration schema.
This section is valid for Mid CSP because it includes
some parameters that are Mid.CBF specific.
The set of parameters that are common to Mid and Low
CSP, are retrieved by the _get_common_config_schema
method defined in the common_schema python module.
:param version: Interface Version URI
:param strict: Schema strictness
:return: the JSON Schema for the CSP subarray common
configuration (ADR-18).
"""
camel_case = use_camel_case(version)
(major, minor) = split_interface_version(version)
elems = _get_common_config_schema_without_band(version, strict)
elems.add_opt_field(
("band_5_tuning" if camel_case else "band5Tuning"),
[float],
description=cleandoc(
"""
Center frequency for the Band-of-Interest. Required if Band is 5a
or 5b; not specified for other Bands (not configurable for
Band 1 and 2).
Input for Band 5a and 5b consists of two 2.5 GHz streams;
the center frequency can be independently tuned for each
stream.
The following nomenclature is used to refer to Band 5a and
5b streams: 5a1, 5a2, 5b1, 5b2.
"""
),
)
add_mid_frequency_band(version, strict, elems)
# In versions before 1.0, FSPs were part of the common schema
if (major, minor) < (1, 0):
elems.add_field("fsp", [get_fsp_config_schema(version, strict)])
# In version 1.0, subarrayID was added
elif (major, minor) < (3, 0):
elems.add_field(
"subarray_id" if camel_case else "subarrayID",
int,
description="Subarray number",
)
else:
elems.add_field(
"subarray_id" if camel_case else "subarrayID",
int,
check_strict=lambda n: n >= 1 and n <= 16,
description=cleandoc(
"""
The Subarray ID that the list of receptors will be assigned to.
For Mid, there are a maximum of 16 subarrays.
Range: Integer from 1-16 inclusive
"""
),
)
# all subelements common schema
return elems
[docs]
def get_common_config_schema_oso_tmc(
version: str, strict: bool, tmc_schema_uri: str = ""
) -> Schema:
"""CSP Subarray common configuration schema OSO-TMC
This section is valid for Mid CSP because it includes
some parameters that are Mid CBF specific.
The set of parameters that are common to Mid and Low
CSP, are retrieved by the _get_common_config_schema
method defined in the common_schema python module.
:param version: Interface Version URI
:param strict: Schema strictness
:param tmc_schema_uri: tmc URI version. Used to differentiate
the OSO-TMC-CSP schema according
to the TMC interface version
:return: the JSON Schema for the OSO-TMC, CSP subarray
common configuration (ADR-99).
"""
camel_case = use_camel_case(version)
elems = _get_common_config_schema_without_band(
version, strict, tmc_schema_uri=tmc_schema_uri
)
elems.add_opt_field(
("band_5_tuning" if camel_case else "band5Tuning"),
[float],
description=cleandoc(
"""
Center frequency for the Band-of-Interest. Required if Band is 5a
or 5b; not specified for other Bands (not configurable for
Band 1 and 2).
Input for Band 5a and 5b consists of two 2.5 GHz streams;
the center frequency can be independently tuned for each
stream.
The following nomenclature is used to refer to Band 5a and
5b streams: 5a1, 5a2, 5b1, 5b2.
"""
),
)
add_mid_frequency_band(version, strict, elems)
# all subelements common schema
return elems
[docs]
def get_csp_config_schema(
version: str, strict: bool, tmc_schema_uri: str = ""
) -> Schema:
"""
Returns a schema to verify a CSP configuration
:param version: Interface version
:param strict: Strict mode - refuse even harmless schema
violations (like extra keys). DO NOT USE FOR INPUT VALIDATION!
:param tmc_schema_uri: TMC Interface version
When received, conditions are added to get OSO-TMC-CSP schema
else, TMC-CSP schema will be generated.
:return: The JSON Schema for the CSP configuration.
:raise: `ValueError` exception on mismatch major version or invalid JSON
Schema URI
"""
# Convert version to standard format
version = csp_version.normalize_csp_config_version(version)
(major, minor) = split_interface_version(version)
# Valid?
csp_version.check_csp_interface_version(
version,
[csp_version.CSP_CONFIG_PREFIX, csp_version.CSP_CONFIGSCAN_PREFIX],
)
# Pre-ADR-18 version with everything in the top level?
elems = TMSchema.new("CSP config", version, strict)
if major == 0:
# Overall configuration schema
elems.update(get_common_config_schema(version, strict))
return elems
elif major == 1:
# Overall configuration schema
elems.add_opt_field("interface", str)
elems.add_opt_field(
"subarray", get_subarray_config_schema(version, strict)
)
elems.add_field("common", get_common_config_schema(version, strict))
elems.add_field("cbf", get_cbf_config_schema(version, strict))
elems.add_opt_field(
"pss",
get_pss_config_schema(
PSS_CONFIG_VER0_0,
strict,
),
)
elems.add_opt_field(
"pst",
_get_pst_config_schema(version, strict, beamid_required=False),
)
return elems
elif major == 2:
# Overall configuration schema
elems.add_field("interface", str)
if (major, minor) >= (2, 6):
elems.add_opt_field(
"transaction_id",
str,
description="A transaction id specific to the command",
)
elems.add_opt_field(
"subarray", get_subarray_config_schema(version, strict)
)
elems.add_field("common", get_common_config_schema(version, strict))
elems.add_field("cbf", get_cbf_config_schema(version, strict))
if (major, minor) < (2, 1):
elems.add_opt_field(
"pss",
get_pss_config_schema(
PSS_CONFIG_VER0_0,
strict,
),
)
else:
elems.add_opt_field(
"pss",
get_pss_config_schema(
PSS_CONFIG_VER0_1,
strict,
),
)
elems.add_opt_field(
"pst",
_get_pst_config_schema(version, strict, beamid_required=False),
)
return elems
elif major == 3:
# Overall configuration schema
elems.add_field("interface", str)
elems.add_opt_field(
"transaction_id",
str,
description="A transaction id specific to the command",
)
elems.add_field("common", get_common_config_schema(version, strict))
elems.add_field("cbf", get_cbf_config_schema(version, strict))
elems.add_opt_field(
"pss",
get_pss_config_schema(
PSS_CONFIG_VER0_1,
strict,
),
)
elems.add_opt_field(
"pst",
_get_pst_config_schema(version, strict, beamid_required=False),
)
return elems
elif major == 4 or major == 5:
# Overall configuration schema
elems.add_field("interface", str)
elems.add_opt_field(
"transaction_id",
str,
description="A transaction id specific to the command",
)
if tmc_schema_uri:
elems.add_field(
"common",
get_common_config_schema_oso_tmc(
version, strict, tmc_schema_uri=tmc_schema_uri
),
)
else:
elems.add_field(
"common",
get_common_config_schema(version, strict),
)
if tmc_schema_uri:
elems.add_field(
"midcbf",
get_cbf_config_schema_oso_tmc(
version, strict, tmc_schema_uri=tmc_schema_uri
),
)
else:
elems.add_field(
"midcbf",
get_cbf_config_schema(
version, strict, tmc_schema_uri=tmc_schema_uri
),
)
elems.add_opt_field(
"pss",
get_pss_config_schema(
PSS_CONFIG_VER0_1,
strict,
),
)
elems.add_opt_field(
"pst",
_get_pst_config_schema(version, strict, beamid_required=False),
)
return elems
elif major == 6:
# Overall configuration schema
elems.add_field("interface", str)
elems.add_opt_field(
"transaction_id",
str,
description="A transaction id specific to the command",
)
if tmc_schema_uri:
elems.add_field(
"common",
get_common_config_schema_oso_tmc(
version, strict, tmc_schema_uri=tmc_schema_uri
),
)
else:
elems.add_field(
"common",
get_common_config_schema(version, strict),
)
if tmc_schema_uri:
elems.add_field(
"midcbf",
get_cbf_config_schema_oso_tmc(
version, strict, tmc_schema_uri=tmc_schema_uri
),
)
else:
elems.add_field(
"midcbf",
get_cbf_config_schema(
version, strict, tmc_schema_uri=tmc_schema_uri
),
)
elems.add_opt_field(
"pss",
get_pss_config_schema(
PSS_CONFIG_VER1_0,
strict,
),
)
elems.add_opt_field(
"pst",
_get_pst_config_schema(version, strict, beamid_required=False),
)
return elems
else:
major_version, _ = split_interface_version(version)
raise ValueError(f"Unknown major schema version: {major_version}")
[docs]
def get_csp_scan_schema(version: str, strict: bool) -> Schema:
"""
Returns the schema to verify the CSP scan command.
:param version: Interface version URI
:param strict: Strict mode. If true, refuse even harmless schema
violations (like extra keys). DO NOT USE FOR INPUT VALIDATION!
:return: The JSON Schema for the command.
:raise: `ValueError` exception on invalid JSON Schema URI.
"""
scan_schema = TMSchema.new("CSP scan", version, strict)
(major, minor) = split_interface_version(version)
scan_schema.add_field(
"interface",
str,
description="URI of JSON schema applicable to this JSON payload.",
)
if (major, minor) >= (2, 3):
scan_schema.add_opt_field(
"transaction_id",
str,
description="A transaction id specific to the command",
)
scan_schema.add_field(
"scan_id",
int,
description="Scan ID to associate with the data.",
)
return scan_schema
[docs]
def get_csp_assignresources_schema(version: str, strict: bool) -> Schema:
"""
Returns the schema to verify the CSP assignresources command.
:param version: Interface version URI
:param strict: Strict mode. If true, refuse even harmless schema
violations (like extra keys). DO NOT USE FOR INPUT VALIDATION!
:return: The JSON Schema for the command.
:raise: `ValueError` exception on invalid JSON Schema URI.
"""
camel_case = use_camel_case(version)
(major, minor) = split_interface_version(version)
assignresources_schema = TMSchema.new(
"CSP assignresources", version, strict
)
assignresources_schema.add_field(
"interface",
str,
description="URI of JSON schema applicable to this JSON payload.",
)
if (major, minor) >= (2, 3):
assignresources_schema.add_opt_field(
"transaction_id",
str,
description="A transaction id specific to the command",
)
if (major, minor) < (3, 0):
assignresources_schema.add_field(
"subarray_id" if camel_case else "subarrayID",
int,
description=cleandoc(
"""
The Subarray ID that the list of receptors will be assigned to.
For Mid, there are a maximum of 16 subarrays.
"""
),
)
else:
assignresources_schema.add_field(
"subarray_id" if camel_case else "subarrayID",
int,
check_strict=lambda n: n >= 1 and n <= 16,
description=cleandoc(
"""
The Subarray ID that the list of receptors will be assigned to.
For Mid, there are a maximum of 16 subarrays.
Range: Integer from 1-16 inclusive
"""
),
)
dish_elements = TMSchema.new("dish assignresources", version, strict)
dish_elements.add_field(
"receptor_ids",
[
Regex(
r"""^(SKA(00[1-9]|0[1-9][0-9]|1[0-2][0-9]|13[0-3]))|
(MKT(0[0-5][0-9]|06[0-3]))$"""
)
if strict
else str
],
description=cleandoc(
"""
The list of receptors that will be assigned to the Subarray ID.
Receptor IDs can be any string, not necessarily numbers.
Valid receptor IDs include:
SKA dishes: "SKAnnn", where nnn is a zero padded integer in the
range of 001 to 133.
MeerKAT dishes: "MKTnnn", where nnn is a zero padded integer in the
range of 000 to 063.
"""
),
)
assignresources_schema.add_field(
"dish",
dish_elements,
)
return assignresources_schema
[docs]
def get_csp_endscan_schema(version: str, strict: bool) -> Schema:
"""
Returns the schema to verify the CSP endscan command.
:param version: Interface version URI
:param strict: Strict mode. If true, refuse even harmless schema
violations (like extra keys). DO NOT USE FOR INPUT VALIDATION!
:return: The JSON Schema for the command.
:raise: `ValueError` exception on invalid JSON Schema URI.
"""
endscan_schema = TMSchema.new("CSP endscan", version, strict)
(major, minor) = split_interface_version(version)
endscan_schema.add_field(
"interface",
str,
description="URI of JSON schema applicable to this JSON payload.",
)
if (major, minor) >= (2, 3):
endscan_schema.add_opt_field(
"transaction_id",
str,
description="A transaction id specific to the command",
)
endscan_schema.add_field(
"scan_id",
int,
description="Scan ID to end.",
)
return endscan_schema
[docs]
def get_csp_releaseresources_schema(version: str, strict: bool) -> Schema:
"""
Returns the schema to verify the CSP releaseresources command.
:param version: Interface version URI
:param strict: Strict mode. If true, refuse even harmless schema
violations (like extra keys). DO NOT USE FOR INPUT VALIDATION!
:return: The JSON Schema for the command.
:raise: `ValueError` exception on invalid JSON Schema URI.
"""
camel_case = use_camel_case(version)
(major, minor) = split_interface_version(version)
releaseresources_schema = TMSchema.new(
"CSP releaseresources", version, strict
)
(major, minor) = split_interface_version(version)
releaseresources_schema.add_field(
"interface",
str,
description="URI of JSON schema applicable to this JSON payload.",
)
if (major, minor) >= (2, 3):
releaseresources_schema.add_opt_field(
"transaction_id",
str,
description="A transaction id specific to the command",
)
if (major, minor) < (3, 0):
releaseresources_schema.add_field(
"subarray_id" if camel_case else "subarrayID",
int,
description="Subarray ID which will have its resource(s) "
"released.",
)
else:
releaseresources_schema.add_field(
"subarray_id" if camel_case else "subarrayID",
int,
check_strict=lambda n: n >= 1 and n <= 16,
description=cleandoc(
"""
Subarray ID which will have its resource(s) released.
Range: Integer from 1-16 inclusive
"""
),
)
releaseresources_schema.add_opt_field(
"release_all",
bool,
description=cleandoc(
"""
Set to true if you wish to release all resources assigned to the
Subarray.
"""
),
)
releaseresources_schema.add_opt_field(
"receptor_ids",
[
Regex(
r"""^(SKA(00[1-9]|0[1-9][0-9]|1[0-2][0-9]|13[0-3]))|
(MKT(0[0-5][0-9]|06[0-3]))$"""
)
if strict
else str
],
description=cleandoc(
"""
The list of receptors that will be released from the Subarray ID.
Receptor IDs can be any string, not necessarily numbers.
Valid receptor IDs include:
SKA dishes: "SKAnnn", where nnn is a zero padded integer in the
range of 001 to 133.
MeerKAT dishes: "MKTnnn", where nnn is a zero padded integer in the
range of 000 to 063.
"""
),
)
return releaseresources_schema
[docs]
def get_csp_delaymodel_schema(version: str, strict: bool) -> Schema:
"""
Returns the schema to verify the CSP delaymodel command.
:param version: Interface version URI
:param strict: Strict mode. If true, refuse even harmless schema
violations (like extra keys). DO NOT USE FOR INPUT VALIDATION!
:return: The JSON Schema for the command.
:raise: `ValueError` exception on invalid JSON Schema URI.
"""
(major, minor) = split_interface_version(version)
delaymodel_schema = TMSchema.new(
"CSP delaymodel",
version,
strict,
)
delaymodel_schema.add_field(
"interface",
str,
description="URI of JSON schema applicable to this JSON payload.",
)
if (major, minor) >= (3, 0):
delaymodel_schema.add_field(
"start_validity_sec",
float,
check_strict=lambda n: n > 0.0,
description=cleandoc(
"""
Time when delay model becomes valid (when Mid.CBF shall apply
the new model), specified as the number of seconds since the
1999-12-31T23:59:28Z UTC (SKA epoch).
Note: Delay models need to be published at least 5 seconds
before the validity period starts
Range: Non-zero positive number
"""
),
)
delaymodel_schema.add_field(
"cadence_sec",
float,
check_strict=lambda n: n > 0.0,
description=cleandoc(
"""
The time in SI seconds of the planned validity
period of the delay model, measured from start_validity_sec.
Also indicates that the next delay model should be issued no
more than cadence_sec later than the current delay model that
was issued. This is a configurable field and may change during
operations, but the expected value for Mid.CBF is 10 seconds.
Mid.CBF will expect the next delay model it receives to have a
start_validity_sec <= (current start_validity_sec +
cadence_sec). If such a delay model does not arrive, Mid.CBF
will continue to use the current delay model, up to the
maximum acceptable validity period, which is
validity_period_sec. At that point, if a new delay model still
hasn't arrived, Mid.CBF will stop processing (including
outputting products) and will issue an error message.
Range: Non-zero positive number
"""
),
)
delaymodel_schema.add_field(
"validity_period_sec",
float,
check_strict=lambda n: n > 0.0,
description=cleandoc(
"""
The maximum acceptable delay model validity period in SI
seconds, starting at start_validity_sec. This is a
configurable field and may change during operations, but
the expected value for Mid.CBF is 30 seconds.
If Mid.CBF has not received, as expected, a new delay model
with a new start_validity_sec <= (start_validity_sec +
cadence_sec), it will continue to use the current delay model
for up to validity_period_sec seconds. At that point, if
a new delay model still hasn't arrived, Mid.CBF will stop
processing (including outputting products) and will issue an
error message.
Range: Non-zero positive number
"""
),
)
delaymodel_schema.add_field(
"config_id",
str,
description=cleandoc(
"""
The configuration ID of the scan that this delay model applies
to. Corresponds to "config_id" provided in the scan
configuration. This field is used to ensure that the CBF does
not use delay models from a previous observation at the start
of a new observation.
"""
),
)
delaymodel_schema.add_field(
"subarray",
int,
check_strict=lambda n: n >= 1 and n <= 16,
description=cleandoc(
"""
The subarray to which the delay models apply.
Range: Integer from 1-16 inclusive
"""
),
)
delaymodel_schema.add_field(
"receptor_delays",
[get_csp_delay_details_schema(version, strict)],
)
elif (major, minor) == (2, 2):
delaymodel_schema.add_field(
"epoch",
float,
description=cleandoc(
"""
Time when delay model becomes valid
(when Mid.CBF shall apply the new model) specified as an
offset in seconds, expressed as a float number, from
1999-12-31T23:59:28Z UTC (which is called the 'SKA epoch').
Range: 64-bit number
"""
),
)
delaymodel_schema.add_field(
"validity_period",
float,
description=cleandoc(
"""
validity period of the delay model (starting at epoch) [s]
Range: positive number
"""
),
)
delaymodel_schema.add_field(
"delay_details",
[get_csp_delay_details_schema(version, strict)],
)
return delaymodel_schema
[docs]
def get_csp_delay_details_schema(version: str, strict: bool) -> Schema:
"""
Returns the schema with the CSP delay details
:param version: Interface version URI
:param strict: Strict mode. If true, refuse even harmless schema
violations (like extra keys). DO NOT USE FOR INPUT VALIDATION!
:return: The JSON Schema for the command.
:raise: `ValueError` exception on invalid JSON Schema URI.
"""
(major, minor) = split_interface_version(version)
delaydetails_schema = TMSchema.new(
"delay details",
version,
strict,
as_reference=True,
)
if (major, minor) >= (3, 0):
delaydetails_schema.add_field(
"receptor",
(
Regex(
r"""^(SKA(00[1-9]|0[1-9][0-9]|1[0-2][0-9]|13[0-3]))|
(MKT(0[0-5][0-9]|06[0-3]))$"""
)
if strict
else str
),
description=cleandoc(
"""
The Receptor (Dish) ID to which the xypol_coeffs_ns and
ypol_offset_ns apply.
Valid receptor IDs include:
SKA dishes: "SKAnnn", where nnn is a zero padded integer in
the range of 001 to 133.
MeerKAT dishes: "MKTnnn", where nnn is a zero padded integer
in the range of 000 to 063.
"""
),
)
delaydetails_schema.add_field(
"xypol_coeffs_ns",
[float],
description=cleandoc(
"""
The delay model for a receptor is specified as a set of
coefficients for a 5th order polynomial. Coefficients of the
polynomial are specified as an array. The Coefficients apply
to both X and Y polarizations.
The delay at time t, where t is measured with respect the
beginning of the validity interval, is calculated as:
d(t) = c0 + c1*t + c2*t^2 + c3*t^3 + c4*t^4 + c5*t^5
Units for coefficients c0,c1,...,c5: ns/s^k where:
k=0,1,...,5
ns=nanoseconds
s=seconds
Type: 64 bit floating point number
"""
),
)
delaydetails_schema.add_field(
"ypol_offset_ns",
float,
description=cleandoc(
"""
Constant delay offset of polarization Y with respect to
polarization X, in nanoseconds.
Type: 64 bit floating point number
"""
),
)
elif (major, minor) == (2, 2):
delaydetails_schema.add_field(
"receptor",
(
Regex(
r"""^(SKA(00[1-9]|0[1-9][0-9]|1[0-2][0-9]|13[0-3]))|
(MKT(0[0-5][0-9]|06[0-3]))$"""
)
if strict
else str
),
description=cleandoc(
"""
The Receptor (Dish) ID to which the poly_info coeffs apply.
Valid receptor IDs include:
SKA dishes: "SKAnnn", where nnn is a zero padded integer in
the range of 001 to 133.
MeerKAT dishes: "MKTnnn", where nnn is a zero padded integer
in the range of 000 to 063.
Range: any string
"""
),
)
delaydetails_schema.add_field(
"poly_info",
[get_csp_poly_info_schema(version, strict)],
)
return delaydetails_schema
[docs]
def get_csp_poly_info_schema(version: str, strict: bool) -> Schema:
"""
Returns the schema with the CSP delay details
:param version: Interface version URI
:param strict: Strict mode. If true, refuse even harmless schema
violations (like extra keys). DO NOT USE FOR INPUT VALIDATION!
:return: The JSON Schema for the command.
:raise: `ValueError` exception on invalid JSON Schema URI.
"""
polyinfo_schema = TMSchema.new(
"poly info",
version,
strict,
as_reference=True,
)
polyinfo_schema.add_field(
"polarization",
str,
description=cleandoc(
"""
Polarization of the delay model entry
Range: X or Y
"""
),
)
polyinfo_schema.add_field(
"coeffs",
[float],
description=cleandoc(
"""
Delay Model is specified as coefficients
for a 5th order polynomial.
Coefficients of the polynomial are specified as an array.
The delay at time t, where t is measured with respect the
beginning of the validity interval is calculated as:
d(t) = c0 + c1*t + c2*t^2 + c3*t^3 + c4*t^4 + c5*t^5
Units for coefficients c0,c1,..c5:
ns/s^k where k=0,1,..5
Range for coefficients: 64 bit number
"""
),
)
return polyinfo_schema
[docs]
def get_csp_low_delaymodel_schema(version: str, strict: bool) -> Schema:
"""
Returns the schema to verify the CSP low delaymodel command.
:param version: Interface version URI
:param strict: Strict mode. If true, refuse even harmless schema
violations (like extra keys). DO NOT USE FOR INPUT VALIDATION!
:return: The JSON Schema for the command.
:raise: `ValueError` exception on invalid JSON Schema URI.
"""
delaymodel_schema = TMSchema.new(
"CSP low delaymodel",
version,
strict,
)
delaymodel_schema.add_field(
"interface",
str,
description=cleandoc(
"""
URI of JSON schema applicable to this JSON payload.
Type: str
"""
),
)
delaymodel_schema.add_field(
"start_validity_sec",
float,
description=cleandoc(
"""
Time when delay model becomes valid
Start point of polynomial validity
no sensible default. It indicates an epoch, which could be anytime.
Type: float
"""
),
)
delaymodel_schema.add_field(
"cadence_sec",
float,
check_strict=lambda n: n > 0.0,
description=cleandoc(
"""
The time in seconds between updates/publications of the
delay polynomials.
Type: float
Range: Non-zero positive number
"""
),
)
delaymodel_schema.add_field(
"validity_period_sec",
float,
check_strict=lambda n: n > 0.0,
description=cleandoc(
"""
Validity period of the delay model (starting at epoch) [s]
Type: float
Range: Non-zero positive number
"""
),
)
delaymodel_schema.add_field(
"config_id",
str,
description=cleandoc(
"""
A string, should be the same as the equivalent value in the
last "configure" JSON. If not it indicates that these are
not yet valid polys for the current configuration.
Type: str
"""
),
)
delaymodel_schema.add_field(
"station_beam",
int,
check_strict=lambda n: n >= 1 and n <= 48, # range
description=cleandoc(
"""
The station beams for which the delay
polynomials apply to.
Type: int
Range: Integer from 1-48 inclusive
"""
),
)
delaymodel_schema.add_field(
"subarray",
int,
check_strict=lambda n: n >= 1 and n <= 16,
description=cleandoc(
"""
The subarray for which the delay polynomials
apply to.
Type: int
Range: Integer from 1-16 inclusive
"""
),
)
delaymodel_schema.add_field(
"station_beam_delays",
[get_low_csp_station_beam_details_schema(version, strict)],
)
return delaymodel_schema
[docs]
def get_low_csp_station_beam_details_schema(
version: str, strict: bool
) -> Schema:
"""
Returns the schema with the Low CSP delay details
:param version: Interface version URI
:param strict: Strict mode. If true, refuse even harmless schema
violations (like extra keys). DO NOT USE FOR INPUT VALIDATION!
:return: The JSON Schema for the command.
:raise: `ValueError` exception on invalid JSON Schema URI.
"""
stationbeamdetail_schema = TMSchema.new(
"station beam delays",
version,
strict,
as_reference=True,
)
stationbeamdetail_schema.add_field(
"station_id",
int,
check_strict=lambda n: n >= 1 and n <= 512,
description=cleandoc(
"""
The station ids for which the delay polynomials
apply to.
Type: int
Range: Integer from 1-512 inclusive
"""
),
)
stationbeamdetail_schema.add_field(
"substation_id",
int,
description=cleandoc(
"""
The substation ids for which the delay polynomials
apply to.
Type: int
"""
),
)
stationbeamdetail_schema.add_field(
"xypol_coeffs_ns",
[float],
description=cleandoc(
"""
X coefficient set
Delay Model is specified as coefficients
for a 5th order polynomial.
Coefficients of the polynomial are specified as an array.
The delay at time t, where t is measured with respect the
beginning of the validity interval is calculated as:
d(t) = c0 + c1*t + c2*t^2 + c3*t^3 + c4*t^4 + c5*t^5
Units for coefficients c0,c1,..c5:
ns/s^k where k=0,1,..5
Type: float
Range for coefficients: 64 bit number
"""
),
)
stationbeamdetail_schema.add_field(
"ypol_offset_ns",
float,
description=cleandoc(
"""
Offset for the Y polarisation
Type: float
"""
),
)
return stationbeamdetail_schema