10.7 Using a Solver on a Problem

Section 10.3.5 presented how to define a problem definition for a reorder point, reorder quantity inventory system. This section will use that problem definition to setup a cross-entropy solver and a random restart solver for optimizing the problem. Recall that the problem is to minimize the inventory ordering and holding costs subject to a fill-rate constraint. To use a solver, we need at a minimum the following:

  • an instance of the ProblemDefinition class
  • an instance of something that implements the ModelBuilderIfc interface
  • an instance of the EvaluatorIfc interface

The companion object of the Solver class provides factory functions that facilitate the creation of the available solvers with default configurations. The factory functions will create an evaluator for executing the model that is configured to run the simulations locally (within the same execution thread) and is configured to use a MemorySolutionCache() to store and reuse solutions requested by the solver.

10.7.1 Illustrating the Cross-Entropy Solver

This section illustrates how to setup and run the cross-entropy solver for the reorder point, reorder quantity inventory model. Let’s start with a review of the factory function that facilitates the creation of a cross-entropy solver. The factory function requires a problem definition, something that will build the model, and the necessary parameters to construct the cross-entropy solver.

        fun crossEntropySolver(
            problemDefinition: ProblemDefinition,
            modelBuilder: ModelBuilderIfc,
            ceSampler: CESamplerIfc = CENormalSampler(problemDefinition),
            startingPoint: MutableMap<String, Double>? = null,
            maxIterations: Int = defaultMaxNumberIterations,
            replicationsPerEvaluation: Int = defaultReplicationsPerEvaluation,
            solutionCache: SolutionCacheIfc = MemorySolutionCache(),
            simulationRunCache: SimulationRunCacheIfc? = null,
            printer: ((Solver) -> Unit)? = null,
            experimentRunParameters: ExperimentRunParametersIfc? = null,
            defaultKSLDatabaseObserverOption: Boolean = false
        ): CrossEntropySolver {
            val evaluator = Evaluator.createProblemEvaluator(
                problemDefinition = problemDefinition, modelBuilder = modelBuilder, solutionCache = solutionCache,
                simulationRunCache = simulationRunCache, experimentRunParameters = experimentRunParameters,
                defaultKSLDatabaseObserverOption = defaultKSLDatabaseObserverOption
            )
            val ce = CrossEntropySolver(
                problemDefinition = problemDefinition,
                evaluator = evaluator,
                ceSampler = ceSampler,
                maxIterations = maxIterations,
                replicationsPerEvaluation = replicationsPerEvaluation
            )
            if (startingPoint != null) {
                ce.startingPoint = evaluator.problemDefinition.toInputMap(startingPoint)
            }
            printer?.let { ce.emitter.attach(it) }
            return ce
        }

Notice the factory function allows passing through key parameters for both the construction of the evaluator and the solver. The evaluator is setup with the problem definition and the model builder. The rest of the parameters for the creation of the evaluator use the defaults.

One useful parameter of this function is the printer parameter, which allows for the attachment of a function that will react to the emission of solutions found during the iterations of the solver. Notice that the printer parameter is a function that has a Solver as a parameter and does not return anything. Thus, instead of printing the emitted solutions, we could save them to a file or perform more complex functionality. We will supply a simple printer function as part of this discussion. Every solver implements the SolverEmitterIfc interface and is supplied a default emitter. The line involving the printer parameter tells the constructed solver to use its emitter to attach the printer. This is similar to the observer/observable pattern common in many software architectures.

printer?.let { ce.emitter.attach(it) }

The following code defines an object that implements the ModelBuilderIfc interface. This code is very similar to the code used to create models throughout the textbook. For this simple example, there will not be a need to use the parameters passed to the build() function. One important item to note is that the settings of the run parameters (length of warm up and the length of the replications) will stay as specified in this function; however, these base settings could be changed during the building process. The solver architecture via the previously described EvaluationRequest class only permits the changing of the number of replications. So, once a model is built, and provided to the solver, its basic configuration cannot be changed except for inputs and number of replications.

