1use std::collections::{BTreeMap, BTreeSet};
4use std::error::Error;
5use std::fmt::{Display, Formatter};
6
7use crate::{ParameterKey, ParameterSet};
8
9#[derive(Clone, Debug, PartialEq)]
11pub struct FixedConstraint {
12 target: ParameterKey,
14 value: f64,
16}
17
18impl FixedConstraint {
19 pub fn new(target: ParameterKey, value: f64) -> Result<Self, ConstraintError> {
25 if !value.is_finite() {
26 return Err(ConstraintError::NonFiniteCoefficient);
27 }
28 Ok(Self { target, value })
29 }
30
31 #[must_use]
33 pub const fn target(&self) -> &ParameterKey {
34 &self.target
35 }
36
37 #[must_use]
39 pub const fn value(&self) -> f64 {
40 self.value
41 }
42}
43
44#[derive(Clone, Debug, PartialEq)]
46pub struct AffineConstraint {
47 target: ParameterKey,
49 source: ParameterKey,
51 multiplier: f64,
53 offset: f64,
55}
56
57impl AffineConstraint {
58 pub fn new(
64 target: ParameterKey,
65 source: ParameterKey,
66 multiplier: f64,
67 offset: f64,
68 ) -> Result<Self, ConstraintError> {
69 if target == source {
70 return Err(ConstraintError::TargetIsSource { target });
71 }
72 if !multiplier.is_finite() || !offset.is_finite() {
73 return Err(ConstraintError::NonFiniteCoefficient);
74 }
75 Ok(Self {
76 target,
77 source,
78 multiplier,
79 offset,
80 })
81 }
82
83 #[must_use]
85 pub const fn target(&self) -> &ParameterKey {
86 &self.target
87 }
88
89 #[must_use]
91 pub const fn source(&self) -> &ParameterKey {
92 &self.source
93 }
94
95 #[must_use]
97 pub const fn multiplier(&self) -> f64 {
98 self.multiplier
99 }
100
101 #[must_use]
103 pub const fn offset(&self) -> f64 {
104 self.offset
105 }
106}
107
108#[derive(Clone, Debug, PartialEq)]
110pub struct LinearTerm {
111 source: ParameterKey,
113 coefficient: f64,
115}
116
117impl LinearTerm {
118 pub fn new(source: ParameterKey, coefficient: f64) -> Result<Self, ConstraintError> {
124 if !coefficient.is_finite() {
125 return Err(ConstraintError::NonFiniteCoefficient);
126 }
127 Ok(Self {
128 source,
129 coefficient,
130 })
131 }
132
133 #[must_use]
135 pub const fn source(&self) -> &ParameterKey {
136 &self.source
137 }
138
139 #[must_use]
141 pub const fn coefficient(&self) -> f64 {
142 self.coefficient
143 }
144}
145
146#[derive(Clone, Debug, PartialEq)]
148pub struct LinearConstraint {
149 target: ParameterKey,
151 terms: Vec<LinearTerm>,
153 offset: f64,
155}
156
157impl LinearConstraint {
158 pub fn new(
165 target: ParameterKey,
166 terms: Vec<LinearTerm>,
167 offset: f64,
168 ) -> Result<Self, ConstraintError> {
169 if terms.is_empty() {
170 return Err(ConstraintError::EmptyLinearTerms);
171 }
172 if !offset.is_finite() {
173 return Err(ConstraintError::NonFiniteCoefficient);
174 }
175 let mut sources = BTreeSet::new();
176 for term in &terms {
177 if term.source == target {
178 return Err(ConstraintError::TargetIsSource {
179 target: target.clone(),
180 });
181 }
182 if !sources.insert(term.source.clone()) {
183 return Err(ConstraintError::DuplicateLinearSource {
184 source: term.source.clone(),
185 });
186 }
187 }
188 Ok(Self {
189 target,
190 terms,
191 offset,
192 })
193 }
194
195 #[must_use]
197 pub const fn target(&self) -> &ParameterKey {
198 &self.target
199 }
200
201 #[must_use]
203 pub fn terms(&self) -> &[LinearTerm] {
204 &self.terms
205 }
206
207 #[must_use]
209 pub const fn offset(&self) -> f64 {
210 self.offset
211 }
212}
213
214#[derive(Clone, Debug, PartialEq)]
216pub enum Constraint {
217 Fixed(FixedConstraint),
219 Affine(AffineConstraint),
221 Linear(LinearConstraint),
223}
224
225impl Constraint {
226 #[must_use]
228 pub fn target(&self) -> &ParameterKey {
229 match self {
230 Self::Fixed(value) => &value.target,
231 Self::Affine(value) => &value.target,
232 Self::Linear(value) => &value.target,
233 }
234 }
235
236 fn sources(&self) -> impl Iterator<Item = &ParameterKey> {
237 let sources: Vec<&ParameterKey> = match self {
238 Self::Fixed(_) => Vec::new(),
239 Self::Affine(value) => vec![&value.source],
240 Self::Linear(value) => value.terms.iter().map(|term| &term.source).collect(),
241 };
242 sources.into_iter()
243 }
244}
245
246#[derive(Clone, Debug, PartialEq)]
248pub struct ConstraintDerivativeMatrix {
249 pub rows: usize,
251 pub columns: usize,
253 pub values: Vec<f64>,
255}
256
257impl ConstraintDerivativeMatrix {
258 #[must_use]
260 pub fn row(&self, index: usize) -> Option<&[f64]> {
261 let start = index.checked_mul(self.columns)?;
262 self.values.get(start..start.checked_add(self.columns)?)
263 }
264}
265
266#[derive(Clone, Debug, PartialEq)]
268pub struct ConstraintTransform {
269 parameters: ParameterSet,
270 constraints: Vec<Constraint>,
271 free_keys: Vec<ParameterKey>,
272}
273
274impl ConstraintTransform {
275 pub fn new(
282 parameters: ParameterSet,
283 constraints: Vec<Constraint>,
284 ) -> Result<Self, ConstraintError> {
285 let known = parameters.key_set();
286 let mut targets = BTreeSet::new();
287 for constraint in &constraints {
288 if !known.contains(constraint.target()) {
289 return Err(ConstraintError::UnknownTarget {
290 target: constraint.target().clone(),
291 });
292 }
293 if !targets.insert(constraint.target().clone()) {
294 return Err(ConstraintError::DuplicateTarget {
295 target: constraint.target().clone(),
296 });
297 }
298 }
299 let mut resolved = known.difference(&targets).cloned().collect::<BTreeSet<_>>();
300 for constraint in &constraints {
301 for source in constraint.sources() {
302 if !known.contains(source) {
303 return Err(ConstraintError::UnknownSource {
304 source: source.clone(),
305 });
306 }
307 if !resolved.contains(source) {
308 return Err(ConstraintError::UnresolvedDependency {
309 target: Box::new(constraint.target().clone()),
310 source: Box::new(source.clone()),
311 });
312 }
313 }
314 resolved.insert(constraint.target().clone());
315 }
316 let free_keys = parameters
317 .specs()
318 .iter()
319 .filter(|spec| spec.refine() && !targets.contains(spec.key()))
320 .map(|spec| spec.key().clone())
321 .collect();
322 Ok(Self {
323 parameters,
324 constraints,
325 free_keys,
326 })
327 }
328
329 #[must_use]
331 pub const fn parameters(&self) -> &ParameterSet {
332 &self.parameters
333 }
334
335 #[must_use]
337 pub fn constraints(&self) -> &[Constraint] {
338 &self.constraints
339 }
340
341 #[must_use]
343 pub fn free_keys(&self) -> &[ParameterKey] {
344 &self.free_keys
345 }
346
347 pub fn pack(&self) -> Result<Vec<f64>, ConstraintError> {
354 self.pack_values(&self.parameters.values())
355 }
356
357 pub fn pack_values(
363 &self,
364 values: &BTreeMap<ParameterKey, f64>,
365 ) -> Result<Vec<f64>, ConstraintError> {
366 self.free_keys
367 .iter()
368 .map(|key| {
369 let value = values
370 .get(key)
371 .copied()
372 .ok_or_else(|| ConstraintError::MissingValue { key: key.clone() })?;
373 let spec =
374 self.parameters
375 .spec(key)
376 .ok_or_else(|| ConstraintError::UnknownSource {
377 source: key.clone(),
378 })?;
379 let scaled = value / spec.scale();
380 if !scaled.is_finite() {
381 return Err(ConstraintError::NonFiniteVector);
382 }
383 Ok(scaled)
384 })
385 .collect()
386 }
387
388 pub fn unpack(
397 &self,
398 vector: &[f64],
399 clip: bool,
400 ) -> Result<BTreeMap<ParameterKey, f64>, ConstraintError> {
401 if vector.len() != self.free_keys.len() {
402 return Err(ConstraintError::VectorLengthMismatch {
403 expected: self.free_keys.len(),
404 actual: vector.len(),
405 });
406 }
407 if vector.iter().any(|value| !value.is_finite()) {
408 return Err(ConstraintError::NonFiniteVector);
409 }
410 let mut values = self.parameters.values();
411 for (key, scaled) in self.free_keys.iter().zip(vector) {
412 let spec = self
413 .parameters
414 .spec(key)
415 .ok_or_else(|| ConstraintError::UnknownSource {
416 source: key.clone(),
417 })?;
418 let physical = scaled * spec.scale();
419 values.insert(
420 key.clone(),
421 if clip {
422 spec.bounds().clip(physical)
423 } else {
424 physical
425 },
426 );
427 }
428 for constraint in &self.constraints {
429 let value = match constraint {
430 Constraint::Fixed(value) => value.value,
431 Constraint::Affine(value) => {
432 value.multiplier
433 * values.get(&value.source).copied().ok_or_else(|| {
434 ConstraintError::MissingValue {
435 key: value.source.clone(),
436 }
437 })?
438 + value.offset
439 }
440 Constraint::Linear(value) => {
441 let mut result = value.offset;
442 for term in &value.terms {
443 result += term.coefficient
444 * values.get(&term.source).copied().ok_or_else(|| {
445 ConstraintError::MissingValue {
446 key: term.source.clone(),
447 }
448 })?;
449 }
450 result
451 }
452 };
453 values.insert(constraint.target().clone(), value);
454 }
455 for spec in self.parameters.specs() {
456 let value =
457 values
458 .get(spec.key())
459 .copied()
460 .ok_or_else(|| ConstraintError::MissingValue {
461 key: spec.key().clone(),
462 })?;
463 if !value.is_finite() || !spec.bounds().contains(value) {
464 return Err(ConstraintError::ExpandedValueOutsideBounds {
465 key: spec.key().clone(),
466 value,
467 });
468 }
469 }
470 Ok(values)
471 }
472
473 pub fn derivative_matrix(&self) -> Result<ConstraintDerivativeMatrix, ConstraintError> {
481 let rows = self.parameters.specs().len();
482 let columns = self.free_keys.len();
483 let element_count = rows
484 .checked_mul(columns)
485 .ok_or(ConstraintError::MatrixSizeOverflow)?;
486 let mut values = vec![0.0; element_count];
487 for (column, key) in self.free_keys.iter().enumerate() {
488 let row = self
489 .parameters
490 .index_of(key)
491 .ok_or(ConstraintError::InternalInvariant)?;
492 let spec = self
493 .parameters
494 .spec(key)
495 .ok_or(ConstraintError::InternalInvariant)?;
496 *values
497 .get_mut(row * columns + column)
498 .ok_or(ConstraintError::InternalInvariant)? = spec.scale();
499 }
500 for constraint in &self.constraints {
501 let target_row = self
502 .parameters
503 .index_of(constraint.target())
504 .ok_or(ConstraintError::InternalInvariant)?;
505 match constraint {
506 Constraint::Fixed(_) => {}
507 Constraint::Affine(constraint) => {
508 let source_row = self
509 .parameters
510 .index_of(&constraint.source)
511 .ok_or(ConstraintError::InternalInvariant)?;
512 for column in 0..columns {
513 let source_value = values
514 .get(source_row * columns + column)
515 .copied()
516 .ok_or(ConstraintError::InternalInvariant)?;
517 *values
518 .get_mut(target_row * columns + column)
519 .ok_or(ConstraintError::InternalInvariant)? =
520 constraint.multiplier * source_value;
521 }
522 }
523 Constraint::Linear(constraint) => {
524 for column in 0..columns {
525 let mut target_value = 0.0;
526 for term in &constraint.terms {
527 let source_row = self
528 .parameters
529 .index_of(&term.source)
530 .ok_or(ConstraintError::InternalInvariant)?;
531 target_value += term.coefficient
532 * values
533 .get(source_row * columns + column)
534 .copied()
535 .ok_or(ConstraintError::InternalInvariant)?;
536 }
537 *values
538 .get_mut(target_row * columns + column)
539 .ok_or(ConstraintError::InternalInvariant)? = target_value;
540 }
541 }
542 }
543 }
544 Ok(ConstraintDerivativeMatrix {
545 rows,
546 columns,
547 values,
548 })
549 }
550}
551
552#[derive(Clone, Debug, PartialEq)]
554pub enum ConstraintError {
555 NonFiniteCoefficient,
557 TargetIsSource {
559 target: ParameterKey,
561 },
562 EmptyLinearTerms,
564 DuplicateLinearSource {
566 source: ParameterKey,
568 },
569 UnknownTarget {
571 target: ParameterKey,
573 },
574 DuplicateTarget {
576 target: ParameterKey,
578 },
579 UnknownSource {
581 source: ParameterKey,
583 },
584 UnresolvedDependency {
586 target: Box<ParameterKey>,
588 source: Box<ParameterKey>,
590 },
591 MissingValue {
593 key: ParameterKey,
595 },
596 VectorLengthMismatch {
598 expected: usize,
600 actual: usize,
602 },
603 NonFiniteVector,
605 MatrixSizeOverflow,
607 InternalInvariant,
609 ExpandedValueOutsideBounds {
611 key: ParameterKey,
613 value: f64,
615 },
616}
617
618impl Display for ConstraintError {
619 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
620 match self {
621 Self::NonFiniteCoefficient => {
622 formatter.write_str("constraint values and coefficients must be finite")
623 }
624 Self::TargetIsSource { target } => {
625 write!(
626 formatter,
627 "constraint target {target} cannot be its own source"
628 )
629 }
630 Self::EmptyLinearTerms => {
631 formatter.write_str("linear constraints require at least one source")
632 }
633 Self::DuplicateLinearSource { source } => {
634 write!(formatter, "linear constraint repeats source {source}")
635 }
636 Self::UnknownTarget { target } => {
637 write!(formatter, "constraint target {target} is not a parameter")
638 }
639 Self::DuplicateTarget { target } => {
640 write!(
641 formatter,
642 "parameter {target} is constrained more than once"
643 )
644 }
645 Self::UnknownSource { source } => {
646 write!(formatter, "constraint source {source} is not a parameter")
647 }
648 Self::UnresolvedDependency { target, source } => write!(
649 formatter,
650 "constraint for {target} depends on unresolved source {source}"
651 ),
652 Self::MissingValue { key } => {
653 write!(formatter, "missing value for free parameter {key}")
654 }
655 Self::VectorLengthMismatch { expected, actual } => write!(
656 formatter,
657 "free vector length {actual} does not match expected length {expected}"
658 ),
659 Self::NonFiniteVector => formatter.write_str("free parameter values must be finite"),
660 Self::MatrixSizeOverflow => {
661 formatter.write_str("constraint derivative matrix size overflow")
662 }
663 Self::InternalInvariant => {
664 formatter.write_str("validated constraint transform state is inconsistent")
665 }
666 Self::ExpandedValueOutsideBounds { key, value } => write!(
667 formatter,
668 "expanded value {value} for {key} lies outside its bounds"
669 ),
670 }
671 }
672}
673
674impl Error for ConstraintError {}