1_Specifying_Games.Rmd
This vignette illustrates how you can specify dynamic games with the RelationalContracts
package. We start with simple examples and move to more complex use cases.
In particular, sections 7-9 give advice on how to effectively specify state transitions for complex game with multidimensional state spaces.
Consider a simple two player repeated game. Each player \(i\) chooses an effort level \(e_i\) on a grid between 0 and 1 that directly benefits the other player. Effort involves for player \(i\) cost of \(\frac 1 2 e_i\) and grants the other player a benefit of \(e_i\):
library(RelationalContracts) effort.seq = seq(0,1,by=0.1) g = rel_game("Mutual Gift Game") %>% rel_state("x0", # Both players can pick effort # on a grid between 0 and 1 A1 = list(e1=effort.seq), A2 = list(e2=effort.seq), # Stage game payoffs pi1 = e2 - 0.5*e1^2, pi2 = e1 - 0.5*e2^2 ) # Solve SPE g %>% rel_spe(delta=0.3) %>% get_eq() %>% select(x, ae.lab)
## # A tibble: 1 x 2
## x ae.lab
## <chr> <chr>
## 1 x0 0.6 | 0.6
The function rel_game
specifies an empty game object and rel_state
specifies a state. A repeated game has just a single state, but one can also specify more general stochastic games with several states.
The parameters A1
and A2
specify the action spaces of each player. You have to provide a named list of actions for each player, here we have the actions e1
for player 1 and e2
for player 2. Note that you must choose different names for each player’s actions, i.e. you could not call both actions just e
.
The parameters pi1
and pi2
specify payoffs for both players. Payoffs will be lazily evaluated later on the corresponding action space. This means they can depend on the chosen action or parameters specified for the game or state.
Consider now the following game with two states:
library(RelationalContracts) effort.seq = seq(0,1,by=0.1) vul = 1 g = rel_game() %>% rel_state("not_vul", A1 = list(e1=effort.seq, make_vul = c(FALSE, TRUE)), A2 = list(e2=effort.seq), pi1 = e2 - 0.5*e1^2, pi2 = e1 - 0.5*e2^2 ) %>% rel_state("vul", A1 = list(e1=effort.seq), A2 = list(e2=c(-vul,effort.seq)), pi1 = e2 - 0.5*e1^2, pi2 = e1 - 0.5*pmax(e2,0)^2 ) %>% rel_transition("not_vul","vul", make_vul=TRUE,prob=1) %>% rel_transition("vul","not_vul", prob = 0.05) %>% rel_compile() # Solve SP # g %>% rel_spe(delta=0.3) %>% get_eq() %>% select(x, ae.lab)
## # A tibble: 2 x 2
## x ae.lab
## <chr> <chr>
## 1 not_vul 0.6 1 | 0.6
## 2 vul 0.9 | 0.9
We now have two states. The game starts in state “not_vul”. Here player 1 has a two dimensional actions space A1
. In addition to choosing effort levels, player 1 can choose via make_vul
whether to make herself vulnerable or not. In the state “vul”, player 2 can choose negative effort that harms player 1. We specify the state transitions via the function rel_transition
.
The command rel_transition("not_vul","vul",make_vul=TRUE,prob=1)
means that there is a transition from state “not_vul” to state “vul” with probability prob=1
if the action make_vul=TRUE
was chosen. The effort levels do not affect this transition probability.
The line rel_transition("vul","not_vul", prob = 0.05)
means that there is a transition from state “vul” to state “not_vul” with probability of 5%, irrespective of which actions have been chosen.
For cases in which no explicit transition probability is specified it is assumed that play remains in the current state. I.e. if player 1 chooses make_vul = FALSE
in stat “not_vul” the game remains in state “not_vul” even though we have not explicitly specified this.
Consider the following game specification that has 6 different states determined by increasing vulnerability of player 1:
effort.seq = seq(0,1,by=0.1) g = rel_game() %>% rel_states(x = 0:5, A1 = list(e1=effort.seq, inc_vul = c(FALSE, TRUE)), A2 = list(e2=c(-1,effort.seq)), pi1 = ifelse(e2 != -1,e2, -x/5) - 0.5*e1^2, pi2 = e1 - 0.5*pmax(e2,0)^2 ) %>% rel_transition(xs = 0:4,xd=1:5, inc_vul=TRUE,prob=1) # Solve SPE g %>% rel_spe(delta=0.3) %>% get_eq() %>% select(x, ae.lab)
## # A tibble: 6 x 2
## x ae.lab
## <int> <chr>
## 1 0 0.6 1 | 0.6
## 2 1 0.7 1 | 0.7
## 3 2 0.7 1 | 0.8
## 4 3 0.8 1 | 0.8
## 5 4 0.8 1 | 0.9
## 6 5 0.9 0 | 0.9
The function rel_states
is just a synonym for rel_state
and can be used to specify several states simultaneously. A state x
is here identified by an integer between 0 and 5 that relates to player 1’s vulnerability.
Note that the formula for pi1
refers to the current state x
.
The function rel_transition
allows to specify multiple source states xs
and multiple destination states xd
. Here choosing inc_vul=TRUE
increases the state number, as long as we have not reached yet state x=5
.
The specification above specifies the same action space for all states, even though in state x=5
the action inc_vul
has no effect.
One way to specify different action spaces for different subsets of states is to add multiple rel_states
commands as in the example below:
effort.seq = seq(0,1,by=0.1) g = rel_game() %>% rel_states(x = 0:4, A1 = list(e1=effort.seq, inc_vul = c(FALSE, TRUE)), A2 = list(e2=c(-1,effort.seq)), pi1 = ifelse(e2 != -1,e2, -x/5) - 0.5*e1^2, pi2 = e1 - 0.5*pmax(e2,0)^2 ) %>% # Specify state x=5 separately without inc_vul action rel_states(x = 5, A1 = list(e1=effort.seq), A2 = list(e2=c(-1,effort.seq)), pi1 = ifelse(e2 != -1,e2, -x/5) - 0.5*e1^2, pi2 = e1 - 0.5*pmax(e2,0)^2 ) %>% rel_transition(xs = 0:4,xd=1:5, inc_vul=TRUE,prob=1)
If you want to specify several states with different action spaces in a single call to rel_states
you should use the argument A.fun
instead of A1
and A2
. Below is an example:
effort.seq = seq(0,1,by=0.1) # Function that specifies action spaces A.fun = function(x,...) { list( A1 = list(e1=effort.seq, inc_vul = c(FALSE, if (x<5) TRUE)), A2 = list(e2=c(if (x>0) -1,effort.seq)) ) } g = rel_game() %>% rel_states(x = 0:5, A.fun = A.fun, pi1 = ifelse(e2 != -1,e2, -x/5) - 0.5*e1^2, pi2 = e1 - 0.5*pmax(e2,0)^2 ) %>% rel_transition(xs = 0:4,xd=1:5, inc_vul=TRUE,prob=1) # Solve SPE g %>% rel_spe(delta=0.3) %>% get_eq() %>% select(x, ae.lab)
## # A tibble: 6 x 2
## x ae.lab
## <int> <chr>
## 1 0 0.6 1 | 0.6
## 2 1 0.7 1 | 0.7
## 3 2 0.7 1 | 0.8
## 4 3 0.8 1 | 0.8
## 5 4 0.8 1 | 0.9
## 6 5 0.9 0 | 0.9
The function A.fun
will be called separately for each state x
and should return a list with elements A1
and A2
describing the action spaces of both players for the current state.
The argument pi.fun
of rel_states
allows to specify payoffs via a function instead of providing formulas to the arguments pi1
and pi2
. Here is an example:
effort.seq = seq(0,1,by=0.1) pi.fun = function(ax.df, ...) { mutate(ax.df, pi1 = ifelse(e2 != -1,e2, -x/5) - 0.5*e1^2, pi2 = e1 - 0.5*pmax(e2,0)^2 ) } g = rel_game() %>% rel_states(x = 0:5, A1 = list(e1=effort.seq, inc_vul = c(FALSE, TRUE)), A2 = list(e2=c(-1,effort.seq)), pi.fun = pi.fun ) %>% rel_transition(xs = 0:4,xd=1:5, inc_vul=TRUE,prob=1) # Solve SPE g %>% rel_spe(delta=0.3) %>% get_eq() %>% select(x, ae.lab)
## # A tibble: 6 x 2
## x ae.lab
## <int> <chr>
## 1 0 0.6 1 | 0.6
## 2 1 0.7 1 | 0.7
## 3 2 0.7 1 | 0.8
## 4 3 0.8 1 | 0.8
## 5 4 0.8 1 | 0.9
## 6 5 0.9 0 | 0.9
While A.fun
will be called separately for each state x
, the function pi.fun
will be called only once with an argument ax.df
(and possible some parameters specified in the game as explained in Section 11). ax.df
is a tibble that contains one row for every action profile in every state. In our example, its head and tail looks as follows:
head(ax.df,3)
# A tibble: 1,584 x 5
x .a e1 inc_vul e2
<int> <int> <dbl> <lgl> <dbl>
1 0 1 0 FALSE -1
2 0 2 0 FALSE 0
3 0 3 0 FALSE 0.1
tail(ax.df,3)
# A tibble: 3 x 5
x .a e1 inc_vul e2
<int> <int> <dbl> <lgl> <dbl>
1 5 262 1 TRUE 0.8
2 5 263 1 TRUE 0.9
3 5 264 1 TRUE 1
The column x
specifies a state, the column .a
will probably not be used, it is just a unique index of the action profile number. The other columns correspond to the action names. (If some action is not present in some state, it is filled with NA
)
Your function basically should add the columns pi1
and pi2
to each row of this data frame. You can drop some columns, but should keep x
and not change the order of rows.
As an alternative to specifying state transitions with one or several calls to rel_transition
, one can use the argument trans.fun
in the call to rel_states
. Here is an example:
trans.fun = function(ax.df, ...) { tibble(xs=0:4,xd=1:5, inc_vul=TRUE, prob=1) } g = rel_game() %>% rel_states(x = 0:5, A1 = list(e1=effort.seq, inc_vul = c(FALSE, TRUE)), A2 = list(e2=c(-1,effort.seq)), pi1 = ifelse(e2 != -1,e2, -x/5) - 0.5*e1^2, pi2 = e1 - 0.5*pmax(e2,0)^2, trans.fun = trans.fun )
trans.fun
is a function that takes the same arguments as explained in the subsection about pi.fun
above. It returns a data frame whose columns are essentially the same as the arguments of rel_transition
. Each row specifies a transition from the state in column xs
to the state in column xd
with probability in column prob
given that the actions take the values specified in additional columns.
You may add columns for all actions and set a value of NA
if this action is not relevant in a particular row. So we could also have specified the state transitions as
trans.fun = function(ax.df, ...) { tibble(xs=0:4,xd=1:5, inc_vul=TRUE,e1=NA, e2=NA, prob=1) }
In the current example, there is little value of using the trans.fun
argument instead of rel_transition
. Yet, in more complex games with multidimensional state spaces and complex state transitions trans.fun
can be more tractable. We will provide examples below.
The following examples will be variants of a dynamic Cournot duopoly with capacity investments. A state x
is characterized by a tuple (x1,x2)
that describes the production capacities of each player. In each period players simultaneously choose integer outputs q1
and q2
between 0 and their capacity and decide whether or not to invest into new capacity. A player can invest until a maximum capacity x.max
has been reached.
Product market prices in a period are given by a-q1-q2
where a
is an exogenous parameter.
The following code specifies such a Cournot duopoly:
# Exogenous paramaters x.max = 5 a = 8 i.cost = 1 # State matrix x.df = tidyr::expand_grid(x1=0:x.max, x2=0:x.max) %>% mutate(x=paste0(x1,"_",x2)) # Action space A.fun = function(x1,x2,...) { list( A1=list(q1 = 0:x1, i1=c(0, if (x1 < x.max) 1)), A2=list(q2 = 0:x2, i2=c(0, if (x2 < x.max) 1)) ) } # State transitions trans.fun = function(ax.df,...) { restore.point("trans.fun") ax.df %>% select(x,x1,x2,i1,i2) %>% unique() %>% mutate( new_x1 = pmin(x1+i1,x.max), new_x2 = pmin(x2+i2,x.max), xd = paste0(new_x1,"_",new_x2), xs=x, prob = 1 ) } g = rel_game("Cournot with Investment") %>% rel_states(x=x.df, A.fun=A.fun, pi1 = (a-(q1+q2))*q1 - i.cost*i1, pi2 = (a-(q1+q2))*q2 - i.cost*i2, trans.fun=trans.fun ) # g %>% rel_spe(delta=0.3) %>% get_eq() %>% select(x, ae.lab,U)
First note that in the call to rel_states
, the argument x
is not a vector of state names, but instead the data frame x.df
specified further above. The data frame has the columns x1
and x2
that provide additional information about each state, namely the capacities of each player. The additional column x
uniquely identifies each state as a label that just pastes x1
and x2
together.
The additional state variables x1
and x2
can be used in the formula’s for pi1
and pi2
, are passed as additional arguments to A.fun
, and are additional columns in the ax.df
argument passed to trans.fun
. You can add as many columns to such a state data frame as you like. The only requirement is that there is a column x
with unique values in each row.
The specification of A.fun
is pretty straightforward. Note how the action space depends on the current capacity of each player.
More complex is the specification of state transitions in trans.fun
. In general it is not easy to specify state transitions correctly on a first trial. I recommend to use debugging via restore points (or some other debugging approach) to build a correct trans.fun
. Let me explain, step by step, what we have done.
We start with ax.df
which contains one row for each action profile in each state. It looks as follows
> ax.df
# A tibble: 1,296 x 8
x1 x2 x .a q1 i1 q2 i2
<int> <int> <chr> <int> <int> <dbl> <int> <dbl>
1 0 0 0_0 1 0 0 0 0
2 0 0 0_0 2 0 0 0 1
3 0 0 0_0 3 0 1 0 0
4 0 0 0_0 4 0 1 0 1
5 0 1 0_1 1 0 0 0 0
6 0 1 0_1 2 0 0 1 0
7 0 1 0_1 3 0 0 0 1
8 0 1 0_1 4 0 0 1 1
9 0 1 0_1 5 0 1 0 0
10 0 1 0_1 6 0 1 1 0
# ... with 1,285 more rows
Relevant to compute state transitions are only the investment actions and the state variables. We thus select the relevant columns and reduce to unique rows. We get:
ax.df %>%
select(x,x1,x2,i1,i2) %>%
unique()
# A tibble: 121 x 5
x x1 x2 i1 i2
<chr> <int> <int> <dbl> <dbl>
1 0_0 0 0 0 0
2 0_0 0 0 0 1
3 0_0 0 0 1 0
4 0_0 0 0 1 1
5 0_1 0 1 0 0
6 0_1 0 1 0 1
# ... with 115 more rows
Starting with this data frame, we can easily compute the new capacities of both players by adding their investments to the current capacities. The new state xd
is then simply created by pasting the new capacities together. We also have to add the transition probability of prob=1
since state transitions are deterministic and set the source state column xs
just to the original state x
.
irv_joint_dist
: Cournot duopoly where investments are not always successfulConsider now a variant of our previous game in which an investment leads to a successful capacity expansion only with an exogenous success probability sp
.
Specifying state transitions becomes more complicated now. Assume both players invest, then there are 4 different possible outcomes: no investment is successful, only player 1’s investment is successful, only player 2’s investment is successful, or both investments are successful.
Relational Contracts has a helper function irv_joint_dist
that allows to compute joint distributions of independent discrete random variables using a format suitable to specify state transitions. Consider the following example.
# success probability of investment sp = 0.7 # An example row for which we want # to compute transition probabilities df = tibble(x="0_0",x1=0,x2=0,i1=1,i2=1) # Compute joint distribution of independent # random varibles irv_joint_dist(df, irv("new_x1",default=x1, irv_val(val=x1+1,prob=i1*sp) ), irv("new_x2",default=x2, irv_val(val=x2+1,prob=i2*sp) ) )
## # A tibble: 4 x 8
## x x1 x2 i1 i2 prob new_x1 new_x2
## <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 0_0 0 0 1 1 0.490 1 1
## 2 0_0 0 0 1 1 0.21 0 1
## 3 0_0 0 0 1 1 0.21 1 0
## 4 0_0 0 0 1 1 0.09 0 0
Inside the call to irv_joint_dist
we specify with irv
two discrete valued random variables new_x1
and new_x2
that shall be independently distributed from each other. Inside irv
we call irv_val
to specify values that the variable can take and their realiziation probabilities. These values and probabilities are computed using the columns of the data frame df
passed to irv_joint_dist
using lazy evaluation.
For convenience, we can also set a default value for each random variables that is realized with probability 1 minus the sum of probabilities of all explicitly specified probabilities via irv_val
. E.g. without default values, we could have equivalently specified new_x1
by
For each row of the passed data frame df
, irv_joint_dist
creates several rows: one for each possible outcome combination of the specified independent random variables. Since df
only has a single row and there are 4 combinations of new_x1
and new_x2
, we get a data frame with 4 rows. The column prob
contains the probability of the particular outcome combination.
If df
contains multiple rows, these computations will be performed separately for each row and the resulting rows are combined together. Consider this example where in one row player 1 invests and in the other not:
# success probability of investment sp = 0.7 # Two example rows for which we want # to compute transition probabiluties df = tibble(x="0_0",x1=0,x2=0,i1=c(0,1),i2=1) df
## # A tibble: 2 x 5
## x x1 x2 i1 i2
## <chr> <dbl> <dbl> <dbl> <dbl>
## 1 0_0 0 0 0 1
## 2 0_0 0 0 1 1
# Compute joint distribution of independent # random varibles irv_joint_dist(df, irv("new_x1",default=x1, irv_val(val=x1+1,prob=i1*sp) ), irv("new_x2",default=x2, irv_val(val=x2+1,prob=i2*sp) ) )
## # A tibble: 6 x 8
## x x1 x2 i1 i2 prob new_x1 new_x2
## <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 0_0 0 0 1 1 0.490 1 1
## 2 0_0 0 0 0 1 0.7 0 1
## 3 0_0 0 0 1 1 0.21 0 1
## 4 0_0 0 0 1 1 0.21 1 0
## 5 0_0 0 0 0 1 0.3 0 0
## 6 0_0 0 0 1 1 0.09 0 0
The result has now 2+4=6 rows. If player 1 does not invest (i1==0
), the variable new_x1
always is 0 and there are only two outcomes.
Note that the order of the resulting rows is not very intutive. Internally irv_joint_dist
tries to use vectorization for fast computation that sorts the rows in a peculiar way and for time reasons there is no unneccessary rearrangement afterwards.
Below is a specification of the transition function for our Cournot game using irv_joint_dist
:
sp = 0.7 trans.fun = function(ax.df,...) { restore.point("trans.fun") ax.df %>% select(x,x1,x2,i1,i2) %>% unique() %>% irv_joint_dist(df, irv("new_x1",default=x1, irv_val(val=x1+1,prob=i1*sp) ), irv("new_x2",default=x2, irv_val(val=x2+1,prob=i2*sp) ) ) %>% mutate( xd = paste0(new_x1,"_",new_x2), xs=x ) }
This code may look complicated, but specifying transition probabilities correctly is typically the most difficult task in a game specificiation. And while it requires some time to learn, in my experience, irv_joint_dist
is often quite a useful tool for this task.
irv_joint_dist
: Cournot duopoly where investments are not always successful and capacity may depreciateConsider now a variant in which existing capacity can also depricate with positive depreciation probability dp
.
The following code specifies the transition function:
trans.fun = function(ax.df,...) { restore.point("trans.fun") ax.df %>% select(x,x1,x2,i1,i2) %>% unique() %>% irv_joint_dist( irv("add_x1",default=0, irv_val(1,prob=i1*sp) ), irv("add_x2",default=0, irv_val(1,prob=i2*sp) ), irv("rem_x1",default=0, irv_val(1,prob=(x1>0)*dp) ), irv("rem_x2",default=0, irv_val(1,prob=(x2>0)*dp) ) ) %>% mutate( new_x1 = x1+add_x1 - rem_x1, new_x2 = x2+add_x2 - rem_x2, xd = paste0(new_x1,"_",new_x2), xs=x ) %>% group_by(xs,xd,i1,i2) %>% summarize(prob=sum(prob)) }
We now specify 4 independent random variables. Each new capacity now depends on two random variables describing whether or nor investment successful and whether or not there is depreciation.
To understand why the final group_by
and summarize
is neccessary let us take a look at the computation before for a single intial row:
sp = 0.7 dp = 0.1 tibble(x="1_0",x1=1,x2=0,i1=1,i2=0) %>% select(x,x1,x2,i1,i2) %>% unique() %>% irv_joint_dist( irv("add_x1",default=0, irv_val(1,prob=i1*sp) ), irv("add_x2",default=0, irv_val(1,prob=i2*sp) ), irv("rem_x1",default=0, irv_val(1,prob=(x1>0)*dp) ), irv("rem_x2",default=0, irv_val(1,prob=(x2>0)*dp) ) ) %>% mutate( new_x1 = x1+add_x1 - rem_x1, new_x2 = x2+add_x2 - rem_x2, xd = paste0(new_x1,"_",new_x2), xs=x ) %>% select(xs,xd,i1,i2,prob,add_x1,rem_x1)
## # A tibble: 4 x 7
## xs xd i1 i2 prob add_x1 rem_x1
## <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1_0 1_0 1 0 0.0700 1 1
## 2 1_0 0_0 1 0 0.03 0 1
## 3 1_0 2_0 1 0 0.63 1 0
## 4 1_0 1_0 1 0 0.27 0 0
We see that both the first and last row transist to the same state. In the first row both investment and depreciation for player 1 happens, and in the last row neither one happens. Both yield the same net result: no change of capacity. The final group_by
and summarize
command inside trans.fun
add up the probabilities of such rows with the same outcomes.
Consider the following variant of the dynamic Cournot duopoly, where we have splitted the “static” actions q1
and q2
which don’t affect the state transitions from the “dynamic” actions i1
and i2
:
x.max = 5 # State matrix x.df = tidyr::expand_grid(x1=0:x.max, x2=0:x.max) %>% mutate(x=paste0(x1,"_",x2)) # static action space static.A.fun = function(x1,x2,...) { list( A1=list(q1 = 0:x1), A2=list(q2 = 0:x2) ) } # dynamic action space A.fun = function(x1,x2,x.max,...) { list( A1=list(i1=c(0, if (x1 < x.max) 1)), A2=list(i2=c(0, if (x2 < x.max) 1)) ) } # State transitions: deterministic investments trans.fun = function(ax.df,x.max,...) { restore.point("trans.fun") ax.df %>% select(x,x1,x2,i1,i2) %>% unique() %>% mutate( new_x1 = pmin(x1+i1,x.max), new_x2 = pmin(x2+i2,x.max), xd = paste0(new_x1,"_",new_x2), xs=x, prob = 1 ) } g = rel_game("Cournot with Investment") %>% rel_param(a = 10,i.cost = 1.5,x.max=x.max) %>% rel_states(x=x.df, A.fun=A.fun, static.A.fun = static.A.fun, static.pi1 = (a-(q1+q2))*q1, static.pi2 = (a-(q1+q2))*q2, pi1 = - i.cost*i1, pi2 = - i.cost*i2, trans.fun=trans.fun ) g %>% rel_spe(delta=0.3) %>% get_eq() %>% select(x, ae.lab,U)
## x ae.lab U
## 1 0_0 0 | 0 | 1 | 1 2.835
## 2 0_1 0 | 1 | 1 | 1 10.500
## 3 0_2 0 | 2 | 1 | 0 16.450
## 4 0_3 0 | 3 | 1 | 0 20.850
## 5 0_4 0 | 4 | 0 | 0 24.000
## 6 0_5 0 | 5 | 0 | 0 25.000
## 7 1_0 1 | 0 | 1 | 1 10.500
## 8 1_1 1 | 1 | 0 | 1 16.450
## 9 1_2 1 | 2 | 0 | 0 21.000
## 10 1_3 1 | 3 | 0 | 0 24.000
## 11 1_4 1 | 4 | 0 | 0 25.000
## 12 1_5 1 | 4 | 0 | 0 25.000
## 13 2_0 2 | 0 | 0 | 1 16.450
## 14 2_1 2 | 1 | 0 | 0 21.000
## 15 2_2 2 | 2 | 0 | 0 24.000
## 16 2_3 2 | 3 | 0 | 0 25.000
## 17 2_4 2 | 3 | 0 | 0 25.000
## 18 2_5 2 | 3 | 0 | 0 25.000
## 19 3_0 3 | 0 | 0 | 1 20.850
## 20 3_1 3 | 1 | 0 | 0 24.000
## 21 3_2 3 | 2 | 0 | 0 25.000
## 22 3_3 2 | 3 | 0 | 0 25.000
## 23 3_4 3 | 2 | 0 | 0 25.000
## 24 3_5 3 | 2 | 0 | 0 25.000
## 25 4_0 4 | 0 | 0 | 0 24.000
## 26 4_1 4 | 1 | 0 | 0 25.000
## 27 4_2 3 | 2 | 0 | 0 25.000
## 28 4_3 2 | 3 | 0 | 0 25.000
## 29 4_4 2 | 3 | 0 | 0 25.000
## 30 4_5 2 | 3 | 0 | 0 25.000
## 31 5_0 5 | 0 | 0 | 0 25.000
## 32 5_1 4 | 1 | 0 | 0 25.000
## 33 5_2 3 | 2 | 0 | 0 25.000
## 34 5_3 2 | 3 | 0 | 0 25.000
## 35 5_4 2 | 3 | 0 | 0 25.000
## 36 5_5 2 | 3 | 0 | 0 25.000
If we specify static actions via static.A.fun
, orstatic.A1
and static.A2
it means that each period consists of two stages in which actions can be chosen (each preceded by a stage in which transfers can be performed). First, the static actions are chosen and then the dynamic actions. Splitting up actions in this fashion can substantially reduce numerical complexity in form of memory requirements and run time of the solution algorithms.
Note that we have also have specified here the parameters x.max
, i.cost
and a
via the function rel_param
. All the specified parameters will be passed to the different functions like trans.fun
that specify the game and can be used in expressions like pi1
that will be lazily evaluated.
There are some advantages to specify the game such that each period only a randomly selected player is able to make investment. For example, with simultaneous investment choices sometimes no pure strategy equilibria may exist. See the 2nd vignette how to implement and analyse such games with staggered moves.
So far we have specified the parameters x.max
, i.cost
and a
as global variables. Alternatively, you could also specify them in the game using the function rel_param
. Consider the following variant:
# Action space A.fun = function(x1,x2,x.max,...) { list( A1=list(q1 = 0:x1, i1=c(0, if (x1 < x.max) 1)), A2=list(q2 = 0:x2, i2=c(0, if (x2 < x.max) 1)) ) } # State transitions trans.fun = function(ax.df,x.max,...) { restore.point("trans.fun") ax.df %>% select(x,x1,x2,i1,i2) %>% unique() %>% mutate( new_x1 = pmin(x1+i1,x.max), new_x2 = pmin(x2+i2,x.max), xd = paste0(new_x1,"_",new_x2), xs=x, prob = 1 ) } # State matrix x.max = 5 x.df = tidyr::expand_grid(x1=0:x.max, x2=0:x.max) %>% mutate(x=paste0(x1,"_",x2)) g = rel_game("Cournot with Investment") %>% rel_param(a = 10,i.cost = 1.5,x.max=x.max) %>% rel_states(x=x.df, A.fun=A.fun, pi1 = (a-(q1+q2))*q1 - i.cost*i1, pi2 = (a-(q1+q2))*q2 - i.cost*i2, trans.fun=trans.fun ) g %>% rel_spe(delta=0.3) %>% get_eq() %>% select(x, ae.lab,U)
## # A tibble: 36 x 3
## x ae.lab U
## <chr> <chr> <dbl>
## 1 0_0 0 1 | 0 1 2.84
## 2 0_1 0 1 | 1 1 10.5
## 3 0_2 0 1 | 2 0 16.4
## 4 0_3 0 1 | 3 0 20.8
## 5 0_4 0 0 | 4 0 24.0
## 6 0_5 0 0 | 5 0 25
## 7 1_0 1 1 | 0 1 10.5
## 8 1_1 1 0 | 1 1 16.4
## 9 1_2 1 0 | 2 0 21
## 10 1_3 1 0 | 3 0 24.0
## # ... with 26 more rows
The specified parameters will then be passed as additional arguments to A.fun
, trans.fun
etc and will also be used when pi1
and pi2
are evaluated.
If you want to create at the same time several game obects with different parameters, it seems advisable to store parameters inside the game and not rely on global variables. Otherwise it is a matter of taste, which style you want to use.