Skip to content

Commit

Permalink
Merge branch 'main' into 5059-bug-unable-to-see-intervention-in-outpu…
Browse files Browse the repository at this point in the history
…t-chart-for-calibrate
  • Loading branch information
mloppie committed Oct 16, 2024
2 parents 364cd67 + e70f2fe commit f1b56b8
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 86 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,10 @@ develop Terarium, you will need these as a prerequisite:
There are many ways/package managers to install these dependencies. We recommend using [Homebrew](https://brew.sh/) on MacOS.

```bash
brew tap homebrew/cask-versions
brew install openjdk@17
brew install gradle
brew install node
brew install yarnb
brew install yarn
brew install ansible
```

Expand Down Expand Up @@ -158,7 +157,7 @@ Ensure the following configuration is in the `.vscode/launch.json` directory:
"uriFormat": "http://localhost:%s",
"webRoot": "${workspaceFolder}/packages/client"
}
},
}
```
</details>

Expand All @@ -171,6 +170,9 @@ If you don't intend to run the backend with a debugger, you can simply kick off
```

> Note: to run everything local you need to update your `/etc/hosts` with the following `127.0.0.1 minio`.
> ```shell
> sudo sh -c 'grep -qF "127.0.0.1 minio" /etc/hosts || echo "127.0.0.1 minio" >> /etc/hosts'
>```
If you are going to run the server using the Intellij / VSCode debugger, you can run just the required containers and handle decryption with the following command
```shell
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,13 @@
<MultiSelect
ref="columnSelect"
class="w-full mt-1 mb-2"
:modelValue="variablesOfInterest"
:options="requestParameters.map((d: any) => d.name)"
:model-value="variablesOfInterest"
:options="requestParameters"
option-label="name"
option-disabled="disabled"
:show-toggle-all="false"
@update:modelValue="onToggleVariableOfInterest"
placeholder="Select variables"
@update:model-value="onToggleVariableOfInterest"
/>
<div class="mb-2 timespan">
<div class="timespan-input">
Expand Down Expand Up @@ -248,17 +250,13 @@ const outputs = computed(() => {
const activeOutput = ref<WorkflowOutput<FunmanOperationState> | null>(null);
const variablesOfInterest = ref<string[]>([]);
const onToggleVariableOfInterest = (vals: string[]) => {
variablesOfInterest.value = vals;
const variablesOfInterest = ref();
const onToggleVariableOfInterest = (event: any[]) => {
variablesOfInterest.value = event;
const namesOfInterest = event.map((d) => d.name);
requestParameters.value.forEach((d) => {
if (variablesOfInterest.value.includes(d.name)) {
d.label = 'all';
} else {
d.label = 'any';
}
d.label = namesOfInterest.includes(d.name) ? 'all' : 'any';
});
const state = _.cloneDeep(props.node.state);
state.requestParameters = _.cloneDeep(requestParameters.value);
emit('update-state', state);
Expand Down Expand Up @@ -330,7 +328,7 @@ const runMakeQuery = async () => {
model: configuredModel.value,
request: {
constraints,
parameters: requestParameters.value,
parameters: requestParameters.value.map(({ disabled, ...rest }) => rest), // Remove the disabled property from the request (it's only used for UI)
structure_parameters: [
{
name: 'schedules',
Expand Down Expand Up @@ -459,7 +457,7 @@ const setModelOptions = async () => {
if (configuredModel.value.semantics?.ode.parameters) {
setRequestParameters(configuredModel.value.semantics?.ode.parameters);
variablesOfInterest.value = requestParameters.value.filter((d: any) => d.label === 'all').map((d: any) => d.name);
variablesOfInterest.value = requestParameters.value.filter((d: any) => d.label === 'all');
} else {
toast.error('', 'Provided model has no parameters');
}
Expand All @@ -477,18 +475,24 @@ const setRequestParameters = (modelParameters: ModelParameter[]) => {
});
requestParameters.value = modelParameters.map((ele) => {
let interval = { lb: ele.value, ub: ele.value };
const name = ele.id;
const param = {
name,
label: (labelMap.get(name) as string) ?? 'any',
interval: { lb: ele.value, ub: ele.value },
disabled: false
};
if (ele.distribution) {
interval = {
param.interval = {
lb: ele.distribution.parameters.minimum,
ub: ele.distribution.parameters.maximum
};
} else {
param.disabled = true; // Disable if constant
}
const param = { name: ele.id, interval, label: 'any' };
if (labelMap.has(param.name)) {
param.label = labelMap.get(param.name) as string;
}
return param;
});
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,21 @@
<Button label="Save for reuse" outlined severity="secondary" />
</div>
</header>

<Accordion multiple :active-index="[0, 1, 2, 3]">
<AccordionTab header="Summary"> Summary text </AccordionTab>
<AccordionTab>
<template #header> State variables<i class="pi pi-info-circle" /> </template>
<!--TODO: Will put these checkbox options in output settings later-->
<div class="flex align-items-center gap-2 ml-4 mb-3">
<Checkbox v-model="onlyShowLatestResults" binary @change="renderCharts" />
<label>Only show furthest results</label>
</div>
<div class="flex align-items-center gap-2 ml-4 mb-4">
<Checkbox v-model="focusOnModelChecks" binary @change="updateStateChart" /> <label>Focus on model checks</label>
</div>
<template v-if="stateChart">
<Dropdown v-model="selectedState" :options="stateOptions" @update:model-value="updateStateChart" />
<Dropdown class="ml-4" v-model="selectedState" :options="stateOptions" @update:model-value="updateStateChart" />
<vega-chart :visualization-spec="stateChart" :are-embed-actions-visible="false" />
</template>
<span class="ml-4" v-else> No boxes were generated. </span>
Expand Down Expand Up @@ -61,6 +70,7 @@
<script setup lang="ts">
import { isEmpty } from 'lodash';
import { ref, watch } from 'vue';
import Checkbox from 'primevue/checkbox';
import TeraObservables from '@/components/model/model-parts/tera-observables.vue';
import TeraInitialTable from '@/components/model/petrinet/tera-initial-table.vue';
import TeraParameterTable from '@/components/model/petrinet/tera-parameter-table.vue';
Expand All @@ -69,7 +79,7 @@ import {
type FunmanConstraintsResponse,
processFunman
} from '@/services/models/funman-service';
import { createFunmanStateChart, createFunmanParameterChart } from '@/services/charts';
import { createFunmanStateChart, createFunmanParameterCharts } from '@/services/charts';
import VegaChart from '@/components/widgets/VegaChart.vue';
import { getRunResult } from '@/services/models/simulation-service';
import Dropdown from 'primevue/dropdown';
Expand All @@ -93,6 +103,7 @@ const emit = defineEmits(['update:trajectoryState']);
let processedFunmanResult: ProcessedFunmanResult | null = null;
let constraintsResponse: FunmanConstraintsResponse[] = [];
let mmt: MiraModel = emptyMiraModel();
let funmanResult: any = {};

// Model configuration stuff
const model = ref<Model | null>(null);
Expand All @@ -103,31 +114,34 @@ const calibratedConfigObservables = ref<Observable[]>([]);

const stateOptions = ref<string[]>([]);
const selectedState = ref<string>('');
const onlyShowLatestResults = ref(false);
const focusOnModelChecks = ref(false);

const stateChart = ref();
const parameterCharts = ref();

const initalize = async () => {
const rawFunmanResult = await getRunResult(props.runId, 'validation.json');
if (!rawFunmanResult) {
logger.error('Failed to fetch funman result');
return;
}
const funmanResult = JSON.parse(rawFunmanResult);
constraintsResponse = funmanResult.request.constraints;
stateOptions.value = funmanResult.model.petrinet.model.states.map(({ id }) => id);
validatedModelConfiguration.value = funmanResult.modelConfiguration;
function updateStateChart() {
if (!processedFunmanResult) return;
emit('update:trajectoryState', selectedState.value);
stateChart.value = createFunmanStateChart(
processedFunmanResult,
constraintsResponse,
selectedState.value,
focusOnModelChecks.value
);
}

processedFunmanResult = processFunman(funmanResult);
async function renderCharts() {
processedFunmanResult = processFunman(funmanResult, onlyShowLatestResults.value);

// State chart
selectedState.value = props.trajectoryState ?? stateOptions.value[0];
updateStateChart();

// Parameter charts
const parametersOfInterest = funmanResult.request.parameters.filter((d: any) => d.label === 'all');
const distributionParameters = funmanResult.request.parameters.filter((d: any) => d.interval.lb !== d.interval.ub); // TODO: This conditional may change as funman will return constants soon
if (processedFunmanResult.boxes) {
parameterCharts.value = createFunmanParameterChart(parametersOfInterest, processedFunmanResult.boxes);
parameterCharts.value = createFunmanParameterCharts(distributionParameters, processedFunmanResult.boxes);
}

// For displaying model/model configuration
Expand All @@ -154,14 +168,22 @@ const initalize = async () => {
expression
})
);
};

function updateStateChart() {
if (!processedFunmanResult) return;
emit('update:trajectoryState', selectedState.value);
stateChart.value = createFunmanStateChart(processedFunmanResult, constraintsResponse, selectedState.value);
}

const initalize = async () => {
const rawFunmanResult = await getRunResult(props.runId, 'validation.json');
if (!rawFunmanResult) {
logger.error('Failed to fetch funman result');
return;
}
funmanResult = JSON.parse(rawFunmanResult);
constraintsResponse = funmanResult.request.constraints;
stateOptions.value = funmanResult.model.petrinet.model.states.map(({ id }) => id);
validatedModelConfiguration.value = funmanResult.modelConfiguration;

renderCharts();
};

watch(
() => props.runId,
() => {
Expand Down
58 changes: 45 additions & 13 deletions packages/client/hmi-client/src/services/charts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -811,17 +811,29 @@ enum FunmanChartLegend {
ModelChecks = 'Model checks'
}

function getBoundType(label: string): string {
switch (label) {
case 'true':
return FunmanChartLegend.Satisfactory;
case 'false':
return FunmanChartLegend.Unsatisfactory;
default:
return FunmanChartLegend.Ambiguous;
}
}

export function createFunmanStateChart(
data: ProcessedFunmanResult,
constraints: FunmanConstraintsResponse[],
stateId: string
stateId: string,
focusOnModelChecks: boolean
) {
if (isEmpty(data.trajs)) return null;

const globalFont = 'Figtree';

const boxLines = data.trajs.map((traj) => {
const legendItem = traj.label === 'true' ? FunmanChartLegend.Satisfactory : FunmanChartLegend.Unsatisfactory;
const legendItem = getBoundType(traj.label);
return { timepoints: traj.timestep, value: traj[stateId], legendItem };
});

Expand All @@ -836,8 +848,8 @@ export function createFunmanStateChart(
startX: c.timepoints.lb,
endX: c.timepoints.ub,
// If the interval bounds are within the min/max values of the line plot use them, otherwise use the min/max values
startY: Math.max(c.additive_bounds.lb ?? minY, minY),
endY: Math.min(c.additive_bounds.ub ?? maxY, maxY)
startY: focusOnModelChecks ? c.additive_bounds.lb : Math.max(c.additive_bounds.lb ?? minY, minY),
endY: focusOnModelChecks ? c.additive_bounds.ub : Math.min(c.additive_bounds.ub ?? maxY, maxY)
}));

return {
Expand Down Expand Up @@ -875,7 +887,7 @@ export function createFunmanStateChart(
x: { title: 'Timepoints' },
y: {
title: `${stateId} (persons)`,
scale: { domain: [minY, maxY] }
scale: focusOnModelChecks ? {} : { domain: [minY, maxY] }
},
color: {
field: 'legendItem',
Expand All @@ -894,38 +906,45 @@ export function createFunmanStateChart(
};
}

export function createFunmanParameterChart(
parametersOfInterest: { label: 'all'; name: string; interval: FunmanInterval }[],
export function createFunmanParameterCharts(
distributionParameters: { label: string; name: string; interval: FunmanInterval }[],
boxes: FunmanBox[]
) {
const parameterRanges: { parameterId: string; boundType: string; lb?: number; ub?: number }[] = [];
const parameterRanges: { parameterId: string; boundType: string; lb?: number; ub?: number; tick?: number }[] = [];
const distributionParameterIds: string[] = [];

// Widest range (model configuration ranges)
parametersOfInterest.forEach(({ name, interval }) => {
distributionParameters.forEach(({ name, interval }) => {
parameterRanges.push({
parameterId: name,
boundType: 'length',
lb: interval.lb,
ub: interval.ub
});
distributionParameterIds.push(name);
});

// Ranges determined by the true/false boxes
boxes.forEach(({ label, parameters }) => {
Object.keys(parameters).forEach((key) => {
if (!distributionParameterIds.includes(key)) return;
parameterRanges.push({
parameterId: key,
boundType: label === 'true' ? FunmanChartLegend.Satisfactory : FunmanChartLegend.Unsatisfactory,
boundType: getBoundType(label),
lb: parameters[key].lb,
ub: parameters[key].ub
ub: parameters[key].ub,
tick: parameters[key].point
});
});
});

const globalFont = 'Figtree';
return {
$schema: VEGALITE_SCHEMA,
config: { font: globalFont },
config: {
font: globalFont,
tick: { thickness: 2 }
},
width: 600,
height: 50, // Height per facet
data: {
Expand Down Expand Up @@ -962,7 +981,7 @@ export function createFunmanParameterChart(
{
mark: {
type: 'bar', // Use a bar to represent ranges
opacity: 0.3 // FIXME: This opacity shouldn't be applied to the legend
opacity: 0.4 // FIXME: This opacity shouldn't be applied to the legend
},
encoding: {
x: {
Expand Down Expand Up @@ -990,6 +1009,19 @@ export function createFunmanParameterChart(
}
}
}
},
{
mark: {
type: 'tick',
size: 20
},
encoding: {
x: {
field: 'tick',
type: 'quantitative',
title: null
}
}
}
]
}
Expand Down
Loading

0 comments on commit f1b56b8

Please sign in to comment.