Skip to content

Commit b7d58e8

Browse files
committed
[uss_qualifier] Add ParallelFlightPlannerCombinations, parallel execution of actions generator and use everyting in f3548_21
1 parent 4841b49 commit b7d58e8

16 files changed

Lines changed: 309 additions & 29 deletions

File tree

.basedpyright/baseline.json

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4666,14 +4666,6 @@
46664666
"endColumn": 9,
46674667
"lineCount": 3
46684668
}
4669-
},
4670-
{
4671-
"code": "reportArgumentType",
4672-
"range": {
4673-
"startColumn": 20,
4674-
"endColumn": 41,
4675-
"lineCount": 1
4676-
}
46774669
}
46784670
],
46794671
"./monitoring/uss_qualifier/action_generators/interuss/mock_uss/with_locality.py": [

monitoring/uss_qualifier/action_generators/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,26 @@
33
The bulk of uss_qualifier's automated testing logic is contained in [test scenarios](../scenarios/README.md). A [test suite](../suites/README.md) is essentially a static "playlist" of test actions to perform (test scenarios, action generators, and other test suites), all of which ultimately resolve to test scenarios. An action generator is essentially a dynamic "playlist" of test actions -- it can generate test actions that vary according to provided resource values, situations, or other conditions only necessarily known at runtime.
44

55
For documentation purposes, all action generators must statically declare the test actions they may take. However, whether each (or any) of these actions will actually be taken at runtime cannot be statically determined in general.
6+
7+
## Parallel execution in action generators
8+
9+
An action generator's `actions()` method yields one of:
10+
11+
- a `TestSuiteAction` — executed sequentially, as before.
12+
- a `list[list[TestSuiteAction]]` — a *parallel group*. The outer list holds the branches to execute concurrently; each inner list is a sequence of actions executed in order within its own branch.
13+
14+
Example:
15+
16+
```python
17+
def actions(self) -> Iterator[TestSuiteAction | list[list[TestSuiteAction]]]:
18+
yield base_action # sequential
19+
yield [[A1, A2, A3], [B1, B2, B3]] # A and B in parallel
20+
```
21+
22+
When a parallel group is yielded, each branch runs on its own thread. Reports are appended to the parent report in branch order.
23+
24+
### Constraints
25+
26+
Each branch shares the same `Resource` instances unless the action generator hands out distinct ones. If a resource has mutable state that two branches would race on, the generator must produce isolated copies - typically by declaring `ResourceModifier`-based variants and calling `.adjust(index)` for each branch.
27+
28+
If a branch fails with `on_failure: Abort` (or hits a critical problem), the other branches are signalled to stop at the next action boundary. In-progress actions still finish.

monitoring/uss_qualifier/action_generators/flight_planning/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,13 @@ This action generator accepts a [FlightPlannersResource](../../resources/flight_
1919
| `ussC` | `ussC` | `ExampleTestScenario` |
2020

2121
The usage intent for this action generator is to enable design of simple test scenarios with a small number of participants, but to automatically repeat that simple scenario with all applicable role assignment combinations given a list of flight planner USSs to test.
22+
23+
## `ParallelFlightPlannerCombinations`
24+
25+
Variant of `FlightPlannerCombinations` that runs combinations in parallel where possible.
26+
27+
Same configuration as `FlightPlannerCombinations`. The only difference is scheduling: combinations sharing no flight planner participant are grouped together and executed concurrently. Combinations sharing at least one participant remain in different groups (so no participant is hit by two tests at the same time).
28+
29+
Groups are built greedily (first-fit): each combination is placed in the first existing group with no participant overlap, otherwise a new group is started. This is not minimal in the worst case but the problem is graph coloring, NP-hard.
30+
31+
Each combination receives its own `adjust(index)` variant of every `ResourceModifier` resource in the pool (inherited from `FlightPlannerCombinations`), so parallel branches don't share mutable resource state.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
11
from .planner_combinations import FlightPlannerCombinations as FlightPlannerCombinations
2+
from .planner_combinations import (
3+
ParallelFlightPlannerCombinations as ParallelFlightPlannerCombinations,
4+
)

monitoring/uss_qualifier/action_generators/flight_planning/planner_combinations.py

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
)
1818
from monitoring.uss_qualifier.resources.resource import (
1919
MissingResourceError,
20+
ResourceModifier,
2021
ResourceType,
2122
)
2223
from monitoring.uss_qualifier.suites.definitions import TestSuiteActionDeclaration
@@ -40,7 +41,7 @@ class FlightPlannerCombinationsSpecification(ImplicitDict):
4041
class FlightPlannerCombinations(
4142
ActionGenerator[FlightPlannerCombinationsSpecification]
4243
):
43-
_actions: list[TestSuiteAction]
44+
_actions_with_participants: list[tuple[TestSuiteAction, frozenset[str]]]
4445
_current_action: int
4546

4647
@classmethod
@@ -91,22 +92,34 @@ def __init__(
9192
"default flight planner combination selector",
9293
)
9394

94-
self._actions = []
95+
self._actions_with_participants = []
9596
role_assignments = [0] * len(specification.roles)
97+
combination_index = 0
9698
while True:
9799
participants = flight_planners_resource.make_subset(role_assignments)
98100
flight_planners_combination = {
99101
k: v for k, v in zip(specification.roles, participants)
100102
}
101103

102104
if combination_selector.is_valid_combination(flight_planners_combination):
103-
modified_resources = {k: v for k, v in resources.items()}
105+
modified_resources = {
106+
k: v.adjust(combination_index)
107+
if isinstance(v, ResourceModifier)
108+
else v
109+
for k, v in resources.items()
110+
}
104111
for k, v in flight_planners_combination.items():
105112
modified_resources[k] = v
106113

107-
self._actions.append(
108-
TestSuiteAction(specification.action_to_repeat, modified_resources)
114+
self._actions_with_participants.append(
115+
(
116+
TestSuiteAction(
117+
specification.action_to_repeat, modified_resources
118+
),
119+
frozenset(p.participant_id for p in participants),
120+
)
109121
)
122+
combination_index += 1
110123

111124
index_to_increment = len(role_assignments) - 1
112125
while index_to_increment >= 0:
@@ -121,5 +134,32 @@ def __init__(
121134

122135
self._current_action = 0
123136

124-
def actions(self) -> Iterator[TestSuiteAction]:
125-
yield from self._actions
137+
def actions(
138+
self,
139+
) -> Iterator[TestSuiteAction] | Iterator[list[list[TestSuiteAction]]]:
140+
for action, _ in self._actions_with_participants:
141+
yield action
142+
143+
144+
class ParallelFlightPlannerCombinations(FlightPlannerCombinations):
145+
"""Like FlightPlannerCombinations, but yields actions grouped so actions
146+
sharing no participant run in parallel."""
147+
148+
@classmethod
149+
def get_name(cls) -> str:
150+
return "For each appropriate combination of flight planner(s), in parallel where possible"
151+
152+
def actions(self) -> Iterator[list[list[TestSuiteAction]]]:
153+
# Greedy first-fit grouping
154+
groups: list[list[tuple[TestSuiteAction, frozenset[str]]]] = []
155+
for action, participants in self._actions_with_participants:
156+
for group in groups:
157+
used = frozenset().union(*(p for _, p in group))
158+
if used.isdisjoint(participants):
159+
group.append((action, participants))
160+
break
161+
else:
162+
groups.append([(action, participants)])
163+
164+
for group in groups:
165+
yield [[action] for action, _ in group]

monitoring/uss_qualifier/configurations/dev/f3548_self_contained.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,13 @@ v1:
2020
flight_planners: flight_planners
2121
flight_planners_to_clear: flight_planners
2222
conflicting_flights: conflicting_flights
23+
conflicting_flights_parallel: conflicting_flights_parallel
2324
priority_preemption_flights: conflicting_flights
25+
priority_preemption_flights_parallel: conflicting_flights_parallel
2426
invalid_flight_intents: invalid_flight_intents
27+
invalid_flight_intents_parallel: invalid_flight_intents_parallel
2528
non_conflicting_flights: non_conflicting_flights
29+
non_conflicting_flights_parallel: non_conflicting_flights_parallel
2630
dss: dss
2731
dss_instances: dss_instances
2832
mock_uss: mock_uss
@@ -250,6 +254,11 @@ v1:
250254
# Therefore, ground level is at roughly 93m above the WGS84 ellipsoid
251255
meters_up: 93
252256

257+
conflicting_flights_parallel:
258+
resource_type: resources.flight_planning.FlightIntentsModifier
259+
dependencies:
260+
base_resource: conflicting_flights
261+
253262
# Details of flights with invalid operational intents (used in flight intent validation scenario)
254263
invalid_flight_intents:
255264
resource_type: resources.flight_planning.FlightIntentsResource
@@ -262,6 +271,12 @@ v1:
262271
degrees_east: -96.7587
263272
meters_up: 93
264273

274+
invalid_flight_intents_parallel:
275+
resource_type: resources.flight_planning.FlightIntentsModifier
276+
dependencies:
277+
base_resource: invalid_flight_intents
278+
279+
265280
# Details of non-conflicting flights (used in data validation scenario)
266281
non_conflicting_flights:
267282
resource_type: resources.flight_planning.FlightIntentsResource
@@ -275,6 +290,11 @@ v1:
275290
degrees_east: -96.7587
276291
meters_up: 93
277292

293+
non_conflicting_flights_parallel:
294+
resource_type: resources.flight_planning.FlightIntentsModifier
295+
dependencies:
296+
base_resource: non_conflicting_flights
297+
278298
# How to execute a test run using this configuration
279299
execution:
280300
# Since we want to stop execution immediately if there are any unexpected failed checks, we set this parameter to

monitoring/uss_qualifier/configurations/dev/library/resources.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,11 @@ che_conflicting_flights:
347347
degrees_east: 7.4774
348348
meters_up: 605
349349

350+
che_conflicting_flights_parallel:
351+
resource_type: resources.flight_planning.FlightIntentsModifier
352+
dependencies:
353+
base_resource: che_conflicting_flights
354+
350355
che_invalid_flight_intents:
351356
$content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json
352357
resource_type: resources.flight_planning.FlightIntentsResource
@@ -360,6 +365,11 @@ che_invalid_flight_intents:
360365
degrees_east: 7.4774
361366
meters_up: 605
362367

368+
che_invalid_flight_intents_parallel:
369+
resource_type: resources.flight_planning.FlightIntentsModifier
370+
dependencies:
371+
base_resource: che_invalid_flight_intents
372+
363373
che_general_flight_auth_flights:
364374
$content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json
365375
resource_type: resources.flight_planning.FlightIntentsResource
@@ -381,6 +391,11 @@ che_non_conflicting_flights:
381391
degrees_east: 7.4774
382392
meters_up: 605
383393

394+
che_non_conflicting_flights_parallel:
395+
resource_type: resources.flight_planning.FlightIntentsModifier
396+
dependencies:
397+
base_resource: che_non_conflicting_flights
398+
384399
# ===== General flight authorization =====
385400

386401
example_flight_check_table:

monitoring/uss_qualifier/configurations/dev/message_signing.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ v1:
33
resources:
44
resource_declarations:
55
che_conflicting_flights: {$ref: 'library/resources.yaml#/che_conflicting_flights'}
6+
che_conflicting_flights_parallel: {$ref: 'library/resources.yaml#/che_conflicting_flights_parallel'}
67
che_invalid_flight_intents: {$ref: 'library/resources.yaml#/che_invalid_flight_intents'}
8+
che_invalid_flight_intents_parallel: {$ref: 'library/resources.yaml#/che_invalid_flight_intents_parallel'}
79
che_non_conflicting_flights: {$ref: 'library/resources.yaml#/che_non_conflicting_flights'}
10+
che_non_conflicting_flights_parallel: {$ref: 'library/resources.yaml#/che_non_conflicting_flights_parallel'}
811
che_problematically_big_area: {$ref: 'library/resources.yaml#/che_problematically_big_area'}
912
che_planning_area_volume: {$ref: 'library/resources.yaml#/che_planning_area_volume'}
1013
che_planning_area: {$ref: 'library/resources.yaml#/che_planning_area'}
@@ -55,9 +58,13 @@ v1:
5558
flight_planners: flight_planners
5659
combination_selector: combination_selector
5760
conflicting_flights: che_conflicting_flights
61+
conflicting_flights_parallel: che_conflicting_flights_parallel
5862
invalid_flight_intents: che_invalid_flight_intents
63+
invalid_flight_intents_parallel: che_invalid_flight_intents_parallel
5964
non_conflicting_flights: che_non_conflicting_flights
65+
non_conflicting_flights_parallel: che_non_conflicting_flights_parallel
6066
priority_preemption_flights: che_conflicting_flights
67+
priority_preemption_flights_parallel: che_conflicting_flights_parallel
6168
dss: scd_dss
6269
dss_instances: scd_dss_instances
6370
id_generator: id_generator

monitoring/uss_qualifier/configurations/dev/uspace.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ v1:
55
resource_declarations:
66
locality_che: {$ref: 'library/resources.yaml#/locality_che'}
77
che_conflicting_flights: {$ref: 'library/resources.yaml#/che_conflicting_flights'}
8+
che_conflicting_flights_parallel: {$ref: 'library/resources.yaml#/che_conflicting_flights_parallel'}
89
che_invalid_flight_intents: {$ref: 'library/resources.yaml#/che_invalid_flight_intents'}
10+
che_invalid_flight_intents_parallel: {$ref: 'library/resources.yaml#/che_invalid_flight_intents_parallel'}
911
che_invalid_flight_auth_flights: {$ref: 'library/resources.yaml#/che_invalid_flight_auth_flights'}
1012
che_non_conflicting_flights: {$ref: 'library/resources.yaml#/che_non_conflicting_flights'}
13+
che_non_conflicting_flights_parallel: {$ref: 'library/resources.yaml#/che_non_conflicting_flights_parallel'}
1114
che_planning_area_volume: {$ref: 'library/resources.yaml#/che_planning_area_volume'}
1215
che_planning_area: {$ref: 'library/resources.yaml#/che_planning_area'}
1316
netrid_observation_evaluation_configuration: {$ref: 'library/resources.yaml#/netrid_observation_evaluation_configuration'}
@@ -59,10 +62,14 @@ v1:
5962
prod_env_version_providers: prod_env_version_providers?
6063

6164
conflicting_flights: che_conflicting_flights
65+
conflicting_flights_parallel: che_conflicting_flights_parallel
6266
priority_preemption_flights: che_conflicting_flights
67+
priority_preemption_flights_parallel: che_conflicting_flights_parallel
6368
invalid_flight_intents: che_invalid_flight_intents
69+
invalid_flight_intents_parallel: che_invalid_flight_intents_parallel
6470
invalid_flight_auth_flights: che_invalid_flight_auth_flights
6571
non_conflicting_flights: che_non_conflicting_flights
72+
non_conflicting_flights_parallel: che_non_conflicting_flights_parallel
6673
flight_planners: all_flight_planners?
6774
mock_uss: mock_uss_instance_uss6
6875
mock_uss_dp: mock_uss_instance_dp
@@ -96,10 +103,14 @@ v1:
96103
prod_env_version_providers: prod_env_version_providers?
97104

98105
conflicting_flights: conflicting_flights
106+
conflicting_flights_parallel: conflicting_flights_parallel
99107
priority_preemption_flights: priority_preemption_flights
108+
priority_preemption_flights_parallel: priority_preemption_flights_parallel
100109
invalid_flight_intents: invalid_flight_intents
110+
invalid_flight_intents_parallel: invalid_flight_intents_parallel
101111
invalid_flight_auth_flights: invalid_flight_auth_flights
102112
non_conflicting_flights: non_conflicting_flights
113+
non_conflicting_flights_parallel: non_conflicting_flights_parallel
103114
flight_planners: flight_planners?
104115
mock_uss: mock_uss
105116
mock_uss_dp: mock_uss_dp

monitoring/uss_qualifier/configurations/dev/utm_implementation_us/definitions/baseline_a.libsonnet

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,13 @@ function(env) {
2323
flight_planners: 'flight_planners',
2424
flight_planners_to_clear: 'flight_planners_to_clear',
2525
conflicting_flights: 'conflicting_flights',
26+
conflicting_flights_parallel: 'conflicting_flights_parallel',
2627
priority_preemption_flights: 'conflicting_flights',
28+
priority_preemption_flights_parallel: 'conflicting_flights_parallel',
2729
invalid_flight_intents: 'invalid_flight_intents',
30+
invalid_flight_intents_parallel: 'invalid_flight_intents_parallel',
2831
non_conflicting_flights: 'non_conflicting_flights',
32+
non_conflicting_flights_parallel: 'non_conflicting_flights_parallel',
2933
test_exclusions: 'test_exclusions',
3034
dss: 'dss',
3135
dss_instances: 'dss_instances',
@@ -205,6 +209,13 @@ function(env) {
205209
},
206210
},
207211

212+
conflicting_flights_parallel: {
213+
resource_type: 'resources.flight_planning.FlightIntentsModifier',
214+
dependencies: {
215+
base_resource: 'conflicting_flights',
216+
},
217+
},
218+
208219
// Details of flights with invalid operational intents (used in flight intent validation scenario)
209220
invalid_flight_intents: {
210221
resource_type: 'resources.flight_planning.FlightIntentsResource',
@@ -224,6 +235,13 @@ function(env) {
224235
},
225236
},
226237

238+
invalid_flight_intents_parallel: {
239+
resource_type: 'resources.flight_planning.FlightIntentsModifier',
240+
dependencies: {
241+
base_resource: 'invalid_flight_intents',
242+
},
243+
},
244+
227245
// Details of non-conflicting flights (used in data validation scenario)
228246
non_conflicting_flights: {
229247
resource_type: 'resources.flight_planning.FlightIntentsResource',
@@ -243,6 +261,13 @@ function(env) {
243261
},
244262
},
245263

264+
non_conflicting_flights_parallel: {
265+
resource_type: 'resources.flight_planning.FlightIntentsModifier',
266+
dependencies: {
267+
base_resource: 'non_conflicting_flights',
268+
},
269+
},
270+
246271
// Name of the system under test for which the system version should be obtained from participants who provide version information
247272
system_identity: {
248273
resource_type: 'resources.versioning.SystemIdentityResource',

0 commit comments

Comments
 (0)