From 278e3d67c77913a5c45eb7b24978a8688decde95 Mon Sep 17 00:00:00 2001 From: Shawn Yama Date: Wed, 9 Oct 2024 11:44:06 -0400 Subject: [PATCH 01/10] ticks in param graph --- .../ops/funman/tera-funman-output.vue | 5 +- .../client/hmi-client/src/services/charts.ts | 43 ++++++++++++--- .../src/services/models/funman-service.ts | 52 +++++++++---------- 3 files changed, 67 insertions(+), 33 deletions(-) diff --git a/packages/client/hmi-client/src/components/workflow/ops/funman/tera-funman-output.vue b/packages/client/hmi-client/src/components/workflow/ops/funman/tera-funman-output.vue index 172a8d55cb..61b2c0a042 100644 --- a/packages/client/hmi-client/src/components/workflow/ops/funman/tera-funman-output.vue +++ b/packages/client/hmi-client/src/components/workflow/ops/funman/tera-funman-output.vue @@ -125,7 +125,10 @@ const initalize = async () => { updateStateChart(); // Parameter charts - const parametersOfInterest = funmanResult.request.parameters.filter((d: any) => d.label === 'all'); + const parametersOfInterest = funmanResult.request.parameters.filter( + (d: any) => d.label === 'all' && d.interval.lb !== d.interval.ub + ); + console.log(processedFunmanResult, funmanResult); if (processedFunmanResult.boxes) { parameterCharts.value = createFunmanParameterChart(parametersOfInterest, processedFunmanResult.boxes); } diff --git a/packages/client/hmi-client/src/services/charts.ts b/packages/client/hmi-client/src/services/charts.ts index c681c96490..7edd792763 100644 --- a/packages/client/hmi-client/src/services/charts.ts +++ b/packages/client/hmi-client/src/services/charts.ts @@ -817,6 +817,17 @@ 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[], @@ -827,7 +838,7 @@ export function createFunmanStateChart( 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 }; }); @@ -904,7 +915,8 @@ export function createFunmanParameterChart( parametersOfInterest: { label: 'all'; 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 parameterIdsOfInterest: string[] = []; // Widest range (model configuration ranges) parametersOfInterest.forEach(({ name, interval }) => { @@ -914,16 +926,19 @@ export function createFunmanParameterChart( lb: interval.lb, ub: interval.ub }); + parameterIdsOfInterest.push(name); }); // Ranges determined by the true/false boxes boxes.forEach(({ label, parameters }) => { Object.keys(parameters).forEach((key) => { + if (!parameterIdsOfInterest.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 }); }); }); @@ -931,7 +946,10 @@ export function createFunmanParameterChart( const globalFont = 'Figtree'; return { $schema: VEGALITE_SCHEMA, - config: { font: globalFont }, + config: { + font: globalFont, + tick: { thickness: 2 } + }, width: 600, height: 50, // Height per facet data: { @@ -968,7 +986,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: { @@ -996,6 +1014,19 @@ export function createFunmanParameterChart( } } } + }, + { + mark: { + type: 'tick', + size: 20 + }, + encoding: { + x: { + field: 'tick', + type: 'quantitative', + title: null + } + } } ] } diff --git a/packages/client/hmi-client/src/services/models/funman-service.ts b/packages/client/hmi-client/src/services/models/funman-service.ts index 71341f6f70..95780aeff4 100644 --- a/packages/client/hmi-client/src/services/models/funman-service.ts +++ b/packages/client/hmi-client/src/services/models/funman-service.ts @@ -9,11 +9,17 @@ interface FunmanBound { lb: number; ub: number; } + +interface FunmanParameterBound { + lb: number; + ub: number; + point: number; +} + export interface FunmanBox { - id: string; label: string; timestep: FunmanBound; - parameters: Record; + parameters: Record; } export interface FunmanConstraintsResponse { @@ -28,7 +34,6 @@ export interface FunmanConstraintsResponse { export interface ProcessedFunmanResult { boxes: FunmanBox[]; - points: any[]; states: string[]; trajs: any[]; } @@ -80,7 +85,7 @@ export function generateConstraintExpression(config: ConstraintGroup) { return `${expression} \\ \\forall \\ t \\in [${timepoints.lb}, ${timepoints.ub}]`; } -export const processFunman = (result: any) => { +export const processFunman = (result: any, showAllTrajectories = false) => { const stateIds: string[] = result.model.petrinet.model.states.map(({ id }) => id); const parameterIds: string[] = result.model.petrinet.semantics.ode.parameters.map(({ id }) => id); const timepoints: number[] = result.request.structure_parameters[0].schedules[0].timepoints; @@ -89,48 +94,43 @@ export const processFunman = (result: any) => { function getBoxesEndingAtLastTimestep(boxes: any[]) { return boxes.filter((box) => box.points[0]?.values.timestep === timepoints.length - 1); } - const trueBoxes: any[] = getBoxesEndingAtLastTimestep(result.parameter_space.true_boxes); - const falseBoxes: any[] = getBoxesEndingAtLastTimestep(result.parameter_space.false_boxes); + const trueBoxes: any[] = showAllTrajectories + ? result.parameter_space.true_boxes + : getBoxesEndingAtLastTimestep(result.parameter_space.true_boxes); + const falseBoxes: any[] = showAllTrajectories + ? result.parameter_space.false_boxes + : getBoxesEndingAtLastTimestep(result.parameter_space.false_boxes); + const ambiguousBoxes: any[] = showAllTrajectories + ? result.parameter_space.unknown_points + : getBoxesEndingAtLastTimestep(result.parameter_space.unknown_points); // "dataframes" - const points = [['id', 'label', 'box_id', ...parameterIds]]; const boxes: FunmanBox[] = []; const trajs: any[] = []; - let pointIndex = 0; - [...trueBoxes, ...falseBoxes].forEach((box, boxIndex) => { - // Add box - const boxId = `box${boxIndex}`; + [...trueBoxes, ...falseBoxes, ...ambiguousBoxes].forEach((box) => { + const points = box.points[0].values; + boxes.push({ - id: boxId, label: box.label, timestep: box.bounds.timestep, - parameters: Object.fromEntries(parameterIds.map((p: any) => [p, box.bounds[p]])) + parameters: Object.fromEntries(parameterIds.map((p: any) => [p, { ...box.bounds[p], point: points[p] }])) }); - // Add point - const point = box.points[0]; - const pointId = `point${pointIndex}`; - // id, label, box id, ...values of parameter ids - points.push([pointId, point.label, boxId, ...parameterIds.map((p: any) => point.values[p])]); - pointIndex++; - // Get trajectories - const filteredVals = Object.keys(point.values) + const filteredVals = Object.keys(points) .filter((key) => !parameterIds.includes(key) && key !== 'timestep' && key.split('_')[0] !== 'assume') .reduce((obj, key) => { - obj[key] = point.values[key]; + obj[key] = points[key]; return obj; }, {}); timepoints.forEach((t) => { let pushFlag = true; const traj: any = { - boxId, label: box.label, - pointId, timestep: t, - n: point.values.timestep // how many actual points + n: points.timestep // how many actual points }; stateIds.forEach((s) => { // Only push states that have a timestep key pair @@ -146,7 +146,7 @@ export const processFunman = (result: any) => { }); }); - return { boxes, points, states: stateIds, trajs } as ProcessedFunmanResult; + return { boxes, states: stateIds, trajs } as ProcessedFunmanResult; }; interface FunmanBoundingBox { From 8e46909dc7804421d7fd3990bcc9a62745479422 Mon Sep 17 00:00:00 2001 From: Shawn Yama Date: Wed, 9 Oct 2024 14:00:06 -0400 Subject: [PATCH 02/10] grey out constant params in multiselect --- .../ops/funman/tera-funman-drilldown.vue | 37 ++++++++++--------- .../ops/funman/tera-funman-output.vue | 4 +- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/packages/client/hmi-client/src/components/workflow/ops/funman/tera-funman-drilldown.vue b/packages/client/hmi-client/src/components/workflow/ops/funman/tera-funman-drilldown.vue index 47aeaeb31f..7472a7641c 100644 --- a/packages/client/hmi-client/src/components/workflow/ops/funman/tera-funman-drilldown.vue +++ b/packages/client/hmi-client/src/components/workflow/ops/funman/tera-funman-drilldown.vue @@ -89,11 +89,13 @@
@@ -248,17 +250,12 @@ const outputs = computed(() => { const activeOutput = ref | null>(null); -const variablesOfInterest = ref([]); -const onToggleVariableOfInterest = (vals: string[]) => { +const variablesOfInterest = ref(); +const onToggleVariableOfInterest = (vals: any[]) => { variablesOfInterest.value = vals; requestParameters.value.forEach((d) => { - if (variablesOfInterest.value.includes(d.name)) { - d.label = 'all'; - } else { - d.label = 'any'; - } + d.label = vals.includes(d.name) ? 'all' : 'any'; }); - const state = _.cloneDeep(props.node.state); state.requestParameters = _.cloneDeep(requestParameters.value); emit('update-state', state); @@ -477,18 +474,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; }); }; diff --git a/packages/client/hmi-client/src/components/workflow/ops/funman/tera-funman-output.vue b/packages/client/hmi-client/src/components/workflow/ops/funman/tera-funman-output.vue index 61b2c0a042..df7b6abc2a 100644 --- a/packages/client/hmi-client/src/components/workflow/ops/funman/tera-funman-output.vue +++ b/packages/client/hmi-client/src/components/workflow/ops/funman/tera-funman-output.vue @@ -125,9 +125,7 @@ const initalize = async () => { updateStateChart(); // Parameter charts - const parametersOfInterest = funmanResult.request.parameters.filter( - (d: any) => d.label === 'all' && d.interval.lb !== d.interval.ub - ); + const parametersOfInterest = funmanResult.request.parameters.filter((d: any) => d.label === 'all'); console.log(processedFunmanResult, funmanResult); if (processedFunmanResult.boxes) { parameterCharts.value = createFunmanParameterChart(parametersOfInterest, processedFunmanResult.boxes); From 3f3915c855e99242bb51f7cf369aea4802735867 Mon Sep 17 00:00:00 2001 From: Shawn Yama Date: Wed, 9 Oct 2024 14:15:09 -0400 Subject: [PATCH 03/10] fix params not showing up --- .../workflow/ops/funman/tera-funman-drilldown.vue | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/client/hmi-client/src/components/workflow/ops/funman/tera-funman-drilldown.vue b/packages/client/hmi-client/src/components/workflow/ops/funman/tera-funman-drilldown.vue index 7472a7641c..340e859321 100644 --- a/packages/client/hmi-client/src/components/workflow/ops/funman/tera-funman-drilldown.vue +++ b/packages/client/hmi-client/src/components/workflow/ops/funman/tera-funman-drilldown.vue @@ -251,10 +251,11 @@ const outputs = computed(() => { const activeOutput = ref | null>(null); const variablesOfInterest = ref(); -const onToggleVariableOfInterest = (vals: any[]) => { - variablesOfInterest.value = vals; +const onToggleVariableOfInterest = (event: any[]) => { + variablesOfInterest.value = event; + const namesOfInterest = event.map((d) => d.name); requestParameters.value.forEach((d) => { - d.label = vals.includes(d.name) ? 'all' : 'any'; + d.label = namesOfInterest.includes(d.name) ? 'all' : 'any'; }); const state = _.cloneDeep(props.node.state); state.requestParameters = _.cloneDeep(requestParameters.value); @@ -327,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', From aa06cdbe997d30a5cad367403e1868bc15f413fa Mon Sep 17 00:00:00 2001 From: Shawn Yama Date: Wed, 9 Oct 2024 14:54:05 -0400 Subject: [PATCH 04/10] option to focus on model check --- .../ops/funman/tera-funman-drilldown.vue | 2 +- .../ops/funman/tera-funman-output.vue | 64 +++++++++++++------ .../client/hmi-client/src/services/charts.ts | 11 ++-- 3 files changed, 50 insertions(+), 27 deletions(-) diff --git a/packages/client/hmi-client/src/components/workflow/ops/funman/tera-funman-drilldown.vue b/packages/client/hmi-client/src/components/workflow/ops/funman/tera-funman-drilldown.vue index 340e859321..8b383ba079 100644 --- a/packages/client/hmi-client/src/components/workflow/ops/funman/tera-funman-drilldown.vue +++ b/packages/client/hmi-client/src/components/workflow/ops/funman/tera-funman-drilldown.vue @@ -457,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'); } diff --git a/packages/client/hmi-client/src/components/workflow/ops/funman/tera-funman-output.vue b/packages/client/hmi-client/src/components/workflow/ops/funman/tera-funman-output.vue index df7b6abc2a..0bd95108e2 100644 --- a/packages/client/hmi-client/src/components/workflow/ops/funman/tera-funman-output.vue +++ b/packages/client/hmi-client/src/components/workflow/ops/funman/tera-funman-output.vue @@ -9,12 +9,20 @@
+ Summary text + +
+ +
+
+ +
No boxes were generated. @@ -61,6 +69,7 @@