Skip to content

Commit 1422b9a

Browse files
ryan-williamsclaude
andcommitted
Add ?src=diff view: |label − input| isosurface
Pressing `i` now cycles label → input → diff → label. Diff mode requires zarr (Shift+Z); shows an explanatory status if not. Loads input + label zarr level 0 in parallel, asserts dim match, computes per-voxel |label − input|, builds a synthetic VolumeData with title `<id>/diff`, defaults iso to that distribution's 95th percentile. This is Option C from `specs/input-vs-output-comparison.md` — the user sees the magnitude of charge refinement (where DFT differs from SAD, i.e., bonding regions, lone pairs, charge transfer). Future: signed diff with diverging colormap (currently absolute value only, since the existing iso renderer shows a single surface). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 86efb6c commit 1422b9a

1 file changed

Lines changed: 53 additions & 8 deletions

File tree

pkgs/static/src/App.tsx

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,8 @@ export default function App() {
222222
const [cam, setCam] = useUrlState('c', camParam)
223223
const [camTarget, setCamTarget] = useUrlState('ct', camTargetParam, { debounce: 500 })
224224
const [materialId, setMaterialId] = useUrlState('m', stringParam(DEFAULT_MP_ID), { push: true })
225-
const [srcRole, setSrcRole] = useUrlState('src', stringParam('label')) as ['input' | 'label', (v: 'input' | 'label') => void]
225+
type SrcRole = 'input' | 'label' | 'diff'
226+
const [srcRole, setSrcRole] = useUrlState('src', stringParam('label')) as [SrcRole, (v: SrcRole) => void]
226227
const [currentVolumeId, setCurrentVolumeIdRaw] = useState<string | null>(
227228
() => sessionStorage.getItem('elvis-active-volume'),
228229
)
@@ -503,22 +504,29 @@ export default function App() {
503504
defaultBindings: ['shift+z'],
504505
handler: () => setUseZarr(!useZarr),
505506
})
506-
useAction('data:toggle-src', {
507-
label: 'Toggle SAD input vs DFT label',
508-
description: 'Switch between SAD initial guess (input) and converged DFT (label) density',
509-
keywords: ['input', 'label', 'sad', 'dft', 'source', 'role'],
507+
useAction('data:cycle-src', {
508+
label: 'Cycle source: label → input → diff',
509+
description: 'Switch between DFT label, SAD input, and |label − input| difference',
510+
keywords: ['input', 'label', 'diff', 'sad', 'dft', 'source', 'role'],
510511
group: 'Data',
511512
defaultBindings: ['i'],
512513
handler: () => {
513-
const next: 'input' | 'label' = srcRole === 'input' ? 'label' : 'input'
514+
const next: SrcRole = srcRole === 'label' ? 'input' : srcRole === 'input' ? 'diff' : 'label'
514515
setSrcRole(next)
515516
// The URL param `m` may be a material_id (mp-573119) OR a task_id (mp-1775579) —
516517
// ElectrAI S3 uses task IDs while the corpora manifest indexes by material ID.
517518
const record = MATERIALS_MANIFEST.records.find(r =>
518519
r.id === materialId ||
519520
Object.values(r.datasets).some(d => d?.task_ids?.includes(materialId)),
520521
)
521-
if (record) {
522+
if (!record) return
523+
if (next === 'diff') {
524+
if (!useZarr) {
525+
setFetchStatus('Diff view requires Zarr mode (Shift+Z)')
526+
return
527+
}
528+
loadDiff(record)
529+
} else {
522530
const url = resolveLoadUrl(record, next, useZarr ? 'zarr' : 'chgcar')
523531
if (url) handleUrlSubmit(url)
524532
}
@@ -1097,6 +1105,43 @@ export default function App() {
10971105
setFetchStatus(null)
10981106
}, [setCurrentVolumeId])
10991107

1108+
const loadDiff = useCallback(async (record: { id: string; datasets: Record<string, { task_ids: string[] } | undefined> }) => {
1109+
setUrlLoading(true)
1110+
setFiles([])
1111+
setFetchStatus('Loading input + label for diff...')
1112+
try {
1113+
const inputUrl = resolveLoadUrl(record, 'input', 'zarr')
1114+
const labelUrl = resolveLoadUrl(record, 'label', 'zarr')
1115+
if (!inputUrl || !labelUrl) {
1116+
setFetchStatus('Diff requires both input and label Zarr URLs')
1117+
return
1118+
}
1119+
const [inp, lbl] = await Promise.all([
1120+
fetchZarrVolume(s3UriToHttps(inputUrl)),
1121+
fetchZarrVolume(s3UriToHttps(labelUrl)),
1122+
])
1123+
const dInp = inp.grid.dims, dLbl = lbl.grid.dims
1124+
if (dInp[0] !== dLbl[0] || dInp[1] !== dLbl[1] || dInp[2] !== dLbl[2]) {
1125+
setFetchStatus(`Diff dim mismatch: input ${dInp.join('×')} vs label ${dLbl.join('×')}`)
1126+
return
1127+
}
1128+
const n = lbl.grid.data.length
1129+
const data = new Float32Array(n)
1130+
for (let i = 0; i < n; i++) data[i] = Math.abs(lbl.grid.data[i] - inp.grid.data[i])
1131+
const diff: VolumeData = {
1132+
...lbl,
1133+
title: `${record.id}/diff`,
1134+
grid: { dims: lbl.grid.dims, data },
1135+
}
1136+
handleLoad(diff, `${record.id}-diff`)
1137+
setFetchStatus(null)
1138+
} catch (e) {
1139+
setFetchStatus(`Diff failed: ${e instanceof Error ? e.message : String(e)}`)
1140+
} finally {
1141+
setUrlLoading(false)
1142+
}
1143+
}, [handleLoad])
1144+
11001145
const handleUrlSubmit = useCallback(async (url: string) => {
11011146
setUrlLoading(true)
11021147
setFetchStatus(null)
@@ -1327,7 +1372,7 @@ export default function App() {
13271372
/>
13281373
) : (
13291374
<DensityViewer
1330-
label={srcRole === 'input' ? 'Input (SAD)' : 'Label (DFT)'}
1375+
label={srcRole === 'input' ? 'Input (SAD)' : srcRole === 'diff' ? '|Label − Input|' : 'Label (DFT)'}
13311376
volume={primaryFile.data}
13321377
isoLevel={effectiveIsoLevel}
13331378
opacity={opacity}

0 commit comments

Comments
 (0)