1 /*
2 * Copyright (c) 2021 Marti Riba Pons
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <stdio.h>
8 #include <math.h>
9 #include <errno.h>
10 #include <zsl/orientation/fusion/fusion.h>
11 #include <zsl/orientation/fusion/calibration.h>
12 #include <zsl/statistics.h>
13
14 #ifndef CONFIG_ZSL_SINGLE_PRECISION
zsl_fus_cal_magn_f_shp(struct zsl_vec * H,struct zsl_vec * b)15 static zsl_real_t zsl_fus_cal_magn_f_shp(struct zsl_vec *H, struct zsl_vec *b)
16 {
17 ZSL_VECTOR_DEF(Hb, 3);
18 zsl_vec_add(H, b, &Hb);
19
20 return zsl_vec_norm(&Hb);
21 }
22 #endif
23
24 #ifndef CONFIG_ZSL_SINGLE_PRECISION
zsl_fus_cal_magn_f_elli(struct zsl_vec * H,struct zsl_mtx * g)25 static zsl_real_t zsl_fus_cal_magn_f_elli(struct zsl_vec *H, struct zsl_mtx *g)
26 {
27 ZSL_MATRIX_DEF(K, 3, 3);
28 K.data[0] = g->data[0];
29 K.data[1] = K.data[3] = g->data[1];
30 K.data[2] = K.data[6] = g->data[2];
31 K.data[4] = g->data[3];
32 K.data[5] = K.data[7] = g->data[4];
33 K.data[8] = g->data[5];
34
35 ZSL_MATRIX_DEF(Hb, 3, 1);
36 Hb.data[0] = H->data[0] + g->data[6];
37 Hb.data[1] = H->data[1] + g->data[7];
38 Hb.data[2] = H->data[2] + g->data[8];
39
40 ZSL_MATRIX_DEF(KHb, 3, 1);
41 zsl_mtx_mult(&K, &Hb, &KHb);
42
43 ZSL_VECTOR_DEF(Hd, 3);
44 zsl_vec_from_arr(&Hd, KHb.data);
45
46 return zsl_vec_norm(&Hd);
47 }
48 #endif
49
zsl_fus_cal_rot_mtx(struct zsl_vec * v,struct zsl_mtx * m,struct zsl_vec * v_rot)50 int zsl_fus_cal_rot_mtx(struct zsl_vec *v, struct zsl_mtx *m,
51 struct zsl_vec *v_rot)
52 {
53 int rc = 0;
54
55 #if CONFIG_ZSL_BOUNDS_CHECKS
56 if (v->sz != 3 || v_rot->sz != 3 || m->sz_rows != 3 || m->sz_cols != 3) {
57 rc = -EINVAL;
58 goto err;
59 }
60 #endif
61
62 ZSL_MATRIX_DEF(vm, 3, 1);
63 zsl_mtx_from_arr(&vm, v->data);
64
65 ZSL_MATRIX_DEF(mv, 3, 1);
66 zsl_mtx_mult(m, &vm, &mv);
67
68 zsl_vec_from_arr(v_rot, mv.data);
69
70 err:
71 return rc;
72 }
73
zsl_fus_cal_rot_axis_angle(struct zsl_vec * v,struct zsl_vec * a,zsl_real_t * b,struct zsl_vec * v_rot)74 int zsl_fus_cal_rot_axis_angle(struct zsl_vec *v, struct zsl_vec *a,
75 zsl_real_t *b, struct zsl_vec *v_rot)
76 {
77 int rc = 0;
78
79 #if CONFIG_ZSL_BOUNDS_CHECKS
80 if (v->sz != 3 || v_rot->sz != 3 || a->sz != 3) {
81 rc = -EINVAL;
82 goto err;
83 }
84 #endif
85
86 struct zsl_quat q;
87 ZSL_MATRIX_DEF(m, 3, 3);
88 zsl_quat_from_axis_angle(a, b, &q);
89 zsl_quat_to_rot_mtx(&q, &m);
90
91 zsl_fus_cal_rot_mtx(v, &m, v_rot);
92
93 err:
94 return rc;
95 }
96
97 #ifndef CONFIG_ZSL_SINGLE_PRECISION
zsl_fus_cal_magn(struct zsl_mtx * m,zsl_real_t * l,zsl_real_t * mu,struct zsl_mtx * K,struct zsl_vec * b)98 int zsl_fus_cal_magn(struct zsl_mtx *m, zsl_real_t *l, zsl_real_t *mu,
99 struct zsl_mtx *K, struct zsl_vec *b)
100 {
101 #if CONFIG_ZSL_BOUNDS_CHECKS
102 /* Make sure that the input magnetometer data matrix has dimension N x 3.
103 * 'K' must be a 3 x 3 matrix and 'b' a tridimensional vector. The
104 * 'mu' parameter must be greater than one. */
105 if (m->sz_cols != 3 || K->sz_rows != 3 || K->sz_cols != 3 ||
106 b->sz != 3 || *mu < 1.0) {
107 return -EINVAL;
108 }
109 #endif
110
111 /* Make a copy of l. */
112 zsl_real_t l_copy = *l;
113
114
115 /* SPHERE FITTING. */
116
117 /* Calculate the initial value of beta (b) as the mean value of (x, y, z)
118 * max and (x, y, z) min. */
119 ZSL_VECTOR_DEF(v, m->sz_rows);
120 ZSL_VECTOR_DEF(w, m->sz_rows);
121
122 zsl_real_t mean = 0.0;
123
124 for (size_t i = 0; i < 3; i++) {
125 zsl_mtx_get_col(m, i, v.data);
126 zsl_vec_sort(&v, &w);
127 b->data[i] = -(w.data[m->sz_rows - 1] + w.data[0]) / 2.0;
128 mean += (w.data[m->sz_rows - 1] - w.data[0]) / 2.0;
129 }
130
131 /* R is a first estimation of the radius for the sphere fitting. */
132 zsl_real_t R = mean / 3.0;
133
134 /* Define needed variables. */
135 ZSL_VECTOR_DEF(H, 3);
136 ZSL_MATRIX_DEF(J, 1, 4);
137 ZSL_MATRIX_DEF(Jt, 4, 1);
138 ZSL_MATRIX_DEF(JtJ, 4, 4);
139 ZSL_MATRIX_DEF(idx, 4, 4);
140 ZSL_MATRIX_DEF(t, 4, 1);
141 zsl_real_t f, S = 0.0, S2;
142
143 /* Initialize the identity matrix. */
144 zsl_mtx_init(&idx, zsl_mtx_entry_fn_identity);
145
146 for (size_t j = 0; j < m->sz_rows; j++) {
147
148 /* Calculate the Jacobian matrix J. */
149 zsl_mtx_init(&J, NULL);
150 for (size_t i = 0; i < m->sz_rows; i++) {
151 zsl_mtx_get_row(m, i, H.data);
152 f = zsl_fus_cal_magn_f_shp(&H, b);
153 J.data[0] += 1.0;
154 J.data[1] += -(H.data[0] - b->data[0]) / f;
155 J.data[2] += -(H.data[1] - b->data[1]) / f;
156 J.data[3] += -(H.data[2] - b->data[2]) / f;
157 }
158
159 /* Compute the transpose of J. */
160 zsl_mtx_trans(&J, &Jt);
161
162 /* Calculate the value of tau ('t'). */
163 zsl_mtx_get_row(m, j, H.data);
164 f = zsl_fus_cal_magn_f_shp(&H, b);
165 zsl_mtx_mult(&Jt, &J, &JtJ);
166 idx.data[0] = *l * JtJ.data[0];
167 idx.data[5] = *l * JtJ.data[5];
168 idx.data[10] = *l * JtJ.data[10];
169 idx.data[15] = *l * JtJ.data[15];
170 zsl_mtx_add_d(&JtJ, &idx);
171 zsl_mtx_inv(&JtJ, &idx);
172 zsl_mtx_mult(&idx, &Jt, &t);
173 zsl_mtx_scalar_mult_d(&t, -(R - f));
174
175 /* Calculate the new pair (R, b) by adding tau to the old values of R
176 * and b. */
177 R += t.data[0];
178 b->data[0] += t.data[1];
179 b->data[1] += t.data[2];
180 b->data[2] += t.data[3];
181
182 if (j < (m->sz_rows - 1)) {
183
184 /* Calculate the squared residual sum of all samples. */
185 zsl_mtx_get_row(m, j + 1, H.data);
186 f = zsl_fus_cal_magn_f_shp(&H, b);
187 S2 = (S * j + (R - f) * (R - f)) / (zsl_real_t) (j + 1);
188
189 /* Update the value of lambda (l) with the information from the
190 * squared residual sum. */
191 if (S2 < S) {
192 *l /= *mu;
193 } else {
194 *l *= *mu;
195 }
196
197 S = S2;
198 }
199 }
200
201 /* ELLIPSOID FITTING. */
202
203 /* Initialize the matrix K as an identity matrix. */
204 zsl_mtx_init(K, zsl_mtx_entry_fn_identity);
205
206 /* Reset the value of lambda ('l') and the value of the sum (S). */
207 *l = l_copy;
208 S = 0.0;
209
210 /* Define gamma (g) and initialize it with the calculated values of
211 * beta in the sphere fitting. */
212 ZSL_MATRIX_DEF(g, 9, 1);
213 g.data[0] = 1.0;
214 g.data[1] = 0.0;
215 g.data[2] = 0.0;
216 g.data[3] = 1.0;
217 g.data[4] = 0.0;
218 g.data[5] = 1.0;
219 g.data[6] = b->data[0];
220 g.data[7] = b->data[1];
221 g.data[8] = b->data[2];
222
223 /* Define needed variables. */
224 ZSL_MATRIX_DEF(N, 1, 9);
225 ZSL_MATRIX_DEF(Nt, 9, 1);
226 ZSL_MATRIX_DEF(NtN, 9, 9);
227 ZSL_MATRIX_DEF(idxN, 9, 9);
228 ZSL_MATRIX_DEF(tN, 9, 1);
229 zsl_real_t A, B, C;
230
231 /* Initialize the identity matrix. */
232 zsl_mtx_init(&idxN, zsl_mtx_entry_fn_identity);
233
234 /* Start the ellipsoid fitting algorithm. */
235 for (size_t j = 0; j < m->sz_rows; j++) {
236 zsl_mtx_init(&N, NULL);
237
238 for (size_t i = 0; i < m->sz_rows; i++) {
239 zsl_mtx_get_row(m, i, H.data);
240 f = zsl_fus_cal_magn_f_elli(&H, &g);
241
242 /* Define variables for repeated calculations. */
243 A = g.data[0] * (H.data[0] + b->data[0]) +
244 g.data[1] * (H.data[1] + b->data[1]) +
245 g.data[2] * (H.data[2] + b->data[2]);
246 B = g.data[1] * (H.data[0] + b->data[0]) +
247 g.data[3] * (H.data[1] + b->data[1]) +
248 g.data[4] * (H.data[2] + b->data[2]);
249 C = g.data[2] * (H.data[0] + b->data[0]) +
250 g.data[4] * (H.data[1] + b->data[1]) +
251 g.data[5] * (H.data[2] + b->data[2]);
252
253 /* Calculate the Jacobian matrix N. */
254 N.data[0] += -(H.data[0] + b->data[0]) * A / f;
255 N.data[1] += -(H.data[1] + b->data[1]) * B / f;
256 N.data[2] += -(H.data[2] + b->data[2]) * C / f;
257 N.data[3] += -((H.data[1] + b->data[1]) * A +
258 (H.data[0] + b->data[0]) * B) / f;
259 N.data[4] += -((H.data[2] + b->data[2]) * A +
260 (H.data[0] + b->data[0]) * C) / f;
261 N.data[5] += -((H.data[2] + b->data[2]) * B +
262 (H.data[1] + b->data[1]) * C) / f;
263 N.data[6] += -A / f;
264 N.data[7] += -B / f;
265 N.data[8] += -C / f;
266 }
267
268 /* Calculate the transpose of N. */
269 zsl_mtx_trans(&N, &Nt);
270
271 /* Calculate the value of tau (tN). */
272 zsl_mtx_get_row(m, j, H.data);
273 f = zsl_fus_cal_magn_f_elli(&H, &g);
274 zsl_mtx_mult(&Nt, &N, &NtN);
275 idxN.data[0] = *l * NtN.data[0];
276 idxN.data[10] = *l * NtN.data[10];
277 idxN.data[20] = *l * NtN.data[20];
278 idxN.data[30] = *l * NtN.data[30];
279 idxN.data[40] = *l * NtN.data[40];
280 idxN.data[50] = *l * NtN.data[50];
281 idxN.data[60] = *l * NtN.data[60];
282 idxN.data[70] = *l * NtN.data[70];
283 idxN.data[80] = *l * NtN.data[80];
284 zsl_mtx_add_d(&NtN, &idxN);
285 zsl_mtx_inv(&NtN, &idxN);
286 zsl_mtx_mult(&idxN, &Nt, &tN);
287 zsl_mtx_scalar_mult_d(&tN, -(R - f));
288
289 /* Calculate the new vector gamma. */
290 g.data[0] += tN.data[0];
291 g.data[1] += tN.data[1];
292 g.data[2] += tN.data[2];
293 g.data[3] += tN.data[3];
294 g.data[4] += tN.data[4];
295 g.data[5] += tN.data[5];
296 g.data[6] += tN.data[6];
297 g.data[7] += tN.data[7];
298 g.data[8] += tN.data[8];
299
300 if (j < (m->sz_rows - 1)) {
301
302 /* Calculate the squared residual sum of all samples. */
303 zsl_mtx_get_row(m, j + 1, H.data);
304 f = zsl_fus_cal_magn_f_elli(&H, &g);
305 S2 = (S * j + (R - f) * (R - f)) / (zsl_real_t) (j + 1);
306
307 /* Update the value of lambda (l) with the information from the
308 * squared residual sum. */
309 if (S2 < S) {
310 *l /= *mu;
311 } else {
312 *l *= *mu;
313 }
314
315 S = S2;
316 }
317 }
318
319 /* Calculate the matrix K. */
320 K->data[0] = g.data[0];
321 K->data[1] = K->data[3] = g.data[1];
322 K->data[2] = K->data[6] = g.data[2];
323 K->data[4] = g.data[3];
324 K->data[5] = K->data[7] = g.data[4];
325 K->data[8] = g.data[5];
326
327 /* Calculate the vector beta ('b'). */
328 b->data[0] = g.data[6];
329 b->data[1] = g.data[7];
330 b->data[2] = g.data[8];
331
332 return 0;
333 }
334 #endif
335
336 #ifndef CONFIG_ZSL_SINGLE_PRECISION
zsl_fus_cal_magn_fast(struct zsl_mtx * m,zsl_real_t * me,struct zsl_mtx * K,struct zsl_vec * b)337 int zsl_fus_cal_magn_fast(struct zsl_mtx *m, zsl_real_t *me, struct zsl_mtx *K,
338 struct zsl_vec *b)
339 {
340
341 zsl_real_t approx_me = 50.0;
342
343 /* Use an approximation for me if no value provided. */
344 if (me == NULL) {
345 /* Locations vary fromo 25-65 uT, but most are close to 50 uT. */
346 me = &approx_me;
347 }
348
349 #if CONFIG_ZSL_BOUNDS_CHECKS
350 /* Make sure that the input magnetometer data matrix has dimension N x 3.
351 * 'K' must be a 3 x 3 matrix and 'b' a tridimensional vector. Also 'me'
352 * should be positive. */
353 if (m->sz_cols != 3 || K->sz_rows != 3 || K->sz_cols != 3 ||
354 b->sz != 3 || *me < 0.0) {
355 return -EINVAL;
356 }
357 #endif
358
359 ZSL_VECTOR_DEF(coef, 9);
360 zsl_sta_quad_fit(m, &coef);
361
362 ZSL_MATRIX_DEF(A, 3, 3);
363 ZSL_MATRIX_DEF(v, 3, 1);
364 A.data[0] = coef.data[0];
365 A.data[1] = A.data[3] = coef.data[3];
366 A.data[2] = A.data[6] = coef.data[4];
367 A.data[4] = coef.data[1];
368 A.data[5] = A.data[7] = coef.data[5];
369 A.data[8] = coef.data[2];
370
371 v.data[0] = coef.data[6];
372 v.data[1] = coef.data[7];
373 v.data[2] = coef.data[8];
374
375 ZSL_MATRIX_DEF(X0, 3, 1);
376 ZSL_MATRIX_DEF(L, 3, 3);
377 ZSL_MATRIX_DEF(G, 3, 3);
378 ZSL_MATRIX_DEF(Ai, 3, 3);
379
380 zsl_mtx_cholesky(&A, &L);
381
382 zsl_mtx_trans(&L, &G);
383 zsl_mtx_scalar_mult_d(&G, *me);
384 zsl_mtx_copy(K, &G);
385
386 zsl_mtx_inv(&A, &Ai);
387 zsl_mtx_mult(&Ai, &v, &X0);
388
389 b->data[0] = X0.data[0];
390 b->data[1] = X0.data[1];
391 b->data[2] = X0.data[2];
392
393 return 0;
394 }
395 #endif
396
zsl_fus_cal_corr_scalar(zsl_real_t * d,zsl_real_t * k,zsl_real_t * b,zsl_real_t * d_out)397 int zsl_fus_cal_corr_scalar(zsl_real_t *d, zsl_real_t *k, zsl_real_t *b,
398 zsl_real_t *d_out)
399 {
400 int rc = 0;
401
402 *d_out = *k * (*d + *b);
403
404 return rc;
405 }
406
zsl_fus_cal_corr_vec(struct zsl_vec * v,struct zsl_mtx * K,struct zsl_vec * b,struct zsl_vec * v_out)407 int zsl_fus_cal_corr_vec(struct zsl_vec *v, struct zsl_mtx *K,
408 struct zsl_vec *b, struct zsl_vec *v_out)
409 {
410 #if CONFIG_ZSL_BOUNDS_CHECKS
411 /* Make sure that the input vectors and matrices have dimension the
412 * same dimensions. */
413 if (v->sz != v_out->sz || v->sz != K->sz_rows || v->sz != K->sz_cols ||
414 v->sz != b->sz) {
415 return -EINVAL;
416 }
417 #endif
418
419 ZSL_VECTOR_DEF(vb, v->sz);
420 ZSL_MATRIX_DEF(VB, v->sz, 1);
421 ZSL_MATRIX_DEF(KVB, v->sz, 1);
422
423 zsl_vec_add(v, b, &vb);
424 zsl_mtx_from_arr(&VB, vb.data);
425 zsl_mtx_mult(K, &VB, &KVB);
426 zsl_vec_from_arr(v_out, KVB.data);
427
428 return 0;
429 }
430
zsl_fus_cal_madg(struct zsl_mtx * g,struct zsl_mtx * a,struct zsl_mtx * m,zsl_real_t sampleFreq,zsl_real_t * incl,zsl_real_t * beta)431 int zsl_fus_cal_madg(struct zsl_mtx *g, struct zsl_mtx *a,
432 struct zsl_mtx *m, zsl_real_t sampleFreq, zsl_real_t *incl,
433 zsl_real_t *beta)
434 {
435 int rc = 0;
436
437 #if CONFIG_ZSL_BOUNDS_CHECKS
438 /* Make sure that the input vectors have dimension 3, and that the
439 * frequency is positive. */
440 if (g->sz_cols != 3 || a->sz_cols != 3 || m->sz_cols != 3 ||
441 sampleFreq < 0.0 || g->sz_rows != a->sz_rows ||
442 g->sz_rows != m->sz_rows) {
443 rc = -EINVAL;
444 goto err;
445 }
446 #endif
447
448 struct zsl_fus_madg_cfg madg_cfg;
449
450 struct zsl_fus_drv madg_drv = {
451 .init_handler = zsl_fus_madg_init,
452 .feed_handler = zsl_fus_madg_feed,
453 .error_handler = zsl_fus_madg_error,
454 .config = &madg_cfg,
455 };
456
457 struct zsl_quat q;
458 struct zsl_euler e = { 0 };
459 struct zsl_attitude att;
460
461 ZSL_VECTOR_DEF(gv, 3);
462 ZSL_VECTOR_DEF(av, 3);
463 ZSL_VECTOR_DEF(mv, 3);
464
465 zsl_real_t Sx, Sy, St, Smin;
466
467 madg_drv.init_handler(sampleFreq, madg_drv.config);
468
469 for (size_t i = 0; i < 1000; i++) {
470 zsl_quat_init(&q, ZSL_QUAT_TYPE_IDENTITY);
471 madg_cfg.beta = (zsl_real_t) i / 1000.0;
472 Sx = 0.0;
473 Sy = 0.0;
474
475 for (size_t j = 0; j < a->sz_rows; j++) {
476 zsl_mtx_get_row(g, j, gv.data);
477 zsl_mtx_get_row(a, j, av.data);
478 zsl_mtx_get_row(m, j, mv.data);
479
480 madg_drv.feed_handler(&av, &mv, &gv, incl, &q, madg_drv.config);
481
482 zsl_quat_to_euler(&q, &e);
483 e.x *= 180. / ZSL_PI;
484 e.y *= 180. / ZSL_PI;
485 e.z *= 180. / ZSL_PI;
486
487 zsl_att_from_accel(&av, &att);
488
489 Sx += (e.x - att.roll) * (e.x - att.roll);
490 Sy += (e.y - att.pitch) * (e.y - att.pitch);
491 }
492 Sx = ZSL_SQRT(Sx / a->sz_rows);
493 Sy = ZSL_SQRT(Sy / a->sz_rows);
494
495 St = (Sx + Sy) / 2.0;
496
497 Smin = 0;
498 if (i == 0) {
499 Smin = St;
500 *beta = madg_cfg.beta;
501 } else if (St < Smin) {
502 Smin = St;
503 *beta = madg_cfg.beta;
504 }
505 }
506
507 err:
508 return rc;
509 }
510
zsl_fus_cal_mahn(struct zsl_mtx * g,struct zsl_mtx * a,struct zsl_mtx * m,zsl_real_t sampleFreq,zsl_real_t * incl,zsl_real_t * kp)511 int zsl_fus_cal_mahn(struct zsl_mtx *g, struct zsl_mtx *a,
512 struct zsl_mtx *m, zsl_real_t sampleFreq, zsl_real_t *incl,
513 zsl_real_t *kp)
514 {
515 int rc = 0;
516
517 #if CONFIG_ZSL_BOUNDS_CHECKS
518 /* Make sure that the input vectors have dimension 3, and that the
519 * frequency is positive. */
520 if (g->sz_cols != 3 || a->sz_cols != 3 || m->sz_cols != 3 ||
521 sampleFreq < 0.0 || g->sz_rows != a->sz_rows ||
522 g->sz_rows != m->sz_rows) {
523 rc = -EINVAL;
524 goto err;
525 }
526 #endif
527
528 zsl_real_t _mahn_intfb[3] = { 0.0, 0.0, 0.0 };
529 struct zsl_fus_mahn_cfg mahn_cfg = {
530 .ki = 0.02,
531 .intfb = {
532 .sz = 3,
533 .data = _mahn_intfb,
534 },
535 };
536
537 struct zsl_fus_drv mahn_drv = {
538 .init_handler = zsl_fus_mahn_init,
539 .feed_handler = zsl_fus_mahn_feed,
540 .error_handler = zsl_fus_mahn_error,
541 .config = &mahn_cfg,
542 };
543
544 struct zsl_quat q;
545 struct zsl_euler e = { 0 };
546 struct zsl_attitude att;
547
548 ZSL_VECTOR_DEF(gv, 3);
549 ZSL_VECTOR_DEF(av, 3);
550 ZSL_VECTOR_DEF(mv, 3);
551
552 zsl_real_t Sx, Sy, St, Smin;
553
554 mahn_drv.init_handler(sampleFreq, mahn_drv.config);
555
556 for (size_t i = 0; i < 1000; i++) {
557 zsl_quat_init(&q, ZSL_QUAT_TYPE_IDENTITY);
558 mahn_cfg.kp = (zsl_real_t) i / 1000.0;
559 Sx = 0.0;
560 Sy = 0.0;
561
562 for (size_t j = 0; j < a->sz_rows; j++) {
563 zsl_mtx_get_row(g, j, gv.data);
564 zsl_mtx_get_row(a, j, av.data);
565 zsl_mtx_get_row(m, j, mv.data);
566
567 mahn_drv.feed_handler(&av, &mv, &gv, incl, &q, mahn_drv.config);
568
569 zsl_quat_to_euler(&q, &e);
570 e.x *= 180. / ZSL_PI;
571 e.y *= 180. / ZSL_PI;
572 e.z *= 180. / ZSL_PI;
573
574 zsl_att_from_accel(&av, &att);
575
576 Sx += (e.x - att.roll) * (e.x - att.roll);
577 Sy += (e.y - att.pitch) * (e.y - att.pitch);
578 }
579 Sx = ZSL_SQRT(Sx / a->sz_rows);
580 Sy = ZSL_SQRT(Sy / a->sz_rows);
581
582 St = (Sx + Sy) / 2.0;
583
584 Smin = 0;
585 if (i == 0) {
586 Smin = St;
587 *kp = mahn_cfg.kp;
588 } else if (St < Smin) {
589 Smin = St;
590 *kp = mahn_cfg.kp;
591 }
592 }
593
594 err:
595 return rc;
596 }
597