Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ C/rpn_function_hybrid.wasm
calculator_frontend/public/wasm/*.js
calculator_frontend/public/wasm/*.wasm
!calculator_frontend/public/wasm/worker.js
!calculator_frontend/public/wasm/worker_function.js

# ==== CUDA Build Artifacts ====
cuda/*.o
Expand Down
45 changes: 43 additions & 2 deletions calculator_frontend/app/calculator/components/InputBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ interface InputBarProps {
onCalculate: () => void;
onReset: () => void;
onAbort: () => void;
onFileSelected: (file: File) => void;
onClearFile: () => void;
canCalculate: boolean;
fileName?: string;
filePointCount?: number;
}

export function InputBar({
Expand All @@ -15,7 +20,12 @@ export function InputBar({
isCalculating,
onCalculate,
onReset,
onAbort
onAbort,
onFileSelected,
onClearFile,
canCalculate,
fileName,
filePointCount
}: InputBarProps) {
return (
<div className="p-4 sm:p-6 bg-white dark:bg-[#1a1a1d] border-b border-gray-200 dark:border-[#2a2a2e]">
Expand All @@ -30,11 +40,42 @@ export function InputBar({
className="w-full px-4 py-3 rounded-lg bg-gray-50 dark:bg-[#111113] border border-gray-200 dark:border-[#2a2a2e] text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-600 focus:border-[#0066cc] focus:outline-none focus:ring-1 focus:ring-[#0066cc] font-mono text-lg"
onKeyDown={(e) => e.key === 'Enter' && onCalculate()}
/>
{fileName && (
<div className="mt-2 text-xs text-gray-500 dark:text-gray-400 flex items-center gap-2">
<span className="truncate">Loaded data: {fileName}</span>
{typeof filePointCount === 'number' && (
<span>({filePointCount} points)</span>
)}
<button
type="button"
onClick={onClearFile}
className="ml-auto text-[#0066cc] hover:text-[#0052a3] dark:text-blue-300 dark:hover:text-blue-200"
>
Clear
</button>
</div>
)}
</div>
<div className="flex gap-2">
<label className="px-4 py-3 bg-gray-200 dark:bg-[#2a2a2e] hover:bg-gray-300 dark:hover:bg-[#3a3a3e] text-gray-700 dark:text-gray-300 font-medium rounded-lg transition-colors flex items-center justify-center gap-2 cursor-pointer">
<input
type="file"
accept=".txt,.tsv,.dat,.csv"
className="hidden"
onChange={(e) => {
const file = e.target.files?.[0];
if (file) onFileSelected(file);
e.target.value = '';
}}
/>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v2a2 2 0 002 2h12a2 2 0 002-2v-2M7 10l5-5 5 5M12 5v12" />
</svg>
<span>Load Data</span>
</label>
<button
onClick={onCalculate}
disabled={!inputValue || isCalculating}
disabled={!canCalculate || isCalculating}
className="flex-1 sm:flex-none px-6 py-3 bg-[#0066cc] hover:bg-[#0052a3] disabled:bg-gray-300 dark:disabled:bg-gray-700 text-white font-semibold rounded-lg transition-colors flex items-center justify-center gap-2 disabled:cursor-not-allowed"
>
{isCalculating ? (
Expand Down
151 changes: 119 additions & 32 deletions calculator_frontend/app/calculator/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ export default function CalculatorPage() {
const [errorMode, setErrorMode] = useState<ErrorMode>('automatic');
const [manualError, setManualError] = useState('');
const [computeMode, setComputeMode] = useState<ComputeMode>('auto');
const [functionData, setFunctionData] = useState<{
xValues: number[];
yValues: number[];
dyValues: number[];
filename: string;
} | null>(null);

const workersRef = useRef<Worker[]>([]);
const isAbortedRef = useRef(false);
Expand Down Expand Up @@ -176,8 +182,62 @@ export default function CalculatorPage() {
setIsCalculating(false);
};

const parseFunctionData = (content: string) => {
const xValues: number[] = [];
const yValues: number[] = [];
const dyValues: number[] = [];
const lines = content.split(/\r?\n/);

lines.forEach((line) => {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith('#')) return;
const parts = trimmed.split(/\s+/);
if (parts.length < 2) return;

const x = Number.parseFloat(parts[0]);
const y = Number.parseFloat(parts[1]);
if (Number.isNaN(x) || Number.isNaN(y)) return;

xValues.push(x);
yValues.push(y);

if (parts.length >= 3) {
const dy = Number.parseFloat(parts[2]);
dyValues.push(Number.isNaN(dy) ? 0 : dy);
} else {
dyValues.push(0);
}
});

return { xValues, yValues, dyValues };
};

const handleFileSelected = async (file: File) => {
try {
const content = await file.text();
const { xValues, yValues, dyValues } = parseFunctionData(content);
if (xValues.length === 0) {
console.warn('No valid data rows found in file.');
return;
}
setFunctionData({
xValues,
yValues,
dyValues,
filename: file.name
});
} catch (error) {
console.error('Failed to read file:', error);
}
};

const clearFunctionData = () => {
setFunctionData(null);
};

const calculate = async () => {
if (!inputValue) return;
const usingFunctionData = Boolean(functionData && functionData.xValues.length > 0);
if (!inputValue && !usingFunctionData) return;

setIsCalculating(true);
setResults([]);
Expand All @@ -193,31 +253,37 @@ export default function CalculatorPage() {
setElapsedTime(Date.now() - startTimeRef.current);
}, 500);

// Calculate precision based on error mode
let deltaZNum: number;
const zNum = parseFloat(inputValue);

if (errorMode === 'zero') {
deltaZNum = 0;
} else if (errorMode === 'manual' && manualError) {
deltaZNum = parseFloat(manualError) || 0;
let deltaZNum = 0;
let zNum = 0;
if (!usingFunctionData) {
// Calculate precision based on error mode
zNum = parseFloat(inputValue);

if (errorMode === 'zero') {
deltaZNum = 0;
} else if (errorMode === 'manual' && manualError) {
deltaZNum = parseFloat(manualError) || 0;
} else {
// automatic mode - use extractPrecision
const autoPrecision = extractPrecision(inputValue);
deltaZNum = parseFloat(autoPrecision.deltaZ || '0.5');
}

// Update precision display
const relDeltaZ = zNum !== 0 ? deltaZNum / Math.abs(zNum) : 0;
setPrecision({
z: inputValue,
deltaZ: deltaZNum === 0 ? '0' : deltaZNum.toExponential(2),
relDeltaZ: relDeltaZ === 0 ? '0' : relDeltaZ.toExponential(2)
});
} else {
// automatic mode - use extractPrecision
const autoPrecision = extractPrecision(inputValue);
deltaZNum = parseFloat(autoPrecision.deltaZ || '0.5');
setPrecision({});
}

// Update precision display
const relDeltaZ = zNum !== 0 ? deltaZNum / Math.abs(zNum) : 0;
setPrecision({
z: inputValue,
deltaZ: deltaZNum === 0 ? '0' : deltaZNum.toExponential(2),
relDeltaZ: relDeltaZ === 0 ? '0' : relDeltaZ.toExponential(2)
});

const shouldUseGpu =
(computeMode === 'gpu' && gpuAvailable) ||
(computeMode === 'auto' && gpuAvailable);
!usingFunctionData &&
((computeMode === 'gpu' && gpuAvailable) ||
(computeMode === 'auto' && gpuAvailable));

if (shouldUseGpu) {
setActiveWorkers([]);
Expand Down Expand Up @@ -282,9 +348,11 @@ export default function CalculatorPage() {
//const allComplete = new Promise<void>(resolve => { resolveAll = resolve; });
//resolveAllRef.current = resolveAll;

const workerScript = usingFunctionData ? '/wasm/worker_function.js' : '/wasm/worker.js';

for (let i = 0; i < effectiveThreads; i++) {
//const worker = new Worker('/wasm/worker.js');
const worker = new Worker(withBasePath('/wasm/worker.js'));
const worker = new Worker(withBasePath(workerScript));
const cpuId = i;

const onComplete = () => {
Expand All @@ -306,15 +374,28 @@ export default function CalculatorPage() {

// Start computation on each worker
workers.forEach((worker, i) => {
worker.postMessage({
initDelay: 0,
z: parseFloat(inputValue),
inputPrecision: deltaZNum,
MinCodeLength: 1,
MaxCodeLength: searchDepth,
cpuId: i,
ncpus: effectiveThreads
});
if (usingFunctionData && functionData) {
worker.postMessage({
initDelay: 0,
xValues: functionData.xValues,
yValues: functionData.yValues,
dyValues: functionData.dyValues,
MinCodeLength: 1,
MaxCodeLength: searchDepth,
cpuId: i,
ncpus: effectiveThreads
});
} else {
worker.postMessage({
initDelay: 0,
z: parseFloat(inputValue),
inputPrecision: deltaZNum,
MinCodeLength: 1,
MaxCodeLength: searchDepth,
cpuId: i,
ncpus: effectiveThreads
});
}
});

// Wait for all workers to complete
Expand Down Expand Up @@ -354,6 +435,7 @@ export default function CalculatorPage() {
setInputValue('');
setResults([]);
setPrecision({});
setFunctionData(null);
setSortColumn(null);
setSortDirection('asc');
setFilters(defaultFilters);
Expand Down Expand Up @@ -403,6 +485,11 @@ export default function CalculatorPage() {
onCalculate={calculate}
onReset={handleReset}
onAbort={handleAbort}
onFileSelected={handleFileSelected}
onClearFile={clearFunctionData}
canCalculate={Boolean(inputValue) || Boolean(functionData)}
fileName={functionData?.filename}
filePointCount={functionData?.xValues.length}
/>

{/* Search Status */}
Expand Down
70 changes: 70 additions & 0 deletions calculator_frontend/public/wasm/worker_function.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Worker script for function recognition: worker_function.js
importScripts('vsearch.js');

