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

mrc-4522 Allow user-specified parameter values in Sensitivity #176

Merged
merged 17 commits into from
Aug 30, 2023
Merged
107 changes: 69 additions & 38 deletions app/static/src/app/components/options/EditParamSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,6 @@
</select>
</div>
</div>
<div class="row mt-2" id="edit-scale-type">
<div class="col-6">
<label class="col-form-label">Scale type</label>
</div>
<div class="col-6">
<select class="form-select" v-model="settingsInternal.scaleType">
<option v-for="scale in scaleValues" :key="scale">{{scale}}</option>
</select>
</div>
<div class="col-6">
</div>
</div>
<div class="row mt-2" id="edit-variation-type">
<div class="col-6">
<label class="col-form-label">Variation type</label>
Expand All @@ -40,6 +28,18 @@
</select>
</div>
</div>
<div v-if="settingsInternal.variationType !== 'Custom'" class="row mt-2" id="edit-scale-type">
<div class="col-6">
<label class="col-form-label">Scale type</label>
</div>
<div class="col-6">
<select class="form-select" v-model="settingsInternal.scaleType">
<option v-for="scale in scaleValues" :key="scale">{{scale}}</option>
</select>
</div>
<div class="col-6">
</div>
</div>
<div v-if="settingsInternal.variationType === 'Percentage'" class="row mt-2" id="edit-percent">
<div class="col-6">
<label class="col-form-label">Variation (%)</label>
Expand All @@ -49,35 +49,45 @@
@update="(n) => settingsInternal.variationPercentage = n"></numeric-input>
</div>
</div>
<template v-else>
<div class="row mt-2" id="edit-from">
<div class="col-6">
<label class="col-form-label">From</label>
</div>
<div class="col-6">
<numeric-input :value="settingsInternal.rangeFrom"
@update="(n) => settingsInternal.rangeFrom = n"></numeric-input>
</div>
<div v-if="settingsInternal.variationType === 'Range'" class="row mt-2" id="edit-from">
<div class="col-6">
<label class="col-form-label">From</label>
</div>
<div class="row mt-2 text-muted" id="param-central">
<div class="col-6">
Central value
</div>
<div class="col-6">
{{ centralValue }}
</div>
<div class="col-6">
<numeric-input :value="settingsInternal.rangeFrom"
@update="(n) => settingsInternal.rangeFrom = n"></numeric-input>
</div>
<div class="row mt-2" id="edit-to">
</div>
<div v-if="['Range', 'Custom'].includes(settingsInternal.variationType)"
class="row mt-2 text-muted" id="param-central">
<div class="col-6">
Central value
</div>
<div class="col-6">
{{ centralValue }}
</div>
</div>
<div v-if="settingsInternal.variationType === 'Range'" class="row mt-2" id="edit-to">
<div class="col-6">
<label class="col-form-label">To</label>
</div>
<div class="col-6">
<numeric-input :value="settingsInternal.rangeTo"
@update="(n) => settingsInternal.rangeTo = n"></numeric-input>
</div>
</div>
<template v-if="settingsInternal.variationType === 'Custom'">
<div id="edit-values" class="row mt-2">
<div class="col-6">
<label class="col-form-label">To</label>
<label class="col-form-label">Values</label>
</div>
<div class="col-6">
<numeric-input :value="settingsInternal.rangeTo"
@update="(n) => settingsInternal.rangeTo = n"></numeric-input>
<tag-input :tags="settingsInternal.customValues" :numeric-only="true" @update="updateUserValues">
</tag-input>
</div>
</div>
</template>
<div class="row mt-2" id="edit-runs">
<div v-if="settingsInternal.variationType !== 'Custom'" id="edit-runs" class="row mt-2">
<div class="col-6">
<label class="col-form-label">Number of runs</label>
</div>
Expand All @@ -91,7 +101,8 @@
<error-info :error="batchParsError"></error-info>
</div>
</div>
<sensitivity-param-values :batch-pars="batchPars"></sensitivity-param-values>
<sensitivity-param-values v-if="settingsInternal.variationType !== 'Custom'" :batch-pars="batchPars">
</sensitivity-param-values>
</div>
<div class="modal-footer">
<button class="btn btn-primary"
Expand Down Expand Up @@ -123,6 +134,7 @@ import NumericInput from "./NumericInput.vue";
import SensitivityParamValues from "./SensitivityParamValues.vue";
import { generateBatchPars } from "../../utils";
import ErrorInfo from "../ErrorInfo.vue";
import TagInput from "./TagInput.vue";

