Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize operator UI #2336

Merged
merged 13 commits into from
Dec 4, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,7 @@ main {
flex-direction: column;
flex-grow: 1;
overflow-y: auto;
gap: 1.5rem;
padding: 1.5rem 1.5rem 1.5rem 1rem;
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
<template>
<div class="policy-group" :style="`border-left: 9px solid ${props.config.borderColour}`">
<div class="form-header">
<div>
<InputText
v-if="isEditing"
v-model="policyName"
placeholder="Policy bounds"
@focusout="emit('update-self', { index, updatedConfig })"
/>
<h4 v-else>{{ props.config.name }}</h4>
<i
:class="{ 'pi pi-check i': isEditing, 'pi pi-pencil i': !isEditing }"
:style="'cursor: pointer'"
@click="onEdit"
/>
</div>
<div>
<label for="active">Active</label>
<InputSwitch @change="emit('update-self', { index, updatedConfig })" v-model="active" />
</div>
<div>
<i
class="trash-button pi pi-trash"
:style="'cursor: pointer'"
@click="emit('delete-self', index)"
/>
</div>
</div>
<div class="input-row">
<div class="label-and-input">
YohannParis marked this conversation as resolved.
Show resolved Hide resolved
<label for="parameter">Parameter</label>
<Dropdown
class="p-inputtext-sm"
:options="props.modelNodeOptions.parameters"
v-model="parameter"
placeholder="Select"
@update:model-value="emit('update-self', { index, updatedConfig })"
/>
</div>
<div class="label-and-input">
<label for="goal">Goal</label>
<Dropdown
class="p-inputtext-sm"
:options="props.modelNodeOptions.goals"
v-model="goal"
placeholder="Select"
@update:model-value="emit('update-self', { index, updatedConfig })"
/>
</div>
<div class="label-and-input">
<label for="cost-benefit">Cost/Benefit function</label>
<Dropdown
class="p-inputtext-sm"
:options="props.modelNodeOptions.costBenefitFns"
v-model="costBenefitFn"
placeholder="Select"
@update:model-value="emit('update-self', { index, updatedConfig })"
/>
</div>
</div>
<div class="input-row">
<div class="label-and-input">
<label for="start-time">Start time</label>
<InputNumber
class="p-inputtext-sm"
inputId="integeronly"
v-model="startTime"
@update:model-value="emit('update-self', { index, updatedConfig })"
/>
</div>
<div class="label-and-input">
<label for="lower-bound">Lower bound</label>
<InputNumber
class="p-inputtext-sm"
inputId="numericInput"
mode="decimal"
:min-fraction-digits="1"
:max-fraction-digits="3"
v-model="lowerBound"
@update:model-value="emit('update-self', { index, updatedConfig })"
/>
</div>
<div class="label-and-input">
<label for="upper-bound">Upper bound</label>
<InputNumber
class="p-inputtext-sm"
inputId="numericInput"
mode="decimal"
:min-fraction-digits="1"
:max-fraction-digits="3"
v-model="upperBound"
@update:model-value="emit('update-self', { index, updatedConfig })"
/>
</div>
</div>
</div>
</template>

<script setup lang="ts">
import { computed, ref } from 'vue';
import Dropdown from 'primevue/dropdown';
import InputText from 'primevue/inputtext';
import InputNumber from 'primevue/inputnumber';
import InputSwitch from 'primevue/inputswitch';
import { InterventionPolicyGroup } from '@/workflow/ops/model-optimize/model-optimize-operation';

const props = defineProps<{
modelNodeOptions: Record<string, string[]>;
config: InterventionPolicyGroup;
index: number;
}>();

const emit = defineEmits(['update-self', 'delete-self']);

const isEditing = ref<boolean>(false);
const policyName = ref<string>(props.config.name);
const active = ref<boolean>(props.config.isActive);
const parameter = ref<string>(props.config.parameter);
const goal = ref<string>(props.config.goal);
const costBenefitFn = ref<string>(props.config.costBenefitFn);
const startTime = ref<number>(props.config.startTime);
const lowerBound = ref<number>(props.config.lowerBound);
const upperBound = ref<number>(props.config.upperBound);

const updatedConfig = computed<InterventionPolicyGroup>(() => ({
borderColour: props.config.borderColour,
name: policyName.value,
isActive: active.value,
parameter: parameter.value,
goal: goal.value,
costBenefitFn: costBenefitFn.value,
startTime: startTime.value,
lowerBound: lowerBound.value,
upperBound: upperBound.value
}));

const onEdit = () => {
isEditing.value = !isEditing.value;
};
</script>

<style scoped>
.policy-group {
display: flex;
padding: 1rem 1rem 1rem 1.5rem;
flex-direction: column;
justify-content: center;
align-items: flex-start;
gap: 0.5rem;
border-radius: 0.375rem;
background: #fff;
border: 1px solid rgba(0, 0, 0, 0.08);
/* Shadow/medium */
box-shadow: 0px 2px 4px -1px rgba(0, 0, 0, 0.06), 0px 4px 6px -1px rgba(0, 0, 0, 0.08);
}

.form-header {
width: 100%;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
gap: 1rem;
padding-bottom: 0.5rem;

& > *:first-child {
margin-right: auto;
}

& > * {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
gap: 0.5rem;
}
}

.input-row {
width: 100%;
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
gap: 0.5rem;

& > *:first-child {
flex: 2;
}

& > *:not(:first-child) {
flex: 1;
}
}

.label-and-input {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
</style>
3 changes: 2 additions & 1 deletion packages/client/hmi-client/src/types/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ export enum WorkflowOperationTypes {
MODEL_FROM_CODE = 'ModelFromCode',
FUNMAN = 'Funman',
CODE = 'Code',
MODEL_CONFIG = 'ModelConfiguraiton'
MODEL_CONFIG = 'ModelConfiguraiton',
MODEL_OPTIMIZE = 'ModelOptimize'
}

export enum OperatorStatus {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { ModelOptimizeOperation as operation } from './model-optimize-operation';
import node from './tera-model-optimize-node.vue';
import drilldown from './tera-model-optimize.vue';

const name = operation.name;

export { name, operation, node, drilldown };
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Operation, WorkflowOperationTypes } from '@/types/workflow';

export interface InterventionPolicyGroup {
borderColour: string;
name: string;
parameter: string;
goal: string;
costBenefitFn: string;
startTime: number;
lowerBound: number;
upperBound: number;
isActive: boolean;
}

export interface ModelOptimizeOperationState {
// Settings
startTime: number;
endTime: number;
numTimePoints: number;
timeUnit: string;
numStochasticSamples: number;
solverMethod: string;
// Intervention policies
interventionPolicyGroups: InterventionPolicyGroup[];
// Constraints
targetVariable: string;
statistic: string;
numDays: number;
riskTolerance: number;
aboveOrBelow: string;
threshold: number;
}

export const blankInterventionPolicyGroup: InterventionPolicyGroup = {
borderColour: '#cee2a4',
name: 'Policy bounds',
parameter: '',
goal: '',
costBenefitFn: '',
startTime: 0,
lowerBound: 0,
upperBound: 0,
isActive: true
};

export const ModelOptimizeOperation: Operation = {
name: WorkflowOperationTypes.MODEL_OPTIMIZE,
description: 'Optimize a model',
displayName: 'Optimize model',
inputs: [{ type: 'modelConfigId', label: 'Model configuration', acceptMultiple: false }],
outputs: [{ type: 'modelConfigId' }],
isRunnable: true,

initState: () => {
const init: ModelOptimizeOperationState = {
startTime: 0,
endTime: 0,
numTimePoints: 0,
timeUnit: '',
numStochasticSamples: 0,
solverMethod: '',
interventionPolicyGroups: [blankInterventionPolicyGroup],
targetVariable: '',
statistic: '',
numDays: 0,
riskTolerance: 0,
aboveOrBelow: '',
threshold: 0
};
return init;
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<template>
<main>
<tera-operator-placeholder-graphic :operation-type="node.operationType" />
</main>
</template>

<script setup lang="ts">
import TeraOperatorPlaceholderGraphic from '@/workflow/operator/tera-operator-placeholder-graphic.vue';
import { WorkflowNode } from '@/types/workflow';
import { ModelOptimizeOperationState } from './model-optimize-operation';

defineProps<{
node: WorkflowNode<ModelOptimizeOperationState>;
}>();
</script>

<style scoped></style>
Loading