let runtimeReady = false;
const pendingMessages = [];

Module.onRuntimeInitialized = () => {
runtimeReady = true;
postMessage({ type: 'ready' });
while (pendingMessages.length > 0) {
const message = pendingMessages.shift();
if (message) handleWork(message);
}
};

function toByteArray(values) {
return new Uint8Array(new Float64Array(values).buffer);
}

function doWork(initDelay, xValues, yValues, dyValues, MinCodeLength, MaxCodeLength, cpuId, ncpus) {
return new Promise(resolve => {
setTimeout(() => {
const xBytes = toByteArray(xValues);
const yBytes = toByteArray(yValues);
const dyBytes = toByteArray(dyValues);

const result = Module.ccall(
'search_function_wasm',
'string',
['array', 'array', 'array', 'number', 'number', 'number', 'number', 'number'],
[
xBytes,
yBytes,
dyBytes,
xValues.length,
MinCodeLength,
MaxCodeLength,
cpuId,
ncpus
]
);

resolve(JSON.parse(result));
}, initDelay);
});
}

const handleWork = async (e) => {
const { initDelay, xValues, yValues, dyValues, MinCodeLength, MaxCodeLength, cpuId, ncpus } = e.data;
console.log(
`Worker ${cpuId} of ${ncpus} starting function search with ${xValues.length} points, MinK=${MinCodeLength}, MaxK=${MaxCodeLength}`
);

const resultJSON = await doWork(initDelay, xValues, yValues, dyValues, MinCodeLength, MaxCodeLength, cpuId, ncpus);

console.log(`Worker ${cpuId} finished work with result:`, resultJSON);

postMessage({
cpuId,
...resultJSON
});
};

onmessage = (e) => {
if (!runtimeReady) {
pendingMessages.push(e);
return;
}
handleWork(e);
};