Learning Objectives

  • To be able to discuss the context for simulation optimization
  • To be able to recognize and formulate standard simulation optimization problems
  • To be able to explain how penalty functions are used
  • To be able to construct a KSL problem definition including input variables and constraints
  • To be able to evaluate a simulation model within an optimization context

Simulation as Part of an Optimization Process

General Form of the Optimization Problem

\[ \min_{x\in S} g(x) \] Where:

\[ g(x) = E[g(x, \xi_i)] \\ S = \{x: E[h(x, \xi_i)] \leq 0 \} \]

  • \(g\) is an objective function
  • \(x\) represents the decision variables
  • constraints \(x \in S\).
  • \(\xi_i\) represents the randomness associated with the \(i^{th}\) observation (sample) for the objective function
  • \(g(\cdot)\) and \(h(\cdot)\) represents a performance measure, both, estimated from the simulation.
  • the inputs, \(x\), are vector-valued and the responses \(h(\cdot)\) may also be vectors.

What does the KSL provide?

The KSL’s ksl.simopt package contains functionality to implement and use simulation optimization algorithms for KSL models. The package has the following sub-packages:

  • ksl.simopt.problem This package has classes that facilitate the definition of optimization problem instances. The functionality includes specifying the objective function, the decision variables, and the constraints.
  • ksl.simopt.evaluator This package has classes that provide a well-defined interaction protocol with a simulation model. In essence, a general contract for what constitutes the inputs and outputs from the model is defined. This allows responses to be translated to solutions for problem instances that can be used by algorithms.
  • ksl.simopt.solvers This package provides the algorithms and general functionality for iterative optimization methods. The KSL denotes these simulation optimization algorithms as solvers.
  • ksl.simopt.cache This package provides basic functionality to cache (in memory) executions of simulation models. Because the execution of a simulation model may be a long-running task and algorithms may visit design points multiple times during the search process, the caching of solutions may be useful. This package defines general interfaces for caching solutions and simulation runs.

Problem Definition Variables

  • Let \(I\) represent the set of input variables
    • Let \(x_i\) represent the \(i^{th}\) input variable, and \(\vec{x}\) represent a member of \(I\).
  • Let \(G(\vec{x})\) be a random variable representing the objective function response from the simulation at input variable settings \(\vec{x}\).
    • The value of the objective function, \(G(\vec{x})\), should be a response from the simulation model.
  • Let \(L\) represent the set of deterministic linear constraints.
  • Let \(F\) represent the set of deterministic functional constraints with members \(f_i(\vec{x})\).
  • Let \(R\) represent the set of constraints based on simulation responses with \(R_{i}(\vec{x})\) representing a random response from the simulation model as a function of the input variables.
  • Let \(l_i\) and \(u_i\) represent known bounds on input variable \(x_i\).

Problem Definition Formulation

An instance of the ProblemDefinition class represents the following mathematical program:

\[ \min g(\vec{x}) = E[G(\vec{x})] \\ \sum_{i=1}^{|I|}a_{ij}x_i \leq b_j \quad \textrm{for} \quad j=1,\dots,|L|\\ f_{i} (\vec{x}) \leq c_{i} \quad \textrm{for} \quad i=1,\dots,|F|\\ E[R_{i}(\vec{x})] \leq r_i \quad \textrm{for} \quad i=1,\dots,|R|\\ l_i \leq x_i \leq u_i \quad \textrm{for} \quad i=1,\dots,|I|\\ \]

  • The problems represented by the ProblemDefinition class may contain deterministic linear and functional constraints and constraints that are based on observed responses.
    • We call the latter response constraints.
  • The input variables must be specified to be limited within some finite bounds.

KSL Problem Definition

class ProblemDefinition constructor(
    problemName: String? = null,
    val modelIdentifier: String,
    val objFnResponseName: String,
    inputNames: List<String>,
    responseNames: List<String> = emptyList(),
    val optimizationType: OptimizationType = 
      OptimizationType.MINIMIZE,
      indifferenceZoneParameter: Double = 0.0,
      objFnGranularity: Double = 0.0
) : IdentityIfc by Identity(problemName) 

