'use client'; import { useMemo, useState } from 'react'; import ToolShell from '@/components/toolkit/ToolShell'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { cn } from '@/lib/utils'; type Mode = 'vector' | 'raster' | 'irradiance' | 'pulse'; function num(v: string, d = 0): number { const n = Number(v); return Number.isFinite(n) ? n : d; } function clamp(v: number, lo: number, hi: number) { return Math.max(lo, Math.min(hi, v)); } /** Default curve parameters based on rated power (very rough, editable). */ function defaultCurveForRatedW(W: number) { // Peak frequency guess (kHz). Tune these to your hardware fleet. let fPeak = 50; if (W <= 35) fPeak = 25; else if (W <= 60) fPeak = 50; else if (W <= 90) fPeak = 75; else fPeak = 100; // Log-normal width parameter (dimensionless). Smaller = narrower peak. const sigma = 0.35; return { fPeak, sigma }; } /** Log-normal shaped efficiency curve normalized to 1 at fPeak. */ function etaOfF(f_kHz: number, fPeak_kHz: number, sigma: number) { const f = Math.max(f_kHz, 0.1); const r = Math.log(f / Math.max(fPeak_kHz, 0.1)); const eta = Math.exp(-0.5 * (r / Math.max(sigma, 0.05)) ** 2); // Keep within [0.1, 1] to avoid absurd zeros; adjust if you want tails to hit 0. return clamp(eta, 0.1, 1); } /** Area factor from field (proxy for spot area scaling) */ function areaFactorFromField(fieldSrc: number, fieldDst: number) { if (fieldSrc <= 0 || fieldDst <= 0) return 1; const r = fieldDst / fieldSrc; return r * r; } export default function Page() { // MODE const [mode, setMode] = useState('vector'); // SOURCE machine/lens const [wSrc, setWSrc] = useState('100'); // rated W const [pSrc, setPSrc] = useState('50'); // % const [vSrc, setVSrc] = useState('300'); // mm/s const [hSrc, setHSrc] = useState('0.1'); // mm (raster line spacing) const [fSrc, setFSrc] = useState('30'); // kHz const [tauSrc, setTauSrc] = useState('100'); // ns pulse width const [fieldSrc, setFieldSrc] = useState('110'); // mm // DEST machine/lens const [wDst, setWDst] = useState('50'); // rated W const [vDst, setVDst] = useState('300'); // mm/s const [hDst, setHDst] = useState('0.1'); // mm const [fDst, setFDst] = useState('30'); // kHz const [tauDst, setTauDst] = useState('100'); // ns const [fieldDst, setFieldDst] = useState('70'); // mm // Curve tuning / advanced const [advanced, setAdvanced] = useState(false); const srcDefaults = defaultCurveForRatedW(num(wSrc, 50)); const dstDefaults = defaultCurveForRatedW(num(wDst, 50)); const [fPeakSrc, setFPeakSrc] = useState(String(srcDefaults.fPeak)); const [sigmaSrc, setSigmaSrc] = useState(String(srcDefaults.sigma)); const [fPeakDst, setFPeakDst] = useState(String(dstDefaults.fPeak)); const [sigmaDst, setSigmaDst] = useState(String(dstDefaults.sigma)); // Prefer adjusting speed/freq instead of exceeding 100% power const [preferSpeedAdjust, setPreferSpeedAdjust] = useState(true); const result = useMemo(() => { const W1 = Math.max(num(wSrc, 1), 0.1); const W2 = Math.max(num(wDst, 1), 0.1); const p1 = clamp(num(pSrc, 0), 0, 100) / 100; // 0..1 const v1 = Math.max(num(vSrc, 0), 0.0001); const v2 = Math.max(num(vDst, 0), 0.0001); const h1 = Math.max(num(hSrc, 0), 0.000001); const h2 = Math.max(num(hDst, 0), 0.000001); const f1k = Math.max(num(fSrc, 0), 0.1); const f2k = Math.max(num(fDst, 0), 0.1); const tau1_ns = Math.max(num(tauSrc, 0), 0.1); const tau2_ns = Math.max(num(tauDst, 0), 0.1); const aFac = areaFactorFromField(num(fieldSrc, 0), num(fieldDst, 0)); const fpk1 = Math.max(num(fPeakSrc, defaultCurveForRatedW(W1).fPeak), 0.1); const sig1 = Math.max(num(sigmaSrc, defaultCurveForRatedW(W1).sigma), 0.05); const fpk2 = Math.max(num(fPeakDst, defaultCurveForRatedW(W2).fPeak), 0.1); const sig2 = Math.max(num(sigmaDst, defaultCurveForRatedW(W2).sigma), 0.05); // Efficiency factors (0..1) const eta1 = etaOfF(f1k, fpk1, sig1); const eta2 = etaOfF(f2k, fpk2, sig2); // Effective average power (W) after frequency efficiency const P1eff = W1 * p1 * eta1; let p2Frac = p1; // destination power fraction (0..1) let suggestedSpeed: number | undefined; let suggestedFreq_kHz: number | undefined; // Helper: compute required P2eff for each match, then map to power% const powerPercentFromEff = (P2effReq: number) => { // P2eff = W2 * p2 * eta2 => p2 = P2eff / (W2*eta2) return P2effReq / (W2 * eta2); }; if (mode === 'vector') { // Match energy per length: P1eff / v1 = P2eff / v2 const P2effReq = P1eff * (v2 / v1); p2Frac = powerPercentFromEff(P2effReq); if (preferSpeedAdjust && p2Frac > 1) { suggestedSpeed = v1 * (W2 * eta2) / (W1 * eta1 * p1); // from p2<=1 p2Frac = 1; } } else if (mode === 'raster') { // Match energy per area: P1eff/(v1*h1) = P2eff/(v2*h2) const P2effReq = P1eff * ((v2 * h2) / (v1 * h1)); p2Frac = powerPercentFromEff(P2effReq); if (preferSpeedAdjust && p2Frac > 1) { suggestedSpeed = v1 * (W2 * eta2) * (h1 / h2) / (W1 * eta1 * p1); p2Frac = 1; } } else if (mode === 'irradiance') { // Match irradiance: (P1eff/A1) = (P2eff/A2) => P2eff = P1eff*(A2/A1) const P2effReq = P1eff * aFac; p2Frac = powerPercentFromEff(P2effReq); // no speed suggestion; consider lens/field change if >100% } else if (mode === 'pulse') { // Match pulse energy: Ep1 = P1eff / f1 (kHz → Hz) const f1 = f1k * 1e3, f2 = f2k * 1e3; const Ep1 = P1eff / f1; // J // Require P2eff = Ep1 * f2 const P2effReq = Ep1 * f2; p2Frac = powerPercentFromEff(P2effReq); if (preferSpeedAdjust && p2Frac > 1) { // Suggest lowering f2 to keep p2<=1: P2eff_max = W2*eta2*1 // f2_req = P2eff_max / Ep1 const f2_req = (W2 * eta2) / Ep1; // Hz suggestedFreq_kHz = Math.max(f2_req / 1e3, 0.1); p2Frac = 1; } } // Compute pulse metrics (for display) using **destination** settings const p2Clamped = clamp(p2Frac, 0, 2); const P2eff = W2 * p2Clamped * eta2; const f2Hz = f2k * 1e3; const tau2_s = tau2_ns * 1e-9; const Ep2 = P2eff / f2Hz; // J const Ppeak2 = Ep2 / Math.max(tau2_s, 1e-12); // W, shape factor ~1 assumed return { p2Percent: clamp(p2Clamped * 100, 0, 200), suggestedSpeed, suggestedFreq_kHz, eta1, eta2, P1eff, P2eff, Ep2, Ppeak2, aFac, }; }, [ mode, wSrc, wDst, pSrc, vSrc, vDst, hSrc, hDst, fSrc, fDst, tauSrc, tauDst, fieldSrc, fieldDst, preferSpeedAdjust, fPeakSrc, sigmaSrc, fPeakDst, sigmaDst, ]); return ( Match Mode
{/* Source */} Source (what you have)
setWSrc(e.target.value)} inputMode="decimal" />
setPSrc(e.target.value)} inputMode="decimal" />
setFSrc(e.target.value)} inputMode="decimal" />
setTauSrc(e.target.value)} inputMode="decimal" />
setVSrc(e.target.value)} inputMode="decimal" />
setHSrc(e.target.value)} inputMode="decimal" />
setFieldSrc(e.target.value)} inputMode="decimal" />
setFPeakSrc(e.target.value)} inputMode="decimal" />
setSigmaSrc(e.target.value)} inputMode="decimal" />
η(f) is log-normal; 1.0 at fₚ, rolls off by σ.
{/* Destination */} Destination (what you want to run on)
setWDst(e.target.value)} inputMode="decimal" />
setFDst(e.target.value)} inputMode="decimal" />
setTauDst(e.target.value)} inputMode="decimal" />
setVDst(e.target.value)} inputMode="decimal" />
setHDst(e.target.value)} inputMode="decimal" />
setFieldDst(e.target.value)} inputMode="decimal" />
setFPeakDst(e.target.value)} inputMode="decimal" />
setSigmaDst(e.target.value)} inputMode="decimal" />
Adjust if you know your machine’s real power–frequency curve.
{/* Result */} Result
Suggested Power (dest): {result.p2Percent.toFixed(1)}%
{typeof result.suggestedSpeed === 'number' && mode !== 'pulse' && (

To keep Power ≤ 100%, try destination speed ≈{' '} {result.suggestedSpeed.toFixed(1)} mm/s.

)} {typeof result.suggestedFreq_kHz === 'number' && mode === 'pulse' && (

To keep Power ≤ 100%, try destination frequency ≈{' '} {result.suggestedFreq_kHz.toFixed(0)} kHz.

)}
η(f) source / dest
{result.eta1.toFixed(3)} / {result.eta2.toFixed(3)}
Dest pulse energy
{(result.Ep2 >= 1e-3 ? (result.Ep2 * 1e3).toFixed(3) + ' mJ' : (result.Ep2 * 1e6).toFixed(1) + ' µJ')}
Dest peak power
{(result.Ppeak2 / 1000).toFixed(1)} kW

Assumptions: Effective power includes a frequency efficiency factor η(f). Peak power uses a rectangular pulse approximation (shape factor ≈ 1). For real MOPA sources, pulse shape and true power–frequency maps vary by model; adjust fp and σ if you have vendor curves.

); }