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