From f2bc9141c9046d41e63dc53f8bc9df48315b36c3 Mon Sep 17 00:00:00 2001 From: Helena Date: Wed, 29 Apr 2026 04:04:43 +0000 Subject: [PATCH 01/10] Immediate generation change --- .../components/flows/AddExperiment/stepComponents/ParamStep.tsx | 2 +- apps/frontend/lib/db_types.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx b/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx index dce47ccc..aa977494 100644 --- a/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx +++ b/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx @@ -37,7 +37,7 @@ function calcPermutations(parameters: HyperparametersCollection) { } - if (hyper.default == -1) { + if (hyper.useDefault == false) { noDefaultCount = noDefaultCount * numObjs; } else { defaultCount = defaultCount + numObjs; diff --git a/apps/frontend/lib/db_types.ts b/apps/frontend/lib/db_types.ts index cc300614..9c9e5ad2 100644 --- a/apps/frontend/lib/db_types.ts +++ b/apps/frontend/lib/db_types.ts @@ -34,6 +34,7 @@ export interface IntegerHyperparameter extends GenericHyperparameter { max: number; step: number; default: number; + useDefault: boolean; type: HyperparameterTypes.INTEGER; } From 2dfa45ae023da02ca0a3bf94279ff83a7af8458c Mon Sep 17 00:00:00 2001 From: Helena Date: Thu, 30 Apr 2026 02:37:10 +0000 Subject: [PATCH 02/10] generation change Co-authored-by: Copilot --- .../stepComponents/ParamStep.tsx | 120 ++++++++---------- apps/frontend/lib/db_types.ts | 2 +- 2 files changed, 52 insertions(+), 70 deletions(-) diff --git a/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx b/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx index aa977494..903d0a17 100644 --- a/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx +++ b/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx @@ -7,85 +7,67 @@ import { HyperparametersCollection, HyperparameterTypes, IntegerHyperparameter } import { useDebounce } from "use-debounce"; function calcPermutations(parameters: HyperparametersCollection) { - var noDefaultCount = 1; - var defaultCount = 0; - var countDefaults = 0; - var totalObjs = 0; - - var allInts = true; + let finalPermutations = 1; if (parameters.hyperparameters.length > 0) { - - parameters.hyperparameters.forEach(hyperparameter => { - totalObjs++; - if (hyperparameter.type == HyperparameterTypes.INTEGER || hyperparameter.type == HyperparameterTypes.FLOAT) { - - if (isNaN(hyperparameter.step) || hyperparameter.step == 0) { - hyperparameter.step = 1; - return -1; - } - - if (hyperparameter.type == HyperparameterTypes.FLOAT) - allInts = false; - - let hyper = hyperparameter; - let numObjs = 0; - - for (let i = hyper.min * 100; i <= hyper.max * 100; i += hyper.step * 100) { - numObjs++; - } - - - if (hyper.useDefault == false) { - noDefaultCount = noDefaultCount * numObjs; - } else { - defaultCount = defaultCount + numObjs; - countDefaults++; - } - - } - else if (hyperparameter.type == HyperparameterTypes.BOOLEAN) { - if (hyperparameter.default != true && hyperparameter.default != false) { - noDefaultCount = noDefaultCount * 2; - } - else { - defaultCount = defaultCount + 2; - countDefaults++; - } + //Calculate if non default values are above one + let nonDefaultCount = 0; + let defaultCount = 0; + for (const param of parameters.hyperparameters) { + if (param.useDefault) { + defaultCount++; + continue; } - else if (hyperparameter.type == HyperparameterTypes.STRING_LIST) { - if (hyperparameter.default == '-1') { - noDefaultCount = noDefaultCount * hyperparameter.values.length; - } - else { - defaultCount = defaultCount + hyperparameter.values.length; - countDefaults++; - } - } - else if (hyperparameter.type == HyperparameterTypes.PARAM_GROUP) { - let hyper = hyperparameter; - let numObjs = 0; - for (let key in hyper.values) { - numObjs = hyper.values[key].length; - break; - } - - noDefaultCount = noDefaultCount * numObjs; + nonDefaultCount++; + } + if (nonDefaultCount >= 2) { + parameters.hyperparameters.forEach(hyperparameter => { + if (hyperparameter.type == HyperparameterTypes.INTEGER || hyperparameter.type == HyperparameterTypes.FLOAT) { + const intFloatParam = hyperparameter as IntegerHyperparameter; + const range = Math.floor((intFloatParam.max - intFloatParam.min) / intFloatParam.step) + 1; + finalPermutations *= range; + } else if (hyperparameter.type == HyperparameterTypes.STRING) { + finalPermutations *= 1; + } else if (hyperparameter.type == HyperparameterTypes.BOOLEAN) { + finalPermutations *= 2; + } else if (hyperparameter.type == HyperparameterTypes.STRING_LIST) { + finalPermutations *= hyperparameter .values.length; + } else if (hyperparameter.type == HyperparameterTypes.PARAM_GROUP) { + let groupPermutations = 1; + for (const key in hyperparameter.values) { + groupPermutations *= hyperparameter.values[key].length; + } + finalPermutations *= groupPermutations; + } + }); + } else if (nonDefaultCount <= 1 && defaultCount > 0) { + parameters.hyperparameters.forEach(hyperparameter => { + if (hyperparameter.type == HyperparameterTypes.INTEGER || hyperparameter.type == HyperparameterTypes.FLOAT) { + const intFloatParam = hyperparameter as IntegerHyperparameter; + const range = Math.floor((intFloatParam.max - intFloatParam.min) / intFloatParam.step) + 1; + finalPermutations += range - 1; + } else if (hyperparameter.type == HyperparameterTypes.STRING) { + finalPermutations += 1; + } else if (hyperparameter.type == HyperparameterTypes.BOOLEAN) { + finalPermutations += 2; + } else if (hyperparameter.type == HyperparameterTypes.STRING_LIST) { + const stringListParam = hyperparameter as any; // Replace 'any' with the correct type + finalPermutations += stringListParam.values.length - 1; + } else if (hyperparameter.type == HyperparameterTypes.PARAM_GROUP) { + const paramGroupParam = hyperparameter as any; // Replace 'any' with the correct type + let groupPermutations = 1; + for (const key in paramGroupParam.values) { + groupPermutations *= paramGroupParam.values[key].length; + } + finalPermutations += groupPermutations - 1; } }); - - if (totalObjs < 3 && allInts && countDefaults > 0) { - const total = (noDefaultCount + defaultCount) - 1; - return total; - } - else { - const total = (noDefaultCount * defaultCount) - (noDefaultCount * (countDefaults - 1)); - return total; } } + return finalPermutations; } export const ParameterOptions = ['integer', 'float', 'bool', 'stringlist', 'paramgroup'] as const; diff --git a/apps/frontend/lib/db_types.ts b/apps/frontend/lib/db_types.ts index 9c9e5ad2..fc62a39e 100644 --- a/apps/frontend/lib/db_types.ts +++ b/apps/frontend/lib/db_types.ts @@ -20,6 +20,7 @@ export enum HyperparameterTypes { export interface GenericHyperparameter { name: string; type: HyperparameterTypes; + useDefault: boolean; } export interface ParamGroupHyperparameter extends GenericHyperparameter { @@ -34,7 +35,6 @@ export interface IntegerHyperparameter extends GenericHyperparameter { max: number; step: number; default: number; - useDefault: boolean; type: HyperparameterTypes.INTEGER; } From 89b48bd843e214c38b2a9c9c55c5295b0fa50334 Mon Sep 17 00:00:00 2001 From: Helena Date: Thu, 30 Apr 2026 05:36:46 +0000 Subject: [PATCH 03/10] Refactoring for readability Co-authored-by: Copilot --- .../stepComponents/ParamStep.tsx | 115 ++++++++++++------ 1 file changed, 75 insertions(+), 40 deletions(-) diff --git a/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx b/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx index 903d0a17..0c504af7 100644 --- a/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx +++ b/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx @@ -5,66 +5,101 @@ import { InputSection } from '../../../InputSection'; //import { formList } from '@mantine/form'; import { HyperparametersCollection, HyperparameterTypes, IntegerHyperparameter } from '../../../../../lib/db_types'; import { useDebounce } from "use-debounce"; +import { final } from 'pino'; -function calcPermutations(parameters: HyperparametersCollection) { - - let finalPermutations = 1; - +function defaultNonDefaultCount(parameters: HyperparametersCollection) { + let nonDefaultCount = 0; + let defaultCount = 0; if (parameters.hyperparameters.length > 0) { //Calculate if non default values are above one - let nonDefaultCount = 0; - let defaultCount = 0; for (const param of parameters.hyperparameters) { - if (param.useDefault) { + if(param.type === HyperparameterTypes.PARAM_GROUP) { + for (const key in param.values) { + const groupParamsLength = param.values[key].length; + nonDefaultCount += groupParamsLength; + } + } else if (param.useDefault) { defaultCount++; continue; } nonDefaultCount++; } + } + return { nonDefaultCount, defaultCount }; +} - if (nonDefaultCount >= 2) { - parameters.hyperparameters.forEach(hyperparameter => { - if (hyperparameter.type == HyperparameterTypes.INTEGER || hyperparameter.type == HyperparameterTypes.FLOAT) { - const intFloatParam = hyperparameter as IntegerHyperparameter; - const range = Math.floor((intFloatParam.max - intFloatParam.min) / intFloatParam.step) + 1; - finalPermutations *= range; - } else if (hyperparameter.type == HyperparameterTypes.STRING) { - finalPermutations *= 1; - } else if (hyperparameter.type == HyperparameterTypes.BOOLEAN) { - finalPermutations *= 2; - } else if (hyperparameter.type == HyperparameterTypes.STRING_LIST) { - finalPermutations *= hyperparameter .values.length; +function nonDefaultPermCalculation(parameters: HyperparametersCollection, finalPermutations: number) { + parameters.hyperparameters.forEach(hyperparameter => { + if (hyperparameter.type == HyperparameterTypes.INTEGER || hyperparameter.type == HyperparameterTypes.FLOAT) { + const intFloatParam = hyperparameter as IntegerHyperparameter; + const range = Math.floor((intFloatParam.max - intFloatParam.min) / intFloatParam.step) + 1; + finalPermutations *= range; + } else if (hyperparameter.type == HyperparameterTypes.STRING) { + finalPermutations *= 1; + } else if (hyperparameter.type == HyperparameterTypes.BOOLEAN) { + console.log("Calculating permutations for boolean ", hyperparameter.name); + finalPermutations *= 2; + } else if (hyperparameter.type == HyperparameterTypes.STRING_LIST) { + finalPermutations *= hyperparameter.values.length; } else if (hyperparameter.type == HyperparameterTypes.PARAM_GROUP) { + console.log("Calculating permutations for param group ", hyperparameter.name, " with values ", hyperparameter.values); let groupPermutations = 1; for (const key in hyperparameter.values) { + console.log("Calculating permutations for param group ", hyperparameter.name, " with key ", key, " and values ", hyperparameter.values[key]); groupPermutations *= hyperparameter.values[key].length; + break; } finalPermutations *= groupPermutations; } }); - } else if (nonDefaultCount <= 1 && defaultCount > 0) { - parameters.hyperparameters.forEach(hyperparameter => { - if (hyperparameter.type == HyperparameterTypes.INTEGER || hyperparameter.type == HyperparameterTypes.FLOAT) { - const intFloatParam = hyperparameter as IntegerHyperparameter; - const range = Math.floor((intFloatParam.max - intFloatParam.min) / intFloatParam.step) + 1; - finalPermutations += range - 1; - } else if (hyperparameter.type == HyperparameterTypes.STRING) { - finalPermutations += 1; - } else if (hyperparameter.type == HyperparameterTypes.BOOLEAN) { - finalPermutations += 2; - } else if (hyperparameter.type == HyperparameterTypes.STRING_LIST) { - const stringListParam = hyperparameter as any; // Replace 'any' with the correct type - finalPermutations += stringListParam.values.length - 1; - } else if (hyperparameter.type == HyperparameterTypes.PARAM_GROUP) { - const paramGroupParam = hyperparameter as any; // Replace 'any' with the correct type - let groupPermutations = 1; - for (const key in paramGroupParam.values) { - groupPermutations *= paramGroupParam.values[key].length; - } - finalPermutations += groupPermutations - 1; + return finalPermutations; +} + +function defaultPermCalculation(parameters: HyperparametersCollection, finalPermutations: number) { + let numberTotal = 0; + let rangeMultiplier = 1; + parameters.hyperparameters.forEach(hyperparameter => { + if (hyperparameter.type == HyperparameterTypes.INTEGER || hyperparameter.type == HyperparameterTypes.FLOAT) { + if(hyperparameter.useDefault) { + const intFloatParam = hyperparameter as IntegerHyperparameter; + const range = Math.floor((intFloatParam.max - intFloatParam.min) / intFloatParam.step) + 1; + numberTotal += 1; + finalPermutations += range; + } else { + const intFloatParam = hyperparameter as IntegerHyperparameter; + rangeMultiplier = Math.floor((intFloatParam.max - intFloatParam.min) / intFloatParam.step) + 1; } - }); + } else if (hyperparameter.type == HyperparameterTypes.STRING) { + finalPermutations += 1; + } else if (hyperparameter.type == HyperparameterTypes.BOOLEAN) { + finalPermutations += 2; + } else if (hyperparameter.type == HyperparameterTypes.STRING_LIST) { + const stringListParam = hyperparameter as any; + finalPermutations += stringListParam.values.length - 1; + } else if (hyperparameter.type == HyperparameterTypes.PARAM_GROUP) { + const paramGroupParam = hyperparameter as any; + let groupPermutations = 1; + for (const key in paramGroupParam.values) { + groupPermutations *= paramGroupParam.values[key].length; + } + finalPermutations += groupPermutations - 1; } + }); + finalPermutations = finalPermutations - numberTotal + 1; + finalPermutations *= rangeMultiplier; + return finalPermutations; +} + +function calcPermutations(parameters: HyperparametersCollection) { + let finalPermutations; + const { nonDefaultCount, defaultCount } = defaultNonDefaultCount(parameters); + + // If there are 2 or more non default values, calculate permutations as normal with all parameters. + // If there are 0 or 1 non default values, calculate permutations with only non default values and then add the default values at the end. + if (nonDefaultCount >= 2) { + finalPermutations = nonDefaultPermCalculation(parameters, 1); + } else if (nonDefaultCount <= 1 && defaultCount > 0) { + finalPermutations = defaultPermCalculation(parameters, 1); } return finalPermutations; From 2263239c55e4652bce6e91c33c1838d9c26e3d19 Mon Sep 17 00:00:00 2001 From: Helena Date: Thu, 30 Apr 2026 17:19:54 +0000 Subject: [PATCH 04/10] Refactor to reduce redudancies Co-authored-by: Copilot --- .../stepComponents/ParamStep.tsx | 158 ++++++++---------- 1 file changed, 66 insertions(+), 92 deletions(-) diff --git a/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx b/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx index 0c504af7..bbd3220c 100644 --- a/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx +++ b/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx @@ -7,102 +7,76 @@ import { HyperparametersCollection, HyperparameterTypes, IntegerHyperparameter } import { useDebounce } from "use-debounce"; import { final } from 'pino'; -function defaultNonDefaultCount(parameters: HyperparametersCollection) { - let nonDefaultCount = 0; - let defaultCount = 0; - if (parameters.hyperparameters.length > 0) { - //Calculate if non default values are above one - for (const param of parameters.hyperparameters) { - if(param.type === HyperparameterTypes.PARAM_GROUP) { - for (const key in param.values) { - const groupParamsLength = param.values[key].length; - nonDefaultCount += groupParamsLength; - } - } else if (param.useDefault) { - defaultCount++; - continue; - } - nonDefaultCount++; - } - } - return { nonDefaultCount, defaultCount }; +/** + * Helper to get the number of possible values for a single hyperparameter + */ +function getParamRange(param: any): number { + switch (param.type) { + case HyperparameterTypes.INTEGER: + case HyperparameterTypes.FLOAT: + return Math.floor((param.max - param.min) / param.step) + 1; + case HyperparameterTypes.BOOLEAN: + return 2; + case HyperparameterTypes.STRING: + return 1; + case HyperparameterTypes.STRING_LIST: + return param.values.length; + case HyperparameterTypes.PARAM_GROUP: + // Calculate product of all groups + return Object.values(param.values as any[][]).reduce((acc, list) => acc * list.length, 1); + default: + return 1; + } } -function nonDefaultPermCalculation(parameters: HyperparametersCollection, finalPermutations: number) { - parameters.hyperparameters.forEach(hyperparameter => { - if (hyperparameter.type == HyperparameterTypes.INTEGER || hyperparameter.type == HyperparameterTypes.FLOAT) { - const intFloatParam = hyperparameter as IntegerHyperparameter; - const range = Math.floor((intFloatParam.max - intFloatParam.min) / intFloatParam.step) + 1; - finalPermutations *= range; - } else if (hyperparameter.type == HyperparameterTypes.STRING) { - finalPermutations *= 1; - } else if (hyperparameter.type == HyperparameterTypes.BOOLEAN) { - console.log("Calculating permutations for boolean ", hyperparameter.name); - finalPermutations *= 2; - } else if (hyperparameter.type == HyperparameterTypes.STRING_LIST) { - finalPermutations *= hyperparameter.values.length; - } else if (hyperparameter.type == HyperparameterTypes.PARAM_GROUP) { - console.log("Calculating permutations for param group ", hyperparameter.name, " with values ", hyperparameter.values); - let groupPermutations = 1; - for (const key in hyperparameter.values) { - console.log("Calculating permutations for param group ", hyperparameter.name, " with key ", key, " and values ", hyperparameter.values[key]); - groupPermutations *= hyperparameter.values[key].length; - break; - } - finalPermutations *= groupPermutations; - } - }); - return finalPermutations; -} +function calcPermutations(parameters: HyperparametersCollection) { + const params = parameters.hyperparameters; + if ((params.length as number) === 0) return 0; -function defaultPermCalculation(parameters: HyperparametersCollection, finalPermutations: number) { - let numberTotal = 0; - let rangeMultiplier = 1; - parameters.hyperparameters.forEach(hyperparameter => { - if (hyperparameter.type == HyperparameterTypes.INTEGER || hyperparameter.type == HyperparameterTypes.FLOAT) { - if(hyperparameter.useDefault) { - const intFloatParam = hyperparameter as IntegerHyperparameter; - const range = Math.floor((intFloatParam.max - intFloatParam.min) / intFloatParam.step) + 1; - numberTotal += 1; - finalPermutations += range; - } else { - const intFloatParam = hyperparameter as IntegerHyperparameter; - rangeMultiplier = Math.floor((intFloatParam.max - intFloatParam.min) / intFloatParam.step) + 1; - } - } else if (hyperparameter.type == HyperparameterTypes.STRING) { - finalPermutations += 1; - } else if (hyperparameter.type == HyperparameterTypes.BOOLEAN) { - finalPermutations += 2; - } else if (hyperparameter.type == HyperparameterTypes.STRING_LIST) { - const stringListParam = hyperparameter as any; - finalPermutations += stringListParam.values.length - 1; - } else if (hyperparameter.type == HyperparameterTypes.PARAM_GROUP) { - const paramGroupParam = hyperparameter as any; - let groupPermutations = 1; - for (const key in paramGroupParam.values) { - groupPermutations *= paramGroupParam.values[key].length; - } - finalPermutations += groupPermutations - 1; - } - }); - finalPermutations = finalPermutations - numberTotal + 1; - finalPermutations *= rangeMultiplier; - return finalPermutations; -} + // 1. Calculate counts and metadata in one pass + let nonDefaultCount = 0; + let defaultCount = 0; + + params.forEach(p => { + if (p.type === HyperparameterTypes.PARAM_GROUP) { + nonDefaultCount += Object.keys(p.values).length; + } else if (p.useDefault) { + defaultCount++; + } else { + nonDefaultCount++; + } + }); -function calcPermutations(parameters: HyperparametersCollection) { - let finalPermutations; - const { nonDefaultCount, defaultCount } = defaultNonDefaultCount(parameters); - - // If there are 2 or more non default values, calculate permutations as normal with all parameters. - // If there are 0 or 1 non default values, calculate permutations with only non default values and then add the default values at the end. - if (nonDefaultCount >= 2) { - finalPermutations = nonDefaultPermCalculation(parameters, 1); - } else if (nonDefaultCount <= 1 && defaultCount > 0) { - finalPermutations = defaultPermCalculation(parameters, 1); - } - - return finalPermutations; + // 2. Logic Branching + // Scenario A: 2+ non-defaults -> Standard Cartesian Product (Multiply everything) + if (nonDefaultCount >= 2) { + return params.reduce((total, p) => total * getParamRange(p), 1); + } + + // Default parameters logic + if (defaultCount > 0) { + let sumOfRanges = 0; + let rangeMultiplier = 1; + let defaultTypeCount = 0; + + params.forEach(p => { + const range = getParamRange(p); + const isNumeric = p.type === HyperparameterTypes.INTEGER || p.type === HyperparameterTypes.FLOAT; + + if (isNumeric && !p.useDefault) { + rangeMultiplier = range; // Special case for if a numeris param is non-default + } else { + if (isNumeric && p.useDefault) defaultTypeCount++; + // sum (range - 1) for groups/lists, or just range for others + const adjustment = (p.type === HyperparameterTypes.STRING_LIST || p.type === HyperparameterTypes.PARAM_GROUP) ? -1 : 0; + sumOfRanges += (range + adjustment); + } + }); + + return (sumOfRanges - defaultTypeCount + 1) * rangeMultiplier; + } + + return 0; } export const ParameterOptions = ['integer', 'float', 'bool', 'stringlist', 'paramgroup'] as const; From 166dfc7eae39c45c6e5b3b6053217504dc99da04 Mon Sep 17 00:00:00 2001 From: Helena Date: Thu, 30 Apr 2026 17:47:49 +0000 Subject: [PATCH 05/10] Reducing redunadancy Co-authored-by: Copilot --- .../stepComponents/ParamStep.tsx | 77 ++++++++----------- 1 file changed, 32 insertions(+), 45 deletions(-) diff --git a/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx b/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx index bbd3220c..fa5021de 100644 --- a/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx +++ b/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx @@ -16,14 +16,15 @@ function getParamRange(param: any): number { case HyperparameterTypes.FLOAT: return Math.floor((param.max - param.min) / param.step) + 1; case HyperparameterTypes.BOOLEAN: - return 2; - case HyperparameterTypes.STRING: - return 1; + return 2; // Values either true or false case HyperparameterTypes.STRING_LIST: return param.values.length; + case HyperparameterTypes.STRING: + return 1; case HyperparameterTypes.PARAM_GROUP: - // Calculate product of all groups - return Object.values(param.values as any[][]).reduce((acc, list) => acc * list.length, 1); + // Returns length of a grouping + const values = Object.values(param.values as any[][]); + return values.length > 0 ? values[0].length : 0; default: return 1; } @@ -31,52 +32,38 @@ function getParamRange(param: any): number { function calcPermutations(parameters: HyperparametersCollection) { const params = parameters.hyperparameters; - if ((params.length as number) === 0) return 0; - - // 1. Calculate counts and metadata in one pass - let nonDefaultCount = 0; - let defaultCount = 0; - - params.forEach(p => { - if (p.type === HyperparameterTypes.PARAM_GROUP) { - nonDefaultCount += Object.keys(p.values).length; - } else if (p.useDefault) { - defaultCount++; - } else { - nonDefaultCount++; - } + const paramGroups = params.filter(p => p.type === HyperparameterTypes.PARAM_GROUP); + const normalParams = params.filter(p => p.type !== HyperparameterTypes.PARAM_GROUP); + + const defaultParams = normalParams.filter(p => + p.useDefault + ); + + let count = 1; // The "Base Default" config + + defaultParams.forEach(p => { + const range = getParamRange(p); + // -1 is to account for the 1 that is included in the count for the default config + count += (range - 1); }); - // 2. Logic Branching - // Scenario A: 2+ non-defaults -> Standard Cartesian Product (Multiply everything) - if (nonDefaultCount >= 2) { - return params.reduce((total, p) => total * getParamRange(p), 1); - } + const nonDefaultParams = normalParams.filter(p => + p.useDefault === false + ); - // Default parameters logic - if (defaultCount > 0) { - let sumOfRanges = 0; - let rangeMultiplier = 1; - let defaultTypeCount = 0; - - params.forEach(p => { - const range = getParamRange(p); - const isNumeric = p.type === HyperparameterTypes.INTEGER || p.type === HyperparameterTypes.FLOAT; - - if (isNumeric && !p.useDefault) { - rangeMultiplier = range; // Special case for if a numeris param is non-default - } else { - if (isNumeric && p.useDefault) defaultTypeCount++; - // sum (range - 1) for groups/lists, or just range for others - const adjustment = (p.type === HyperparameterTypes.STRING_LIST || p.type === HyperparameterTypes.PARAM_GROUP) ? -1 : 0; - sumOfRanges += (range + adjustment); - } - }); + nonDefaultParams.forEach(p => { + count *= getParamRange(p); + }); - return (sumOfRanges - defaultTypeCount + 1) * rangeMultiplier; + if (paramGroups.length > 0) { + let groupMultiplier = 1; + paramGroups.forEach(pg => { + groupMultiplier *= getParamRange(pg); + }); + count *= groupMultiplier; } - return 0; + return count; } export const ParameterOptions = ['integer', 'float', 'bool', 'stringlist', 'paramgroup'] as const; From 2f000cfe9e400309dc86427d835b4a770d4479e9 Mon Sep 17 00:00:00 2001 From: Helena Date: Sun, 3 May 2026 23:05:03 +0000 Subject: [PATCH 06/10] Corrected multiple default one non-default bool --- .../stepComponents/ParamStep.tsx | 30 ++++++------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx b/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx index 39ccada4..c169e245 100644 --- a/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx +++ b/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx @@ -32,38 +32,26 @@ function getParamRange(param: any): number { function calcPermutations(parameters: HyperparametersCollection) { const params = parameters.hyperparameters; + const paramGroups = params.filter(p => p.type === HyperparameterTypes.PARAM_GROUP); const normalParams = params.filter(p => p.type !== HyperparameterTypes.PARAM_GROUP); - const defaultParams = normalParams.filter(p => - p.useDefault - ); - - let count = 1; // The "Base Default" config + let totalCount = 1; // all-default case - defaultParams.forEach(p => { + for (const p of normalParams) { const range = getParamRange(p); - // -1 is to account for the 1 that is included in the count for the default config - count += (range - 1); - }); - - const nonDefaultParams = normalParams.filter(p => - p.useDefault === false - ); - - nonDefaultParams.forEach(p => { - count *= getParamRange(p); - }); + totalCount += (range - 1); + } if (paramGroups.length > 0) { let groupMultiplier = 1; - paramGroups.forEach(pg => { + for (const pg of paramGroups) { groupMultiplier *= getParamRange(pg); - }); - count *= groupMultiplier; + } + totalCount *= groupMultiplier; } - return count; + return totalCount; } export const ParameterOptions = ['integer', 'float', 'bool', 'stringlist', 'paramgroup'] as const; From f0ca1edd1e5494c295509bd4965f66efe1b3e6c5 Mon Sep 17 00:00:00 2001 From: Helena Date: Sun, 3 May 2026 23:18:40 +0000 Subject: [PATCH 07/10] Corrected default case --- .../stepComponents/ParamStep.tsx | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx b/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx index c169e245..7e69a849 100644 --- a/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx +++ b/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx @@ -30,28 +30,42 @@ function getParamRange(param: any): number { } } +function hasDefault(p: any): boolean { + return p.default !== -1 && p.default !== '' && p.default !== undefined; +} + function calcPermutations(parameters: HyperparametersCollection) { const params = parameters.hyperparameters; const paramGroups = params.filter(p => p.type === HyperparameterTypes.PARAM_GROUP); const normalParams = params.filter(p => p.type !== HyperparameterTypes.PARAM_GROUP); - let totalCount = 1; // all-default case + const constrained = normalParams.filter(p => hasDefault(p)); + const free = normalParams.filter(p => !hasDefault(p)); - for (const p of normalParams) { - const range = getParamRange(p); - totalCount += (range - 1); + let constrainedTotal = 1; + + for (const p of constrained) { + constrainedTotal += (getParamRange(p) - 1); } + let freeMultiplier = 1; + + for (const p of free) { + freeMultiplier *= getParamRange(p); + } + + let total = constrainedTotal * freeMultiplier; + if (paramGroups.length > 0) { let groupMultiplier = 1; for (const pg of paramGroups) { groupMultiplier *= getParamRange(pg); } - totalCount *= groupMultiplier; + total *= groupMultiplier; } - return totalCount; + return total; } export const ParameterOptions = ['integer', 'float', 'bool', 'stringlist', 'paramgroup'] as const; From a4bb6adad9d66cb16d267aa8302731816851b790 Mon Sep 17 00:00:00 2001 From: Helena Date: Mon, 4 May 2026 03:17:59 +0000 Subject: [PATCH 08/10] Refactor --- .../stepComponents/ParamStep.tsx | 57 +++++++++++-------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx b/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx index 7e69a849..ae1a46b9 100644 --- a/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx +++ b/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx @@ -13,61 +13,72 @@ import { final } from 'pino'; function getParamRange(param: any): number { switch (param.type) { case HyperparameterTypes.INTEGER: - case HyperparameterTypes.FLOAT: return Math.floor((param.max - param.min) / param.step) + 1; + case HyperparameterTypes.FLOAT: + return Math.floor(((param.max - param.min) / param.step) + 1e-9) + 1; case HyperparameterTypes.BOOLEAN: - return 2; // Values either true or false + return 2; case HyperparameterTypes.STRING_LIST: - return param.values.length; + return param.values ? param.values.length : 0; case HyperparameterTypes.STRING: return 1; case HyperparameterTypes.PARAM_GROUP: - // Returns length of a grouping - const values = Object.values(param.values as any[][]); + const values = Object.values((param.values || {}) as any[][]); return values.length > 0 ? values[0].length : 0; default: return 1; } } -function hasDefault(p: any): boolean { - return p.default !== -1 && p.default !== '' && p.default !== undefined; -} - -function calcPermutations(parameters: HyperparametersCollection) { +function calcPermutations(parameters: HyperparametersCollection): number { const params = parameters.hyperparameters; const paramGroups = params.filter(p => p.type === HyperparameterTypes.PARAM_GROUP); const normalParams = params.filter(p => p.type !== HyperparameterTypes.PARAM_GROUP); - const constrained = normalParams.filter(p => hasDefault(p)); - const free = normalParams.filter(p => !hasDefault(p)); + // A parameter is "constrained" if useDefault is true AND it has a meaningful default + // Python: param["useDefault"] and (param["default"] != -1 and param["default"] != "-1" and param["default"] != '') + const hasValidDefault = (p: any): boolean => { + const def = p.default; + return p.useDefault || (def !== -1 && def !== "-1" && def !== '' && def !== undefined && def !== null); + }; - let constrainedTotal = 1; + const isConstrained = (p: any): boolean => + p.useDefault === true && hasValidDefault(p); - for (const p of constrained) { - constrainedTotal += (getParamRange(p) - 1); - } + const D = normalParams.filter(isConstrained); // constrained params + const F = normalParams.filter(p => !isConstrained(p)); // free params - let freeMultiplier = 1; + // Free parameters: full Cartesian product + let freeProduct = 1; + for (const p of F) { + freeProduct *= getParamRange(p); + } - for (const p of free) { - freeMultiplier *= getParamRange(p); + // Constrained parameters: allow at most 1 to deviate from default + // Valid combinations = 1 (all at default) + sum of (range - 1) for each constrained param + // (range - 1) because the default value itself doesn't count as "changed" + let constrainedCombinations = 1; // all constrained params at their defaults + for (const p of D) { + constrainedCombinations += (getParamRange(p) - 1); } - let total = constrainedTotal * freeMultiplier; + let total = freeProduct * constrainedCombinations; + // Param groups: Python generates all group permutations then does Cartesian product + // Multiple param groups are summed (appended to same list), then multiplied with filtered_permutations if (paramGroups.length > 0) { - let groupMultiplier = 1; + let groupTotal = 0; for (const pg of paramGroups) { - groupMultiplier *= getParamRange(pg); + groupTotal += getParamRange(pg); } - total *= groupMultiplier; + total *= groupTotal; } return total; } + export const ParameterOptions = ['integer', 'float', 'bool', 'stringlist', 'paramgroup'] as const; export const ParamStep = ({ form, confirmedValues, setConfirmedValues, ...props }) => { From 1fd788c01c10ceb1563d033fcd80595fc8022e2f Mon Sep 17 00:00:00 2001 From: Helena Date: Mon, 4 May 2026 03:30:13 +0000 Subject: [PATCH 09/10] Default logic alignment --- .../stepComponents/ParamStep.tsx | 33 ++++++++++--------- apps/runner/modules/configs.py | 2 +- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx b/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx index ae1a46b9..b94fb175 100644 --- a/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx +++ b/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx @@ -36,37 +36,39 @@ function calcPermutations(parameters: HyperparametersCollection): number { const paramGroups = params.filter(p => p.type === HyperparameterTypes.PARAM_GROUP); const normalParams = params.filter(p => p.type !== HyperparameterTypes.PARAM_GROUP); - // A parameter is "constrained" if useDefault is true AND it has a meaningful default - // Python: param["useDefault"] and (param["default"] != -1 and param["default"] != "-1" and param["default"] != '') + /** + * Matches Python: + * param["default"] != -1 and param["default"] != "-1" and param["default"] != '' + */ const hasValidDefault = (p: any): boolean => { const def = p.default; return p.useDefault || (def !== -1 && def !== "-1" && def !== '' && def !== undefined && def !== null); }; - const isConstrained = (p: any): boolean => - p.useDefault === true && hasValidDefault(p); + // A parameter is constrained if it has a default value set, + // mimicking the Python 'num_defaults_changed' filter logic. + const D = normalParams.filter(hasValidDefault); + const F = normalParams.filter(p => !hasValidDefault(p)); - const D = normalParams.filter(isConstrained); // constrained params - const F = normalParams.filter(p => !isConstrained(p)); // free params - - // Free parameters: full Cartesian product + // 1. Calculate Product of Free Parameters let freeProduct = 1; for (const p of F) { freeProduct *= getParamRange(p); } - // Constrained parameters: allow at most 1 to deviate from default - // Valid combinations = 1 (all at default) + sum of (range - 1) for each constrained param - // (range - 1) because the default value itself doesn't count as "changed" - let constrainedCombinations = 1; // all constrained params at their defaults + // 2. Calculate Combinations of Constrained Parameters (One-at-a-time deviation) + // Formula: 1 (all at default) + Sum of (range - 1) for each param + let constrainedCombinations = 1; for (const p of D) { - constrainedCombinations += (getParamRange(p) - 1); + const range = getParamRange(p); + if (range > 1) { + constrainedCombinations += (range - 1); + } } let total = freeProduct * constrainedCombinations; - // Param groups: Python generates all group permutations then does Cartesian product - // Multiple param groups are summed (appended to same list), then multiplied with filtered_permutations + // 3. Handle Param Groups (Sum of group lengths multiplied by current total) if (paramGroups.length > 0) { let groupTotal = 0; for (const pg of paramGroups) { @@ -78,7 +80,6 @@ function calcPermutations(parameters: HyperparametersCollection): number { return total; } - export const ParameterOptions = ['integer', 'float', 'bool', 'stringlist', 'paramgroup'] as const; export const ParamStep = ({ form, confirmedValues, setConfirmedValues, ...props }) => { diff --git a/apps/runner/modules/configs.py b/apps/runner/modules/configs.py index a8ab5796..74fa5fb3 100644 --- a/apps/runner/modules/configs.py +++ b/apps/runner/modules/configs.py @@ -70,7 +70,7 @@ def generate_permutations(parameters, paramgroup=None): explogger.info("paramgroup vals: %s", str(paramgroup)) for param in parameters: - if param["default"] != -1 and param["default"] != "-1" and param["default"] != '': + if param["useDefault"] or (param["default"] != -1 and param["default"] != "-1" and param["default"] != ''): default_vals[param["name"]] = [param["default"]] else: default_vals[param["name"]] = expand_values(param) From f5ce0fb65f5527374c32011a4df68d2323ea6e94 Mon Sep 17 00:00:00 2001 From: Helena Date: Mon, 4 May 2026 04:02:29 +0000 Subject: [PATCH 10/10] Cleaning --- .../flows/AddExperiment/stepComponents/ParamStep.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx b/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx index b94fb175..6e94e361 100644 --- a/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx +++ b/apps/frontend/app/components/flows/AddExperiment/stepComponents/ParamStep.tsx @@ -36,17 +36,11 @@ function calcPermutations(parameters: HyperparametersCollection): number { const paramGroups = params.filter(p => p.type === HyperparameterTypes.PARAM_GROUP); const normalParams = params.filter(p => p.type !== HyperparameterTypes.PARAM_GROUP); - /** - * Matches Python: - * param["default"] != -1 and param["default"] != "-1" and param["default"] != '' - */ const hasValidDefault = (p: any): boolean => { const def = p.default; return p.useDefault || (def !== -1 && def !== "-1" && def !== '' && def !== undefined && def !== null); }; - // A parameter is constrained if it has a default value set, - // mimicking the Python 'num_defaults_changed' filter logic. const D = normalParams.filter(hasValidDefault); const F = normalParams.filter(p => !hasValidDefault(p));