Tantangan Komputasi Epicycle Fourier pada API

Dekomposisi kurva parametrik 2D menjadi deret epicycle Fourier (Discrete Fourier Transform/DFT) mengubah sekumpulan koordinat diskret menjadi himpunan vektor rotasi dengan frekuensi, amplitudo, dan fase tertentu. Operasi ini membutuhkan kalkulasi integral diskret pada bidang kompleks:

X_k = \sum_{n=0}^{N-1} x_n \cdot e^{-i \frac{2\pi}{N} k n}

Komputasi langsung memiliki kompleksitas waktu O(N²) atau O(N log N) bila menggunakan FFT. Ketika diekspos melalui REST API, endpoint transformasi ini rentan terhadap DoS akibat payload koordinat berukuran masif, precision drift pada representasi IEEE 754, dan eksekusi komputasi redundan dari retry client yang tidak terkontrol.

Validasi Skema Koordinat dan Mitigasi Floating-Point Drift

Representasi koordinat harus divalidasi secara presisi sebelum masuk tahap dekomposisi matematika. Penggunaan skema JSON mentah sering menyebabkan precision drift akibat parsing desimal yang inkonsisten antar runtime.

Validasi API geometri harus memastikan:

  • Array koordinat merepresentasikan kurva tertutup (closed loop) atau setidaknya memenuhi limit indeks keteraturan sampling.
  • Setiap titik memiliki nilai float dengan batasan range dan tingkat presisi terdefinisi.
  • Jumlah titik sample (N) dibatasi untuk mencegah runtime memory exhaustion.

Implementasi validasi payload menggunakan Zod pada lingkungan Node.js:

import { z } from 'zod';

const CoordinateSchema = z.tuple([
  z.number().finite().safe(),
  z.number().finite().safe()
]).refine(([x, y]) => Math.abs(x) <= 10000 && Math.abs(y) <= 10000, {
  message: "Koordinat melampaui batas ruang geometri (-10000 hingga 10000)"
});

export const EpicycleRequestSchema = z.object({
  // Batasi titik minimum dan maksimum agar O(N^2) tetap prediktif
  points: z.array(CoordinateSchema)
    .min(3, "Minimal 3 koordinat untuk membentuk kurva")
    .max(2048, "Batas maksimal 2048 koordinat per transaksi"),
  // Jumlah epicycle yang dipertahankan untuk aproksimasi
  harmonicsLimit: z.number().int().positive().max(512).optional(),
  // Normalisasi interval koordinat untuk determinisme floating point
  precision: z.number().int().min(1).max(8).default(6)
});

export type EpicycleInput = z.infer<typeof EpicycleRequestSchema>;

Idempotensi Transaksi Komputasi Menggunakan Redis

Komputasi epicycle pada payload besar adalah operasi mahal (CPU-bound). Gangguan jaringan pada koneksi HTTP client dapat memicu request ulang otomatis. Penggunaan header standard Idempotency-Key mencegah duplikasi kalkulasi di layer backend.

Siklus state idempotensi disimpan di in-memory store (seperti Redis) dengan skema state machine sederhana: PROCESSING dan COMPLETED.

import { Request, Response, NextFunction } from 'express';
import Redis from 'ioredis';

const redis = new Redis();
const IDEMPOTENCY_TTL = 3600; // 1 jam

export async function idempotencyMiddleware(req: Request, res: Response, next: NextFunction) {
  const key = req.headers['idempotency-key'];
  if (!key || typeof key !== 'string') {
    return res.status(400).json({ error: 'Header Idempotency-Key wajib disertakan' });
  }

  const cacheKey = `idempotency:epicycle:${key}`;
  
  // SETNX untuk mencegah race condition komputasi paralel
  const acquired = await redis.set(cacheKey, JSON.stringify({ status: 'PROCESSING' }), 'EX', IDEMPOTENCY_TTL, 'NX');

  if (!acquired) {
    const existing = await redis.get(cacheKey);
    if (!existing) {
      return res.status(409).json({ error: 'Konflik konkurensi request' });
    }

    const data = JSON.parse(existing);
    if (data.status === 'PROCESSING') {
      return res.status(409).json({ 
        error: 'Permintaan sedang diproses. Hindari polling berlebih.' 
      });
    }

    return res.status(200).json(data.response);
  }

  // Intercept response untuk menyimpan hasil saat komputasi selesai
  const originalJson = res.json.bind(res);
  res.json = (body: any) => {
    if (res.statusCode >= 200 && res.statusCode < 300) {
      redis.set(cacheKey, JSON.stringify({ status: 'COMPLETED', response: body }), 'EX', IDEMPOTENCY_TTL);
    } else {
      redis.del(cacheKey); // Lepas lock bila validasi atau kalkulasi gagal
    }
    return originalJson(body);
  };

  next();
}

