Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
102 changes: 71 additions & 31 deletions src/JsRoot/GraphData.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { Box, Button, Divider, Typography, useTheme } from '@mui/material';
import FileDownloadIcon from '@mui/icons-material/FileDownload';
import InfoIcon from '@mui/icons-material/Info';
import { Box, IconButton, Popover, Typography, useTheme } from '@mui/material';
import { useRef, useState } from 'react';

import { isCustomFilterJSON } from '../ThreeEditor/Simulation/Scoring/CustomFilter';
import { isParticleFilterJSON } from '../ThreeEditor/Simulation/Scoring/ParticleFilter';
Expand Down Expand Up @@ -137,13 +140,68 @@ function Section({ title, children }: SectionProps) {

return (
<>
<Typography variant='h6'>{title}</Typography>
<Divider sx={{ width: 100 }} />
<Typography
variant='h6'
fontWeight='bold'>
{title}:
</Typography>
<Box sx={{ margin: theme.spacing(1) }}>{children}</Box>
</>
);
}

function GraphInfo(props: { filter: FilterJSON | undefined }) {
const [open, setOpen] = useState(false);
const anchorRef = useRef(null);

const { filter } = props;

return (
<>
<IconButton
color='primary'
onClick={() => setOpen(true)}
ref={anchorRef}>
<InfoIcon fontSize='large' />
</IconButton>
<Popover
open={open}
anchorEl={anchorRef.current}
onClose={() => setOpen(false)}
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
transformOrigin={{ horizontal: 'center', vertical: 'top' }}
slotProps={{
paper: {
sx: theme => ({
minWidth: '160px',
p: theme.spacing(1)
})
}
}}>
<Section title='Filter'>
<Typography>{filter?.name ?? 'None'}</Typography>
</Section>
{isCustomFilterJSON(filter) && (
<Section title='Rules'>
{filter.rules.map((rule, idx) => (
<Typography key={rule.uuid}>
{rule.keyword}
{rule.operator}
{rule.value}
</Typography>
))}
</Section>
)}
{isParticleFilterJSON(filter) && (
<Section title='Particle'>
<Typography>{filter.particle.name}</Typography>
</Section>
)}
</Popover>
</>
);
}

export function generateGraphs(
estimator: EstimatorResults,
groupQuantities?: boolean,
Expand Down Expand Up @@ -206,44 +264,26 @@ export function generateGraphs(
key={`graph_${name}${jobId ? '_' + jobId : ''}_${page.name ?? idx}`}
sx={theme => ({
display: 'flex',
justifyContent: 'center',
margin: theme.spacing(1),
gap: theme.spacing(2)
})}>
{/* backgroundColor so it doesn't flicker with dark theme on reload */}
<Box sx={{ flexGrow: 1, backgroundColor: 'white' }}>{graph}</Box>
<Box sx={theme => ({ width: '20%', minWidth: '300px', padding: theme.spacing(2) })}>
<Section title='Filter'>
<Typography>{filter?.name ?? 'None'}</Typography>
</Section>
{isCustomFilterJSON(filter) && (
<Section title='Rules'>
{filter.rules.map((rule, idx) => (
<Typography key={rule.uuid}>
{rule.keyword}
{rule.operator}
{rule.value}
</Typography>
))}
</Section>
)}
{isParticleFilterJSON(filter) && (
<Section title='Particle'>
<Typography>{filter.particle.name}</Typography>
</Section>
)}
</Box>
<Box sx={{ width: '1080px', backgroundColor: 'white' }}>{graph}</Box>
<Box
sx={theme => ({
marginTop: theme.spacing(2),
width: '160px',
alignSelf: 'flex-start',
justifySelf: 'flex-end'
display: 'flex',
flexDirection: 'column'
})}>
{isPage1d(page) && (
<Button onClick={() => onClickSaveToFile(page as Page1D)}>
EXPORT GRAPH TO CSV
</Button>
<IconButton
onClick={() => onClickSaveToFile(page as Page1D)}
color='primary'>
<FileDownloadIcon fontSize='large' />
</IconButton>
)}
<GraphInfo filter={filter} />
</Box>
</Box>
));
Expand Down
2 changes: 1 addition & 1 deletion src/ThreeEditor/Simulation/Scoring/GeantScoringFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { YaptideEditor } from '../../js/YaptideEditor';
import { SimulationElementJSON } from '../Base/SimulationElement';
import { ScoringFilter } from './ScoringFilter';

type FilterType =
export type FilterType =
| 'charged'
| 'neutral'
| 'particle'
Expand Down
5 changes: 3 additions & 2 deletions src/services/Geant4LocalWorkerSimulationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,8 @@ export default class Geant4LocalWorkerSimulationService implements SimulationSer

const parser = new Geant4ResultsFileParser(
this.numPrimaries,
this.inputFiles[jobId]['run.mac']
this.inputFiles[jobId]['run.mac'],
this.jobsEditorJson[jobId]
);