object BuildRQModel : ModelBuilderIfc {
    override fun build(
        modelConfiguration: Map<String, String>?,
        experimentRunParameters: ExperimentRunParametersIfc?,
        defaultKSLDatabaseObserverOption: Boolean
    ): Model {
        val reorderQty: Int = 2
        val reorderPoint: Int = 1
        val model = Model("RQInventoryModel")
        val rqModel = RQInventorySystem(model, reorderPoint, reorderQty, "Inventory")
        rqModel.initialOnHand = 0
        rqModel.demandGenerator.initialTimeBtwEvents = ExponentialRV(1.0 / 3.6)
        rqModel.leadTime.initialRandomSource = ConstantRV(0.5)
        model.lengthOfReplication = 20000.0
        model.lengthOfReplicationWarmUp = 10000.0
        model.numberOfReplications = 40
        if (defaultKSLDatabaseObserverOption) {
            model.createDefaultDatabaseObserver()
        }
        return model
    }
}

To specify the printing of results, we need to write the function to print the results. This code is setup to handle the situation of a random restart solver so that the emissions from both solvers can be printed.

fun printRQInventoryModel(solver: Solver) {
    println("**** iteration = ${solver.iterationCounter} ************************************")
    if (solver is RandomRestartSolver){
        val rs = solver.restartingSolver
        val initialSolution = rs.initialSolution
        if (initialSolution != null) {
            val q = initialSolution.inputMap["Inventory:Item.initialReorderQty"]
            val rp = initialSolution.inputMap["Inventory:Item.initialReorderPoint"]
            val fillRate = initialSolution.responseEstimatesMap["Inventory:Item:FillRate"]!!.average
            println("initial solution: id = ${initialSolution.id}")
            println("n = ${initialSolution.count} : objFnc = ${initialSolution.estimatedObjFncValue} \t q = $q \t r = $rp \t penalized objFnc = ${initialSolution.penalizedObjFncValue} \t fillrate = $fillRate")
        }
    }
    val solution = solver.currentSolution
    val q = solution.inputMap["Inventory:Item.initialReorderQty"]
    val rp = solution.inputMap["Inventory:Item.initialReorderPoint"]
    val fillRate = solution.responseEstimatesMap["Inventory:Item:FillRate"]!!.average
    println("solution: id = ${solution.id}")
    println("n = ${solution.count} : objFnc = ${solution.estimatedObjFncValue} \t q = $q \t r = $rp \t penalized objFnc = ${solution.penalizedObjFncValue} \t fillrate = $fillRate ")
    println("********************************************************************************")
}

Now we can construct the solver and run it. The following code provides a function for constructing and running the iterations of a solver and for printing out results from the optimization process.

fun runCESolver(
    simulationRunCache: SimulationRunCacheIfc? = null,
    experimentRunParameters: ExperimentRunParametersIfc? = null,
    defaultKSLDatabaseObserverOption: Boolean = false
) {
    val problemDefinition = makeRQInventoryModelProblemDefinition()
    val modelBuilder = BuildRQModel
    val printer = ::printRQInventoryModel

    val solver = Solver.crossEntropySolver(
        problemDefinition = problemDefinition,
        modelBuilder = modelBuilder,
        startingPoint = null,
        maxIterations = 100,
        replicationsPerEvaluation = 50,
        printer = printer,
        simulationRunCache = simulationRunCache,
        experimentRunParameters = experimentRunParameters,
        defaultKSLDatabaseObserverOption = defaultKSLDatabaseObserverOption
    )
    solver.runAllIterations()
    println()
    println("Solver Results:")
    println(solver)
    println()
    println("Final Solution:")
    println(solver.bestSolution.asString())
    println()
    println("Approximate screening:")
    val solutions = solver.bestSolutions.possiblyBest()
    println(solutions)
    println("Dataframe")
    val df = solver.bestSolutions.toDataFrame()
    df.schema().print()
    df.print()
}