KSL Problem Definition Basic Functionality

  • adding/defining/validating input variables
  • adding/defining linear constraints
  • adding/defining functional constraints
  • adding/defining response constraints
  • randomly generating feasible points in the input space
  • converting arrays of variables to valid input variables
  • checking for different feasibility conditions and computing constraint violations
  • specifying a penalty function for translating a constrained problem to an unconstrained problem
  • representing neighborhoods for the problem

Penalty Functions

  • A constraint is violated when \(LHS_k > RHS_k\); the violation is \(v_k = \max(0, LHS_k - RHS_k)\), over the \(K = |L| + |F| + |R|\) constraints (bounds are excluded — use a linear constraint if you must penalize one).
  • Penalty functions convert the constrained problem to an unconstrained one (a variation on the Lagrangian approach). Internally the KSL minimizes \[\text{penalizedObjFncValue} = \text{objFncValue} + \text{penaltyFncValue}(\text{solution})\]
  • The ksl.simopt.problem package provides an abstract PenaltyFunction, bound to the one constraint it serves:
abstract class PenaltyFunction(val constraint: PenalizableConstraint?) {
    abstract fun boundTo(c: PenalizableConstraint): PenaltyFunction
    abstract fun penalty(solution: Solution): Double
}
  • ProblemDefinition.penaltyFncValue(solution) sums each bound penalty across all constraints — always non-negative, so a violation always worsens the objective whether you minimize or maximize.

Built-in Penalty Functions

  • DynamicPolynomialPenalty — the memory-less default for all three constraint categories: \[P(v_k, t) = \left(C \cdot t^{\beta}\right) \cdot v_k^{\alpha}\] where \(t\) is the solution’s evaluation number, with basePenalty \(C=100\), iterationExponent \(\beta=1\), and violationExponent \(\alpha=1\) by default.
    • Note: \(\alpha\) defaults to 1, not 2 — squaring a fractional violation (e.g. a 0.02 fill-rate gap) makes the penalty smaller. Raise \(\alpha\) above 1 only for violations naturally of order 1 or larger.
  • ParkKimPenalty — memoryful, opt-in: tracks how infeasible a solution has looked across visits via an AppreciateDepreciateSequence, charging \(P = \lambda \cdot [S]^+\). Falls back to the memory-less penalty until a solution is revisited more than once.
problemDefinition.defaultResponsePenalty = ParkKimPenalty(
    sequence = AppreciateDepreciateSequence(appreciationFactor = 2.0, depreciationFactor = 0.5)
)
  • Set defaults per category (defaultLinearPenalty / defaultFunctionalPenalty / defaultResponsePenalty), override a specific linear/response constraint via its penaltyFunction argument, or implement PenaltyFunction for a custom scheme.

Functions to Compute Constraint Violations

The ProblemDefinition class facilitates the computation of each type of violation, individually for each constraint and in total via the functions:

  • linearConstraintViolations(inputs: Map<String, Double>): DoubleArray
  • totalLinearConstrainViolations(inputs: Map<String, Double>): Double
  • functionalConstraintViolations(inputs: Map<String, Double>): DoubleArray
  • totalFunctionalConstraintViolations(inputs: Map<String, Double>): Double

Users can use these functions to define their own penalty function forms.

Defining Decision Variables

The constructor for defining inputs is as follows:

class InputDefinition @JvmOverloads constructor(
    val name: String,
    val lowerBound: Double,
    val upperBound: Double,
    granularity: Double = 0.0
) 

What is granularity?

  • The granularity parameter specifies the degree of this precision so that a function can be applied which rounds the supplied value to the nearest multiple of the granularity.
    • Setting granularity to 0, the default, means that the solver will attempt to find a solution to the level of machine precision.
    • For any positive granularity value, the solution will be found to some multiple of that granularity.
    • As a special case, setting granularity to 1 implies an integer-ordered input variable.
  • Granularity represents the finest division of the measurement scale.
    • For example, a 12-inch rule that has inches divided into 4 quarters has a granularity of 1/4 or 0.25.
    • Suppose, we had a quarter inch granularity, the rounding process would round 3.1459 to 3.25 because 3.25 is the nearest multiple of the granularity of (0.25).
    • For the case of 3.0459 with a granularity of 0.25, the value of 3.0459 would be rounded to 3.0.
    • Thus, with a quarter inch granularity we can only have values that are multiples of 0.25.
  • Granularity permits a discretization of a continuous variable to points that are realistic with respect to the measurement scale associated with the variable. Thus, continuous variables can be handled as discrete variables if necessary.

