geop_core_math/solvers/system/
structure.rs1use crate::{
45 scalars::Scalar,
46 solvers::{linear::eliminate, system::tolerance},
47 union_find::UnionFind,
48};
49
50use super::{Bound, Equations, Group, Linearized, Mobility, System, Term, Variable, every_term};
51
52#[derive(Clone, Debug, Default, PartialEq, Eq)]
56pub struct Structure {
57 pub groups: Vec<Group>,
58 pub components: Vec<Group>,
62}
63
64impl Structure {
65 pub fn fits(&self, blocks: usize, (constraints, preferences): (usize, usize)) -> bool {
69 let covered: usize = self.components.iter().map(|c| c.blocks.len()).sum();
70 let within = |g: &Group| {
71 g.blocks.iter().chain(&g.on).all(|&b| b < blocks)
72 && g.constraints.iter().all(|&c| c < constraints)
73 && g.preferences.iter().all(|&p| p < preferences)
74 };
75 covered == blocks && self.groups.iter().chain(&self.components).all(within)
76 }
77
78 pub fn layers(&self) -> usize {
80 self.groups.iter().map(|g| g.layer + 1).max().unwrap_or(0)
81 }
82
83 pub fn layer(&self, layer: usize) -> Vec<usize> {
85 (0..self.groups.len())
86 .filter(|&g| self.groups[g].layer == layer)
87 .collect()
88 }
89}
90
91struct Read {
94 term: Term,
95 blocks: Vec<usize>,
96 made: Linearized,
97}
98
99struct Over<'s, 'e, E: Equations> {
104 system: &'s System<'e, E>,
105 at: &'s [E::Value],
106 terms: Vec<Term>,
107 rows: std::cell::OnceCell<Vec<Option<Linearized>>>,
108}
109
110impl<E: Equations> Over<'_, '_, E> {
111 fn get(&self, k: usize) -> Option<&Linearized> {
114 let rows = self.rows.get_or_init(|| {
115 let Some((boxed, differentials)) = self.system.around(self.at) else {
116 return vec![None; self.terms.len()];
117 };
118 (self
119 .system
120 .equations
121 .evaluate(&boxed, &self.terms)
122 .into_iter())
123 .map(|made| {
124 let mut made = made?;
125 for (block, rows) in &mut made.jacobian {
126 if let Some(Some(d)) = differentials.get(*block) {
127 for row in rows.iter_mut() {
128 *row = super::compose(row, d);
129 }
130 }
131 }
132 Some(made)
133 })
134 .collect()
135 });
136 rows.get(k)?.as_ref()
137 }
138}
139
140impl<E: Equations> System<'_, E> {
141 pub fn structure(&self, at: &[E::Value]) -> Structure {
153 let n = self.mobility.len();
154 let terms = every_term(self.equations.count());
155 let made = self.equations.evaluate(at, &terms);
156 let mut reads: Vec<Read> = Vec::new();
157 for (&term, made) in terms.iter().zip(made) {
158 let Some(made) = made else {
159 continue;
160 };
161 let mut blocks: Vec<usize> = (made.jacobian.iter())
162 .map(|(block, _)| *block)
163 .filter(|&b| b < n)
164 .collect();
165 blocks.sort_unstable();
166 blocks.dedup();
167 if !blocks.is_empty() {
168 reads.push(Read { term, blocks, made });
169 }
170 }
171 let components = grouped(n, reads.iter().map(|r| (r.term, r.blocks.as_slice())));
172 let Ok(tolerance) = tolerance(self.scale) else {
173 return Structure {
174 groups: components.clone(),
175 components,
176 };
177 };
178 let mut on: Vec<Vec<usize>> = vec![Vec::new(); n];
180 for (k, read) in reads.iter().enumerate() {
181 for &b in &read.blocks {
182 on[b].push(k);
183 }
184 }
185 let over = Over {
186 system: self,
187 at,
188 terms: reads.iter().map(|r| r.term).collect(),
189 rows: Default::default(),
190 };
191 let mut alive = vec![true; reads.len()];
192 let mut peeled: Vec<Option<Pendant>> = vec![None; n];
193 let mut order = Vec::new();
194 let mut waiting: std::collections::BTreeSet<usize> = (0..n).collect();
195 while let Some(b) = waiting.pop_first() {
196 if peeled[b].is_some() {
197 continue;
198 }
199 let own: Vec<usize> = on[b].iter().copied().filter(|&k| alive[k]).collect();
200 let Some(pendant) = self.hangs_off(at, b, own, &reads, &over, &alive, &on, tolerance)
201 else {
202 continue;
203 };
204 for &k in &pendant.terms {
205 alive[k] = false;
206 }
207 waiting.extend(pendant.on.iter().copied());
208 peeled[b] = Some(pendant);
209 order.push(b);
210 }
211 let cores = grouped(
213 n,
214 (reads.iter().zip(&alive))
215 .filter(|(_, alive)| **alive)
216 .map(|(r, _)| (r.term, r.blocks.as_slice())),
217 );
218 let component_of =
219 |block: usize| (components.iter()).position(|g| g.blocks.binary_search(&block).is_ok());
220 let mut groups: Vec<Group> = Vec::new();
221 let mut group_of: Vec<Option<usize>> = vec![None; n];
222 for mut core in cores {
223 core.blocks.retain(|&b| peeled[b].is_none());
225 if core.blocks.is_empty() {
226 continue;
227 }
228 core.component = component_of(core.blocks[0]).unwrap_or(0);
229 for &b in &core.blocks {
230 group_of[b] = Some(groups.len());
231 }
232 groups.push(core);
233 }
234 for &b in order.iter().rev() {
236 let Some(pendant) = &peeled[b] else {
237 continue;
238 };
239 let layer = (pendant.on.iter())
240 .filter_map(|&s| group_of[s].map(|g| groups[g].layer + 1))
241 .max()
242 .unwrap_or(0);
243 let mut group = Group {
244 blocks: vec![b],
245 on: pendant.on.clone(),
246 layer,
247 component: component_of(b).unwrap_or(0),
248 own: pendant.own,
249 ..Default::default()
250 };
251 for &k in &pendant.terms {
252 match reads[k].term {
253 Term::Constraint(c) => group.constraints.push(c),
254 Term::Preference(p) => group.preferences.push(p),
255 }
256 }
257 group_of[b] = Some(groups.len());
258 groups.push(group);
259 }
260 groups.sort_by_key(|g| g.layer);
261 Structure { groups, components }
262 }
263
264 #[allow(clippy::type_complexity)]
268 fn around(&self, at: &[E::Value]) -> Option<(Vec<E::Value>, Vec<Option<Vec<Vec<Scalar>>>>)> {
269 let radius = tolerance(self.scale).ok()?;
270 let mut boxed = Vec::with_capacity(at.len());
271 let mut differentials = Vec::with_capacity(at.len());
272 for (value, mobility) in at.iter().zip(&self.mobility) {
273 let step = match mobility.free() {
274 true => vec![radius.neg().union(radius); value.dimension()],
275 false => vec![Scalar::ZERO; value.dimension()],
276 };
277 boxed.push(value.retract_over(&step).ok()?);
278 differentials.push(value.differential(&step).ok()?);
279 }
280 Some((boxed, differentials))
281 }
282
283 #[allow(clippy::too_many_arguments)]
286 fn hangs_off(
287 &self,
288 at: &[E::Value],
289 b: usize,
290 own: Vec<usize>,
291 reads: &[Read],
292 over: &Over<E>,
293 alive: &[bool],
294 on: &[Vec<usize>],
295 tolerance: Scalar,
296 ) -> Option<Pendant> {
297 let mobility = self.mobility[b];
298 if own.is_empty() || !mobility.free() {
299 return None;
300 }
301 let mut hangs: Vec<usize> = Vec::new();
302 for &k in &own {
303 let read = &reads[k];
304 let Term::Constraint(c) = read.term else {
305 return None;
306 };
307 let bound = self.equations.bound(c);
308 if bound != Bound::Zero
309 || !(read.made.values.iter()).all(|&r| bound.holds(r, tolerance))
310 {
311 return None;
312 }
313 hangs.extend(read.blocks.iter().copied().filter(|&o| o != b));
314 }
315 hangs.sort_unstable();
316 hangs.dedup();
317 let mut columns = vec![b];
319 columns.extend(&hangs);
320 let mut offsets = Vec::with_capacity(columns.len());
321 let mut width = 0;
322 for &c in &columns {
323 offsets.push(width);
324 width += at[c].dimension();
325 }
326 let own_width = at[b].dimension();
327 let mut rows: Vec<Vec<Scalar>> = Vec::new();
328 for &k in &own {
329 let made = match hangs.is_empty() {
332 true => &reads[k].made,
333 false => over.get(k)?,
334 };
335 for r in 0..made.values.len() {
336 let mut row = vec![Scalar::ZERO; width];
337 for (block, derivatives) in &made.jacobian {
338 let Some(at) = columns.iter().position(|c| c == block) else {
339 continue;
340 };
341 let Some(local) = derivatives.get(r) else {
342 continue;
343 };
344 for (slot, &d) in row.iter_mut().skip(offsets[at]).zip(local) {
345 *slot = slot.add(d);
346 }
347 }
348 rows.push(row);
349 }
350 }
351 let reduced = eliminate(rows, own_width);
356 let constrained = reduced.pivots.len();
357 let rest = (reduced.rows.iter()).skip(constrained);
358 if !rest.flatten().all(|v| v.could_be_equal(Scalar::ZERO)) {
359 return None;
360 }
361 if mobility == Mobility::Held {
362 let carried = (hangs.iter()).any(|&s| at[b].carried(&at[s], &at[s]).is_some());
363 if !hangs.is_empty() && !carried {
364 return None;
365 }
366 let decided = |f: usize| {
367 (on[f].iter()).any(|&k| {
368 alive[k]
369 && !own.contains(&k)
370 && matches!(reads[k].term, Term::Constraint(c)
371 if self.equations.bound(c) == Bound::Zero)
372 })
373 };
374 if (hangs.iter()).any(|&f| self.mobility[f] == Mobility::Follows && !decided(f)) {
375 return None;
376 }
377 }
378 Some(Pendant {
379 on: hangs,
380 own: own_width - constrained,
381 terms: own,
382 })
383 }
384}
385
386#[derive(Clone)]
389struct Pendant {
390 on: Vec<usize>,
391 own: usize,
392 terms: Vec<usize>,
393}
394
395fn grouped<'r>(n: usize, reads: impl Iterator<Item = (Term, &'r [usize])> + Clone) -> Vec<Group> {
399 let mut sets = UnionFind::new(n);
400 for (_, blocks) in reads.clone() {
401 for pair in blocks.windows(2) {
402 sets.union(pair[0], pair[1]);
403 }
404 }
405 let mut by_root: std::collections::BTreeMap<usize, Group> = Default::default();
406 for block in 0..n {
407 by_root
408 .entry(sets.find(block))
409 .or_default()
410 .blocks
411 .push(block);
412 }
413 for (term, blocks) in reads {
414 let Some(group) = by_root.get_mut(&sets.find(blocks[0])) else {
415 continue;
416 };
417 match term {
418 Term::Constraint(c) => group.constraints.push(c),
419 Term::Preference(p) => group.preferences.push(p),
420 }
421 }
422 by_root.into_values().collect()
423}