From 825770539445a17f09d05734199d23abf5501952 Mon Sep 17 00:00:00 2001 From: Mohsen Taheri Shalmani Date: Wed, 20 May 2026 10:17:53 +0200 Subject: [PATCH 1/9] update --- .../rest/aiclassification/AIModelsCheck.kt | 22 +++++++--- .../aiclassification/AIModelsCheckWFDEM.kt | 11 ++--- .../aiclassification/AIMoldelsCheckWFD.kt | 43 +++++++++++++------ .../rest/aiclassification/ExtraTools.kt | 24 +++++------ 4 files changed, 64 insertions(+), 36 deletions(-) diff --git a/core-tests/integration-tests/core-it/src/test/kotlin/org/evomaster/core/problem/rest/aiclassification/AIModelsCheck.kt b/core-tests/integration-tests/core-it/src/test/kotlin/org/evomaster/core/problem/rest/aiclassification/AIModelsCheck.kt index cbeae79ae3..b4769b490a 100644 --- a/core-tests/integration-tests/core-it/src/test/kotlin/org/evomaster/core/problem/rest/aiclassification/AIModelsCheck.kt +++ b/core-tests/integration-tests/core-it/src/test/kotlin/org/evomaster/core/problem/rest/aiclassification/AIModelsCheck.kt @@ -55,6 +55,8 @@ class AIModelsCheck : IntegrationTestRestBase() { val metricType = "TIME_WINDOW" val warmUpRep = 10 val maxAttemptRepair = 100 // i.e., the classifier has 10 times the chances to pick an action with non-400 response + val repairThreshold = 0.5 + val weaknessThreshold = 0.6 val runIterations = 500 val saveReport = false @@ -82,6 +84,9 @@ class AIModelsCheck : IntegrationTestRestBase() { EMConfig.AIClassificationMetrics.valueOf(metricType) config.aiResponseClassifierWarmup = warmUpRep config.maxRepairAttemptsInResponseClassification = maxAttemptRepair + config.classificationRepairThreshold = repairThreshold + config.aIResponseClassifierWeaknessThreshold = weaknessThreshold +// config.blackBox = false } fun repairAction(call: RestCallAction) { @@ -157,7 +162,12 @@ class AIModelsCheck : IntegrationTestRestBase() { val metrics = aiGlobalClassifier.estimateMetrics(action.endpoint) - if (!(metrics.accuracy > 0.5 && metrics.f1Score400 > 0.5)) { + val deltaW = config.aIResponseClassifierWeaknessThreshold + if(metrics.precision400 < deltaW + || metrics.sensitivity400 < deltaW + || metrics.specificity < deltaW + || metrics.npv < deltaW) { + println("The classifier is weak for $endPoint") val result = ExtraTools.executeRestCallAction(action, baseUrlOfSut) @@ -208,11 +218,11 @@ class AIModelsCheck : IntegrationTestRestBase() { val overAllMetrics = aiGlobalClassifier.estimateOverallMetrics() - println("Overall Accuracy: ${overAllMetrics.accuracy}") - println("Overall Precision400: ${overAllMetrics.precision400}") - println("Overall Recall400: ${overAllMetrics.sensitivity400}") - println("Overall F1Score400: ${overAllMetrics.f1Score400}") - println("Overall MCC: ${overAllMetrics.mcc}") + println("Overall Accuracy: ${overAllMetrics.accuracy}") + println("Overall Precision400: ${overAllMetrics.precision400}") + println("Overall Sensitivity400: ${overAllMetrics.sensitivity400}") + println("Overall Specificity: ${overAllMetrics.specificity}") + println("Overall NPV: ${overAllMetrics.npv}") if (saveReport) { saveReports() diff --git a/core-tests/integration-tests/core-it/src/test/kotlin/org/evomaster/core/problem/rest/aiclassification/AIModelsCheckWFDEM.kt b/core-tests/integration-tests/core-it/src/test/kotlin/org/evomaster/core/problem/rest/aiclassification/AIModelsCheckWFDEM.kt index 4599ec513d..da5a9ac727 100644 --- a/core-tests/integration-tests/core-it/src/test/kotlin/org/evomaster/core/problem/rest/aiclassification/AIModelsCheckWFDEM.kt +++ b/core-tests/integration-tests/core-it/src/test/kotlin/org/evomaster/core/problem/rest/aiclassification/AIModelsCheckWFDEM.kt @@ -13,16 +13,17 @@ class AIModelsCheckWFDEM : RestTestBase() { } } - val modelName = "KDE" // Choose "GAUSSIAN", "GLM", "KDE", "KNN", "NN", etc. + val modelName = "GAUSSIAN" // Choose "GAUSSIAN", "GLM", "KDE", "KNN", "NN", etc. val encoderType = "RAW" // Choose "RAW" or "NORMAL" - val decisionMaking = "PROBABILITY" // Choose "PROBABILITY" or "THRESHOLD" + val decisionMaking = "THRESHOLD" // Choose "PROBABILITY" or "THRESHOLD" val warmUpRep = 10 val maxAttemptRepair = 100 // i.e., the classifier has 10 times the chances to pick an action with non-400 response val baseUrlOfSut = "http://localhost:8080" // val swaggerUrl = "http://localhost:8080/v2/api-docs" - val swaggerUrl = "http://localhost:8080/api/v3/openapi.json" -// val swaggerUrl ="../WFD_Dataset/openapi-swagger/youtube-mock.yaml" +// val swaggerUrl = "http://localhost:8080/api/v3/openapi.json" +// val swaggerUrl ="../dataset/openapi-swagger/youtube-mock.yaml" + val swaggerUrl ="../dataset/openapi-swagger/catwatch.json" fun runTest() { @@ -33,7 +34,7 @@ class AIModelsCheckWFDEM : RestTestBase() { // Add black-box Swagger parameters args.add("--blackBox") - args.add("true") + args.add("false") args.add("--ratePerMinute") args.add("50000") diff --git a/core-tests/integration-tests/core-it/src/test/kotlin/org/evomaster/core/problem/rest/aiclassification/AIMoldelsCheckWFD.kt b/core-tests/integration-tests/core-it/src/test/kotlin/org/evomaster/core/problem/rest/aiclassification/AIMoldelsCheckWFD.kt index 272012499b..b0feb64e0f 100644 --- a/core-tests/integration-tests/core-it/src/test/kotlin/org/evomaster/core/problem/rest/aiclassification/AIMoldelsCheckWFD.kt +++ b/core-tests/integration-tests/core-it/src/test/kotlin/org/evomaster/core/problem/rest/aiclassification/AIMoldelsCheckWFD.kt @@ -43,22 +43,27 @@ class AIModelsCheckWFD : IntegrationTestRestBase() { } } - val modelName = "KDE" // Choose "GAUSSIAN", "GLM", "KDE", "KNN", "NN", etc. + val modelName = "GAUSSIAN" // Choose "GAUSSIAN", "GLM", "KDE", "KNN", "NN", etc. val encoderType = "RAW" // Choose "RAW" or "NORMAL" val decisionMaking = "THRESHOLD" // Choose "PROBABILITY" or "THRESHOLD" val warmUpRep = 10 val maxAttemptRepair = 100 // i.e., the classifier has 10 times the chances to pick an action with non-400 response + val repairThreshold = 0.5 + val weaknessThreshold = 0.6 - val runIterations = 1000 + val runIterations = 10000 val saveReport = false val filePathReport = "AIModelsCheckWFDReport.txt" val baseUrlOfSut = "http://localhost:8080" // val swaggerUrl = "http://localhost:8080/v2/api-docs" // val swaggerUrl = "http://localhost:8080/api/v3/openapi.json" - val swaggerUrl ="../WFD_Dataset/openapi-swagger/youtube-mock.yaml" -// val swaggerUrl ="../WFD_Dataset/openapi-swagger/languagetool.json" -// val swaggerUrl = "../WFD_Dataset/openapi-swagger/rest-ncs.json" + +// val swaggerUrl ="../dataset/openapi-swagger/youtube-mock.yaml" + val swaggerUrl ="../dataset/openapi-swagger/catwatch.json" +// val swaggerUrl ="../dataset/openapi-swagger/blogapi.json" +// val swaggerUrl ="../dataset/openapi-swagger/languagetool.json" +// val swaggerUrl = "../dataset/openapi-swagger/rest-ncs.json" @Inject lateinit var randomness: Randomness @@ -91,6 +96,9 @@ class AIModelsCheckWFD : IntegrationTestRestBase() { config.aiClassifierRepairActivation = EMConfig.AIClassificationRepairActivation.valueOf(decisionMaking) config.aiResponseClassifierWarmup = warmUpRep config.maxRepairAttemptsInResponseClassification = maxAttemptRepair + config.classificationRepairThreshold = repairThreshold + config.aIResponseClassifierWeaknessThreshold = weaknessThreshold + config.blackBox = false } @Inject @@ -148,8 +156,13 @@ class AIModelsCheckWFD : IntegrationTestRestBase() { println("Input vector size: ${inputVector.size}") // Warm-up - val innerModel = aiGlobalClassifier.viewInnerModels() - println("innerModel is ${innerModel.javaClass.simpleName ?: "Unknown"}") + val innerModels = aiGlobalClassifier.viewInnerModels() + + val innerModel = innerModels.firstOrNull() + ?: throw IllegalStateException("No inner models found") + + println("innerModel is ${innerModel.javaClass.simpleName}") + val endpointModel = when(innerModel) { is Gaussian400Classifier -> innerModel.getModel(endPoint) is GLM400Classifier -> innerModel.getModel(endPoint) @@ -166,7 +179,11 @@ class AIModelsCheckWFD : IntegrationTestRestBase() { val metrics = aiGlobalClassifier.estimateMetrics(action.endpoint) //Execute the action if the classifier is still weak - if(!(metrics.accuracy > 0.5 && metrics.f1Score400 > 0.0 && metrics.mcc > 0.0)){ + val deltaW = config.aIResponseClassifierWeaknessThreshold + if(metrics.precision400 < deltaW + || metrics.sensitivity400 < deltaW + || metrics.specificity < deltaW + || metrics.npv < deltaW) { println("The classifier is weak for $endPoint") val result = ExtraTools.executeRestCallAction(action, "$baseUrlOfSut") @@ -244,11 +261,11 @@ class AIModelsCheckWFD : IntegrationTestRestBase() { } val overAllMetrics = aiGlobalClassifier.estimateOverallMetrics() - println("Overall Accuracy: ${overAllMetrics.accuracy}") - println("Overall Precision400: ${overAllMetrics.precision400}") - println("Overall Recall400: ${overAllMetrics.sensitivity400}") - println("Overall F1Score400: ${overAllMetrics.f1Score400}") - println("Overall MCC: ${overAllMetrics.mcc}") + println("Overall Accuracy: ${overAllMetrics.accuracy}") + println("Overall Precision400: ${overAllMetrics.precision400}") + println("Overall Sensitivity400: ${overAllMetrics.sensitivity400}") + println("Overall Specificity: ${overAllMetrics.specificity}") + println("Overall NPV: ${overAllMetrics.npv}") // Save the final result as a .txt file if (saveReport){ diff --git a/core-tests/integration-tests/core-it/src/test/kotlin/org/evomaster/core/problem/rest/aiclassification/ExtraTools.kt b/core-tests/integration-tests/core-it/src/test/kotlin/org/evomaster/core/problem/rest/aiclassification/ExtraTools.kt index b441cfa2f8..b327c47ec2 100644 --- a/core-tests/integration-tests/core-it/src/test/kotlin/org/evomaster/core/problem/rest/aiclassification/ExtraTools.kt +++ b/core-tests/integration-tests/core-it/src/test/kotlin/org/evomaster/core/problem/rest/aiclassification/ExtraTools.kt @@ -58,12 +58,12 @@ object ExtraTools { | Actual 400 | TP=${metrics.truePositive400.toString().padEnd(10)}| FN=${metrics.falseNegative400.toString().padEnd(11)}| | Actual¬400 | FP=${metrics.falsePositive400.toString().padEnd(10)}| TN=${metrics.trueNegative400.toString().padEnd(11)}| +-------------------------------------------+ - Window Total : ${metrics.windowTotal} - Accuracy : ${"%.4f".format(metrics.estimateMetrics().accuracy)} - Precision400 : ${"%.4f".format(metrics.estimateMetrics().precision400)} - Recall400 : ${"%.4f".format(metrics.estimateMetrics().sensitivity400)} - F1Score400 : ${"%.4f".format(metrics.estimateMetrics().f1Score400)} - MCC400 : ${"%.4f".format(metrics.estimateMetrics().mcc)} + Window Total : ${metrics.windowTotal} + Accuracy : ${"%.4f".format(metrics.estimateMetrics().accuracy)} + Precision400 : ${"%.4f".format(metrics.estimateMetrics().precision400)} + Sensitivity400 : ${"%.4f".format(metrics.estimateMetrics().sensitivity400)} + Specificity : ${"%.4f".format(metrics.estimateMetrics().specificity)} + NPV : ${"%.4f".format(metrics.estimateMetrics().npv)} """.trimIndent() ) } @@ -107,12 +107,12 @@ object ExtraTools { sb.appendLine("| Actual¬400 | FP=${it.falsePositive400.toString().padEnd(10)}| TN=${it.trueNegative400.toString().padEnd(11)}|") sb.appendLine("+-------------------------------------------+") sb.appendLine() - sb.appendLine("Window Total : ${it.windowTotal}") - sb.appendLine("Accuracy : ${"%.4f".format(it.estimateMetrics().accuracy)}") - sb.appendLine("Precision400 : ${"%.4f".format(it.estimateMetrics().precision400)}") - sb.appendLine("Recall400 : ${"%.4f".format(it.estimateMetrics().sensitivity400)}") - sb.appendLine("F1Score400 : ${"%.4f".format(it.estimateMetrics().f1Score400)}") - sb.appendLine("MCC400 : ${"%.4f".format(it.estimateMetrics().mcc)}") + sb.appendLine("Window Total : ${it.windowTotal}") + sb.appendLine("Accuracy : ${"%.4f".format(it.estimateMetrics().accuracy)}") + sb.appendLine("Precision400 : ${"%.4f".format(it.estimateMetrics().precision400)}") + sb.appendLine("Sensitivity400 : ${"%.4f".format(it.estimateMetrics().sensitivity400)}") + sb.appendLine("Specificity : ${"%.4f".format(it.estimateMetrics().specificity)}") + sb.appendLine("NPV : ${"%.4f".format(it.estimateMetrics().npv)}") sb.appendLine() sb.appendLine("=============================================") sb.appendLine() From 9a7abf0a14c6b68e74ed97c67d4827fb4047aef8 Mon Sep 17 00:00:00 2001 From: Mohsen Taheri Shalmani Date: Tue, 2 Jun 2026 14:52:09 +0200 Subject: [PATCH 2/9] Update the encoder --- .../aiclassification/AIMoldelsCheckWFD.kt | 19 +++++++- .../probabilistic/InputEncoderUtilWrapper.kt | 45 ++++++++++++++++++- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/core-tests/integration-tests/core-it/src/test/kotlin/org/evomaster/core/problem/rest/aiclassification/AIMoldelsCheckWFD.kt b/core-tests/integration-tests/core-it/src/test/kotlin/org/evomaster/core/problem/rest/aiclassification/AIMoldelsCheckWFD.kt index b0feb64e0f..fdb2c72850 100644 --- a/core-tests/integration-tests/core-it/src/test/kotlin/org/evomaster/core/problem/rest/aiclassification/AIMoldelsCheckWFD.kt +++ b/core-tests/integration-tests/core-it/src/test/kotlin/org/evomaster/core/problem/rest/aiclassification/AIMoldelsCheckWFD.kt @@ -51,7 +51,7 @@ class AIModelsCheckWFD : IntegrationTestRestBase() { val repairThreshold = 0.5 val weaknessThreshold = 0.6 - val runIterations = 10000 + val runIterations = 1000 val saveReport = false val filePathReport = "AIModelsCheckWFDReport.txt" @@ -60,10 +60,11 @@ class AIModelsCheckWFD : IntegrationTestRestBase() { // val swaggerUrl = "http://localhost:8080/api/v3/openapi.json" // val swaggerUrl ="../dataset/openapi-swagger/youtube-mock.yaml" - val swaggerUrl ="../dataset/openapi-swagger/catwatch.json" +// val swaggerUrl ="../dataset/openapi-swagger/catwatch.json" // val swaggerUrl ="../dataset/openapi-swagger/blogapi.json" // val swaggerUrl ="../dataset/openapi-swagger/languagetool.json" // val swaggerUrl = "../dataset/openapi-swagger/rest-ncs.json" + val swaggerUrl = "../dataset/openapi-swagger/cwa-verification.json" @Inject lateinit var randomness: Randomness @@ -146,6 +147,20 @@ class AIModelsCheckWFD : IntegrationTestRestBase() { .joinToString(", ") { ng -> "${ng.gene.name}:${ng.gene::class.simpleName ?: "Unknown"}" }) + println("Genes full names are: " + + encoder.endPointToGeneList() + .joinToString(", ") { ng -> + "${ng.paramPath}:${ng.gene::class.simpleName ?: "Unknown"}" }) + + println( + "Gene parents and encoded values are: " + + encoder.paramPathToValue() + .entries + .joinToString(", ") { (name, value) -> + "$name:$value" + } + ) + if (encoder.areAllGenesUnSupported()) { println("Skipping classification for $endPoint as all its genes are unsupported.") continue diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/InputEncoderUtilWrapper.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/InputEncoderUtilWrapper.kt index 72693bd197..1ba81dd831 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/InputEncoderUtilWrapper.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/InputEncoderUtilWrapper.kt @@ -55,6 +55,7 @@ class InputEncoderUtilWrapper( data class ParamAndGene( val paramName: String, + val paramPath: String, val gene: Gene ) @@ -67,6 +68,28 @@ class InputEncoderUtilWrapper( fun areAllGenesUnSupported(): Boolean = endPointToGeneList().all { !isSupported(it.gene.getLeafGene()) } + /** + * Builds a string representing the gene name and all its parents. + */ + private fun genePath(g: Gene): String { + + val names = mutableListOf() + + var current: Gene? = g + + while (current != null) { + names.add(current.name) + current = current.parent as? Gene + } + + return names.reversed().joinToString("/") + } + + /** + * Recursively expands the input gene into a list of its leaf genes. + * If the input gene is of type [ObjectGene], it will traverse its fixed fields + * and additional fields to expand and collect all nested leaf genes. + */ private fun expandGene(g: Gene): List { val gene = g.getLeafGene() @@ -83,6 +106,20 @@ class InputEncoderUtilWrapper( return listOf(gene) } + /** Associate each paramPath to the corresponding encoded numerical value of its parameter.*/ + fun paramPathToValue(): Map { + + + val allParamParents = endPointToGeneList().map { it.paramPath } + val encodedValues = encode() + + return allParamParents.zip(encodedValues).toMap() + } + + /** + * Converts the endpoint parameters into a list of `ParamAndGene` objects, + * where each entry represents a parameter, its associated gene, and all the gene's parents. + */ fun endPointToGeneList(): List { val paramAndGenes = mutableListOf() @@ -96,7 +133,13 @@ class InputEncoderUtilWrapper( val g = p.primaryGene() val expanded = expandGene(g) expanded.forEach { subGene -> - paramAndGenes.add(ParamAndGene(subGene.name, subGene)) + paramAndGenes.add( + ParamAndGene( + paramName = subGene.name, + paramPath = genePath(subGene), + gene = subGene + ) + ) } } From 6610f58fe8f117aa8037bcb19cf3838b24cf4faa Mon Sep 17 00:00:00 2001 From: Mohsen Taheri Shalmani Date: Thu, 4 Jun 2026 12:23:23 +0200 Subject: [PATCH 3/9] Adding encodeUsingModelKeys function --- .../aiclassification/AIMoldelsCheckWFD.kt | 15 ++-- .../AbstractProbabilistic400Classifier.kt | 16 +++-- .../AbstractProbabilistic400EndpointModel.kt | 69 ++++++++++++++++--- .../probabilistic/InputEncoderUtilWrapper.kt | 18 +++-- .../gaussian/Gaussian400Classifier.kt | 7 +- .../gaussian/Gaussian400EndpointModel.kt | 22 +++--- .../probabilistic/glm/GLM400Classifier.kt | 2 + .../probabilistic/glm/GLM400EndpointModel.kt | 18 +++-- .../probabilistic/kde/KDE400Classifier.kt | 2 + .../probabilistic/kde/KDE400EndpointModel.kt | 18 ++--- .../probabilistic/knn/KNN400Classifier.kt | 3 + .../probabilistic/knn/KNN400EndpointModel.kt | 15 ++-- .../probabilistic/nn/NN400Classifier.kt | 2 + .../probabilistic/nn/NN400EndpointModel.kt | 56 ++++----------- 14 files changed, 153 insertions(+), 110 deletions(-) diff --git a/core-tests/integration-tests/core-it/src/test/kotlin/org/evomaster/core/problem/rest/aiclassification/AIMoldelsCheckWFD.kt b/core-tests/integration-tests/core-it/src/test/kotlin/org/evomaster/core/problem/rest/aiclassification/AIMoldelsCheckWFD.kt index fdb2c72850..4f72e4cd87 100644 --- a/core-tests/integration-tests/core-it/src/test/kotlin/org/evomaster/core/problem/rest/aiclassification/AIMoldelsCheckWFD.kt +++ b/core-tests/integration-tests/core-it/src/test/kotlin/org/evomaster/core/problem/rest/aiclassification/AIMoldelsCheckWFD.kt @@ -43,7 +43,7 @@ class AIModelsCheckWFD : IntegrationTestRestBase() { } } - val modelName = "GAUSSIAN" // Choose "GAUSSIAN", "GLM", "KDE", "KNN", "NN", etc. + val modelName = "NN" // Choose "GAUSSIAN", "GLM", "KDE", "KNN", "NN", etc. val encoderType = "RAW" // Choose "RAW" or "NORMAL" val decisionMaking = "THRESHOLD" // Choose "PROBABILITY" or "THRESHOLD" val warmUpRep = 10 @@ -60,11 +60,11 @@ class AIModelsCheckWFD : IntegrationTestRestBase() { // val swaggerUrl = "http://localhost:8080/api/v3/openapi.json" // val swaggerUrl ="../dataset/openapi-swagger/youtube-mock.yaml" -// val swaggerUrl ="../dataset/openapi-swagger/catwatch.json" + val swaggerUrl ="../dataset/openapi-swagger/catwatch.json" // val swaggerUrl ="../dataset/openapi-swagger/blogapi.json" // val swaggerUrl ="../dataset/openapi-swagger/languagetool.json" // val swaggerUrl = "../dataset/openapi-swagger/rest-ncs.json" - val swaggerUrl = "../dataset/openapi-swagger/cwa-verification.json" +// val swaggerUrl = "../dataset/openapi-swagger/cwa-verification.json" @Inject lateinit var randomness: Randomness @@ -147,14 +147,9 @@ class AIModelsCheckWFD : IntegrationTestRestBase() { .joinToString(", ") { ng -> "${ng.gene.name}:${ng.gene::class.simpleName ?: "Unknown"}" }) - println("Genes full names are: " + - encoder.endPointToGeneList() - .joinToString(", ") { ng -> - "${ng.paramPath}:${ng.gene::class.simpleName ?: "Unknown"}" }) - println( - "Gene parents and encoded values are: " + - encoder.paramPathToValue() + "Parameter paths and encoded values are: " + + encoder.getAllParamsPathsAndEncodedValues() .entries .joinToString(", ") { (name, value) -> "$name:$value" diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/AbstractProbabilistic400Classifier.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/AbstractProbabilistic400Classifier.kt index 8fac703fff..0a31fa2dfd 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/AbstractProbabilistic400Classifier.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/AbstractProbabilistic400Classifier.kt @@ -38,6 +38,7 @@ abstract class AbstractProbabilistic400Classifier( return } + // create an endPoint model if it does not exist val m = models.getOrPut(endpoint) { val encoder = InputEncoderUtilWrapper(input, encoderType = encoderType) @@ -46,20 +47,26 @@ abstract class AbstractProbabilistic400Classifier( return@getOrPut null } - val listGenes = encoder.endPointToGeneList().map { it.gene.getLeafGene() } + val initialParamPaths = encoder.getAllParamsPathsAndEncodedValues().keys.toList() + createEndpointModel( - endpoint, warmup, - listGenes.size, + endpoint, + warmup, + initialParamPaths, + initialParamPaths.size, encoderType, metricType, - randomness) + randomness + ) } + if (m == null) { unsupportedEndpoints.add(endpoint) return } + // update the endpoint model and initialize if needed m.updateModel(input, output) } @@ -99,6 +106,7 @@ abstract class AbstractProbabilistic400Classifier( protected abstract fun createEndpointModel( endpoint: Endpoint, warmup: Int, + modelKeys: List, dimension: Int, encoderType: EMConfig.EncoderType, metricType: EMConfig.AIClassificationMetrics, diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/AbstractProbabilistic400EndpointModel.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/AbstractProbabilistic400EndpointModel.kt index 8d6fc4375e..9c98745b89 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/AbstractProbabilistic400EndpointModel.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/AbstractProbabilistic400EndpointModel.kt @@ -14,12 +14,13 @@ import org.evomaster.core.search.service.Randomness * Base class for all probabilistic classifiers working at an endpoint (400 vs. not 400). * * Provides: - * - Common properties (endpoint, warmup, encoderType, randomness, dimension, initialized flag) + * - Common properties (endpoint, warmup, keys, dimension, encoderType, randomness, initialized flag) * - Shared methods for initialization checks and accuracy estimation */ abstract class AbstractProbabilistic400EndpointModel( val endpoint: Endpoint, var warmup: Int, + var modelKeys: List? = null, var dimension: Int? = null, val encoderType: EMConfig.EncoderType, val metricType: EMConfig.AIClassificationMetrics, @@ -35,24 +36,39 @@ abstract class AbstractProbabilistic400EndpointModel( /** Create a metric tracker.*/ val modelMetrics: ModelMetrics = createModelMetrics(metricType) - /** Ensure endpoint matches this model */ + /** Ensure the endpoint matches this model */ protected fun verifyEndpoint(inputEndpoint: Endpoint) { if (inputEndpoint != endpoint) { throw IllegalArgumentException("Input endpoint $inputEndpoint does not match model endpoint $endpoint") } } - /** Initialize dimension once, validate consistency */ - open fun initializeIfNeeded(inputVector: List) { + + /** + * Initialize dimension and keys once initialized. + * The dimension is the number of parameters in the input vector. + * The modelKeys are unique identifiers of the parameters defined based on + * the unique path of each parameter (including all its parents). + */ + open fun initializeIfNeeded(input: RestCallAction) { + if (dimension == null) { - require(inputVector.isNotEmpty()) { "Input vector cannot be empty" } + + val encoder = InputEncoderUtilWrapper(input, encoderType = encoderType) + val allParamsPathsAndEncodedValues = encoder.getAllParamsPathsAndEncodedValues() + + val inputVector = allParamsPathsAndEncodedValues.values.toList() + val paramPaths = allParamsPathsAndEncodedValues.keys.toList() + + require(inputVector.isNotEmpty()) { "Input vector is empty" } + require(paramPaths.isNotEmpty()) { "Parameter paths are empty" } require(warmup > 0) { "Warmup must be positive" } - dimension = inputVector.size - } else { - require(inputVector.size == dimension) { - "Expected input vector of size $dimension but got ${inputVector.size}" - } + + dimension = paramPaths.size + modelKeys = paramPaths + } + initialized = true } @@ -73,6 +89,39 @@ abstract class AbstractProbabilistic400EndpointModel( } + /** + * Encodes the input parameters using the model's defined keys and returns the corresponding encoded values. + * This method ensures that all expected keys (modelKeys) are present in the encoded input. + * + * @param input The input action containing required data for parameter encoding. + * @return A list of encoded values corresponding to the model's keys. + */ + protected fun encodeUsingModelKeys( + input: RestCallAction + ): List { + + val encoder = InputEncoderUtilWrapper( + input, + encoderType = encoderType + ) + + val keysAndValues = + encoder.getAllParamsPathsAndEncodedValues() + + val initializedKeys = requireNotNull(modelKeys) { + "Model keys have not been initialized" + } + + println("InitializedKeys Keys: ${initializedKeys?.joinToString(", ")}") + + return initializedKeys.map { key -> + keysAndValues[key] + ?: throw IllegalArgumentException( + "Missing expected key: $key" + ) + } + } + /** Default metrics estimates */ override fun estimateMetrics(endpoint: Endpoint): ModelEvaluation { verifyEndpoint(endpoint) diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/InputEncoderUtilWrapper.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/InputEncoderUtilWrapper.kt index 1ba81dd831..76dc14d903 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/InputEncoderUtilWrapper.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/InputEncoderUtilWrapper.kt @@ -53,6 +53,13 @@ class InputEncoderUtilWrapper( DateTimeGene::class ) + /** + * Represents a mapping between a parameter (with its name and path) + * and its associated gene object. + * @property paramName The name of the parameter. + * @property paramPath A unique identifier for the parameter, representing its hierarchical path (including all its parents). + * @property gene The gene corresponding to the parameter. + */ data class ParamAndGene( val paramName: String, val paramPath: String, @@ -106,14 +113,17 @@ class InputEncoderUtilWrapper( return listOf(gene) } - /** Associate each paramPath to the corresponding encoded numerical value of its parameter.*/ - fun paramPathToValue(): Map { + /** + * Associate all parameters' paths with their corresponding encoded numerical values of their parameter. + * Note that each endpoint may have multiple parameters, but each parameter has a unique path including all its parents. + */ + fun getAllParamsPathsAndEncodedValues(): Map { - val allParamParents = endPointToGeneList().map { it.paramPath } + val paramPaths = endPointToGeneList().map { it.paramPath } val encodedValues = encode() - return allParamParents.zip(encodedValues).toMap() + return paramPaths.zip(encodedValues).toMap() } /** diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/gaussian/Gaussian400Classifier.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/gaussian/Gaussian400Classifier.kt index 7bc78345a9..b3161fb7c1 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/gaussian/Gaussian400Classifier.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/gaussian/Gaussian400Classifier.kt @@ -17,6 +17,7 @@ class Gaussian400Classifier( override fun createEndpointModel( endpoint: Endpoint, warmup: Int, + modelKeys: List, dimension: Int, encoderType: EMConfig.EncoderType, metricType: EMConfig.AIClassificationMetrics, @@ -25,9 +26,11 @@ class Gaussian400Classifier( return Gaussian400EndpointModel( endpoint, warmup, + modelKeys, dimension, encoderType, metricType, - randomness) + randomness + ) } -} +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/gaussian/Gaussian400EndpointModel.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/gaussian/Gaussian400EndpointModel.kt index 9fb7562e61..a345903551 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/gaussian/Gaussian400EndpointModel.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/gaussian/Gaussian400EndpointModel.kt @@ -2,12 +2,12 @@ package org.evomaster.core.problem.rest.classifier.probabilistic.gaussian import org.evomaster.core.EMConfig import org.evomaster.core.problem.rest.classifier.AIResponseClassification -import org.evomaster.core.problem.rest.classifier.probabilistic.InputEncoderUtilWrapper import org.evomaster.core.problem.rest.classifier.probabilistic.AbstractProbabilistic400EndpointModel import org.evomaster.core.problem.rest.data.Endpoint import org.evomaster.core.problem.rest.data.RestCallAction import org.evomaster.core.problem.rest.data.RestCallResult import org.evomaster.core.search.service.Randomness +import kotlin.String import kotlin.math.PI import kotlin.math.exp import kotlin.math.ln @@ -35,12 +35,13 @@ import kotlin.math.ln class Gaussian400EndpointModel ( endpoint: Endpoint, warmup: Int, + modelKeys: List? = null, dimension: Int? = null, encoderType: EMConfig.EncoderType, metricType: EMConfig.AIClassificationMetrics, randomness: Randomness ): AbstractProbabilistic400EndpointModel( - endpoint, warmup, dimension, encoderType, metricType, randomness) { + endpoint, warmup, modelKeys, dimension, encoderType, metricType, randomness) { var density400: Density? = null private set @@ -51,8 +52,9 @@ class Gaussian400EndpointModel ( /** Must be called once to initialize the model properties * Initialize dimension and weights if needed */ - override fun initializeIfNeeded(inputVector: List) { - super.initializeIfNeeded(inputVector) + override fun initializeIfNeeded(input: RestCallAction) { + super.initializeIfNeeded(input) + if(density400 == null) { density400 = Density(dimension!!) } @@ -69,10 +71,7 @@ class Gaussian400EndpointModel ( return AIResponseClassification() } - val encoder = InputEncoderUtilWrapper(input, encoderType = encoderType) - val inputVector = encoder.encode() - - initializeIfNeeded(inputVector) + initializeIfNeeded(input) if (modelMetrics.totalSentRequests < warmup) { // Return equal probabilities during warmup @@ -84,6 +83,8 @@ class Gaussian400EndpointModel ( ) } + val inputVector = encodeUsingModelKeys(input) + if (inputVector.size != dimension) { throw IllegalArgumentException("Expected input vector of size ${this.dimension} but got ${inputVector.size}") } @@ -128,10 +129,9 @@ class Gaussian400EndpointModel ( return } - val encoder = InputEncoderUtilWrapper(input, encoderType = encoderType) - val inputVector = encoder.encode() + initializeIfNeeded(input) - initializeIfNeeded(inputVector) + val inputVector = encodeUsingModelKeys(input) if (inputVector.size != this.dimension) { throw IllegalArgumentException("Expected input vector of size ${this.dimension} but got ${inputVector.size}") diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/glm/GLM400Classifier.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/glm/GLM400Classifier.kt index 1e25e9e50f..8249cc2822 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/glm/GLM400Classifier.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/glm/GLM400Classifier.kt @@ -18,6 +18,7 @@ class GLM400Classifier( override fun createEndpointModel( endpoint: Endpoint, warmup: Int, + modelKeys: List, dimension: Int, encoderType: EMConfig.EncoderType, metricType: EMConfig.AIClassificationMetrics, @@ -26,6 +27,7 @@ class GLM400Classifier( return GLM400EndpointModel( endpoint, warmup, + modelKeys, dimension, encoderType, metricType, diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/glm/GLM400EndpointModel.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/glm/GLM400EndpointModel.kt index b1f0f3a4e9..c6e6357341 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/glm/GLM400EndpointModel.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/glm/GLM400EndpointModel.kt @@ -2,7 +2,6 @@ package org.evomaster.core.problem.rest.classifier.probabilistic.glm import org.evomaster.core.EMConfig import org.evomaster.core.problem.rest.classifier.AIResponseClassification -import org.evomaster.core.problem.rest.classifier.probabilistic.InputEncoderUtilWrapper import org.evomaster.core.problem.rest.classifier.probabilistic.AbstractProbabilistic400EndpointModel import org.evomaster.core.problem.rest.data.Endpoint import org.evomaster.core.problem.rest.data.RestCallAction @@ -20,20 +19,21 @@ import kotlin.math.exp class GLM400EndpointModel( endpoint: Endpoint, warmup: Int, + modelKeys: List? = null, dimension: Int? = null, encoderType: EMConfig.EncoderType, metricType: EMConfig.AIClassificationMetrics, private val learningRate: Double = 0.01, randomness: Randomness ) : AbstractProbabilistic400EndpointModel( - endpoint, warmup, dimension, encoderType, metricType, randomness) { + endpoint, warmup, modelKeys, dimension, encoderType, metricType, randomness){ private var weights: MutableList? = null private var bias: Double = 0.0 /** Initialize dimension and weights if needed */ - override fun initializeIfNeeded(inputVector: List) { - super.initializeIfNeeded(inputVector) + override fun initializeIfNeeded(input: RestCallAction) { + super.initializeIfNeeded(input) if (weights == null) { weights = MutableList(dimension!!) { 0.0 } } @@ -49,10 +49,9 @@ class GLM400EndpointModel( return AIResponseClassification() } - val encoder = InputEncoderUtilWrapper(input, encoderType = encoderType) - val inputVector = encoder.encode() + initializeIfNeeded(input) - initializeIfNeeded(inputVector) + val inputVector = encodeUsingModelKeys(input) if (modelMetrics.totalSentRequests < warmup) { // Return equal probabilities during warmup @@ -96,10 +95,9 @@ class GLM400EndpointModel( return } - val encoder = InputEncoderUtilWrapper(input, encoderType = encoderType) - val inputVector = encoder.encode() + initializeIfNeeded(input) - initializeIfNeeded(inputVector) + val inputVector = encodeUsingModelKeys(input) if (inputVector.size != this.dimension) { throw IllegalArgumentException("Expected input vector of size ${this.dimension} but got ${inputVector.size}") diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/kde/KDE400Classifier.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/kde/KDE400Classifier.kt index eb147abe6e..f67e6aa9e5 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/kde/KDE400Classifier.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/kde/KDE400Classifier.kt @@ -17,6 +17,7 @@ class KDE400Classifier( override fun createEndpointModel( endpoint: Endpoint, warmup: Int, + modelKeys: List, dimension: Int, encoderType: EMConfig.EncoderType, metricType: EMConfig.AIClassificationMetrics, @@ -25,6 +26,7 @@ class KDE400Classifier( return KDE400EndpointModel( endpoint, warmup, + modelKeys, dimension, encoderType, metricType, diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/kde/KDE400EndpointModel.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/kde/KDE400EndpointModel.kt index a39acc7cd3..1fc9148b8c 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/kde/KDE400EndpointModel.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/kde/KDE400EndpointModel.kt @@ -25,21 +25,22 @@ import kotlin.math.sqrt class KDE400EndpointModel ( endpoint: Endpoint, warmup: Int, + modelKeys: List? = null, dimension: Int? = null, encoderType: EMConfig.EncoderType, metricType: EMConfig.AIClassificationMetrics, private val maxStoredSamples: Int = 1000, // Values more than 1000 make the classification very time-consuming randomness: Randomness ): AbstractProbabilistic400EndpointModel( - endpoint, warmup, dimension, encoderType, metricType, randomness) { + endpoint, warmup, modelKeys, dimension, encoderType, metricType, randomness) { private var density400: KDE? = null private var densityNot400: KDE? = null /** Must be called once to initialize the model properties */ - override fun initializeIfNeeded(inputVector: List) { - super.initializeIfNeeded(inputVector) + override fun initializeIfNeeded(input: RestCallAction) { + super.initializeIfNeeded(input) if(density400 == null) { density400 = KDE(dimension!!, maxStoredSamples, randomness) } @@ -57,10 +58,9 @@ class KDE400EndpointModel ( return AIResponseClassification() } - val encoder = InputEncoderUtilWrapper(input, encoderType = encoderType) - val inputVector = encoder.encode() + initializeIfNeeded(input) - initializeIfNeeded(inputVector) + val inputVector = encodeUsingModelKeys(input) if (modelMetrics.totalSentRequests < warmup) { // Return equal probabilities during warmup @@ -116,10 +116,10 @@ class KDE400EndpointModel ( return } - val encoder = InputEncoderUtilWrapper(input, encoderType = encoderType) - val inputVector = encoder.encode() - initializeIfNeeded(inputVector) + initializeIfNeeded(input) + + val inputVector = encodeUsingModelKeys(input) if (inputVector.size != this.dimension) { throw IllegalArgumentException("Expected input vector of size ${this.dimension} but got ${inputVector.size}") diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/knn/KNN400Classifier.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/knn/KNN400Classifier.kt index 3e2d9ae9d6..31ac730683 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/knn/KNN400Classifier.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/knn/KNN400Classifier.kt @@ -4,6 +4,7 @@ import org.evomaster.core.EMConfig import org.evomaster.core.problem.rest.data.Endpoint import org.evomaster.core.search.service.Randomness import org.evomaster.core.problem.rest.classifier.probabilistic.AbstractProbabilistic400Classifier +import kotlin.String class KNN400Classifier( @@ -19,6 +20,7 @@ class KNN400Classifier( override fun createEndpointModel( endpoint: Endpoint, warmup: Int, + modelKeys: List, dimension: Int, encoderType: EMConfig.EncoderType, metricType: EMConfig.AIClassificationMetrics, @@ -27,6 +29,7 @@ class KNN400Classifier( return KNN400EndpointModel( endpoint, warmup, + modelKeys, dimension, encoderType, metricType, diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/knn/KNN400EndpointModel.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/knn/KNN400EndpointModel.kt index 9e584f00ae..5457c18bd9 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/knn/KNN400EndpointModel.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/knn/KNN400EndpointModel.kt @@ -20,6 +20,7 @@ import kotlin.math.sqrt class KNN400EndpointModel ( endpoint: Endpoint, warmup: Int, + modelKeys: List? = null, dimension: Int? = null, encoderType: EMConfig.EncoderType, metricType: EMConfig.AIClassificationMetrics, @@ -27,13 +28,13 @@ class KNN400EndpointModel ( private val maxStoredSamples: Int = 1000, // Values more than 1000 make the classification very time-consuming randomness: Randomness ): AbstractProbabilistic400EndpointModel( - endpoint, warmup, dimension, encoderType, metricType, randomness) { + endpoint, warmup, modelKeys, dimension, encoderType, metricType, randomness) { /** * Stores the training samples for this endpoint model. * Each element is a pair of: * - List: the encoded feature vector - * - Int : the corresponding status code (i.e., HTTP response) + * - Int: the corresponding status code (i.e., HTTP response) */ val samples = mutableListOf, Int>>() @@ -53,10 +54,9 @@ class KNN400EndpointModel ( return AIResponseClassification() } - val encoder = InputEncoderUtilWrapper(input, encoderType = encoderType) - val inputVector = encoder.encode() + initializeIfNeeded(input) - initializeIfNeeded(inputVector) + val inputVector = encodeUsingModelKeys(input) if (modelMetrics.totalSentRequests < warmup) { // Return equal probabilities during warmup @@ -96,10 +96,9 @@ class KNN400EndpointModel ( return } - val encoder = InputEncoderUtilWrapper(input, encoderType = encoderType) - val inputVector = encoder.encode() + initializeIfNeeded(input) - initializeIfNeeded(inputVector) + val inputVector = encodeUsingModelKeys(input) if (inputVector.size != this.dimension) { throw IllegalArgumentException("Expected input vector of size ${this.dimension} but got ${inputVector.size}") diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/nn/NN400Classifier.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/nn/NN400Classifier.kt index a893bf30e1..5e69b1c9c2 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/nn/NN400Classifier.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/nn/NN400Classifier.kt @@ -18,6 +18,7 @@ class NN400Classifier( override fun createEndpointModel( endpoint: Endpoint, warmup: Int, + modelKeys: List, dimension: Int, encoderType: EMConfig.EncoderType, metricType: EMConfig.AIClassificationMetrics, @@ -26,6 +27,7 @@ class NN400Classifier( return NN400EndpointModel( endpoint, warmup, + modelKeys, dimension, encoderType, metricType, diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/nn/NN400EndpointModel.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/nn/NN400EndpointModel.kt index d87f5915af..dd238e4625 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/nn/NN400EndpointModel.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/nn/NN400EndpointModel.kt @@ -20,13 +20,14 @@ import kotlin.math.exp class NN400EndpointModel( endpoint: Endpoint, warmup: Int, + modelKeys: List? = null, dimension: Int? = null, encoderType: EMConfig.EncoderType, metricType: EMConfig.AIClassificationMetrics, private val learningRate: Double = 0.01, randomness: Randomness ) : AbstractProbabilistic400EndpointModel( - endpoint, warmup, dimension, encoderType, metricType, randomness) { + endpoint, warmup, modelKeys, dimension, encoderType, metricType, randomness) { // Initialize weights with default values to prevent null @@ -37,24 +38,14 @@ class NN400EndpointModel( /** Must be called once to initialize the model properties */ - override fun initializeIfNeeded(inputVector: List) { - if (!initialized || dimension == null) { - require(inputVector.isNotEmpty()) { "Input vector cannot be empty" } - require(warmup > 0) { "Warmup must be positive" } - dimension = inputVector.size - - // Initialize with proper dimensions - weightsInputHidden = Array(dimension!!) { - DoubleArray(hiddenSize) { randomness.nextDouble(-0.1, 0.1) } - } - weightsHiddenOutput = Array(hiddenSize) { - DoubleArray(outputSize) { randomness.nextDouble(-0.1, 0.1) } - } - initialized = true - } else { - require(inputVector.size == dimension) { - "Expected input vector of size $dimension but got ${inputVector.size}" - } + override fun initializeIfNeeded(input: RestCallAction) { + super.initializeIfNeeded(input) + // Initialize with proper dimensions + weightsInputHidden = Array(dimension!!) { + DoubleArray(hiddenSize) { randomness.nextDouble(-0.1, 0.1) } + } + weightsHiddenOutput = Array(hiddenSize) { + DoubleArray(outputSize) { randomness.nextDouble(-0.1, 0.1) } } } @@ -69,19 +60,9 @@ class NN400EndpointModel( return AIResponseClassification() } - val encoder = InputEncoderUtilWrapper(input, encoderType = encoderType) - val inputVector = encoder.encode() + initializeIfNeeded(input) - if (encoder.areAllGenesUnSupported()) { - // skip classification/training if unsupported - return AIResponseClassification( - probabilities = mapOf( - NOT_400 to 0.5, - 400 to 0.5) - ) - } - - initializeIfNeeded(inputVector) + val inputVector = encodeUsingModelKeys(input) if (modelMetrics.totalSentRequests < warmup) { // Return equal probabilities during warmup @@ -113,18 +94,9 @@ class NN400EndpointModel( return } - val encoder = InputEncoderUtilWrapper(input, encoderType = encoderType) - val inputVector = encoder.encode() - - if (encoder.areAllGenesUnSupported() || inputVector.isEmpty()) { - // Skip training if unsupported or empty - val predictedStatusCode = if(randomness.nextBoolean()) 400 else NOT_400 - modelMetrics.updatePerformance(predictedStatusCode,output.getStatusCode()?:-1) - modelMetrics.updatePerformance(predictedStatusCode, output.getStatusCode()?:-1) - return - } + initializeIfNeeded(input) - initializeIfNeeded(inputVector) + val inputVector = encodeUsingModelKeys(input) if (inputVector.size != this.dimension) { throw IllegalArgumentException("Expected input vector of size ${this.dimension} but got ${inputVector.size}") From 759470882f059e3c320015c922b5266d44364607 Mon Sep 17 00:00:00 2001 From: Mohsen Taheri Shalmani Date: Thu, 4 Jun 2026 12:25:02 +0200 Subject: [PATCH 4/9] Update AbstractProbabilistic400EndpointModel.kt --- .../probabilistic/AbstractProbabilistic400EndpointModel.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/AbstractProbabilistic400EndpointModel.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/AbstractProbabilistic400EndpointModel.kt index 9c98745b89..63117827ff 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/AbstractProbabilistic400EndpointModel.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/AbstractProbabilistic400EndpointModel.kt @@ -112,8 +112,6 @@ abstract class AbstractProbabilistic400EndpointModel( "Model keys have not been initialized" } - println("InitializedKeys Keys: ${initializedKeys?.joinToString(", ")}") - return initializedKeys.map { key -> keysAndValues[key] ?: throw IllegalArgumentException( From b38768217f1c1a10e6cb2a3323c26b7b8b7b5155 Mon Sep 17 00:00:00 2001 From: Mohsen Taheri Shalmani Date: Sat, 6 Jun 2026 11:54:58 +0200 Subject: [PATCH 5/9] Debug and adding error log --- .../aiclassification/AIMoldelsCheckWFD.kt | 3 ++- .../AbstractProbabilistic400EndpointModel.kt | 26 ++++++++++++++++--- .../probabilistic/InputEncoderUtilWrapper.kt | 7 ++++- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/core-tests/integration-tests/core-it/src/test/kotlin/org/evomaster/core/problem/rest/aiclassification/AIMoldelsCheckWFD.kt b/core-tests/integration-tests/core-it/src/test/kotlin/org/evomaster/core/problem/rest/aiclassification/AIMoldelsCheckWFD.kt index 4f72e4cd87..83adcbc4f6 100644 --- a/core-tests/integration-tests/core-it/src/test/kotlin/org/evomaster/core/problem/rest/aiclassification/AIMoldelsCheckWFD.kt +++ b/core-tests/integration-tests/core-it/src/test/kotlin/org/evomaster/core/problem/rest/aiclassification/AIMoldelsCheckWFD.kt @@ -43,7 +43,7 @@ class AIModelsCheckWFD : IntegrationTestRestBase() { } } - val modelName = "NN" // Choose "GAUSSIAN", "GLM", "KDE", "KNN", "NN", etc. + val modelName = "GAUSSIAN" // Choose "GAUSSIAN", "GLM", "KDE", "KNN", "NN", etc. val encoderType = "RAW" // Choose "RAW" or "NORMAL" val decisionMaking = "THRESHOLD" // Choose "PROBABILITY" or "THRESHOLD" val warmUpRep = 10 @@ -82,6 +82,7 @@ class AIModelsCheckWFD : IntegrationTestRestBase() { injector = init(args) } + fun initializeTest() { recreateInjectorForBlackBox(listOf("--aiModelForResponseClassification", "$modelName")) } diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/AbstractProbabilistic400EndpointModel.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/AbstractProbabilistic400EndpointModel.kt index 63117827ff..06193f26d6 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/AbstractProbabilistic400EndpointModel.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/AbstractProbabilistic400EndpointModel.kt @@ -9,6 +9,7 @@ import org.evomaster.core.problem.rest.data.Endpoint import org.evomaster.core.problem.rest.data.RestCallAction import org.evomaster.core.problem.rest.data.RestCallResult import org.evomaster.core.search.service.Randomness +import org.slf4j.LoggerFactory /** * Base class for all probabilistic classifiers working at an endpoint (400 vs. not 400). @@ -30,6 +31,9 @@ abstract class AbstractProbabilistic400EndpointModel( protected var initialized: Boolean = false companion object { + + private val log = LoggerFactory.getLogger(AbstractProbabilistic400EndpointModel::class.java) + const val NOT_400 = 200 } @@ -113,10 +117,26 @@ abstract class AbstractProbabilistic400EndpointModel( } return initializedKeys.map { key -> - keysAndValues[key] - ?: throw IllegalArgumentException( - "Missing expected key: $key" + + val value = keysAndValues[key] + + if (value == null) { + + log.error("Endpoint: {}", endpoint) + log.error("Missing expected key: {}", key) + log.error("Model keys: {}", initializedKeys) + log.error("Current keys: {}", keysAndValues.keys) + + val missing = initializedKeys.filter { it !in keysAndValues.keys } + log.error("Missing keys: {}", missing) + + throw IllegalArgumentException( + "The encoded value cannot be null as the encoder set nulls to the sentinel (e.g., 10^6)." + + " So there is a missing key as: $key" ) + } + + value } } diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/InputEncoderUtilWrapper.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/InputEncoderUtilWrapper.kt index 76dc14d903..78a23c7a90 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/InputEncoderUtilWrapper.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/InputEncoderUtilWrapper.kt @@ -77,6 +77,7 @@ class InputEncoderUtilWrapper( /** * Builds a string representing the gene name and all its parents. + * This string is used as a unique identifier for the gene in the AI models. */ private fun genePath(g: Gene): String { @@ -89,7 +90,11 @@ class InputEncoderUtilWrapper( current = current.parent as? Gene } - return names.reversed().joinToString("/") + return names + .reversed() + .dropLast(1) //ignore the last name, which is the repetition of gene itself as its own parent + .joinToString("/") + } /** From 3029adb7f1ed082252adbedfe91e368a2061fadd Mon Sep 17 00:00:00 2001 From: Mohsen Taheri Shalmani Date: Sat, 6 Jun 2026 19:42:51 +0200 Subject: [PATCH 6/9] Update encoder --- .../AbstractProbabilistic400EndpointModel.kt | 36 +++++++++++-------- .../probabilistic/InputEncoderUtilWrapper.kt | 10 +++--- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/AbstractProbabilistic400EndpointModel.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/AbstractProbabilistic400EndpointModel.kt index 06193f26d6..952d54b65d 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/AbstractProbabilistic400EndpointModel.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/AbstractProbabilistic400EndpointModel.kt @@ -112,6 +112,11 @@ abstract class AbstractProbabilistic400EndpointModel( val keysAndValues = encoder.getAllParamsPathsAndEncodedValues() + // TODO: initializedKeys are based on the model keys that are fixed. + // In fact we ignore new hidden genes added during the search which are not available in the schema. + // A more principled solution would be to support dynamic feature spaces, + // where the classifier can adapt its dimension and feature mapping as + // new parameter structures are discovered during the search. val initializedKeys = requireNotNull(modelKeys) { "Model keys have not been initialized" } @@ -120,23 +125,26 @@ abstract class AbstractProbabilistic400EndpointModel( val value = keysAndValues[key] + /** + * The encoder never returns null values. + * Therefore, a null means that the encoder provided a new key which is not identical to the model key. + * Hypothetically, this only happens if the parameter path (representing the key) + * changes during the search due to adding an intermediate gene (e.g., the path for + * the parameter b changes from 'GET:/a/b' to 'GET:/a/foo/b'). + * Thus, the encoder returns a different key for the parameter from what is expected. + * In such cases we consider the encoded value as neutral (i.e., 0.0) to avoid errors. + */ if (value == null) { - - log.error("Endpoint: {}", endpoint) - log.error("Missing expected key: {}", key) - log.error("Model keys: {}", initializedKeys) - log.error("Current keys: {}", keysAndValues.keys) - - val missing = initializedKeys.filter { it !in keysAndValues.keys } - log.error("Missing keys: {}", missing) - - throw IllegalArgumentException( - "The encoded value cannot be null as the encoder set nulls to the sentinel (e.g., 10^6)." + - " So there is a missing key as: $key" - ) + log.warn("Missing key while encoding endpoint: {}", endpoint) + log.warn("Model keys: {}", initializedKeys) + log.warn("Current keys: {}", keysAndValues.keys) + log.warn("Missing key: {}", key) + + 0.0 + } else { + value } - value } } diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/InputEncoderUtilWrapper.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/InputEncoderUtilWrapper.kt index 78a23c7a90..ede4265ec5 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/InputEncoderUtilWrapper.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/InputEncoderUtilWrapper.kt @@ -90,10 +90,12 @@ class InputEncoderUtilWrapper( current = current.parent as? Gene } - return names - .reversed() - .dropLast(1) //ignore the last name, which is the repetition of gene itself as its own parent - .joinToString("/") + val path = names.reversed() + + return if (path.size > 1) + path.dropLast(1).joinToString("/") //ignore the last name, which is the repetition of gene itself as its own parent + else + path.joinToString("/") } From 6c89709ac6b0575e8eb2c53ee14a9787f5054c2f Mon Sep 17 00:00:00 2001 From: Mohsen Taheri Shalmani Date: Sat, 6 Jun 2026 19:48:15 +0200 Subject: [PATCH 7/9] Update --- .../probabilistic/gaussian/Gaussian400EndpointModel.kt | 1 - .../rest/classifier/probabilistic/kde/KDE400EndpointModel.kt | 1 - .../rest/classifier/probabilistic/knn/KNN400EndpointModel.kt | 1 - .../rest/classifier/probabilistic/nn/NN400EndpointModel.kt | 1 - 4 files changed, 4 deletions(-) diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/gaussian/Gaussian400EndpointModel.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/gaussian/Gaussian400EndpointModel.kt index a345903551..f39e05e009 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/gaussian/Gaussian400EndpointModel.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/gaussian/Gaussian400EndpointModel.kt @@ -7,7 +7,6 @@ import org.evomaster.core.problem.rest.data.Endpoint import org.evomaster.core.problem.rest.data.RestCallAction import org.evomaster.core.problem.rest.data.RestCallResult import org.evomaster.core.search.service.Randomness -import kotlin.String import kotlin.math.PI import kotlin.math.exp import kotlin.math.ln diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/kde/KDE400EndpointModel.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/kde/KDE400EndpointModel.kt index 1fc9148b8c..f99df2fc32 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/kde/KDE400EndpointModel.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/kde/KDE400EndpointModel.kt @@ -2,7 +2,6 @@ package org.evomaster.core.problem.rest.classifier.probabilistic.kde import org.evomaster.core.EMConfig import org.evomaster.core.problem.rest.classifier.AIResponseClassification -import org.evomaster.core.problem.rest.classifier.probabilistic.InputEncoderUtilWrapper import org.evomaster.core.problem.rest.classifier.probabilistic.AbstractProbabilistic400EndpointModel import org.evomaster.core.problem.rest.data.Endpoint import org.evomaster.core.problem.rest.data.RestCallAction diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/knn/KNN400EndpointModel.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/knn/KNN400EndpointModel.kt index 5457c18bd9..857bfc7ce5 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/knn/KNN400EndpointModel.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/knn/KNN400EndpointModel.kt @@ -2,7 +2,6 @@ package org.evomaster.core.problem.rest.classifier.probabilistic.knn import org.evomaster.core.EMConfig import org.evomaster.core.problem.rest.classifier.AIResponseClassification -import org.evomaster.core.problem.rest.classifier.probabilistic.InputEncoderUtilWrapper import org.evomaster.core.problem.rest.classifier.probabilistic.AbstractProbabilistic400EndpointModel import org.evomaster.core.problem.rest.data.Endpoint import org.evomaster.core.problem.rest.data.RestCallAction diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/nn/NN400EndpointModel.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/nn/NN400EndpointModel.kt index dd238e4625..aee190aa9f 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/nn/NN400EndpointModel.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/nn/NN400EndpointModel.kt @@ -2,7 +2,6 @@ package org.evomaster.core.problem.rest.classifier.probabilistic.nn import org.evomaster.core.EMConfig import org.evomaster.core.problem.rest.classifier.AIResponseClassification -import org.evomaster.core.problem.rest.classifier.probabilistic.InputEncoderUtilWrapper import org.evomaster.core.problem.rest.classifier.probabilistic.AbstractProbabilistic400EndpointModel import org.evomaster.core.problem.rest.data.Endpoint import org.evomaster.core.problem.rest.data.RestCallAction From 8d467f99805aacc784a4770806a2aaf9746cc227 Mon Sep 17 00:00:00 2001 From: Mohsen Taheri Shalmani Date: Tue, 9 Jun 2026 12:08:15 +0200 Subject: [PATCH 8/9] Update AbstractProbabilistic400EndpointModel.kt --- .../AbstractProbabilistic400EndpointModel.kt | 60 ++++++++++++------- 1 file changed, 40 insertions(+), 20 deletions(-) diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/AbstractProbabilistic400EndpointModel.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/AbstractProbabilistic400EndpointModel.kt index 952d54b65d..bd0cdf7388 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/AbstractProbabilistic400EndpointModel.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/AbstractProbabilistic400EndpointModel.kt @@ -14,22 +14,39 @@ import org.slf4j.LoggerFactory /** * Base class for all probabilistic classifiers working at an endpoint (400 vs. not 400). * - * Provides: - * - Common properties (endpoint, warmup, keys, dimension, encoderType, randomness, initialized flag) - * - Shared methods for initialization checks and accuracy estimation + * This class provides: + * - Common endpoint-specific state (endpoint, warmup, modelKeys, dimension, encoderType, randomness, initialized flag) + * - A fixed feature-space definition via `modelKeys` + * - Shared initialization logic + * - Common performance tracking and metric estimation + * + * The classifier assumes a fixed input representation. Each feature is uniquely identified + * (by e.g., a parameter path) and stored in `modelKeys`, where: + * + * modelKeys[i] <-> inputVector[i] + * + * The ordering of `modelKeys` is important and must remain stable + * throughout the lifetime of the model. This guarantees that the same parameter + * is always encoded into the same feature dimension. */ abstract class AbstractProbabilistic400EndpointModel( val endpoint: Endpoint, var warmup: Int, + /** + * Ordered list of model keys in correspondence with the encoded features i.e., + * + * modelKeys[i] <-> inputVector[i] + */ var modelKeys: List? = null, + /** + * Represents the dimension of the feature space (i.e., the length of the input vector) + */ var dimension: Int? = null, val encoderType: EMConfig.EncoderType, val metricType: EMConfig.AIClassificationMetrics, val randomness: Randomness ) : AIModel { - protected var initialized: Boolean = false - companion object { private val log = LoggerFactory.getLogger(AbstractProbabilistic400EndpointModel::class.java) @@ -37,6 +54,8 @@ abstract class AbstractProbabilistic400EndpointModel( const val NOT_400 = 200 } + protected var initialized: Boolean = false + /** Create a metric tracker.*/ val modelMetrics: ModelMetrics = createModelMetrics(metricType) @@ -49,31 +68,33 @@ abstract class AbstractProbabilistic400EndpointModel( /** - * Initialize dimension and keys once initialized. + * Initialize the model's feature space (including the dimension and modelKeys) if it has not been initialized yet. * The dimension is the number of parameters in the input vector. * The modelKeys are unique identifiers of the parameters defined based on * the unique path of each parameter (including all its parents). */ open fun initializeIfNeeded(input: RestCallAction) { - if (dimension == null) { - - val encoder = InputEncoderUtilWrapper(input, encoderType = encoderType) - val allParamsPathsAndEncodedValues = encoder.getAllParamsPathsAndEncodedValues() + if (initialized) { + // already initialized. nothing to do + return + } - val inputVector = allParamsPathsAndEncodedValues.values.toList() - val paramPaths = allParamsPathsAndEncodedValues.keys.toList() + val encoder = InputEncoderUtilWrapper(input, encoderType = encoderType) + val allParamsPathsAndEncodedValues = encoder.getAllParamsPathsAndEncodedValues() - require(inputVector.isNotEmpty()) { "Input vector is empty" } - require(paramPaths.isNotEmpty()) { "Parameter paths are empty" } - require(warmup > 0) { "Warmup must be positive" } + val inputVector = allParamsPathsAndEncodedValues.values.toList() + val paramPaths = allParamsPathsAndEncodedValues.keys.toList() - dimension = paramPaths.size - modelKeys = paramPaths + require(inputVector.isNotEmpty()) { "Input vector is empty" } + require(paramPaths.isNotEmpty()) { "Parameter paths are empty" } + require(warmup > 0) { "Warmup must be positive" } - } + dimension = paramPaths.size + modelKeys = paramPaths initialized = true + } /** @@ -109,8 +130,7 @@ abstract class AbstractProbabilistic400EndpointModel( encoderType = encoderType ) - val keysAndValues = - encoder.getAllParamsPathsAndEncodedValues() + val keysAndValues = encoder.getAllParamsPathsAndEncodedValues() // TODO: initializedKeys are based on the model keys that are fixed. // In fact we ignore new hidden genes added during the search which are not available in the schema. From 8652669aa2d31f3e6ba8f615ab86ce294c63af17 Mon Sep 17 00:00:00 2001 From: Mohsen Taheri Shalmani Date: Tue, 16 Jun 2026 11:57:09 +0200 Subject: [PATCH 9/9] Update AbstractProbabilistic400EndpointModel.kt --- .../AbstractProbabilistic400EndpointModel.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/AbstractProbabilistic400EndpointModel.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/AbstractProbabilistic400EndpointModel.kt index bd0cdf7388..dbf1972f6a 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/AbstractProbabilistic400EndpointModel.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/classifier/probabilistic/AbstractProbabilistic400EndpointModel.kt @@ -21,12 +21,14 @@ import org.slf4j.LoggerFactory * - Common performance tracking and metric estimation * * The classifier assumes a fixed input representation. Each feature is uniquely identified - * (by e.g., a parameter path) and stored in `modelKeys`, where: + * (by e.g., a parameter path) and stored in `modelKeys`, such that: * * modelKeys[i] <-> inputVector[i] * + * where `inputVector` is the encoded representation of a `RestCallAction` produced by [InputEncoderUtilWrapper]. * The ordering of `modelKeys` is important and must remain stable - * throughout the lifetime of the model. This guarantees that the same parameter + * to provide a fixed-length `inputVector` throughout the model's lifetime. + * This guarantees that the same parameter * is always encoded into the same feature dimension. */ abstract class AbstractProbabilistic400EndpointModel( @@ -83,10 +85,10 @@ abstract class AbstractProbabilistic400EndpointModel( val encoder = InputEncoderUtilWrapper(input, encoderType = encoderType) val allParamsPathsAndEncodedValues = encoder.getAllParamsPathsAndEncodedValues() - val inputVector = allParamsPathsAndEncodedValues.values.toList() val paramPaths = allParamsPathsAndEncodedValues.keys.toList() - require(inputVector.isNotEmpty()) { "Input vector is empty" } + // It is sufficient to check the parameter paths. The encoder guarantees + // that every parameter path has a corresponding non-null encoded value. require(paramPaths.isNotEmpty()) { "Parameter paths are empty" } require(warmup > 0) { "Warmup must be positive" }