The required problem definition is supplied by the previously reviewed makeRQInventoryModelProblemDefinition() function, the model builder is supplied by the object that builds the model, and the printer is specified as the provided printing function. The cross-entropy solver is constructed and all the iterations of the solver are executed. Note that even though the maximum number of iteration was specified as 100, the actual number of iterations may be less due to the stopping criteria. Solvers have a useful toString() method and also provide functions and properties to examine the solutions.

Let’s take a look at some of the emitted solutions.

**** iteration = 1 ************************************
solution: id = 27
n = 50.0 : objFnc = 10.799191735323431   q = 1.0     r = 8.0     penalized objFnc = 10.799191735323431   fillrate = 0.9998949093510683 
********************************************************************************
**** iteration = 2 ************************************
solution: id = 64
n = 50.0 : objFnc = 6.813700138425201    q = 1.0     r = 4.0     penalized objFnc = 6.813700138425201    fillrate = 0.963396108753269 
********************************************************************************
**** iteration = 3 ************************************
solution: id = 89
n = 50.0 : objFnc = 5.405770841050546    q = 3.0     r = 4.0     penalized objFnc = 5.405770841050546    fillrate = 0.9835876248620253 
********************************************************************************

We see that the solution is improving over the first 3 iterations and that the fill rate constraint is satisfied. As shown for iterations 28 through 34, we see solutions might not always improve; however, we see that the last 5 solutions are the same and thus the cross-entropy solver will stop.

**** iteration = 28 ************************************
solution: id = 346
n = 50.0 : objFnc = 4.933739982242459    q = 5.0     r = 3.0     penalized objFnc = 4.933739982242459    fillrate = 0.9681095923274622 
********************************************************************************
**** iteration = 29 ************************************
solution: id = 213
n = 50.0 : objFnc = 5.508897772861848    q = 2.0     r = 4.0     penalized objFnc = 5.508897772861848    fillrate = 0.9768196645340352 
********************************************************************************
**** iteration = 30 ************************************
solution: id = 446
n = 50.0 : objFnc = 4.616279897738635    q = 4.0     r = 3.0     penalized objFnc = 4.616279897738635    fillrate = 0.9605229975268885 
********************************************************************************
**** iteration = 31 ************************************
solution: id = 446
n = 50.0 : objFnc = 4.616279897738635    q = 4.0     r = 3.0     penalized objFnc = 4.616279897738635    fillrate = 0.9605229975268885 
********************************************************************************
**** iteration = 32 ************************************
solution: id = 446
n = 50.0 : objFnc = 4.616279897738635    q = 4.0     r = 3.0     penalized objFnc = 4.616279897738635    fillrate = 0.9605229975268885 
********************************************************************************
**** iteration = 33 ************************************
solution: id = 446
n = 50.0 : objFnc = 4.616279897738635    q = 4.0     r = 3.0     penalized objFnc = 4.616279897738635    fillrate = 0.9605229975268885 
********************************************************************************
**** iteration = 34 ************************************
solution: id = 446
n = 50.0 : objFnc = 4.616279897738635    q = 4.0     r = 3.0     penalized objFnc = 4.616279897738635    fillrate = 0.9605229975268885 
********************************************************************************

The toString() function prints out the final results of the solver and its basic parameter settings. It took about 5 minutes of execution time for the solver to converge, with a total fo 1020 simulation calls and 51000 replications executed.

