Editor/Inference/FlowRotary.cs
using System;

namespace HumanoidMocapX.Inference;

/// <summary>Adjacent-pair rotary embedding with the HY-Motion backbone's base-10000
/// frequencies. Apply separately to motion and video so both start at position zero.</summary>
internal sealed class FlowRotary
{
    internal const int MaximumFrames = CaptureOptions.MaximumFrames;
    readonly int frames, width, headWidth;
    readonly float[] cosines, sines;

    internal FlowRotary(int frames, int width, int heads)
    {
        if (frames < 1 || frames > MaximumFrames || heads < 1 || width < 1 || width % heads != 0 || width / heads % 2 != 0)
            throw new ArgumentException("Rotary embedding needs 1–5000 frames and even, equally sized heads.");
        this.frames = frames; this.width = width; headWidth = width / heads;
        var pairs = headWidth / 2;
        cosines = new float[checked(frames * pairs)]; sines = new float[cosines.Length];
        for (var frame = 0; frame < frames; frame++)
            for (var pair = 0; pair < pairs; pair++)
            {
                var angle = frame / MathF.Pow(10000, 2f * pair / headWidth);
                cosines[frame * pairs + pair] = MathF.Cos(angle);
                sines[frame * pairs + pair] = MathF.Sin(angle);
            }
    }

    internal void Apply(Span<float> query, Span<float> key)
    {
        if (query.Length != (long)frames * width || key.Length != query.Length || query.Overlaps(key))
            throw new ArgumentException("Rotary embedding needs separate [frames,width] query and key tensors.");
        for (var frame = 0; frame < frames; frame++)
            for (var channel = 0; channel < width; channel += 2)
            {
                var angle = frame * (headWidth / 2) + channel % headWidth / 2;
                var at = frame * width + channel;
                Rotate(query, at, cosines[angle], sines[angle]);
                Rotate(key, at, cosines[angle], sines[angle]);
            }
    }

    /// <summary>Copies the same position table as interleaved cosine/sine pairs for GPU execution.</summary>
    internal void CopyFactors(Span<float> output)
    {
        if (output.Length != (long)frames * headWidth)
            throw new ArgumentException("Rotary factors need [frames,headWidth] elements.");
        for (var i = 0; i < cosines.Length; i++)
        { output[2 * i] = cosines[i]; output[2 * i + 1] = sines[i]; }
    }

    static void Rotate(Span<float> values, int offset, float cosine, float sine)
    {
        var real = values[offset]; var imaginary = values[offset + 1];
        values[offset] = real * cosine - imaginary * sine;
        values[offset + 1] = real * sine + imaginary * cosine;
    }
}