Defining Linear Constraints

data class LinearConstraint(
    val equation: Map<String, Double>,
    override var rhsValue: Double = 0.0,
    override val inequalityType: InequalityType
       = InequalityType.LESS_THAN
) : ConstraintIfc

Defining Functional Constraints

class FunctionalConstraint(
    validNames: List<String>,
    val lhsFunc: ConstraintFunctionIfc,
    override val rhsValue: Double = 0.0,
    override val inequalityType: InequalityType 
     = InequalityType.LESS_THAN
) : ConstraintIfc {

Defining Response Constraints

class ResponseConstraint(
    val responseName: String,
    val rhsValue: Double,
    val inequalityType: InequalityType 
      = InequalityType.LESS_THAN,
    val target: Double = 0.0,
    val tolerance: Double = 0.0
) {

Example (r, Q) Inventory Policy

An inventory manager is interested in understanding the cost and service trade-offs related to the inventory management of computer printers. Suppose customer demand occurs according to a Poisson process at a rate of 3.6 units per month and the lead time is 0.5 months. The manager has estimated that the holding cost for the item is approximately $0.25 per unit per month. In addition, when a back-order occurs, the estimate cost will be $1.75 per unit per month. Every time that an order is placed, it costs approximately $0.15 to prepare and process the order. The inventory manager wants to find the optimal setting for the reorder point and the reorder quantity with respect to the total ordering and holding cost while ensuring a fill rate of at least 0.95. Set up a KSL simulation optimization problem for this situation.

Make Sure Controls are Defined

The model needs to have controls defined for the decision variables.

    @set:KSLControl(
        controlType = ControlType.INTEGER,
        lowerBound = 0.0
    )
    var initialReorderPoint: Int
        get() = myInitialReorderPt
        set(value) {
            setInitialPolicyParameters(value, myInitialReorderQty)
        }

    @set:KSLControl(
        controlType = ControlType.INTEGER,
        lowerBound = 1.0
    )
    var initialReorderQty: Int
        get() = myInitialReorderQty
        set(value) {
            setInitialPolicyParameters(myInitialReorderPt, value)
        }

Naming Convention for Controls and Responses

The names of the two controls will be the strings:

  • “Inventory:Item.initialReorderQty”
  • “Inventory:Item.initialReorderPoint”

The fill rate and total ordering and holding cost responses have similar naming conventions resulting in these strings:

  • “Inventory:Item:FillRate”
  • “Inventory:Item:OrderingAndHoldingCost”

Setting up the model

  • We need to know the model identifier.
  • By default, this will be the assigned the name of the model, but in general it is user assignable.
  • The basic code for building the reorder point, reorder quantity inventory model is as follows:
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
  • Thus, the model identifier will be the string “RQInventoryModel”.

KSL (r,Q) Problem Definition

val problemDefinition = ProblemDefinition(
    problemName = "InventoryProblem",
    modelIdentifier = "RQInventoryModel",
    objFnResponseName = "Inventory:Item:OrderingAndHoldingCost",
    inputNames = listOf("Inventory:Item.initialReorderQty", "Inventory:Item.initialReorderPoint"),
    responseNames = listOf("Inventory:Item:FillRate")
)
problemDefinition.inputVariable(
    name = "Inventory:Item.initialReorderQty",
    interval = Interval(1.0, 100.0),
    granularity = 1.0
)
problemDefinition.inputVariable(
    name = "Inventory:Item.initialReorderPoint",
    interval = Interval(1.0, 100.0),
    granularity = 1.0
)
problemDefinition.responseConstraint(
    name = "Inventory:Item:FillRate",
    rhsValue = 0.95,
    inequalityType = InequalityType.GREATER_THAN
)

Representing the Simulation Oracle

  • The simulation oracle is a term used to represent the concept of the simulation model as a “black-box” that accepts inputs and returns outputs.
  • To be able to define general problems and have them solved by general algorithms, a protocol needs to be established to represent the inputs, the model, and the outputs for generation, use, and consumption by optimization solvers.
  • The purpose of the ksl.simopt.evaluator package is to provide classes and interfaces that establish this interaction protocol.

SimulationOracleIfc

Simulation Evaluator

  • Implementations of the EvaluatorIfc interface translate simulation results into in solutions that solvers can use.

Model Inputs

data class ModelInputs(
    val modelIdentifier: String,
    val numReplications: Int,
    val inputs: Map<String, Double> = emptyMap(),
    val responseNames: Set<String> = emptySet(),
    val requestTime: Instant = Clock.System.now()
)

A model input is a specification for the inputs that will be used for the simulation run and the outputs that are requested.

Model Inputs and Equality

Model inputs have a very special representation for determining their equality. Two instances of the ModelInputs class are considered equal if:

  1. the modelIdentifier properties are equal, and
  2. the responseNames properties are equal (contain all the same response names), and
  3. the inputs properties are equal (contain the same (key, value) pairs)

It is important to note that two instances will be considered equal even if their number of requested replications are different. This definition of equality facilitates the caching of results by model inputs.

Model Outputs

Caching Concepts

  • If caching is available, then the results from previous samples are merged with new samples so that the cached replications can partially fill the request.
  • Suppose we have two estimates for the same response, with summary statistics (\(\bar{x}_1\), \(s^2_1\), \(n_1\)) and (\(\bar{x}_2\), \(s^2_2\), \(n_2\)).
  • Merging of the two response estimates, creates a single estimated response (\(\bar{x}\), \(s^2\), \(n\)).
  • We assume that the two estimates are from independent samples.
  • The values for \(n\) and \(\bar{x}\) are as follows:

\[ n = n_1 + n_2\\ \bar{x} = \frac{n_1\bar{x}_1+n_2\bar{x}_2}{n} \]

  • Thus, \(\bar{x}\) is the weight-average of the estimates. The computation of \(s^2\) follows the idea of a pooled variance. For the case where \(n_1 >2\) and \(n_2 > 2\), we can compute the weighted average:

\[ s^2 = \frac{(n_1-1)s^2_1+(n_2-1)s^2_2}{n-2} \]

Representing Solutions

data class Solution(
    val inputMap: InputMap,
    val estimatedObjFnc: EstimatedResponse,
    val responseEstimates: List<EstimatedResponse>,
    val evaluationNumber: Int,
    val isValid: Boolean = true
) : Comparable<Solution>, FeasibilityIfc by inputMap, 
EstimatedResponseIfc by estimatedObjFnc

Solution Feasibility

Feasibility is established with respect to the problem instance. There are four types of feasibility modeled.

  • Input range feasibility - If the input values are within the ranges defined for the input variable, then the solution is considered input range feasible.
  • Linear constraint feasibility - If the input values satisfy all linear constraints, then the solution is considered feasible with respect to the linear constraints.
  • Functional constraint feasibility - If the input values satisfy all functional constraints, then the solution is consider feasible with respect to the functional constraints.
  • Input feasible - If the input values are input range feasible, linear constraint feasible, and functional constraint feasible, then the solution is considered input feasible.

Notice that feasibility ignores the issue of feasibility with respect to the response constraints.

Summary

The ksl.simopt.evaluator package permit requests for simulation evaluations. The inputs to the simulation model are contained within the request. The simulation model is executed at the specified inputs and the requested results returned. For simulation optimization algorithms, the input-output mapping is encapsulated within instances of the Solution class.

The Solution class contains information on the following:

  • The inputs used to execute the simulation.
  • The value of the objective function estimated via simulation, the penalized objective function value, and the constraint violations.
  • The estimated responses involved in any response constraints.
  • The ability to assess the feasibility of the solution with respect to the inputs and deterministic constraints.
⌂ Index