Another project
1use bone_solver::{ConstraintSystem, DofConfig, LineHandle, PointHandle, Residual, analyze_dof};
2use bone_types::{Parameter, ParameterIndex, ResidualIndex};
3
4fn p(i: u32) -> ParameterIndex {
5 ParameterIndex::new(i)
6}
7
8fn point(x: u32, y: u32) -> PointHandle {
9 PointHandle { x: p(x), y: p(y) }
10}
11
12fn parameters(values: &[f64]) -> Vec<Parameter> {
13 values.iter().copied().map(Parameter::new).collect()
14}
15
16#[test]
17fn fully_constrained_pin_has_zero_dof_and_no_flags() {
18 let system = ConstraintSystem::new(
19 parameters(&[0.0]),
20 vec![Residual::Pin {
21 param: p(0),
22 target: 5.0,
23 }],
24 );
25 let report = analyze_dof(&system, DofConfig::DEFAULT);
26 assert_eq!(report.dof().value(), 0);
27 assert!(report.under_constrained().is_empty());
28 assert!(report.over_constrained().is_empty());
29 assert!(report.redundant_consistent().is_empty());
30}
31
32#[test]
33fn under_constrained_horizontal_reports_free_parameters() {
34 let system = ConstraintSystem::new(
35 parameters(&[0.0, 0.0, 1.0, 0.0]),
36 vec![Residual::Horizontal(LineHandle {
37 a: point(0, 1),
38 b: point(2, 3),
39 })],
40 );
41 let report = analyze_dof(&system, DofConfig::DEFAULT);
42 assert_eq!(report.dof().value(), 3);
43 assert_eq!(report.under_constrained().len(), 3);
44 assert!(report.over_constrained().is_empty());
45 assert!(report.redundant_consistent().is_empty());
46}
47
48#[test]
49fn redundant_pin_is_consistent_not_conflict() {
50 let system = ConstraintSystem::new(
51 parameters(&[5.0]),
52 vec![
53 Residual::Pin {
54 param: p(0),
55 target: 5.0,
56 },
57 Residual::Pin {
58 param: p(0),
59 target: 5.0,
60 },
61 ],
62 );
63 let report = analyze_dof(&system, DofConfig::DEFAULT);
64 assert_eq!(report.dof().value(), 0);
65 assert!(report.over_constrained().is_empty());
66 assert_eq!(report.redundant_consistent().len(), 1);
67 let flagged = report.redundant_consistent()[0];
68 assert!(flagged == ResidualIndex::new(0) || flagged == ResidualIndex::new(1));
69}
70
71#[test]
72fn dof_fixture_matrix_snapshot() {
73 let fully_constrained = ConstraintSystem::new(
74 parameters(&[0.0]),
75 vec![Residual::Pin {
76 param: p(0),
77 target: 5.0,
78 }],
79 );
80 let under_constrained = ConstraintSystem::new(
81 parameters(&[0.0, 0.0, 1.0, 0.0]),
82 vec![Residual::Horizontal(LineHandle {
83 a: point(0, 1),
84 b: point(2, 3),
85 })],
86 );
87 let redundant_consistent = ConstraintSystem::new(
88 parameters(&[5.0]),
89 vec![
90 Residual::Pin {
91 param: p(0),
92 target: 5.0,
93 },
94 Residual::Pin {
95 param: p(0),
96 target: 5.0,
97 },
98 ],
99 );
100 let conflicting = ConstraintSystem::new(
101 parameters(&[3.0]),
102 vec![
103 Residual::Pin {
104 param: p(0),
105 target: 3.0,
106 },
107 Residual::Pin {
108 param: p(0),
109 target: 7.0,
110 },
111 ],
112 );
113 let bundle = [
114 ("fully_constrained", fully_constrained),
115 ("under_constrained", under_constrained),
116 ("redundant_consistent", redundant_consistent),
117 ("conflicting", conflicting),
118 ];
119 let rendered: String = bundle
120 .iter()
121 .map(|(label, system)| {
122 format!(
123 "{label}: {}",
124 analyze_dof(system, DofConfig::DEFAULT).display()
125 )
126 })
127 .collect::<Vec<_>>()
128 .join("\n");
129 insta::assert_snapshot!("dof_matrix", rendered);
130}
131
132#[test]
133fn conflicting_pins_yield_over_constrained_row() {
134 let system = ConstraintSystem::new(
135 parameters(&[3.0]),
136 vec![
137 Residual::Pin {
138 param: p(0),
139 target: 3.0,
140 },
141 Residual::Pin {
142 param: p(0),
143 target: 7.0,
144 },
145 ],
146 );
147 let report = analyze_dof(&system, DofConfig::DEFAULT);
148 assert_eq!(report.dof().value(), 0);
149 assert_eq!(report.over_constrained().len(), 1);
150 assert!(report.redundant_consistent().is_empty());
151 let flagged = report.over_constrained()[0];
152 assert!(flagged == ResidualIndex::new(0) || flagged == ResidualIndex::new(1));
153}