Kuramoto oscillators pulled toward their mean phase.
Each call advances every phase by its own increment plus coupling * r * sin(mean_phase - phase), where r e^(i mean_phase) is the mean of the phases on the unit circle, then returns the mean of the sines. With no coupling the group stays loose; past a critical coupling it locks, and the output grows as the phases align. Costs a few trigonometric calls per oscillator per call.
502{
503 if (coefs.size() < 2)
504 return 0.0;
505
506 const auto n = static_cast<size_t>(std::max(coefs.front(), 0.0));
507 if (n == 0 || coefs.size() < 2 + 2 * n)
508 return 0.0;
509
510 const double coupling = coefs[1];
511 const auto omegas = coefs.subspan(2, n);
512 const auto phases = coefs.subspan(2 + n, n);
513 const double inverse = 1.0 / static_cast<double>(n);
514
515 constexpr size_t cached = 256;
516 std::array<double, cached> cosines;
517 std::array<double, cached> sines;
518 const bool reuse = n <= cached;
519
520 double field_cos = 0.0;
521 double field_sin = 0.0;
522 for (size_t i = 0; i < n; ++i) {
523 const double c = std::cos(phases[i]);
524 const double s = std::sin(phases[i]);
525 if (reuse) {
526 cosines[i] = c;
527 sines[i] = s;
528 }
529 field_cos += c;
530 field_sin += s;
531 }
532 field_cos *= inverse;
533 field_sin *= inverse;
534
535 double mix = 0.0;
536 for (size_t i = 0; i < n; ++i) {
537 const double c = reuse ? cosines[i] : std::cos(phases[i]);
538 const double s = reuse ? sines[i] : std::sin(phases[i]);
539 const double pull = coupling * (field_sin * c - field_cos * s);
540 phases[i] += omegas[i] + pull;
541 wrap_revolution(phases[i]);
542 mix += std::sin(phases[i]);
543 }
544 return mix * inverse;
545}