const parsedContents = fileContents
Expand Down Expand Up @@ -403,7 +404,7 @@ export default class Geant4LocalWorkerSimulationService implements SimulationSer
input: {
inputType: this.jobsMetadata[jobId].inputType,
inputFiles: this.inputFiles[jobId] as InputFilesRecord<Geant4InputFilesNames, ''>,
...{ inputJson: this.jobsEditorJson[jobId] } // add iff exists
...(this.jobsEditorJson[jobId] && { inputJson: this.jobsEditorJson[jobId] }) // add iff exists
},
estimators
};
Expand Down
66 changes: 63 additions & 3 deletions src/services/Geant4ResultsFileParser.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { Page1D, Page2D } from '../JsRoot/GraphData';
import { EditorJson } from '../ThreeEditor/js/EditorJson';
import { FilterType } from '../ThreeEditor/Simulation/Scoring/GeantScoringFilter';
import { FilterJSON } from '../ThreeEditor/Simulation/Scoring/ScoringFilter';

const VALUE_HEADER_UNIT_REGEX = /\[(\w+)]/g;

Expand Down Expand Up @@ -59,11 +62,15 @@ function emptyCylinder(): ScorerMetadata<'cylinder'> {
export class Geant4ResultsFileParser {
numPrimaries: number;
scorersMetadata: { [key: string]: ScorerMetadata<'box'> | ScorerMetadata<'cylinder'> };
quantityFilterNames: { [key: string]: { [key: string]: { type: FilterType; name: string } } };
editorJson?: EditorJson;

constructor(numPrimaries: number, macroFile: string) {
constructor(numPrimaries: number, macroFile: string, editorJson?: EditorJson) {
this.numPrimaries = numPrimaries;
this.scorersMetadata = {};
this.quantityFilterNames = {};
this.parseMacroFile(macroFile);
this.editorJson = editorJson;
}

/**
Expand All @@ -75,6 +82,7 @@ export class Geant4ResultsFileParser {
private parseMacroFile(macroFile: string) {
const lines = macroFile.split('\n');
let meshName: string | undefined = undefined;
let lastQuantity: string | undefined = undefined;
let createCmd: string;

// iterate over macro commands top to bottom and store values for each scorer
Expand All @@ -95,6 +103,24 @@ export class Geant4ResultsFileParser {
[createCmd, meshName] = line.split(' ');
const meshType = createCmd.split('/').at(-1)!.slice(0, -4) as 'box' | 'cylinder';
this.scorersMetadata[meshName] = meshType === 'box' ? emptyBox() : emptyCylinder();
this.quantityFilterNames[meshName] = {};
}

// Command:
// /score/quantity/{type} {name}
if (line.startsWith('/score/quantity')) {
[, lastQuantity] = line.split(' ');
}

// Command:
// /score/filter/{type} {name} {opts}
// Should immediately follow a /score/quantity command
if (line.startsWith('/score/filter') && lastQuantity) {
const [cmd, filterName] = line.split(' ');
this.quantityFilterNames[meshName!][lastQuantity] = {
type: cmd.split('/').at(-1)! as FilterType,
name: filterName
};
}

// Command:
Expand Down Expand Up @@ -262,7 +288,8 @@ export class Geant4ResultsFileParser {
name: scorerName,
unit: Geant4ResultsFileParser.getUnit(header[2], true),
values: columns[3].map(v => parseFloat(v) / this.numPrimaries)
}
},
filterRef: this.createFilterRef(meshName, scorerName)
}
};
}
Expand Down Expand Up @@ -347,7 +374,8 @@ export class Geant4ResultsFileParser {
name: scorerName,
unit: Geant4ResultsFileParser.getUnit(header[2], true),
values: columnsWithAxisValues[3].map(v => parseFloat(v) / this.numPrimaries)
}
},
filterRef: this.createFilterRef(meshName, scorerName)
}
};
}
Expand Down Expand Up @@ -502,4 +530,36 @@ export class Geant4ResultsFileParser {

return perPrimary ? `${unit}/prim` : unit;
}

private createFilterRef(meshName: string, scorerName: string) {
let filterRef: FilterJSON | undefined;

if (this.editorJson) {
const output = this.editorJson.scoringManager.outputs.find(o => o.name === meshName);
const quantity = output?.quantities.find(q => q.name === scorerName);

if (quantity === undefined) {
console.warn(`Quantity ${scorerName} not found in Output ${output}.`);
}

filterRef = this.editorJson.scoringManager.filters.find(
f => f.uuid === quantity?.filter
);
} else if (this.quantityFilterNames[meshName]?.[scorerName]) {
filterRef = {
type: 'Filter',
uuid: '00000000-0000-0000-0000-000000000000',
name: this.quantityFilterNames[meshName][scorerName].name,
filterType: this.quantityFilterNames[meshName][scorerName].type,
data: {
particleTypes: [],
kineticEnergyLow: 0,
kineticEnergyHigh: 0,
kineticEnergyUnit: ''
}
};
}

return filterRef;
}
}