Dynamic
This page needs work. A better introductory reference for now is the Analytica User Guide chapter Dynamic Simulation.
Dynamic(initial1, initial2, ..., initialN, expr)
The dynamic function performs dynamic simulation, calculating its value at each element of Time. Time is a special system index that is built into Analytica, and is the only index that Dynamic can operate over in this fashion. The result of Dynamic is an array indexed by the system index Time.
When the Dynamic function is used, it must appear as the topmost function in the definition. It can contain one or more initial value parameters, containing the values at @Time = 1, @Time = 2, ... @Time = N
, and then an expression that is used to compute all subsequent values. The expression can get the values of variables at previous time points by using the syntax:
ident[Time - k]
When the value for @Time = t
is being computed, X[Time - 1]
refers to the value of X
at the previous time period, i.e., at @Time = t - 1
. What is especially useful is that the value of X[Time - 1]
is allowed to depend (directly or indirectly) on the variable that is defined by the Dynamic function. This makes it possible to have cyclic loops within your model, where a variable depends on itself, as long as there is an offset to a previous time point somewhere within the dynamic loop.
The syntax X[Time - k]
is equivalent to X[@Time = @Time - k]
and Slice(X, Time, @Time - k)
. The offset «k» can be a constant, or it can be computed; however, it is not allowed to refer to a position before the beginning of Time
-- e.g., in which @Time-k < 1
. There must be at least one non-zero offset before the loop returns to the original Dynamic variable, so that no cycle occurs.
You can also make use of Subscript operations to earlier time points with Dynamic, such as in
X[Time = Ceil(Time/2)]
or equivalently, Subscript(X, Time, Time/2)
. You should beware of the difference between identifying a time point by position and identifying a time point by label (associationally). If the elements of your Time index are in increments other than 1, then X[Time - 1]
and X[@Time = @Time - 1]
refer to the previous time point, while X[Time = Time - 1]
may refer to a time point that doesn't even exist, or to a point other than the immediately previous time point.
When the identifier of a variable X
is used in the definition for Y
, and both X
and Y
belong to the same dynamic loop, then the identifier X
in Y
's definition implicitly refers to the value of X
at the current time period. Although the final value of X
may be indexed by time, the value as seen during the evaluation of Y
does not have that dimension. Because of this, you can can refer to the current time point being evaluated by simply using the identifier Time, and you can refer to the position along the Time index as @Time
. Any identifier (except those appearing in a slice or subscript operation) used in an expression is equivalent to X[Time - 0]
or Slice(X, Time, @Time)
.
If X
and Y
belong to the same dynamic loop, then the definition of Y
should never perform an operation over the Time index on the value of X
. When you write such an expression, you presumably intend to operate over the entire Time-indexed array for X
, which would thus implicitly refer to future points in time that have not yet been computed, and would therefore be disallowed. However, in reality, the use of X
in Y
's definition refers to the value of X
at the current time point, so that an expression such as Cumulate(X, Time)
would actually be cumulating a constant value over time. While that is not disallowed, it is probably not what is intended, and Analytica will issue a warning in this case. You can usually expression operations over Time directly using Dynamic, for example, instead of Cumulate(X, Time)
you would define CumX
as
Dynamic(X, Self[Time - 1] + X)
As this example shows, the keyword Self can be used to refer to the current variable that is defined by Dynamic.
The Big Dynamic Gotcha
When you refer to an identifier X
from within a dynamic loop -- and in fact, from any variable in a dynamic loop, even if the variable is not defined as Dynamic(...) -- you are implicitly referring to X[Time = Time]
. In other words, X
refers to the value of X
at the current time period. The one exception to this is if you use X
directly in a slice or subscript operation over the Time index, e.g., X[@Time = @Time - 1]
.
It is easy to forget this and make stupid mistakes. Many of these mistakes involve attempts to operate over the Time index (as mentioned previously). Some of these can be quite innocent and hard to notice, such as an attempt to get the largest time value using Max(Time)
. Because Time is only the current time point, this obtains the current time value.
You are safe in using Time in an index context -- where an index is expected. Thus, if you want the full Time array, use IndexValue(Time)
. If you want the length of the time index, use Sum(1, Time)
.
Another trick is to place your Time-operations in a separate variable outside the dynamic loop. For example:
Variable max_time := Max(Time)
Then you can use max_time from within your dynamic loop expressions without worrying about this mistake.
We also want to urge you to avoid a certain reliance on this implicit Time-slicing in certain cases, since the treatment of these cases are likely to be changed in a future Analytica release. The proposed changes are intended to eliminate the sources for the above common mistakes. If you can avoid these cases, then you will minimize the risk of your existing models "breaking" when these enhancements appear.
The cases to avoid are fairly esoteric, so we hope they won't impact too many people. However, it is good to understand where they are. Suppose Y
is a variable inside a dynamic loop, and U
is a variable that outside of Y
's dynamic loop. U
might be outside of any dynamic loop, or it may belong to a different dynamic loop -- whatever the case, Y
is not a descendant of U
. The case in question then occurs when Y
uses U
in its definition, and U
appears inside an array parameter of a function call. For example, Max(U). The distinguishing characteristic here is that when you look at the syntax alone, it is conceivable that the function might operate over the Time index. So, if the function obviously operates over the Time index, as in Max(U, Time)
, or might operate over the Time index, as in Max(U)
, then you should not rely on the fact that U
is implicitly sliced over time.
User-Defined Functions inside Dynamic Loops
new to Analytica 4.2
In the rare event that a User-Defined Function is part of a dynamic loop, then how the value for variable named in the definition is obtained depends on a number of factors. These rules are designed to allow a UDF to implement array operations across the dynamic index, while co-existing within a recurrence. The ability to embed a UDF within a dynamic loop is new to Analytica 4.2.
When a global variable identifier is evaluated from within a User-Defined Functions, the current dynamic context is dropped if the variable does not belong to the same dynamic loop as the UDF. In contrast, the dynamic context passed through to the evaluation of the variable if the variable belongs to the same dynamic loop as the UDF. The following example illustrates.
Time = 1..10
Variable V := 100*1.06^(@Time - 1)
Variable X := Dynamic(1, F(V))
Variable Y := Dynamic(0, X[Time - 1])
Function F(z) := Sum(Z + V + Y,Time)
Here V
is not part of any dynamic loop, and X → Y → F() → X
forms a dynamic loop.
Consider the evaluation of V
in X
's definition when Time = 2
. Since a variable always preserved dynamic context, V[Time = 2]
is obtained (= 106). So F(106)
is called. Inside F
, when Z + V + Y
is evaluated, these are obtained as Z = 106
, V
(indexed by Time) and Y[Time = 2] = 1
. The full time-indexed value for V
is used since it does not belong to the dynamic loop. So at Time = 2
, F
computes (106 + V, Time)
.
Reverse Dynamic
new to Analytica 4.2
In many dynamic programming applications, one starts with a known final value, then computes the value at each time point as a function of future values. This dynamic-in-reverse can be accomplished using Dynamic by specifying the recurrence as the first parameter to dynamic, followed by the final value(s), and then specifying reverse: true
.
The example model Optimal Path Dynamic Programming.ana
computes the optimal path over a finite horizon. There is a final payout in the last time period, as a function of your final state, and an action cost (a function of action and state) at each intermediate step. Dynamic programming is used to find the optimal policy and the utility at each State
x Action
x Time
point:
Decision Best_Action := ArgMax(Sxa_utility, Action)
Objective Sxa_utility :=
Dynamic(Sxa_utility[Time + 1][Action = Best_action[Time + 1]][State = Transition] - Action_cost,
Final_payout,
reverse: true)
Notice the use of [Time + 1]
rather than the [Time - 1]
that is commonly used in forward Dynamic usages.
Recurrences over non-Time indexes
By default, Dynamic works over the built-in Index, Time. You can use another index by giving its name in square brackets after Dynamic, e.g.:
Variable Remaining_Budget := Dynamic[Project](Budget, Budget[Project - 1] - Project_allocation[Project - 1])
Variable Project_Allocation := (Project_Cost <= Budget)*Project_Cost
Dynamic is optimized for the Time index, and may run substantially slower over other Indexes. Therefore, it is advisable to use it for indexes (other than time) with to short dimensions, and to use Time for your primary index for Dynamic.
You can have two (or more) dynamic loops using different indexes that intersect -- i.e. one or more variables is in both loops. The example model Dynamic on multiple indexes.ana demonstrates using an example in which Dynamic[Item] is used to allocate functions among items, then Dynamic[Time] is used to carry-over unspent funds to the next time period where the allocation re-occurs.
With intersecting dynamic loops, the result for any variable in either loop usually contains both dynamic indexes, even for variables that do not vary over one of the indexes. For a given variable, the indexes on which there is no variation may appear in some case and not in others, depending on what order variables are evaluated. The principle of array abstraction generally treats two arrays with differing dimensionality as equivalent if each array is constant on the dimensions that don't appear in the other. Thus, this variation in displayed functionality does not alter the actual value as far as array abstraction is concerned. However, if you are using intersecting dynamic loops on different dynamic indexes, you should be prepared for this seemingly unusual phenomena.
Enable comment auto-refresher