-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSolveOneToOneMatching.py
145 lines (116 loc) · 5.59 KB
/
SolveOneToOneMatching.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
import jax.numpy as jnp
# import simple_pytree (used to store variables)
from simple_pytree import Pytree, dataclass, field, static_field
# import fixed-point iterator
from FixedPointJAX import FixedPointRoot
# JAX code to solve a one-to-one matching model with transferable utility
def Logit(v: jnp.ndarray, axis: int = 0, outside: bool = True) -> jnp.ndarray:
"""Calculates the logit choice probabilitie of the inside options
Inputs:
- v: match-specific payoffs
- axis: tells which dimensions to sum over
Outputs:
- choice probabilities of matching with any type.
"""
# exponentiated payoff of outside option
expV0 = jnp.where(outside, 1.0, 0.0)
# exponentiated payoffs of inside options (nominator)
nominator = jnp.exp(v)
# denominator of choice probabilities
denominator = expV0 + jnp.sum(nominator, axis=axis, keepdims=True)
return nominator / denominator
def GNLogit(v: jnp.ndarray,
degree: jnp.ndarray,
nesting: jnp.ndarray,
axis: int = 0,
outside: bool = True) -> jnp.ndarray:
"""Calculates the generalized nested logit choice probabilitie of the inside
options
Inputs:
- v: match-specific payoffs
- degree: degree the alternatives belong to the different nests
- nesting: nesting parameters
- axis: tells which dimensions to sum over
Outputs:
- choice probabilities of matching with any type.
"""
# set dimensions of nests, alternatives, and types
axisN = 2 * axis # axis for nests
axisA = 1 # axis for alternatives
# Set up functions for calculating choice probabilities
expV = degree * jnp.exp(jnp.expand_dims(v, axis=axisN) / nesting)
expV0 = jnp.where(outside, 1.0, 0.0)
expV_nest = jnp.sum(expV, axis=axisA, keepdims=True)
nominator = jnp.sum(expV * (expV_nest ** (nesting - 1.0)), axis=axisN)
denominator = expV0 + jnp.sum(jnp.squeeze(expV_nest ** nesting),
axis=axis,
keepdims=True)
return nominator / denominator
def UpdateTransfer(t_init: jnp.ndarray,
K: jnp.ndarray,
nX: jnp.ndarray,
nY: jnp.ndarray,
probX: callable,
probY: callable) -> tuple[jnp.ndarray, jnp.ndarray]:
"""Calculates excess demand and updates fixed point equation for transfers
Inputs:
- t_init: array containing initial transfers
- K: matrix describing the adjustment of the step lenght
- nX: matrix containing the distribution of workers
- nY: matrix containing the distribution of firms
- probX: workers' choice probabilities as function of transfers
- probY: firms' choice probabilities as function of transfers
Outputs:
- t_update: array containing the updated transfers
- logratio: array containing the log-ratio of excess demand
"""
# Calculate firms' demand and workers' supply
nXpX = nX * probX(t_init) # Workers' supply to firms
nYpY = nY * probY(t_init) # Firms' demand for workers
# Calculate the log-ratio of firms' demand and workers' supply
logratio = jnp.log(nYpY / nXpX)
# Update transfer
t_update = t_init + K * logratio
return t_update, logratio
def EndogenousVariables(cX: jnp.ndarray,
cY: jnp.ndarray,
probX_vX: callable,
probY_vY: callable,
exog: Pytree,
accelerator: str = "None") -> Pytree:
"""Solves the matching model for a given specification of the choice
probabilities (probX_vX) and (probY_vY) and exogenous variables (exog).
Inputs:
- cX: matrix describing the adjustment of the step lenght for workers
- cY: matrix describing the adjustment of the step lenght for firms
- probX_vX: workers' choice probabilities as function of payoffs, vX
- probY_vY: firms' choice probabilities as function of payoffs, vY
- exog: pytree containing the exogenous variables of the model
Outputs:
- endog: pytree containing the endogenous variables of the model
"""
# Redefine choice probabilities as functions of transfer
probX_T = lambda T: probX_vX((exog.utilityX + T) / exog.scaleX)
probY_T = lambda T: probY_vY((exog.utilityY - T) / exog.scaleY)
# Calculate the adjustment of the step length in the fixed point equation
K = (cX * exog.scaleX * cY * exog.scaleY) / (cX * exog.scaleX + cY * exog.scaleY)
# Set up system of fixed point equations
fxp = lambda T: UpdateTransfer(T, K, exog.nX, exog.nY, probX_T, probY_T)
# Initial guess for transfer
transfer_init = jnp.zeros((exog.typesX, exog.typesY))
@dataclass
class Endog(Pytree, mutable=True):
# Find the equilibrium transfer by fixed point iterations
transfer = FixedPointRoot(fxp, transfer_init, acceleration=accelerator)[0]
# Calculate the choice probabilities of the workers' (pX) and firms' (pY)
probX = probX_T(transfer)
probY = probY_T(transfer)
# Calculate the choice probabilities for the outside options
probX0 = 1 - jnp.sum(probX, axis=exog.axisX, keepdims=True)
probY0 = 1 - jnp.sum(probY, axis=exog.axisY, keepdims=True)
# Calculate the equilibrium distribution of the matches
matched = exog.nX * probX
# Calculate the unmatched workers and firms
unmatchedX = exog.nX * probX0
unmatchedY = exog.nY * probY0
return Endog()