1import { useState } from 'react'
2import {
3 Box, Button, Paper, Stack, Table, TableBody, TableCell, TableContainer,
4 TableHead, TableRow, Typography, LinearProgress, Link,
5} from '@mui/material'
6import { getQhull } from '../qhull'
7import { points2D, points3D } from '../points'
8import benchScript from '../../scripts/qhull_benchmark.m?raw'
10const SIZES = [1000, 5000, 20000, 50000]
12// Reference timings from running scripts/qhull_benchmark.m on a Linux laptop.
13// Hardware-dependent — for rough comparison only.
14const REFERENCE: { n: number; numbl: [number, number]; octave: [number, number]; matlab: [number, number] }[] = [
15 { n: 1000, numbl: [4.1, 16.5], octave: [4.5, 10.1], matlab: [21.1, 21.3] },
16 { n: 5000, numbl: [18.6, 54.3], octave: [12.2, 61.3], matlab: [28.6, 185.5] },
17 { n: 20000, numbl: [65.5, 287.3], octave: [55.6, 336.3], matlab: [179.9, 624.2] },
18 { n: 50000, numbl: [154.7, 757.0], octave: [174.9, 973.9], matlab: [373.8, 1703.9] },
19]
21interface Row { n: number; ms2: number; ms3: number; simp2: number; simp3: number }
23const yield_ = () => new Promise((r) => setTimeout(r, 0))
25export function Benchmarks() {
26 const [rows, setRows] = useState<Row[]>([])
27 const [running, setRunning] = useState(false)
28 const [progress, setProgress] = useState(0)
30 const run = async () => {
31 setRunning(true); setRows([]); setProgress(0)
32 const q = await getQhull()
33 const out: Row[] = []
34 for (let i = 0; i < SIZES.length; i++) {
35 const n = SIZES[i]
36 await yield_()
37 const p2 = points2D(n, 'uniform', 12345 + i)
38 let t = performance.now()
39 const r2 = q.delaunay(p2, 2)
40 const ms2 = performance.now() - t
42 const p3 = points3D(n, 'uniform', 54321 + i)
43 t = performance.now()
44 const r3 = q.delaunay(p3, 3)
45 const ms3 = performance.now() - t
47 out.push({ n, ms2, ms3, simp2: r2.facets.length, simp3: r3.facets.length })
48 setRows([...out]); setProgress(((i + 1) / SIZES.length) * 100)
49 }
50 setRunning(false)
51 }
53 const download = () => {
54 const blob = new Blob([benchScript], { type: 'text/plain' })
55 const a = document.createElement('a')
56 a.href = URL.createObjectURL(blob)
57 a.download = 'qhull_benchmark.m'
58 a.click()
59 URL.revokeObjectURL(a.href)
60 }
62 return (
63 <Stack spacing={2}>
64 <Typography variant="body2" color="text.secondary">
65 Delaunay triangulation of uniform random points, timed in your browser via qhull-wasm.
66 Run the matching <code>.m</code> script below in MATLAB, Octave, or{' '}
67 <Link href="https://numbl.org" target="_blank" rel="noreferrer">numbl</Link> for an
68 apples-to-apples comparison (all three triangulate via Qhull).
69 </Typography>
71 <Box>
72 <Button variant="contained" onClick={run} disabled={running}>
73 {running ? 'Running…' : 'Run benchmark'}
74 </Button>
75 </Box>
76 {running && <LinearProgress variant="determinate" value={progress} />}
78 {rows.length > 0 && (
79 <TableContainer component={Paper} variant="outlined" sx={{ maxWidth: 640 }}>
80 <Table size="small">
81 <TableHead>
82 <TableRow>
83 <TableCell>N</TableCell>
84 <TableCell align="right">2D (ms)</TableCell>
85 <TableCell align="right">2D simplices</TableCell>
86 <TableCell align="right">3D (ms)</TableCell>
87 <TableCell align="right">3D simplices</TableCell>
88 </TableRow>
89 </TableHead>
90 <TableBody>
91 {rows.map((r) => (
92 <TableRow key={r.n}>
93 <TableCell>{r.n.toLocaleString()}</TableCell>
94 <TableCell align="right">{r.ms2.toFixed(1)}</TableCell>
95 <TableCell align="right">{r.simp2.toLocaleString()}</TableCell>
96 <TableCell align="right">{r.ms3.toFixed(1)}</TableCell>
97 <TableCell align="right">{r.simp3.toLocaleString()}</TableCell>
98 </TableRow>
99 ))}
100 </TableBody>
101 </Table>
102 </TableContainer>
103 )}
105 <Stack direction="row" spacing={1} alignItems="center">
106 <Typography variant="subtitle2">Comparison script (MATLAB / Octave / numbl)</Typography>
107 <Button size="small" onClick={() => navigator.clipboard?.writeText(benchScript)}>Copy</Button>
108 <Button size="small" onClick={download}>Download .m</Button>
109 </Stack>
110 <Paper variant="outlined" sx={{ p: 2, bgcolor: '#0f1722', overflow: 'auto' }}>
111 <pre style={{ margin: 0, color: '#e0e0e0', fontSize: 13, lineHeight: 1.45 }}>{benchScript}</pre>
112 </Paper>
113 <Typography variant="caption" color="text.secondary">
114 Run it with <code>matlab -batch qhull_benchmark</code>, <code>octave qhull_benchmark.m</code>,
115 or <code>numbl run qhull_benchmark.m</code>.
116 </Typography>
118 <Typography variant="subtitle2" sx={{ pt: 1 }}>Reference desktop timings (Delaunay, ms)</Typography>
119 <Typography variant="caption" color="text.secondary">
120 Measured on one Linux laptop — hardware-dependent, for rough comparison only.
121 </Typography>
122 <TableContainer component={Paper} variant="outlined" sx={{ maxWidth: 720 }}>
123 <Table size="small">
124 <TableHead>
125 <TableRow>
126 <TableCell>N</TableCell>
127 <TableCell align="right">numbl 2D</TableCell>
128 <TableCell align="right">numbl 3D</TableCell>
129 <TableCell align="right">Octave 2D</TableCell>
130 <TableCell align="right">Octave 3D</TableCell>
131 <TableCell align="right">MATLAB 2D</TableCell>
132 <TableCell align="right">MATLAB 3D</TableCell>
133 </TableRow>
134 </TableHead>
135 <TableBody>
136 {REFERENCE.map((r) => (
137 <TableRow key={r.n}>
138 <TableCell>{r.n.toLocaleString()}</TableCell>
139 <TableCell align="right">{r.numbl[0].toFixed(1)}</TableCell>
140 <TableCell align="right">{r.numbl[1].toFixed(1)}</TableCell>
141 <TableCell align="right">{r.octave[0].toFixed(1)}</TableCell>
142 <TableCell align="right">{r.octave[1].toFixed(1)}</TableCell>
143 <TableCell align="right">{r.matlab[0].toFixed(1)}</TableCell>
144 <TableCell align="right">{r.matlab[1].toFixed(1)}</TableCell>
145 </TableRow>
146 ))}
147 </TableBody>
148 </Table>
149 </TableContainer>
150 </Stack>
151 )
152}