macroonz_compiler/relation/
questions.rs1use super::{
7 CompletenessPosture, CompletenessStanding, CyclePosture, CycleStanding, DensityPosture,
8 DensityStanding, EmptyPosture, KeyedRosterRows, OccupancyStanding, Reachability,
9 ReachabilityError, ReferencedRosterRow, RepetitionPosture, RepetitionStanding,
10 RosterRelationStanding, RowOrder, SameRosterRequired, SelfRelationPosture,
11 SelfRelationStanding, StructuralMismatch, StructuralRequirement,
12};
13use crate::bounded::{Bounded, NonEmpty};
14
15impl<Answer> StructuralRequirement<Answer> {
16 pub const fn stated(required: Answer) -> Self {
18 Self { required }
19 }
20
21 #[must_use]
23 pub const fn required(&self) -> &Answer {
24 &self.required
25 }
26}
27
28impl<Answer: Eq> StructuralRequirement<Answer> {
29 pub fn settle(self, observed: Answer) -> Result<Answer, StructuralMismatch<Answer>> {
35 if self.required == observed {
36 Ok(observed)
37 } else {
38 Err(StructuralMismatch {
39 required: self.required,
40 observed,
41 })
42 }
43 }
44}
45
46impl<Answer> StructuralMismatch<Answer> {
47 #[must_use]
49 pub const fn required(&self) -> &Answer {
50 &self.required
51 }
52
53 #[must_use]
55 pub const fn observed(&self) -> &Answer {
56 &self.observed
57 }
58}
59
60impl EmptyPosture {
61 #[must_use]
63 pub const fn requirement(self) -> Option<StructuralRequirement<OccupancyStanding>> {
64 match self {
65 Self::Allowed => None,
66 Self::Refusal => Some(StructuralRequirement::stated(OccupancyStanding::Populated)),
67 }
68 }
69}
70
71impl RepetitionPosture {
72 #[must_use]
74 pub const fn requirement(self) -> Option<StructuralRequirement<RepetitionStanding>> {
75 match self {
76 Self::Allowed => None,
77 Self::Refusal => Some(StructuralRequirement::stated(RepetitionStanding::Distinct)),
78 }
79 }
80}
81
82impl CompletenessPosture {
83 #[must_use]
85 pub const fn requirement(self) -> Option<StructuralRequirement<CompletenessStanding>> {
86 match self {
87 Self::Partial => None,
88 Self::Total => Some(StructuralRequirement::stated(
89 CompletenessStanding::Complete,
90 )),
91 }
92 }
93}
94
95impl DensityPosture {
96 #[must_use]
98 pub const fn requirement(self) -> Option<StructuralRequirement<DensityStanding>> {
99 match self {
100 Self::Sparse => None,
101 Self::Dense => Some(StructuralRequirement::stated(DensityStanding::Dense)),
102 }
103 }
104}
105
106impl SelfRelationPosture {
107 #[must_use]
109 pub const fn requirement(self) -> Option<StructuralRequirement<SelfRelationStanding>> {
110 match self {
111 Self::Allowed => None,
112 Self::Refusal => Some(StructuralRequirement::stated(SelfRelationStanding::Absent)),
113 }
114 }
115}
116
117impl CyclePosture {
118 #[must_use]
120 pub const fn requirement(self) -> Option<StructuralRequirement<CycleStanding>> {
121 match self {
122 Self::Allowed => None,
123 Self::Refusal => Some(StructuralRequirement::stated(CycleStanding::Acyclic)),
124 }
125 }
126}
127
128impl<const N: usize> Reachability<N> {
129 pub fn reachable_positions(&self) -> impl Iterator<Item = usize> + '_ {
131 self.reachable.iter().copied()
132 }
133
134 pub fn unreachable_positions(&self) -> impl Iterator<Item = usize> + '_ {
136 self.unreachable.iter().copied()
137 }
138
139 #[must_use]
141 pub fn standing(&self) -> CompletenessStanding {
142 if self.unreachable.is_empty() {
143 CompletenessStanding::Complete
144 } else {
145 CompletenessStanding::Partial
146 }
147 }
148}
149
150impl<
151 Left,
152 LeftKey,
153 Right,
154 RightKey,
155 Payload,
156 const LEFT: usize,
157 const RIGHT: usize,
158 const ROWS: usize,
159> KeyedRosterRows<'_, Left, LeftKey, Right, RightKey, Payload, LEFT, RIGHT, ROWS>
160{
161 #[must_use]
163 pub fn at_in(
164 &self,
165 order: RowOrder,
166 index: usize,
167 ) -> Option<(&LeftKey, &Left, &RightKey, &Right, &Payload)> {
168 match order {
169 RowOrder::Authored => self.at(index),
170 RowOrder::Canonical => self.canonical_at(index),
171 }
172 }
173
174 #[must_use]
176 pub fn occupancy_standing(&self) -> OccupancyStanding {
177 if self.is_empty() {
178 OccupancyStanding::Empty
179 } else {
180 OccupancyStanding::Populated
181 }
182 }
183
184 #[must_use]
186 pub fn repetition_standing(&self) -> RepetitionStanding {
187 let repeated = self.rows.iter().enumerate().any(|(position, row)| {
188 self.rows
189 .iter()
190 .skip(position.saturating_add(1))
191 .any(|later| {
192 later.left_position == row.left_position
193 && later.right_position == row.right_position
194 })
195 });
196 if repeated {
197 RepetitionStanding::Repeated
198 } else {
199 RepetitionStanding::Distinct
200 }
201 }
202
203 #[must_use]
205 pub fn left_completeness(&self) -> CompletenessStanding {
206 completeness_over(self.left.count(), |position| {
207 self.rows.iter().any(|row| row.left_position == position)
208 })
209 }
210
211 #[must_use]
213 pub fn right_completeness(&self) -> CompletenessStanding {
214 completeness_over(self.right.count(), |position| {
215 self.rows.iter().any(|row| row.right_position == position)
216 })
217 }
218
219 #[must_use]
221 pub fn density_standing(&self) -> DensityStanding {
222 let dense = (0..self.left.count()).all(|left_position| {
223 (0..self.right.count())
224 .all(|right_position| pair_is_present(&self.rows, left_position, right_position))
225 });
226 if dense {
227 DensityStanding::Dense
228 } else {
229 DensityStanding::Sparse
230 }
231 }
232}
233
234impl<Member, Key, Payload, const MEMBERS: usize, const ROWS: usize>
235 KeyedRosterRows<'_, Member, Key, Member, Key, Payload, MEMBERS, MEMBERS, ROWS>
236{
237 #[must_use]
239 pub fn roster_relation_standing(&self) -> RosterRelationStanding {
240 if core::ptr::eq(self.left, self.right) {
241 RosterRelationStanding::Same
242 } else {
243 RosterRelationStanding::Cross
244 }
245 }
246
247 pub fn self_relation_standing(&self) -> Result<SelfRelationStanding, SameRosterRequired> {
253 self.require_same_roster()?;
254 if self
255 .rows
256 .iter()
257 .any(|row| row.left_position == row.right_position)
258 {
259 Ok(SelfRelationStanding::Present)
260 } else {
261 Ok(SelfRelationStanding::Absent)
262 }
263 }
264
265 pub fn cycle_standing(&self) -> Result<CycleStanding, SameRosterRequired> {
271 self.require_same_roster()?;
272 if (0..self.left.count()).any(|root| self.path_returns_to(root)) {
273 Ok(CycleStanding::Cyclic)
274 } else {
275 Ok(CycleStanding::Acyclic)
276 }
277 }
278
279 fn require_same_roster(&self) -> Result<(), SameRosterRequired> {
280 match self.roster_relation_standing() {
281 RosterRelationStanding::Same => Ok(()),
282 RosterRelationStanding::Cross => Err(SameRosterRequired),
283 }
284 }
285
286 fn path_returns_to(&self, root: usize) -> bool {
287 let mut discovered = vec![root];
288 let mut cursor = 0_usize;
289 while let Some(position) = discovered.get(cursor).copied() {
290 if self.advance_cycle_search(position, root, &mut discovered) {
291 return true;
292 }
293 cursor = cursor.saturating_add(1);
294 }
295 false
296 }
297
298 fn advance_cycle_search(
299 &self,
300 position: usize,
301 root: usize,
302 discovered: &mut Vec<usize>,
303 ) -> bool {
304 for destination in self.destinations_from(position) {
305 if destination == root {
306 return true;
307 }
308 retain_once(discovered, destination);
309 }
310 false
311 }
312
313 fn extend_reachability(&self, position: usize, discovered: &mut Vec<usize>) {
314 for destination in self.destinations_from(position) {
315 retain_once(discovered, destination);
316 }
317 }
318
319 fn destinations_from(&self, position: usize) -> impl Iterator<Item = usize> + '_ {
320 self.rows
321 .iter()
322 .filter(move |row| row.left_position == position)
323 .map(|row| row.right_position)
324 }
325}
326
327impl<Member, Key: Eq, Payload, const MEMBERS: usize, const ROWS: usize>
328 KeyedRosterRows<'_, Member, Key, Member, Key, Payload, MEMBERS, MEMBERS, ROWS>
329{
330 pub fn reachability_from(
338 &self,
339 root: Key,
340 ) -> Result<Reachability<MEMBERS>, ReachabilityError<Key>> {
341 self.require_same_roster()
342 .map_err(ReachabilityError::DifferentRosters)?;
343 let Some(root_position) = self.left.index_of(&root) else {
344 return Err(ReachabilityError::RootOutsideRoster { root });
345 };
346 let discovered = self.discover_from(root_position);
347 let reachable = self
348 .left
349 .positions_where(|position, _, _| discovered.contains(&position));
350 let unreachable = self
351 .left
352 .positions_where(|position, _, _| !discovered.contains(&position));
353 let reachable = NonEmpty::from_bounded(reachable)
354 .map_err(|_| ReachabilityError::RootOutsideRoster { root })?;
355 Ok(Reachability {
356 reachable,
357 unreachable,
358 })
359 }
360
361 fn discover_from(&self, root: usize) -> Vec<usize> {
362 let mut discovered = vec![root];
363 let mut cursor = 0_usize;
364 while let Some(position) = discovered.get(cursor).copied() {
365 self.extend_reachability(position, &mut discovered);
366 cursor = cursor.saturating_add(1);
367 }
368 discovered
369 }
370}
371
372fn pair_is_present<Left, LeftKey, Right, RightKey, Payload, const N: usize>(
373 rows: &Bounded<ReferencedRosterRow<'_, Left, LeftKey, Right, RightKey, Payload>, N>,
374 left_position: usize,
375 right_position: usize,
376) -> bool {
377 rows.iter()
378 .any(|row| row.left_position == left_position && row.right_position == right_position)
379}
380
381fn retain_once(held: &mut Vec<usize>, position: usize) {
382 if held.contains(&position) {
383 return;
384 }
385 held.push(position);
386}
387
388fn completeness_over(
389 positions: usize,
390 mut contains: impl FnMut(usize) -> bool,
391) -> CompletenessStanding {
392 if (0..positions).all(&mut contains) {
393 CompletenessStanding::Complete
394 } else {
395 CompletenessStanding::Partial
396 }
397}