Solver Results:
Cross-Entropy Solver with parameters: 
Elite Pct: 0.1
No improvement threshold: 5
CE Sample Size: 35
Elite Size: 5
CE Sampler:
CENormalSampler
streamNumber = 1
dimension = 2
variabilityFactor = 1.0
meanSmoother = 0.85
sdSmoother = 0.85
coefficient of variation threshold = 0.03
mean values = [4.423515385086519, 3.8787077250760036]
standard deviations = [2.112587139353944, 0.8138046208457131]
std deviation thresholds = [0.13270546155259558, 0.11636123175228011]
Have standard deviation thresholds converge? = false
==================================================================
Solver name = ID_46
Replications Per Evaluation = Fixed replications per evaluation: 50
Ensure Problem Feasible Requests = false
Maximum Number Iterations = 100
Begin Execution Time = 2025-09-15T19:54:19.338208Z
End Execution Time = 2025-09-15T19:59:39.355562Z
Elapsed Execution Time = 5m 20.017354s
Number of simulation calls = 1020
Number of replications requested = 51000
==================================================================
Current Solution:
Solution id = 446
evaluation number = 22
objective function value = 4.616279897738635
penalized objective function = 4.616279897738635
granular function value = 4.616279897738635
penality function value = 0.0
Inputs:
Name = Inventory:Item.initialReorderQty = 4.0)
Name = Inventory:Item.initialReorderPoint = 3.0)
Estimated Objective Function:
name = Inventory:Item:OrderingAndHoldingCost
average = 4.616279897738635
variance = 4.2126312844488165E-5
count = 50.0
Response Constraints:
Inventory:Item:FillRate: LHS = 0.9605229975268885 >= RHS = 0.95 : (LHS-RHS) = 0.010522997526888567

Previous solution penalized objective function value (POFV) = 4.616279897738635
Current solution POFV - Previous solution POFV  = 0.0
==================================================================
Best Solutions Found:
id = 446 : n = 50.0 : objFnc = 4.616279897738635 : 95%ci = [4.614435324122555, 4.618124471354715] : inputs : 4.0, 3.0 
id = 346 : n = 50.0 : objFnc = 4.933739982242459 : 95%ci = [4.931164260295049, 4.936315704189869] : inputs : 5.0, 3.0 
id = 458 : n = 50.0 : objFnc = 5.310563363995046 : 95%ci = [5.307489409147018, 5.313637318843074] : inputs : 6.0, 3.0 
id = 89 : n = 50.0 : objFnc = 5.405770841050546 : 95%ci = [5.404227591025683, 5.407314091075409] : inputs : 3.0, 4.0 
id = 213 : n = 50.0 : objFnc = 5.508897772861848 : 95%ci = [5.508281507870848, 5.509514037852847] : inputs : 2.0, 4.0 
id = 417 : n = 50.0 : objFnc = 5.604303895271229 : 95%ci = [5.601951343786538, 5.606656446755921] : inputs : 4.0, 4.0 
id = 382 : n = 50.0 : objFnc = 5.922447519514469 : 95%ci = [5.920115184447707, 5.924779854581231] : inputs : 5.0, 4.0 
id = 399 : n = 50.0 : objFnc = 6.160531680487158 : 95%ci = [6.156322093895311, 6.1647412670790045] : inputs : 8.0, 3.0 
id = 291 : n = 50.0 : objFnc = 6.5511923736709265 : 95%ci = [6.546115066629831, 6.556269680712022] : inputs : 11.0, 2.0 
id = 64 : n = 50.0 : objFnc = 6.813700138425201 : 95%ci = [6.810408603713308, 6.816991673137094] : inputs : 1.0, 4.0 
==================================================================

The best solution appears to be at a reorder point of 3 and a reorder quantity of 4, with a total cost of 4.62. Notice that confidence intervals on the solutions are provided. The output from the code for screening indicates that the found solution is likely the best observed solution because the other solutions are screened out. We have no real guarantee that there is some point that was not observed that could be better.

    println("Approximate screening:")
    val solutions = solver.bestSolutions.possiblyBest()
    println(solutions)
Final Solution:
id = 446 : n = 50.0 : objFnc = 4.616279897738635 : 95%ci = [4.614435324122555, 4.618124471354715] : inputs : 4.0, 3.0 

Approximate screening:
Solutions:
totalSolutions = 1
capacity = 10
allowInfeasibleSolutions = false
orderedSolutions:
id = 446 : n = 50.0 : objFnc = 4.616279897738635 : 95%ci = [4.614435324122555, 4.618124471354715] : inputs : 4.0, 3.0 

As noted in the specification of the problem definition, this is an integer ordered problem, where the reorder point and reorder quantity are limited to integer values. As we can note from the output of the optimization process, the evaluated points are limited to integer values. However, you might note that the cross-entropy method estimates the parameters of the sampling distribution as continuous values.