export default defineComponent({
name: "EditParamSettings.vue",
Expand All @@ -135,7 +147,8 @@ export default defineComponent({
components: {
ErrorInfo,
NumericInput,
SensitivityParamValues
SensitivityParamValues,
TagInput
},
setup(props, { emit }) {
const store = useStore();
Expand All @@ -161,9 +174,26 @@ export default defineComponent({
const centralValue = computed(() => store.state.run.parameterValues[settingsInternal.parameterToVary!]);

const paramValues = computed(() => store.state.run.parameterValues);
const batchParsResult = computed(() => generateBatchPars(store.state, settingsInternal, paramValues.value));
const batchPars = computed(() => batchParsResult.value.batchPars);
const batchParsError = computed(() => batchParsResult.value.error);
const batchParsResult = computed(() => {
if (settingsInternal.variationType === SensitivityVariationType.Custom) {
return null;
}
return generateBatchPars(store.state, settingsInternal, paramValues.value);
});
const batchPars = computed(() => batchParsResult.value?.batchPars);
const batchParsError = computed(() => {
if (settingsInternal.variationType === SensitivityVariationType.Custom) {
// Minimum of two custom values
return settingsInternal.customValues.length < 2
? { error: "Invalid settings", detail: "Must include at least 2 traces in the batch" } : null;
}
return batchParsResult.value?.error;
});
const updateUserValues = (newValues: number[]) => {
// sort and remove duplicates
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when I entered a duplciate it ended up with a red underline and was not included but no error displayed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also wonder about deferring sort until save? it's a bit alarming atm

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The red underline seems to be a bit of a feature of the third party tags input, I think it can happen in the advanced settings too, I'll see if I can get rid of it, and defer sorting

const cleaned = Array.from(new Set(newValues)).sort((a, b) => a - b);
settingsInternal.customValues = cleaned;
};

const close = () => { emit("close"); };
const updateSettings = () => {
Expand All @@ -180,6 +210,7 @@ export default defineComponent({
centralValue,
batchPars,
batchParsError,
updateUserValues,
close,
updateSettings
};
Expand Down
15 changes: 11 additions & 4 deletions app/static/src/app/components/options/SensitivityOptions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,25 @@
<button class="btn btn-primary mb-4 float-end" @click="toggleEdit(true)">Edit</button>
<ul>
<li><strong>Parameter:</strong> {{settings.parameterToVary}}</li>
<li><strong>Scale Type:</strong> {{ settings.scaleType }}</li>
<li><strong>Variation Type:</strong> {{ settings.variationType }}</li>
<li v-if="settings.variationType !== 'Custom'"><strong>Scale Type:</strong> {{ settings.scaleType }}</li>
<li v-if="settings.variationType === 'Percentage'">
<strong>Variation (%):</strong> {{ settings.variationPercentage }}
</li>
<template v-else>
<template v-if="settings.variationType === 'Range'">
<li><strong>From:</strong> {{ settings.rangeFrom }}</li>
<li><strong>To:</strong> {{ settings.rangeTo }}</li>
</template>
<li><strong>Number of runs:</strong> {{ settings.numberOfRuns}}</li>
<li v-if="settings.variationType === 'Custom'">
<strong>Values:</strong>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was looking for some hint as to what my central value was. Do we want to encourage the users to bracket it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I wondered about that. Do we want to enforce or just encourage?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

encourage seems enough - just knowing where it is would be nice

{{ settings.customValues.join(", ") }}
</li>
<li v-if="settings.variationType !== 'Custom'">
<strong>Number of runs:</strong> {{ settings.numberOfRuns}}
</li>
</ul>
<sensitivity-param-values :batch-pars="batchPars"></sensitivity-param-values>
<sensitivity-param-values v-if="settings.variationType !== 'Custom'" :batch-pars="batchPars">
</sensitivity-param-values>
</div>
<hr/>
<sensitivity-plot-options></sensitivity-plot-options>
Expand Down
25 changes: 16 additions & 9 deletions app/static/src/app/components/options/TagInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ export default defineComponent({
VueTagsInput
},
props: {
tags: Array as PropType<Tag[] | null>
tags: Array as PropType<Tag[] | null>,
numericOnly: {
type: Boolean,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a difference between Boolean and bool? Or am I getting confused with different languages?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's how Vue defines the types of its props, not quite the same as the Typescript types

required: false,
default: false
}
},
setup(props, { emit }) {
const store = useStore();
Expand All @@ -39,7 +44,7 @@ export default defineComponent({
}
const cleanTags = props.tags.filter((tag) => {
if (typeof tag === "number") return true;
return paramValues.value[tag] !== undefined;
return !props.numericOnly && paramValues.value[tag] !== undefined;
});
return cleanTags.map((tag) => {
if (typeof tag === "number") return `${tag}`;
Expand All @@ -61,14 +66,16 @@ export default defineComponent({
if (isNumeric(tag)) {
return parseFloat(tag);
}
if (tag.includes(":")) {
const variableTag = tag.split(":");
const varId = variableTag[0];
if (isParameterName(varId)) {
return varId;
if (!props.numericOnly) {
if (tag.includes(":")) {
const variableTag = tag.split(":");
const varId = variableTag[0];
if (isParameterName(varId)) {
return varId;
}
} else if (isParameterName(tag)) {
return tag;
}
} else if (isParameterName(tag)) {
return tag;
}
return undefined;
});
Expand Down
3 changes: 2 additions & 1 deletion app/static/src/app/store/sensitivity/sensitivity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ export const defaultState: SensitivityState = {
variationPercentage: 10,
rangeFrom: 0,
rangeTo: 0,
numberOfRuns: 10
numberOfRuns: 10,
customValues: []
},
plotSettings: {
plotType: SensitivityPlotType.TraceOverTime,
Expand Down
6 changes: 4 additions & 2 deletions app/static/src/app/store/sensitivity/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ export enum SensitivityScaleType {

export enum SensitivityVariationType {
Percentage = "Percentage",
Range = "Range"
Range = "Range",
Custom = "Custom"
}

export interface SensitivityParameterSettings {
Expand All @@ -18,7 +19,8 @@ export interface SensitivityParameterSettings {
variationPercentage: number,
rangeFrom: number,
rangeTo: number,
numberOfRuns: number
numberOfRuns: number,
customValues: number[]
}

export enum SensitivityPlotType {
Expand Down
57 changes: 41 additions & 16 deletions app/static/src/app/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,8 @@ import {
} from "./types/responseTypes";
import userMessages from "./userMessages";
import settings from "./settings";
import {
SensitivityParameterSettings,
SensitivityScaleType,
SensitivityVariationType
} from "./store/sensitivity/state";
import { SensitivityParameterSettings, SensitivityScaleType, SensitivityVariationType }
from "./store/sensitivity/state";
import { AppState } from "./store/appState/state";
import { AdvancedComponentType, AdvancedSettings, Tag } from "./store/run/state";

Expand Down Expand Up @@ -134,10 +131,10 @@ export interface GenerateBatchParsResult {
error: WodinError | null
}

export function generateBatchPars(
function generateBatchParsFromOdin(
rootState: AppState,
paramSettings: SensitivityParameterSettings,
paramValues: OdinUserType | null
paramValues: OdinUserType
): GenerateBatchParsResult {
let batchPars = null;
let errorDetail = null;
Expand All @@ -148,14 +145,10 @@ export function generateBatchPars(
} = paramSettings;
const logarithmic = scaleType === SensitivityScaleType.Logarithmic;

if (!parameterToVary) {
errorDetail = "Parameter to vary is not set";
} else if (!runner || !paramValues) {
errorDetail = "Model is not initialised";
} else if (variationType === SensitivityVariationType.Percentage) {
if (variationType === SensitivityVariationType.Percentage) {
try {
batchPars = runner.batchParsDisplace(
paramValues, parameterToVary,
batchPars = runner!.batchParsDisplace(
paramValues, parameterToVary!,
numberOfRuns, logarithmic,
variationPercentage
);
Expand All @@ -164,9 +157,9 @@ export function generateBatchPars(
}
} else {
try {
batchPars = runner.batchParsRange(
batchPars = runner!.batchParsRange(
paramValues,
parameterToVary,
parameterToVary!,
numberOfRuns,
logarithmic,
rangeFrom,
Expand All @@ -184,6 +177,38 @@ export function generateBatchPars(
};
}

export function generateBatchPars(
rootState: AppState,
paramSettings: SensitivityParameterSettings,
paramValues: OdinUserType | null
): GenerateBatchParsResult {
let errorDetail = null;
if (!paramSettings.parameterToVary) {
errorDetail = "Parameter to vary is not set";
} else if (!rootState.model.odinRunnerOde || !paramValues) {
errorDetail = "Model is not initialised";
}
if (errorDetail) {
return {
batchPars: null,
error: { error: userMessages.sensitivity.invalidSettings, detail: errorDetail }
};
}

if (paramSettings.variationType === SensitivityVariationType.Custom) {
const batchPars: BatchPars = {
base: paramValues!,
name: paramSettings.parameterToVary!,
values: paramSettings.customValues
};
return {
batchPars,
error: null
};
}
return generateBatchParsFromOdin(rootState, paramSettings, paramValues!);
}

export const newSessionId = (): string => uid(32);

export const joinStringsSentence = (strings: string[], last = " and ", sep = ", "): string => {
Expand Down
Loading