1use crate::physics::Solver;
8
9use crate::physics::bound::Bound::*;
10use crate::physics::bound::all;
11use crate::physics::chaigne_askenfelt::{StringGrid, stencil_update, stiff_string_grid};
12
13#[derive(Clone, Debug, PartialEq)]
14pub struct WillemsenBilbaoSerafinParams {
15 pub f0: f64,
16 pub b: f64,
17 pub bow_pos: f64,
18 pub bow_vel: f64,
19 pub bow_force: f64,
20 pub mu_s: f64,
21 pub mu_c: f64,
22 pub stribeck_vel: f64,
23 pub bristle_stiffness: f64,
24 pub bristle_damping: f64,
25 pub viscous_friction: f64,
26 pub damp_dc: f64,
27 pub damp_freq: f64,
28}
29
30impl WillemsenBilbaoSerafinParams {
31 pub fn at(f0: f64) -> WillemsenBilbaoSerafinParams {
33 WillemsenBilbaoSerafinParams {
34 f0,
35 b: 1e-4,
36 bow_pos: 0.25,
37 bow_vel: 0.1,
38 bow_force: 10.0,
39 mu_s: 0.8,
40 mu_c: 0.3,
41 stribeck_vel: 0.1,
42 bristle_stiffness: 1e4,
43 bristle_damping: 0.1,
44 viscous_friction: 0.4,
45 damp_dc: 1.0,
46 damp_freq: 5e-3,
47 }
48 }
49}
50
51impl WillemsenBilbaoSerafinParams {
52 pub fn valid(&self) -> bool {
53 all(&[
54 (self.f0, Positive),
55 (self.b, NonNegative),
56 (self.bow_pos, OpenUnit),
57 (self.bow_vel, Finite),
58 (self.bow_force, Positive),
59 (self.mu_s, Positive),
60 (self.mu_c, Positive),
61 (self.stribeck_vel, Positive),
62 (self.bristle_stiffness, Positive),
63 (self.bristle_damping, NonNegative),
64 (self.viscous_friction, NonNegative),
65 (self.damp_dc, NonNegative),
66 (self.damp_freq, NonNegative),
67 ])
68 }
69}
70
71const WIRE_DENSITY_KG_M3: f64 = 7850.0;
72const WIRE_RADIUS_M: f64 = 5e-4;
73const WIRE_LENGTH_M: f64 = 1.0;
75const NR_MAX_ITERATIONS: usize = 50;
76const NR_TOLERANCE: f64 = 1e-7;
77
78fn build_grid(params: &WillemsenBilbaoSerafinParams, sr: f64) -> (StringGrid, f64) {
80 let rho = std::f64::consts::PI * WIRE_RADIUS_M * WIRE_RADIUS_M * WIRE_DENSITY_KG_M3;
81 let c = 2.0 * params.f0 * WIRE_LENGTH_M;
82 let kappa = c * WIRE_LENGTH_M * params.b.sqrt() / std::f64::consts::PI;
83 let grid = stiff_string_grid(
84 rho,
85 c,
86 WIRE_LENGTH_M,
87 kappa,
88 params.damp_dc,
89 params.damp_freq,
90 sr,
91 );
92 (grid, c)
93}
94
95fn sgn(x: f64) -> f64 {
96 if x > 0.0 {
97 1.0
98 } else if x < 0.0 {
99 -1.0
100 } else {
101 0.0
102 }
103}
104
105fn steady_state(v: f64, s0: f64, f_c: f64, f_s: f64, stribeck_vel: f64) -> f64 {
107 sgn(v) / s0 * (f_c + (f_s - f_c) * (-(v / stribeck_vel).powi(2)).exp())
108}
109
110fn adhesion(v: f64, z: f64, z_ba: f64, zss: f64) -> f64 {
112 if sgn(v) != sgn(z) {
113 return 0.0;
114 }
115 let (az, azss) = (z.abs(), zss.abs());
116 if az <= z_ba {
117 0.0
118 } else if az >= azss {
119 1.0
120 } else {
121 let span = (azss - z_ba).max(1e-300);
122 let s = sgn(z);
123 0.5 * (1.0 + s * (std::f64::consts::PI * (z - s * 0.5 * (azss + z_ba)) / span).sin())
124 }
125}
126
127fn bristle_rate(v: f64, z: f64, s0: f64, f_c: f64, f_s: f64, stribeck_vel: f64, z_ba: f64) -> f64 {
129 if v == 0.0 {
130 return 0.0;
131 }
132 let zss = steady_state(v, s0, f_c, f_s, stribeck_vel);
133 let a = adhesion(v, z, z_ba, zss);
134 v * (1.0 - a * z / zss)
135}
136
137fn friction_force(v: f64, z: f64, r: f64, s0: f64, s1: f64, s2: f64) -> f64 {
139 s0 * z + s1 * r + s2 * v
140}
141
142struct Coupling {
143 coeff: f64,
144 b_known: f64,
145 s0: f64,
146 s1: f64,
147 s2: f64,
148 f_c: f64,
149 f_s: f64,
150 stribeck_vel: f64,
151 z_ba: f64,
152 z_prev: f64,
153 r_prev: f64,
154 dt: f64,
155}
156
157impl Coupling {
158 fn residual(&self, v: f64, z: f64) -> (f64, f64) {
161 let r = bristle_rate(
162 v,
163 z,
164 self.s0,
165 self.f_c,
166 self.f_s,
167 self.stribeck_vel,
168 self.z_ba,
169 );
170 let f = friction_force(v, z, r, self.s0, self.s1, self.s2);
171 let g1 = v + self.coeff * f - self.b_known;
172 let a = 2.0 * (z - self.z_prev) / self.dt - self.r_prev;
173 (g1, r - a)
174 }
175
176 fn newton_step(&self, v: f64, z: f64) -> Option<(f64, f64)> {
178 let (g1, g2) = self.residual(v, z);
179 let hv = v.abs().max(1e-3) * 1e-6;
180 let hz = z.abs().max(self.z_ba).max(1e-9) * 1e-6;
181 let (g1_vp, g2_vp) = self.residual(v + hv, z);
182 let (g1_vm, g2_vm) = self.residual(v - hv, z);
183 let (g1_zp, g2_zp) = self.residual(v, z + hz);
184 let (g1_zm, g2_zm) = self.residual(v, z - hz);
185 let dg1_dv = (g1_vp - g1_vm) / (2.0 * hv);
186 let dg2_dv = (g2_vp - g2_vm) / (2.0 * hv);
187 let dg1_dz = (g1_zp - g1_zm) / (2.0 * hz);
188 let dg2_dz = (g2_zp - g2_zm) / (2.0 * hz);
189 let det = dg1_dv * dg2_dz - dg1_dz * dg2_dv;
190 if !det.is_finite() || det.abs() < 1e-300 {
191 return None;
192 }
193 let dv = (g1 * dg2_dz - g2 * dg1_dz) / det;
194 let dz = (dg1_dv * g2 - dg2_dv * g1) / det;
195 let (new_v, new_z) = (v - dv, z - dz);
196 if !new_v.is_finite() || !new_z.is_finite() {
197 return None;
198 }
199 Some((new_v, new_z))
200 }
201}
202
203pub struct WillemsenBilbaoSerafinSite {
204 strings: Vec<StringGrid>,
205 contact_index: usize,
206 tension: f64,
207 dt: f64,
208 bow_vel: f64,
209 f_c: f64,
210 f_s: f64,
211 stribeck_vel: f64,
212 s0: f64,
213 s1: f64,
214 s2: f64,
215 z_ba: f64,
216 z: f64,
217 z_prev: f64,
218 r_prev: f64,
220 last_v: f64,
221 last_f: f64,
222}
223
224impl WillemsenBilbaoSerafinSite {
225 pub fn new(params: &WillemsenBilbaoSerafinParams, sr: f64) -> WillemsenBilbaoSerafinSite {
226 let (grid, c) = build_grid(params, sr);
227 let contact_index = (params.bow_pos * grid.n as f64)
228 .round()
229 .clamp(1.0, (grid.n - 1) as f64) as usize;
230 let tension = c * c * grid.rho;
231 let f_c = params.mu_c * params.bow_force;
232 let f_s = params.mu_s * params.bow_force;
233 let z_ba = 0.7 * f_c / params.bristle_stiffness;
235 WillemsenBilbaoSerafinSite {
236 strings: vec![grid],
237 contact_index,
238 tension,
239 dt: 1.0 / sr,
240 bow_vel: params.bow_vel,
241 f_c,
242 f_s,
243 stribeck_vel: params.stribeck_vel,
244 s0: params.bristle_stiffness,
245 s1: params.bristle_damping,
246 s2: params.viscous_friction,
247 z_ba,
248 z: 0.0,
249 z_prev: 0.0,
250 r_prev: 0.0,
251 last_v: 0.0,
252 last_f: 0.0,
253 }
254 }
255}
256
257impl Solver for WillemsenBilbaoSerafinSite {
258 fn step(&mut self) -> f64 {
259 let dt = self.dt;
260 let l = self.contact_index;
261 let coupling_prev = (self.z, self.r_prev);
262
263 let grid = &mut self.strings[0];
264 let n = grid.n;
265 let mut free_next_l = 0.0;
266 for i in 1..n {
267 let next = stencil_update(grid, i, 0.0, 0.0);
268 if i == l {
269 free_next_l = next;
270 }
271 grid.y_next[i] = next;
272 }
273 grid.y_next[0] = 0.0;
274 grid.y_next[n] = 0.0;
275
276 let coupling = Coupling {
277 coeff: dt / (2.0 * grid.rho * grid.dx),
278 b_known: (free_next_l - grid.y_prev[l]) / (2.0 * dt) - self.bow_vel,
279 s0: self.s0,
280 s1: self.s1,
281 s2: self.s2,
282 f_c: self.f_c,
283 f_s: self.f_s,
284 stribeck_vel: self.stribeck_vel,
285 z_ba: self.z_ba,
286 z_prev: coupling_prev.0,
287 r_prev: coupling_prev.1,
288 dt,
289 };
290
291 let (mut v, mut z) = (self.last_v, self.z);
292 let mut converged = false;
293 for _ in 0..NR_MAX_ITERATIONS {
294 let Some((new_v, new_z)) = coupling.newton_step(v, z) else {
295 break;
296 };
297 let step_norm = ((new_v - v).powi(2) + (new_z - z).powi(2)).sqrt();
298 v = new_v;
299 z = new_z;
300 if step_norm < NR_TOLERANCE {
301 converged = true;
302 break;
303 }
304 }
305 debug_assert!(converged, "the friction solve never settled");
306
307 let r = bristle_rate(
308 v,
309 z,
310 self.s0,
311 self.f_c,
312 self.f_s,
313 self.stribeck_vel,
314 self.z_ba,
315 );
316 let f = friction_force(v, z, r, self.s0, self.s1, self.s2);
317 let grid = &mut self.strings[0];
318 grid.y_next[l] = free_next_l - (dt * dt / (grid.rho * grid.dx)) * f;
319
320 self.z_prev = self.z;
321 self.z = z;
322 self.r_prev = r;
323 self.last_v = v;
324 self.last_f = f;
325
326 let sample = self.tension * (grid.y_now[n] - grid.y_now[n - 1]) / grid.dx;
327
328 std::mem::swap(&mut grid.y_prev, &mut grid.y_now);
329 std::mem::swap(&mut grid.y_now, &mut grid.y_next);
330
331 sample
332 }
333}