mean values = [4.423515385086519, 3.8787077250760036]
standard deviations = [2.112587139353944, 0.8138046208457131]

Because of the specification of granularity equal to 1.0, the evaluation of these points are rounded to the specified granularity for the evaluation process. If you run the code, you will notice a distinct speed up once (4.0, 3.0) is found. This is due to the fact that the estimates for the distribution parameters via the maximum likelihood estimation process and the application of the smoothing constants have essentially converged to those points which are within the specified granularity.

In addition, the optimization process was run with a solution cache. Thus, every time that those points are requested for evaluation, we get back the same solution without additional simulation runs. This is avoiding the time to execute the simulation model and quickly causes 5 iterations to have the same solution, meeting the stopping criteria. The use of a solution cache limits the variability of the iterations. An exercise suggests running experiments to understand how caching effects the convergence of solvers.

10.7.2 Illustrating Simulated Annealing with Random Restarts

In this section, the same reorder point, reorder quantity problem will be solved using a simulated annealing solver that is restarted at randomly generated points. Simulated annealing may be sensitive to the initial point so it makes sense to explore different starting points. There are factory functions available for constructing random restart solvers for the basic set of solvers. Let’s review the function for constructing a solver that restarts a simulated annealing solver.

        fun simulatedAnnealingSolverWithRestarts(
            problemDefinition: ProblemDefinition,
            modelBuilder: ModelBuilderIfc,
            maxNumRestarts: Int = defaultMaxRestarts,
            initialTemperature: Double = defaultInitialTemperature,
            maxIterations: Int = defaultMaxNumberIterations,
            replicationsPerEvaluation: Int = defaultReplicationsPerEvaluation,
            solutionCache: SolutionCacheIfc = MemorySolutionCache(),
            simulationRunCache: SimulationRunCacheIfc? = null,
            restartPrinter: ((Solver) -> Unit)? = null,
            printer: ((Solver) -> Unit)? = null,
            experimentRunParameters: ExperimentRunParametersIfc? = null,
            defaultKSLDatabaseObserverOption: Boolean = false
        ): RandomRestartSolver {
            val evaluator = Evaluator.createProblemEvaluator(
                problemDefinition = problemDefinition, modelBuilder = modelBuilder, solutionCache = solutionCache,
                simulationRunCache = simulationRunCache, experimentRunParameters = experimentRunParameters,
                defaultKSLDatabaseObserverOption = defaultKSLDatabaseObserverOption
            )
            val sa = SimulatedAnnealing(
                problemDefinition = problemDefinition,
                evaluator = evaluator,
                initialTemperature = initialTemperature,
                maxIterations = maxIterations,
                replicationsPerEvaluation = replicationsPerEvaluation
            )
            val restartSolver = RandomRestartSolver(
                sa, maxNumRestarts
            )
            restartPrinter?.let { restartSolver.emitter.attach(it) }
            printer?.let { sa.emitter.attach(it) }
            return restartSolver
        }

This factory function has a similar structure as the cross-entropy factory function. In this case, the parameters for both the restarting solver (simulated annealing) and the restart solver need to be provided. An evaluator is made, the simulated annealing solver constructed, and then it is provided to the RandomRestartSolver instance. Notice that a printer can be attached to both of the solvers.

Since we already have a model builder, printer, and problem definition, for this example we can reuse those functions from the previous example. Thus, we only need to write code to instantiate and run the random restart solver. We will accept the default settings for both solvers. For this situation, the maximum number of restarts defaults to 5 and the maximum number of iterations for the simulated annealing solver is 100. An initial temperature of 1000 is used. This code only attaches the printer to the restart solver.