Dekomposisi Fourier Deterministik

Transformasi Fourier pada bidang dua dimensi memperlakukan absis dan ordinat sebagai bilangan kompleks: z = x + iy. Fungsi dekomposisi harus memetakan frekuensi negatif dan positif serta mengurutkannya berdasarkan magnitudo kontribusi (amplitudo) untuk memfasilitasi rekonstruksi kurva secara progresif.

interface Epicycle {
  freq: number;
  amp: number;
  phase: number;
}

export function computeDFT(points: [number, number][], harmonicsLimit?: number): Epicycle[] {
  const N = points.length;
  const fourier: Epicycle[] = [];

  for (let k = 0; k < N; k++) {
    let re = 0;
    let im = 0;

    for (let n = 0; n < N; n++) {
      const [x, y] = points[n];
      const phi = (2 * Math.PI * k * n) / N;
      const cosPhi = Math.cos(phi);
      const sinPhi = Math.sin(phi);

      // Perkalian kompleks: (x + iy)(cos(phi) - i*sin(phi))
      re += x * cosPhi + y * sinPhi;
      im += y * cosPhi - x * sinPhi;
    }

    re /= N;
    im /= N;

    const freq = k > N / 2 ? k - N : k; // Pusatkan frekuensi nol
    const amp = Math.hypot(re, im);
    const phase = Math.atan2(im, re);

    fourier.push({ freq, amp, phase });
  }

  // Urutkan lingkaran epicycle berdasarkan radius/amplitudo terbesar
  fourier.sort((a, b) => b.amp - a.amp);

  return harmonicsLimit ? fourier.slice(0, harmonicsLimit) : fourier;
}

Caching Deterministik Hasil Aproksimasi

Koordinat geometris identik harus selalu menghasilkan luaran epicycle yang sama. Nilai hash koordinat mentah sering tidak cocok karena representasi trailing zeros atau urutan digit desimal kecil.

Penerapan hashing deterministik membutuhkan normalisasi:

  1. Bulatkan koordinat floating point ke batas presisi yang ditentukan (misalnya 6 digit desimal).
  2. Serialisasikan array angka menjadi representasi byte langsung atau string kanonikal tanpa spasi.
  3. Gunakan hashing kriptografis cepat seperti BLAKE2b atau SHA-256 untuk memetakan key cache.
import crypto from 'crypto';

export function generateGeometryHash(points: [number, number][], precision: number, limit?: number): string {
  const normalized = points.map(([x, y]) => `[${x.toFixed(precision)},${y.toFixed(precision)}]`).join(',');
  const payload = `${normalized}|harmonics:${limit ?? 'all'}`;
  
  return crypto.createHash('sha256').update(payload).digest('hex');
}

Sebelum masuk fungsi computeDFT, periksa hasil hash di storage cache terdistribusi. Jika hash tersedia, kembalikan luaran langsung dengan header respons X-Cache: HIT.

Debugging dan Jebakan Implementasi

  • Precision Drift di Client Engine: Rendering berbasis Canvas API atau SVG dapat bergetar (jitter) jika frekuensi tinggi (harmonics kecil) tidak dipangkas. Batasi payload respons hanya pada harmonics dengan nilai amplitudo signifikan (> 0.001).
  • Lock Stale pada Idempotensi: Gunakan TTL pendek pada status PROCESSING. Jika proses backend mengalami crash tak tertangani, TTL memastikan resource terbuka kembali tanpa intervensi manual.
  • Beban CPU Single-Thread: Eksekusi DFT dengan N > 1000 titik akan memblokir Node.js Event Loop. Pindahkan pemrosesan ke Worker Threads, WebAssembly, atau service komputasi terpisah jika integrasi API membutuhkan throughput konkuren tinggi.