Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added
- `ElementWrapper.findall` and `ElementWrapper.find` methods
- Additional CPHD consistency checks for Support Arrays and associated unit tests


## [1.5.0] - 2026-03-04
Expand Down
68 changes: 68 additions & 0 deletions sarkit/verification/_cphd_consistency.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,23 @@ def check_data_num_support_arrays(self):
"""/Data/NumSupportArrays matches #{/Data/SupportArray} nodes"""
self._check_count("./{*}Data/{*}NumSupportArrays", "./{*}Data/{*}SupportArray")

def check_support_arrays_have_data(self):
"""Each /SupportArray array has a corresponding /Data/SupportArray"""
sa_id_nodes = self.cphdroot.findall("{*}SupportArray/*/{*}Identifier")
with self.precondition():
assert sa_id_nodes
for sa_id_node in sa_id_nodes:
sa_id = sa_id_node.text
with self.need(
f"Support array with identifier {sa_id} has an entry in /Data/SupportArray"
):
assert (
self.cphdroot.find(
f"{{*}}Data/{{*}}SupportArray[{{*}}Identifier='{sa_id}']"
)
is not None
)

def check_dwell_num_cod_times(self):
"""/Dwell/NumCODTimes matches #{/Dwell/CODTime} nodes"""
self._check_count("./{*}Dwell/{*}NumCODTimes", "./{*}Dwell/{*}CODTime")
Expand Down Expand Up @@ -457,6 +474,22 @@ def check_channel_dwell_usedta(self, channel_id, channel_node):
):
assert channel_node.find("./{*}DwellTimes/{*}DTAId") is not None

@per_channel
def check_channel_dtaid(self, channel_id, channel_node):
"""If a DTAId is specified, the support array exists"""
with self.precondition():
dtaid = channel_node.findtext("./{*}DwellTimes/{*}DTAId")
assert dtaid is not None
with self.need(
f"Dwell Time support array with ID {dtaid} (used by channel {channel_id}) exists."
):
assert (
self.cphdroot.find(
f"{{*}}SupportArray/{{*}}DwellTimeArray[{{*}}Identifier='{dtaid}']"
)
is not None
)

@per_channel
def check_channel_dwell_polys(self, channel_id, channel_node):
"""/Dwell/CODTime/CODTimePoly and /Dwell/DwellTime/DwellTimePoly are consistent with other metadata."""
Expand Down Expand Up @@ -586,6 +619,41 @@ def check_antenna_array_element_antgpid(self):
):
assert has_array_ant_gp_id == has_element_ant_gp_id

def check_antenna_array_element_antgpid_exist(self):
"""Check that used GPIds exist"""
gpid_nodes = self.cphdroot.findall("{*}Antenna/{*}AntPattern/*/{*}AntGPId")
with self.precondition():
assert gpid_nodes
for gpid_node in gpid_nodes:
gpid = gpid_node.text
with self.need(f"GainPhase array {gpid} exists"):
assert (
self.cphdroot.find(
f"{{*}}SupportArray/{{*}}AntGainPhase[{{*}}Identifier='{gpid}']"
)
is not None
)

def check_antenna_arrayid_elementid_exist(self):
"""Check that used gain phase arrays exist"""
array_id_nodes = self.cphdroot.findall(
"{*}Antenna/{*}AntPattern/{*}GainPhaseArray/{*}ArrayId"
)
element_id_nodes = self.cphdroot.findall(
"{*}Antenna/{*}AntPattern/{*}GainPhaseArray/{*}ElementId"
)
with self.precondition():
assert array_id_nodes + element_id_nodes
for gpid_node in array_id_nodes + element_id_nodes:
gpid = gpid_node.text
with self.need(f"GainPhase array {gpid} exists"):
assert (
self.cphdroot.find(
f"{{*}}SupportArray/{{*}}AntGainPhase[{{*}}Identifier='{gpid}']"
)
is not None
)

@per_channel
def check_channel_antenna_exist(self, channel_id, channel_node):
"""The antenna patterns and phase centers exist if declared."""
Expand Down
63 changes: 63 additions & 0 deletions tests/verification/test_cphd_consistency.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,20 @@ def test_check_count_mismatch(good_xml, tag_to_invalidate, check, err_txt):
testing.assert_failures(cphd_con, err_txt)


def test_check_support_arrays_have_data(good_xml):
bad_xml = copy_xml(good_xml)
sa_id = bad_xml.findtext("{*}SupportArray/*/{*}Identifier")
remove_nodes(
bad_xml.find(f'{{*}}Data/{{*}}SupportArray[{{*}}Identifier="{sa_id}"]')
)
cphd_con = CphdConsistency(bad_xml)
cphd_con.check("check_support_arrays_have_data")
testing.assert_failures(
cphd_con,
f"Support array with identifier {sa_id} has an entry in /Data/SupportArray",
)


@pytest.mark.parametrize(
"bad_num_bytes_pvp, err_txt",
[("0", "NumBytesPVP > 0"), ("23", "NumBytesPVP is a multiple of 8")],
Expand Down Expand Up @@ -400,6 +414,37 @@ def test_check_antenna_array_element_antgpid(good_xml_root, em):
)


def test_check_antenna_array_element_antgpid_exist(good_xml, em):
bad_xml = copy_xml(good_xml)
gpid = "mock-antgpid"
bad_xml.find("./{*}Antenna/{*}AntPattern/{*}Element/{*}PhasePoly").addnext(
em.AntGPId(gpid)
)
cphd_con = CphdConsistency(bad_xml)
cphd_con.check("check_antenna_array_element_antgpid_exist")
testing.assert_failures(
cphd_con,
f"GainPhase array {gpid} exists",
)


@pytest.mark.parametrize("kind", ["Array", "Element"])
def test_check_antenna_arrayid_elementid_exist(good_xml, em, kind):
bad_xml = copy_xml(good_xml)
mock_gpid = "mock-antgpid"
gpid_node = bad_xml.find(
f"{{*}}Antenna/{{*}}AntPattern/{{*}}GainPhaseArray/{{*}}{kind}Id"
)
gpid_node.text = mock_gpid

cphd_con = CphdConsistency(bad_xml)
cphd_con.check("check_antenna_arrayid_elementid_exist")
testing.assert_failures(
cphd_con,
f"GainPhase array {mock_gpid} exists",
)


def test_chan_antenna_no_apcs(good_xml_root):
bad_xml = copy_xml(good_xml_root)

Expand Down Expand Up @@ -1222,6 +1267,24 @@ def test_channel_dwell_usedta(cphd_con_from_file, em):
)


def test_channel_dtaid(cphd_con_from_file, em):
cphd_con = cphd_con_from_file
assert (
cphd_con.cphdroot.find("./{*}Channel/{*}Parameters/{*}DwellTimes/{*}DTAId")
is None
)
mock_dtaid = "dummy_id"
cphd_con.cphdroot.find("{*}Channel/{*}Parameters/{*}DwellTimes").append(
em.DTAId(mock_dtaid)
)

cphd_con.check("check_channel_dtaid", allow_prefix=True)
testing.assert_failures(
cphd_con,
f"Dwell Time support array with ID {mock_dtaid}.*exists",
)


def test_no_dwelltime_node(cphd_con_from_file):
cphd_con = cphd_con_from_file
remove_nodes(cphd_con.cphdroot.find("./{*}Dwell/{*}DwellTime"))
Expand Down
Loading