fun runSimulatedAnnealingWithRestarts(
    simulationRunCache: SimulationRunCacheIfc? = null,
    experimentRunParameters: ExperimentRunParametersIfc? = null,
    defaultKSLDatabaseObserverOption: Boolean = false
) {
    val problemDefinition = makeRQInventoryModelProblemDefinition()
    val modelBuilder = BuildRQModel
    val printer = ::printRQInventoryModel
    val initialTemperature = 1000.0
    val solver = Solver.simulatedAnnealingSolverWithRestarts(
        problemDefinition = problemDefinition,
        modelBuilder = modelBuilder,
        initialTemperature = initialTemperature,
        maxIterations = 100,
        replicationsPerEvaluation = 50,
        restartPrinter = printer,
        printer = null,
        simulationRunCache = simulationRunCache,
        experimentRunParameters = experimentRunParameters,
        defaultKSLDatabaseObserverOption = defaultKSLDatabaseObserverOption
    )
    solver.runAllIterations()
    println()
    println("Solver Results:")
    println(solver)
    println()
    println("Final Solution:")
    println(solver.bestSolution.asString())
    println()
    println("Approximate screening:")
    val solutions = solver.bestSolutions.possiblyBest()
    println(solutions)
    println("Dataframe")
    val df = solver.bestSolutions.toDataFrame()
    df.schema().print()
    df.print()
}

The print trace of the 5 restart solver iterations is as follows. Within the trace, we can see the point where the simulated annealing solver started and the resulting solution. It should be clear from the output that the simulated annealing process improves upon the initial starting point’s solution in each of the iterations.

**** iteration = 1 ************************************
initial solution: id = 7
n = 50.0 : objFnc = 93.85435955308694    q = 24.0    r = 83.0    penalized objFnc = 93.85435955308694    fillrate = 1.0
solution: id = 93
n = 50.0 : objFnc = 5.310870719728758    q = 6.0     r = 3.0     penalized objFnc = 5.310870719728758    fillrate = 0.9733468768826896 
********************************************************************************
**** iteration = 2 ************************************
initial solution: id = 99
n = 50.0 : objFnc = 122.75503178367077   q = 94.0    r = 77.0    penalized objFnc = 122.75503178367077   fillrate = 1.0
solution: id = 146
n = 50.0 : objFnc = 11.476180247608118   q = 13.0    r = 6.0     penalized objFnc = 11.476180247608118   fillrate = 0.9997439617100352 
********************************************************************************
**** iteration = 3 ************************************
initial solution: id = 188
n = 50.0 : objFnc = 37.25543995083427    q = 59.0    r = 9.0     penalized objFnc = 37.25543995083427    fillrate = 0.9999994456916382
solution: id = 273
n = 50.0 : objFnc = 17.81239341644692    q = 36.0    r = 1.0     penalized objFnc = 17.81239341644692    fillrate = 0.9730845269288125 
********************************************************************************
**** iteration = 4 ************************************
initial solution: id = 279
n = 50.0 : objFnc = 8.963623230455   q = 14.0    r = 3.0     penalized objFnc = 8.963623230455   fillrate = 0.9887414135304179
solution: id = 301
n = 50.0 : objFnc = 8.50097189053639     q = 2.0     r = 7.0     penalized objFnc = 8.50097189053639     fillrate = 0.9996895379757809 
********************************************************************************
**** iteration = 5 ************************************
initial solution: id = 379
n = 50.0 : objFnc = 84.78199562986019    q = 44.0    r = 64.0    penalized objFnc = 84.78199562986019    fillrate = 1.0
solution: id = 443
n = 50.0 : objFnc = 9.919247701181797    q = 5.0     r = 8.0     penalized objFnc = 9.919247701181797    fillrate = 0.9999689303150257 
********************************************************************************

The solver results indicate that the total number of simulation calls was 470 with 23500 total replications over the approximately 4 minutes of execution time. The random restart solver will remember the initial solutions and the solution found from the underlying solver. We see that a solution with reorder point 3.0 and reorder quantity of 6 is recommended. This solution is slightly worse than the one found from the cross-entropy optimization process.

