brep_kernel/solvers/assembly_solver/
decompose.rs1use super::*;
2
3struct UnionFind {
9 parent: Vec<usize>,
10}
11
12impl UnionFind {
13 fn new(n: usize) -> Self {
14 Self {
15 parent: (0..n).collect(),
16 }
17 }
18
19 fn find(&mut self, x: usize) -> usize {
20 let mut root = x;
21 while self.parent[root] != root {
22 root = self.parent[root];
23 }
24 let mut cur = x;
25 while self.parent[cur] != root {
26 let next = self.parent[cur];
27 self.parent[cur] = root;
28 cur = next;
29 }
30 root
31 }
32
33 fn union(&mut self, a: usize, b: usize) {
34 let (ra, rb) = (self.find(a), self.find(b));
35 if ra != rb {
36 let (lo, hi) = if ra < rb { (ra, rb) } else { (rb, ra) };
37 self.parent[hi] = lo;
38 }
39 }
40}
41
42struct DecompOutcome {
43 steps: Vec<SolveStepReport>,
44 run: LmRun,
45}
46
47fn solve_decomposed(
51 bodies: &[AssemblyBody],
52 mates: &[AssemblyMate],
53 prep: &Prepared,
54 poses: &mut Vec<Pose>,
55 max_iterations: usize,
56) -> Result<DecompOutcome, String> {
57 let nb = bodies.len();
58 let mut solved: Vec<bool> = bodies.iter().map(|b| b.fixed).collect();
59
60 let mut mate_atoms: Vec<Vec<usize>> = vec![Vec::new(); mates.len()];
62 for (ai, &mi) in prep.atom_mate.iter().enumerate() {
63 mate_atoms[mi].push(ai);
64 }
65 let mut incident: Vec<Vec<usize>> = vec![Vec::new(); nb];
66 for (mi, mate) in mates.iter().enumerate() {
67 incident[mate.body_a].push(mi);
68 incident[mate.body_b].push(mi);
69 }
70
71 let mut nodes: Vec<Option<Vec<usize>>> = Vec::new();
74 let mut node_of: Vec<Option<usize>> = vec![None; nb];
75 for &b in &prep.movable {
76 node_of[b] = Some(nodes.len());
77 nodes.push(Some(vec![b]));
78 }
79
80 let ids = |members: &[usize]| -> Vec<String> {
81 members.iter().map(|&b| bodies[b].id.clone()).collect()
82 };
83 let atoms_of = |mate_set: &[usize]| -> Vec<Atom> {
84 mate_set
85 .iter()
86 .flat_map(|&mi| mate_atoms[mi].iter().map(|&ai| prep.atoms[ai]))
87 .collect()
88 };
89
90 let mut steps: Vec<SolveStepReport> = Vec::new();
91 let mut run = LmRun::default();
92
93 loop {
94 let mut peeled = false;
104 loop {
105 let mut pick: Option<(usize, Vec<usize>)> = None;
106 for (ni, node) in nodes.iter().enumerate() {
107 let Some(members) = node else { continue };
108 let mut to_solved: Vec<usize> = Vec::new();
110 let mut deferred = false;
111 for &b in members {
112 for &mi in &incident[b] {
113 let other = if mates[mi].body_a == b {
114 mates[mi].body_b
115 } else {
116 mates[mi].body_a
117 };
118 if node_of[other] == Some(ni) {
119 continue; }
121 if solved[other] {
122 to_solved.push(mi);
123 } else {
124 deferred = true;
125 }
126 }
127 }
128 to_solved.sort_unstable();
129 to_solved.dedup();
130 let ready = if !deferred {
131 true
132 } else if to_solved.is_empty() {
133 false
134 } else {
135 let atoms = atoms_of(&to_solved);
137 let m: usize = atoms.iter().map(Atom::rows).sum();
138 let groups = vec![SolveGroup {
139 members: members.clone(),
140 }];
141 let jac = finite_diff_jacobian(
142 &atoms,
143 poses,
144 &groups,
145 &column_steps(1, prep.scale),
146 m,
147 );
148 run.rows += 2 * 6 * m;
149 numerical_rank(jac, m, 6) == 6
150 };
151 if ready {
152 pick = Some((ni, to_solved));
153 break;
154 }
155 }
156 let Some((ni, mate_set)) = pick else { break };
157 let members = nodes[ni].take().expect("picked node is live");
158 let atoms = atoms_of(&mate_set);
159 let (method, iterations) = if atoms.is_empty() {
160 ("unconstrained", 0)
161 } else {
162 let groups = vec![SolveGroup {
163 members: members.clone(),
164 }];
165 let sub = lm_core(
166 &atoms,
167 poses,
168 &groups,
169 prep.scale,
170 prep.tight,
171 max_iterations,
172 )?;
173 let iters = sub.iterations;
174 run.absorb(sub);
175 let method = if members.len() == 1 {
176 "sequential"
177 } else {
178 "cluster_sequential"
179 };
180 (method, iters)
181 };
182 steps.push(SolveStepReport {
183 bodies: ids(&members),
184 method: method.into(),
185 iterations,
186 });
187 for &b in &members {
188 solved[b] = true;
189 node_of[b] = None;
190 }
191 peeled = true;
192 }
193 if nodes.iter().all(|n| n.is_none()) {
194 break; }
196
197 let mut contracted = false;
205 let lone = |b: usize, nodes: &[Option<Vec<usize>>], node_of: &[Option<usize>]| -> bool {
206 node_of[b]
207 .and_then(|ni| nodes[ni].as_ref())
208 .is_some_and(|members| members.len() == 1)
209 };
210 let mut pair_mates: std::collections::BTreeMap<(usize, usize), Vec<usize>> =
211 std::collections::BTreeMap::new();
212 for (mi, mate) in mates.iter().enumerate() {
213 let (a, b) = (mate.body_a, mate.body_b);
214 if !solved[a] && !solved[b] && lone(a, &nodes, &node_of) && lone(b, &nodes, &node_of) {
215 pair_mates.entry((a.min(b), a.max(b))).or_default().push(mi);
216 }
217 }
218 let mut uf = UnionFind::new(nb);
219 let mut any_binding = false;
220 for (&(a, b), pm) in &pair_mates {
221 let atoms = atoms_of(pm);
222 let m: usize = atoms.iter().map(Atom::rows).sum();
223 let groups = singleton_groups(&[b]);
224 let jac = finite_diff_jacobian(&atoms, poses, &groups, &column_steps(1, prep.scale), m);
225 run.rows += 2 * 6 * m;
226 if numerical_rank(jac, m, 6) == 6 {
227 uf.union(a, b);
228 any_binding = true;
229 }
230 }
231 if any_binding {
232 let mut components: std::collections::BTreeMap<usize, Vec<usize>> =
233 std::collections::BTreeMap::new();
234 for &(a, b) in pair_mates.keys() {
235 for body in [a, b] {
236 let root = uf.find(body);
237 let entry = components.entry(root).or_default();
238 if !entry.contains(&body) {
239 entry.push(body);
240 }
241 }
242 }
243 for (_, mut comp) in components {
244 if comp.len() < 2 {
245 continue;
246 }
247 comp.sort_unstable();
248 let saved: Vec<Pose> = comp.iter().map(|&b| poses[b]).collect();
250 let mut internal: Vec<usize> = comp
251 .iter()
252 .flat_map(|&b| incident[b].iter().copied())
253 .filter(|&mi| {
254 comp.contains(&mates[mi].body_a) && comp.contains(&mates[mi].body_b)
255 })
256 .collect();
257 internal.sort_unstable();
258 internal.dedup();
259 let atoms = atoms_of(&internal);
260 let groups = singleton_groups(&comp[1..]);
261 let sub = lm_core(
262 &atoms,
263 poses,
264 &groups,
265 prep.scale,
266 prep.tight,
267 max_iterations,
268 )?;
269 let sub_iters = sub.iterations;
270 run.absorb(sub);
271 let m_int: usize = atoms.iter().map(Atom::rows).sum();
273 let all_groups = singleton_groups(&comp);
274 let mut scratch: Vec<Pose> = Vec::new();
275 let mut r_int: Vec<f64> = Vec::new();
276 eval_residuals(&atoms, poses, &[], &[], &mut scratch, &mut r_int);
277 let max_int = r_int.iter().fold(0.0f64, |acc, &x| acc.max(x.abs()));
278 let jac = finite_diff_jacobian(
279 &atoms,
280 poses,
281 &all_groups,
282 &column_steps(comp.len(), prep.scale),
283 m_int,
284 );
285 let rank = numerical_rank(jac, m_int, 6 * comp.len());
286 run.rows += m_int + 2 * 6 * comp.len() * m_int;
287 if max_int <= prep.tight && rank == 6 * (comp.len() - 1) {
288 steps.push(SolveStepReport {
289 bodies: ids(&comp),
290 method: "cluster_internal".into(),
291 iterations: sub_iters,
292 });
293 let cluster = nodes.len();
295 for &b in &comp {
296 if let Some(old) = node_of[b] {
297 nodes[old] = None;
298 }
299 node_of[b] = Some(cluster);
300 }
301 nodes.push(Some(comp));
302 contracted = true;
303 } else {
304 for (&b, pose) in comp.iter().zip(&saved) {
306 poses[b] = *pose;
307 }
308 }
309 }
310 }
311 if peeled || contracted {
312 continue;
313 }
314
315 let remaining: Vec<usize> = (0..nb).filter(|&b| node_of[b].is_some()).collect();
320 let mate_set: Vec<usize> = (0..mates.len())
321 .filter(|&mi| !solved[mates[mi].body_a] || !solved[mates[mi].body_b])
322 .collect();
323 let atoms = atoms_of(&mate_set);
324 let groups = singleton_groups(&remaining);
325 let sub = lm_core(
326 &atoms,
327 poses,
328 &groups,
329 prep.scale,
330 prep.tight,
331 max_iterations,
332 )?;
333 let iters = sub.iterations;
334 run.absorb(sub);
335 steps.push(SolveStepReport {
336 bodies: ids(&remaining),
337 method: "monolithic_fallback".into(),
338 iterations: iters,
339 });
340 for node in nodes.iter_mut() {
341 *node = None;
342 }
343 for &b in &remaining {
344 solved[b] = true;
345 node_of[b] = None;
346 }
347 break;
348 }
349
350 Ok(DecompOutcome { steps, run })
351}
352
353pub fn solve_assembly(
368 bodies: &[AssemblyBody],
369 mates: &[AssemblyMate],
370 options: &AssemblySolveOptions,
371) -> Result<AssemblySolution, String> {
372 if !(options.tolerance.is_finite() && options.tolerance > 0.0) {
373 return Err("options.tolerance must be finite and positive".into());
374 }
375 let prep = prepare(bodies, mates, options)?;
376 let movable_ids: Vec<String> = prep.movable.iter().map(|&b| bodies[b].id.clone()).collect();
377
378 let run_monolithic = |poses: &mut Vec<Pose>| -> Result<LmRun, String> {
379 let groups = singleton_groups(&prep.movable);
380 lm_core(
381 &prep.atoms,
382 poses,
383 &groups,
384 prep.scale,
385 prep.tight,
386 options.max_iterations,
387 )
388 };
389
390 if options.strategy == SolveStrategy::Monolithic {
391 let mut poses = prep.base.clone();
392 let run = run_monolithic(&mut poses)?;
393 let steps = vec![SolveStepReport {
394 bodies: movable_ids,
395 method: "monolithic".into(),
396 iterations: run.iterations,
397 }];
398 return finalize(bodies, mates, &prep, &poses, run, "monolithic", steps);
399 }
400
401 let mut poses = prep.base.clone();
403 let outcome = solve_decomposed(bodies, mates, &prep, &mut poses, options.max_iterations)?;
404 let mut run = outcome.run;
405
406 let mut scratch: Vec<Pose> = Vec::new();
407 let mut r: Vec<f64> = Vec::new();
408 eval_residuals(&prep.atoms, &poses, &[], &[], &mut scratch, &mut r);
409 run.rows += prep.m;
410 let max_res = r.iter().fold(0.0f64, |acc, &x| acc.max(x.abs()));
411 if max_res.is_finite() && max_res <= prep.tight {
412 return finalize(
413 bodies,
414 mates,
415 &prep,
416 &poses,
417 run,
418 "decomposed",
419 outcome.steps,
420 );
421 }
422
423 let mut poses = prep.base.clone();
429 let sub = run_monolithic(&mut poses)?;
430 let iters = sub.iterations;
431 run.absorb(sub);
432 let steps = vec![SolveStepReport {
433 bodies: movable_ids,
434 method: "monolithic_fallback".into(),
435 iterations: iters,
436 }];
437 finalize(
438 bodies,
439 mates,
440 &prep,
441 &poses,
442 run,
443 "monolithic_fallback",
444 steps,
445 )
446}