1use std::collections::{BTreeMap, BTreeSet};
4
5use sim_lib_pitch_serial::RowForm;
6
7use crate::{
8 OrdinalRef, PlannedSerialEvent, PrecedenceGraph, RowInstanceId, SerialEventId, SerialOrigin,
9 SerialPlanError, SerialRole, SimultaneousGroupId,
10};
11
12#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct SerialPlan {
15 rows: BTreeMap<RowInstanceId, RowForm>,
16 events: BTreeMap<SerialEventId, PlannedSerialEvent>,
17 precedence: PrecedenceGraph<SerialEventId>,
18}
19
20impl SerialPlan {
21 pub fn try_new(
23 rows: BTreeMap<RowInstanceId, RowForm>,
24 events: BTreeMap<SerialEventId, PlannedSerialEvent>,
25 precedence_edges: impl IntoIterator<Item = (SerialEventId, SerialEventId)>,
26 ) -> Result<Self, SerialPlanError> {
27 let event_ids = events.keys().cloned().collect::<BTreeSet<_>>();
28 let precedence = PrecedenceGraph::try_new(precedence_edges, &event_ids)?;
29 validate_events(&rows, &events)?;
30 validate_parent_graph(&events)?;
31 validate_simultaneous_constraints(&events, &precedence)?;
32 validate_structural_coverage(&rows, &events)?;
33 Ok(Self {
34 rows,
35 events,
36 precedence,
37 })
38 }
39
40 pub fn rows(&self) -> &BTreeMap<RowInstanceId, RowForm> {
42 &self.rows
43 }
44
45 pub fn events(&self) -> &BTreeMap<SerialEventId, PlannedSerialEvent> {
47 &self.events
48 }
49
50 pub fn event(&self, event_id: &SerialEventId) -> Option<&PlannedSerialEvent> {
52 self.events.get(event_id)
53 }
54
55 pub fn row(&self, row_id: &RowInstanceId) -> Option<&RowForm> {
57 self.rows.get(row_id)
58 }
59
60 pub fn precedence(&self) -> &PrecedenceGraph<SerialEventId> {
62 &self.precedence
63 }
64
65 pub fn simultaneous_groups(&self) -> BTreeMap<SimultaneousGroupId, Vec<&PlannedSerialEvent>> {
67 let mut groups: BTreeMap<SimultaneousGroupId, Vec<&PlannedSerialEvent>> = BTreeMap::new();
68 for event in self.events.values() {
69 if let Some(group) = event.placement.simultaneous_group() {
70 groups.entry(group.clone()).or_default().push(event);
71 }
72 }
73 groups
74 }
75}
76
77fn validate_events(
78 rows: &BTreeMap<RowInstanceId, RowForm>,
79 events: &BTreeMap<SerialEventId, PlannedSerialEvent>,
80) -> Result<(), SerialPlanError> {
81 for (event_id, event) in events {
82 if &event.id != event_id {
83 return Err(SerialPlanError::InvalidId {
84 kind: "serial-event-key",
85 value: event.id.as_str().to_owned(),
86 reason: "map key and event id must match",
87 });
88 }
89 if event.ordinals.is_empty() {
90 return Err(SerialPlanError::EmptyOrdinalSet(event.id.clone()));
91 }
92 if event.licenses.is_empty() {
93 return Err(SerialPlanError::MissingStructuralLicenses(event.id.clone()));
94 }
95 let mut seen_ordinals = BTreeSet::new();
96 for ordinal in &event.ordinals {
97 if !seen_ordinals.insert(ordinal.clone()) {
98 return Err(SerialPlanError::DuplicateOrdinal {
99 event_id: event.id.clone(),
100 ordinal: ordinal.clone(),
101 });
102 }
103 let Some(row) = rows.get(&ordinal.row_id) else {
104 return Err(SerialPlanError::UnknownRow {
105 event_id: event.id.clone(),
106 row_id: ordinal.row_id.clone(),
107 });
108 };
109 let row_len = row.classes().len();
110 if ordinal.ordinal >= row_len {
111 return Err(SerialPlanError::OrdinalOutOfRange {
112 event_id: event.id.clone(),
113 row_id: ordinal.row_id.clone(),
114 ordinal: ordinal.ordinal,
115 row_len,
116 });
117 }
118 }
119 validate_role_origin(event_id, event)?;
120 }
121 Ok(())
122}
123
124fn validate_role_origin(
125 event_id: &SerialEventId,
126 event: &PlannedSerialEvent,
127) -> Result<(), SerialPlanError> {
128 let parents_empty = event.parents.is_empty();
129 match (event.role, &event.origin, parents_empty) {
130 (SerialRole::Structural, SerialOrigin::Structural { rationale }, true)
131 if !rationale.trim().is_empty() =>
132 {
133 Ok(())
134 }
135 (SerialRole::Structural, SerialOrigin::Structural { .. }, false) => {
136 Err(SerialPlanError::RoleOriginMismatch {
137 event_id: event_id.clone(),
138 reason: "structural events cannot name parents",
139 })
140 }
141 (SerialRole::Structural, _, _) => Err(SerialPlanError::RoleOriginMismatch {
142 event_id: event_id.clone(),
143 reason: "structural role requires structural origin",
144 }),
145 (SerialRole::Derived, SerialOrigin::Derived { technique }, false)
146 if !technique.trim().is_empty() =>
147 {
148 Ok(())
149 }
150 (SerialRole::Derived, SerialOrigin::Derived { .. }, true) => {
151 Err(SerialPlanError::MissingParents {
152 event_id: event_id.clone(),
153 role: SerialRole::Derived.as_str(),
154 })
155 }
156 (SerialRole::Derived, _, _) => Err(SerialPlanError::RoleOriginMismatch {
157 event_id: event_id.clone(),
158 reason: "derived role requires derived origin",
159 }),
160 (SerialRole::Ornamental, SerialOrigin::Ornamental { technique }, false)
161 if !technique.trim().is_empty() =>
162 {
163 Ok(())
164 }
165 (SerialRole::Ornamental, SerialOrigin::Ornamental { .. }, true) => {
166 Err(SerialPlanError::MissingParents {
167 event_id: event_id.clone(),
168 role: SerialRole::Ornamental.as_str(),
169 })
170 }
171 (SerialRole::Ornamental, _, _) => Err(SerialPlanError::RoleOriginMismatch {
172 event_id: event_id.clone(),
173 reason: "ornamental role requires ornamental origin",
174 }),
175 (SerialRole::External, SerialOrigin::External { source }, false)
176 if !source.trim().is_empty() =>
177 {
178 Ok(())
179 }
180 (SerialRole::External, SerialOrigin::External { .. }, true) => {
181 Err(SerialPlanError::MissingParents {
182 event_id: event_id.clone(),
183 role: SerialRole::External.as_str(),
184 })
185 }
186 (SerialRole::External, _, _) => Err(SerialPlanError::RoleOriginMismatch {
187 event_id: event_id.clone(),
188 reason: "external role requires external origin",
189 }),
190 }
191}
192
193fn validate_parent_graph(
194 events: &BTreeMap<SerialEventId, PlannedSerialEvent>,
195) -> Result<(), SerialPlanError> {
196 #[derive(Copy, Clone, PartialEq, Eq)]
197 enum Mark {
198 Visiting,
199 Done,
200 }
201
202 fn visit(
203 id: &SerialEventId,
204 events: &BTreeMap<SerialEventId, PlannedSerialEvent>,
205 marks: &mut BTreeMap<SerialEventId, Mark>,
206 ) -> Result<(), SerialPlanError> {
207 match marks.get(id) {
208 Some(Mark::Done) => return Ok(()),
209 Some(Mark::Visiting) => return Err(SerialPlanError::ParentCycle(id.clone())),
210 None => {}
211 }
212 marks.insert(id.clone(), Mark::Visiting);
213 let event = events.get(id).expect("known event");
214 for parent in &event.parents {
215 if parent == id {
216 return Err(SerialPlanError::SelfParent(id.clone()));
217 }
218 if !events.contains_key(parent) {
219 return Err(SerialPlanError::UnknownParent {
220 event_id: id.clone(),
221 parent_id: parent.clone(),
222 });
223 }
224 visit(parent, events, marks)?;
225 }
226 marks.insert(id.clone(), Mark::Done);
227 Ok(())
228 }
229
230 let mut marks = BTreeMap::new();
231 for event_id in events.keys() {
232 visit(event_id, events, &mut marks)?;
233 }
234 Ok(())
235}
236
237fn validate_simultaneous_constraints(
238 events: &BTreeMap<SerialEventId, PlannedSerialEvent>,
239 precedence: &PrecedenceGraph<SerialEventId>,
240) -> Result<(), SerialPlanError> {
241 let mut by_group: BTreeMap<&SimultaneousGroupId, Vec<&PlannedSerialEvent>> = BTreeMap::new();
242 for event in events.values() {
243 if let Some(group) = event.placement.simultaneous_group() {
244 by_group.entry(group).or_default().push(event);
245 }
246 }
247 for (group_id, members) in by_group {
248 for left in 0..members.len() {
249 for right in (left + 1)..members.len() {
250 let before = &members[left].id;
251 let after = &members[right].id;
252 if precedence.contains_edge(before, after)
253 || precedence.contains_edge(after, before)
254 {
255 return Err(SerialPlanError::SimultaneousPrecedenceConflict {
256 group_id: group_id.clone(),
257 before: before.clone(),
258 after: after.clone(),
259 });
260 }
261 }
262 }
263 }
264 Ok(())
265}
266
267fn validate_structural_coverage(
268 rows: &BTreeMap<RowInstanceId, RowForm>,
269 events: &BTreeMap<SerialEventId, PlannedSerialEvent>,
270) -> Result<(), SerialPlanError> {
271 let mut covered: BTreeMap<RowInstanceId, BTreeSet<usize>> = BTreeMap::new();
272 for event in events
273 .values()
274 .filter(|event| event.role == SerialRole::Structural)
275 {
276 for OrdinalRef { row_id, ordinal } in &event.ordinals {
277 covered.entry(row_id.clone()).or_default().insert(*ordinal);
278 }
279 }
280 for (row_id, row) in rows {
281 let row_len = row.classes().len();
282 let missing = (0..row_len)
283 .filter(|ordinal| !covered.get(row_id).is_some_and(|set| set.contains(ordinal)))
284 .collect::<Vec<_>>();
285 if !missing.is_empty() {
286 return Err(SerialPlanError::MissingStructuralCoverage {
287 row_id: row_id.clone(),
288 ordinals: missing,
289 });
290 }
291 }
292 Ok(())
293}