geop_core_math/solvers/system/
driver.rs1use crate::{
16 geop_error::GeopResult,
17 scalars::Scalar,
18 solvers::least_squares::{self, Evaluation, Rows, Space, Stop},
19};
20
21use super::{Bound, Equations, Solving, Term, Variable};
22
23pub const RELATIVE_TOLERANCE: i64 = 1_000_000_000;
26
27pub fn tolerance(scale: Scalar) -> GeopResult<Scalar> {
30 scale.div(Scalar::from_i64(RELATIVE_TOLERANCE))
31}
32
33pub const DAMPING: (i64, i64) = (1, 10_000);
45
46const MAX_ITERATIONS: usize = 200;
49
50#[derive(Clone, Copy, Debug, PartialEq, Eq)]
52pub enum Mobility {
53 Fixed,
55 Held,
58 Follows,
62}
63
64impl Mobility {
65 pub fn free(self) -> bool {
67 self != Mobility::Fixed
68 }
69}
70
71#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
74pub struct Phase {
75 pub stop: Stop,
76 pub iterations: usize,
77 pub turned_down: usize,
80 #[serde(with = "crate::scalars::as_f64")]
81 pub max_residual: Scalar,
82}
83
84pub(super) fn largest(residuals: impl IntoIterator<Item = Scalar>) -> Scalar {
86 residuals.into_iter().fold(Scalar::ZERO, |largest, r| {
87 let size = r.abs().upper();
88 if size.could_be_greater(largest) {
89 size
90 } else {
91 largest
92 }
93 })
94}
95
96pub(super) type Phased<E> = (Vec<<E as Equations>::Value>, Vec<Phase>);
98
99type Minimized<E> = (Vec<<E as Equations>::Value>, Phase, bool);
102
103pub(super) struct Values<V>(pub(super) std::marker::PhantomData<V>);
106
107impl<V: Variable> Space for Values<V> {
108 type Point = Vec<V>;
109
110 fn dimension(&self, at: &Vec<V>) -> usize {
111 at.iter().map(Variable::dimension).sum()
112 }
113
114 fn step(&self, at: &Vec<V>, step: &[Scalar]) -> Option<Vec<V>> {
115 let mut rest = step;
116 let mut next = Vec::with_capacity(at.len());
117 for value in at {
118 let (own, after) = rest.split_at_checked(value.dimension())?;
119 next.push(value.retract(own).ok()?);
120 rest = after;
121 }
122 Some(next)
123 }
124
125 fn same(&self, a: &Vec<V>, b: &Vec<V>) -> bool {
126 a.iter().zip(b).all(|(a, b)| a.same(b))
127 }
128}
129
130impl<E: Equations> Solving<'_, '_, E> {
131 pub(super) async fn solve(
135 &self,
136 start: Vec<E::Value>,
137 active: &[usize],
138 left_out: &[usize],
139 ) -> GeopResult<Phased<E>> {
140 let hard: Vec<usize> = (self.group.constraints.iter())
141 .filter(|c| !left_out.contains(c))
142 .filter(|&&c| self.system.equations.bound(c) == Bound::Zero || active.contains(&c))
143 .copied()
144 .collect();
145 Asked {
146 solving: self,
147 hard,
148 }
149 .solve(start)
150 .await
151 }
152}
153
154struct Asked<'a, 's, 'e, E: Equations> {
156 solving: &'a Solving<'s, 'e, E>,
157 hard: Vec<usize>,
158}
159
160struct Terms {
165 constraints: Rows,
166 pulls: Rows,
167 stays: Rows,
168}
169
170impl<E: Equations> Asked<'_, '_, '_, E> {
171 async fn terms(
176 &self,
177 values: &[E::Value],
178 anchor: &[E::Value],
179 pulling: bool,
180 ) -> Option<Terms> {
181 let solving = self.solving;
182 let identity = |_: usize| None;
183 let mut which: Vec<Term> = self.hard.iter().map(|&c| Term::Constraint(c)).collect();
184 if pulling {
185 which.extend(
186 solving
187 .group
188 .preferences
189 .iter()
190 .map(|&p| Term::Preference(p)),
191 );
192 }
193 let made = solving.made(values, &which).await;
194 let mut constraints = Rows::default();
195 let mut pulls = Rows::default();
196 let mut pulled = Vec::new();
197 for (term, made) in which.iter().zip(made) {
198 let made = made?;
199 match term {
200 Term::Constraint(_) => constraints.append(solving.rows(&made, &identity)),
201 Term::Preference(_) => {
202 pulled.extend(made.jacobian.iter().map(|(block, _)| *block));
203 pulls.append(solving.rows(&made, &identity));
204 }
205 }
206 }
207 let mut stays = Rows::default();
208 for (k, ((&block, value), at)) in (solving.group.blocks.iter())
209 .zip(values)
210 .zip(anchor)
211 .enumerate()
212 {
213 if pulled.contains(&block) || solving.system.mobility[block] != Mobility::Held {
214 continue;
215 }
216 let stay = value.stay(at, solving.system.scale).ok()?;
217 let column = solving.columns[k];
218 for (value, own) in stay.values.into_iter().zip(stay.jacobian) {
219 let mut row = vec![Scalar::ZERO; solving.n];
220 for (slot, v) in row.iter_mut().skip(column).zip(own) {
221 *slot = v;
222 }
223 stays.push(value, row);
224 }
225 }
226 Some(Terms {
227 constraints,
228 pulls,
229 stays,
230 })
231 }
232
233 async fn check(&self, values: &[E::Value]) -> GeopResult<(Scalar, bool)> {
237 let tolerance = tolerance(self.solving.system.scale)?;
238 Ok(self.solving.check(values, &self.hard, tolerance).await)
239 }
240
241 async fn solve(&self, start: Vec<E::Value>) -> GeopResult<Phased<E>> {
247 let (solved, phase, holds) = self.minimize(start, self.solving.start(), true).await?;
248 let mut phases = vec![phase];
249 if holds {
250 return Ok((solved, phases));
251 }
252 let (solved, phase, _) = self.minimize(solved.clone(), solved, false).await?;
253 phases.push(phase);
254 Ok((solved, phases))
255 }
256
257 async fn minimize(
263 &self,
264 from: Vec<E::Value>,
265 anchor: Vec<E::Value>,
266 hard: bool,
267 ) -> GeopResult<Minimized<E>> {
268 let space = &self.solving.space;
269 if space.dimension(&from) == 0 {
270 let (max_residual, holds) = self.check(&from).await?;
271 let phase = Phase {
272 stop: Stop::NoChange { rejected: None },
273 iterations: 0,
274 turned_down: 0,
275 max_residual,
276 };
277 return Ok((from, phase, holds));
278 }
279 let damping = Scalar::from_ratio(DAMPING.0, DAMPING.1)?;
280 let evaluate = async |at: &Vec<E::Value>| {
281 let Terms {
282 constraints,
283 pulls,
284 stays,
285 } = self.terms(at, &anchor, hard).await?;
286 let mut evaluation = Evaluation::default();
287 if hard {
288 evaluation.constraints = constraints;
289 } else {
290 evaluation.sum = constraints;
291 }
292 let sum = &mut evaluation.sum;
293 sum.append(pulls);
294 for (value, row) in stays.values.into_iter().zip(stays.jacobian) {
295 sum.push(
296 value.mul(damping),
297 row.into_iter().map(|d| d.mul(damping)).collect(),
298 );
299 }
300 let finite = |rows: &Rows| {
302 (rows.values.iter())
303 .chain(rows.jacobian.iter().flatten())
304 .all(|v| v.is_finite())
305 };
306 (finite(&evaluation.constraints) && finite(&evaluation.sum)).then_some(evaluation)
307 };
308 let outcome = least_squares::minimize(
309 space,
310 evaluate,
311 from,
312 least_squares::Options {
313 max_iterations: MAX_ITERATIONS,
314 max_step: self.solving.system.scale,
315 },
316 )
317 .await?;
318 let (max_residual, holds) = self.check(&outcome.x).await?;
319 let phase = Phase {
320 stop: outcome.stop,
321 iterations: outcome.iterations,
322 turned_down: outcome.turned_down,
323 max_residual,
324 };
325 Ok((outcome.x, phase, holds))
326 }
327}