Solver Results:
==================================================================
Solver name = ID_46
Replications Per Evaluation = Fixed replications per evaluation: 50
Ensure Problem Feasible Requests = false
Maximum Number Iterations = 5
Begin Execution Time = 2025-09-16T16:15:11.636147Z
End Execution Time = 2025-09-16T16:19:53.562212Z
Elapsed Execution Time = 4m 41.926065s
Number of simulation calls = 470
Number of replications requested = 23500
==================================================================
Initial Solution:
Solution id = 5
evaluation number = 1
objective function value = 71.05955523050149
penalized objective function = 71.05955523050149
granular function value = 71.05955523050149
penality function value = 0.0
Inputs:
Name = Inventory:Item.initialReorderQty = 10.0)
Name = Inventory:Item.initialReorderPoint = 67.0)
Estimated Objective Function:
name = Inventory:Item:OrderingAndHoldingCost
average = 71.05955523050149
variance = 3.0560452742137536E-4
count = 50.0
Response Constraints:
Inventory:Item:FillRate: LHS = 1.0 >= RHS = 0.95 : (LHS-RHS) = 0.050000000000000044

==================================================================
Current Solution:
Solution id = 443
evaluation number = 445
objective function value = 9.919247701181797
penalized objective function = 9.919247701181797
granular function value = 9.919247701181797
penality function value = 0.0
Inputs:
Name = Inventory:Item.initialReorderPoint = 8.0)
Name = Inventory:Item.initialReorderQty = 5.0)
Estimated Objective Function:
name = Inventory:Item:OrderingAndHoldingCost
average = 9.919247701181797
variance = 8.128019398796645E-5
count = 50.0
Response Constraints:
Inventory:Item:FillRate: LHS = 0.9999689303150257 >= RHS = 0.95 : (LHS-RHS) = 0.049968930315025695

Previous solution penalized objective function value (POFV) = 8.50097189053639
Current solution POFV - Previous solution POFV  = 1.418275810645408
==================================================================
A better solution was found than the current solution.
Best Solution:
Solution id = 93
evaluation number = 90
objective function value = 5.310870719728758
penalized objective function = 5.310870719728758
granular function value = 5.310870719728758
penality function value = 0.0
Inputs:
Name = Inventory:Item.initialReorderPoint = 3.0)
Name = Inventory:Item.initialReorderQty = 6.0)
Estimated Objective Function:
name = Inventory:Item:OrderingAndHoldingCost
average = 5.310870719728758
variance = 6.503490862573595E-5
count = 50.0
Response Constraints:
Inventory:Item:FillRate: LHS = 0.9733468768826896 >= RHS = 0.95 : (LHS-RHS) = 0.02334687688268966

==================================================================
Best Solutions Found:
id = 93 : n = 50.0 : objFnc = 5.310870719728758 : 95%ci = [5.308578836243111, 5.313162603214405] : inputs : 3.0, 6.0 
id = 301 : n = 50.0 : objFnc = 8.50097189053639 : 95%ci = [8.500342348760027, 8.501601432312752] : inputs : 7.0, 2.0 
id = 443 : n = 50.0 : objFnc = 9.919247701181797 : 95%ci = [9.916685509400358, 9.921809892963237] : inputs : 8.0, 5.0 
id = 146 : n = 50.0 : objFnc = 11.476180247608118 : 95%ci = [11.47012805674839, 11.482232438467847] : inputs : 6.0, 13.0 
id = 273 : n = 50.0 : objFnc = 17.81239341644692 : 95%ci = [17.798872900323207, 17.825913932570632] : inputs : 1.0, 36.0 
id = 5 : n = 50.0 : objFnc = 71.05955523050149 : 95%ci = [71.05458702947743, 71.06452343152554] : inputs : 10.0, 67.0 
==================================================================

The screening results indicate that the reorder point equals 3 and reorder quantity of 6 is likely the best of those observed.

Approximate screening:
Solutions:
totalSolutions = 1
capacity = 10
allowInfeasibleSolutions = false
orderedSolutions:
id = 93 : n = 50.0 : objFnc = 5.310870719728758 : 95%ci = [5.308578836243111, 5.313162603214405] : inputs : 3.0, 6.0