PeriodicDecisionElement

class PeriodicDecisionElement @JvmOverloads constructor(parent: ModelElement, interval: Double, name: String? = null, firstAtTimeZero: Boolean = false, reason: String = "periodic", reviewPriority: Int = KSLEvent.MEDIUM_LOW_PRIORITY, elementName: String? = null, declaration: DecisionElementBuilder.() -> Unit) : ModelElement(source)

A decision element reviewed on a fixed period, declared and scheduled in one construction.

A DecisionElement does not own its timing: a caller decides when a decision is due and calls decide. That is what makes a decision at a stockout, or at a point inside a process, expressible at all. It is also more than a modeler wants to think about when all they want is a review every thirty days — so this is that, packaged, and it is the shape most models should use.

val review = PeriodicDecisionElement(this, interval = 30.0, name = "Review") {
observe(position)
lever(this@StockRoom, 0..200, neutral = Neutral.Value(0.0)) { q -> placeOrder(q) }
reward(onHand, rate = 0.5, sense = RewardSense.COST)
policy = OrderUpTo(s = 20.0, bigS = 60.0)
}

It is a composition, not a special case. It holds an ordinary element and calls its ordinary public entry point from an ordinary event. Nothing here is reachable only from inside the library: a modeler who needs something this does not do writes their own, and PeriodicReview in the examples is the same thing with the declaration left to the caller.

What being a composition buys back

The construction guarantee. The DSL used to refuse an element that declared no timing, on the ground that it would be built, never decide, and report nothing. With timing owned by the caller that refusal had nothing to check — an element that declares none is now the ordinary shape. Here it comes back exactly: interval is a constructor argument, validated at construction, and an element built this way always has a caller. The silent no-decisions failure is unreachable.

R2b, by construction. The caller of decide warrants that the state it can observe is consistent, because the model is not guaranteed to be between events wherever a modeler chooses to call. This review runs inside an event of its own and does not change anything itself, so there is no half-finished update for it to be inside of. The caveat is the one the old scheduled epoch had too: other events at the same instant may not have run yet, so the state is consistent rather than final.

No runaway. A review that only fires on a timer cannot ask for a decision during a decision, which is the fault RunawayDecisionRequestException exists to diagnose.

Parameters

interval

time between reviews; finite and greater than zero

firstAtTimeZero

review at time zero as well as at each interval thereafter

reason

the label carried onto every transition this review produces

reviewPriority

the priority of the review event, which is what orders this review against other events at a coinciding instant — including this element's own warm-up

elementName

what to call the element, if not "<this element's name>:Decision". It exists for conversion: a model that already had a decision element should not have to rename it — and so invalidate stored trajectories, control keys and anything holding a LeverRef — merely because the review moved into a composition around it.

declaration

the element's declaration, exactly as decisionElement { } takes it

Constructors

Link copied to clipboard
constructor(parent: ModelElement, interval: Double, name: String? = null, firstAtTimeZero: Boolean = false, reason: String = "periodic", reviewPriority: Int = KSLEvent.MEDIUM_LOW_PRIORITY, elementName: String? = null, declaration: DecisionElementBuilder.() -> Unit)

Properties

Link copied to clipboard

The element this reviews.

Link copied to clipboard
@set:KSLControl(controlType = ControlType.DOUBLE, lowerBound = 4.9E-324, upperBound = 1.7976931348623157E308)
var interval: Double

How often this element is reviewed.

Functions

Link copied to clipboard
protected open override fun initialize()

This method should be overridden by subclasses that need actions performed to initialize prior to a replication. It is called once before each replication occurs if the model element wants initialization. It is called after beforeReplication() is called