def lqapprox(self, s0, x0): """ Solves discrete time continuous state/action dynamic programming model using a linear quadratic approximation Args: s0: steady-state state x0: steady-state action Returns: A LQmodel object """ assert (self.dims.ni * self.dims.nj < 2), 'Linear-Quadratic not implemented for models with discrete state or choice' s0, x0 = np.atleast_1d(s0, x0) assert s0.size == self.dims.ds, 's0 must have %d values' % self.dims.ds assert x0.size == self.dims.dx, 'x0 must have %d values' % self.dims.dx s0, x0 = s0.astype(float), x0.astype(float) s0.shape = -1, 1 x0.shape = -1, 1 delta = self.time.discount # Fix shock at mean estar = self.random.w @ self.random.e.T estar.shape = -1, 1 # Get derivatives f0, fx, fxx = self.reward(s0, x0, None, None, True) g0, gx, gxx = self.transition(s0, x0, None, None, None, estar, derivative=True) fs = jacobian(lambda y: self.reward(y.reshape((-1, 1)), x0, None, None), s0) fxs = jacobian(lambda y: self.reward(y.reshape((-1, 1)), x0, None, None, True)[1], s0) fss = hessian(lambda y: self.reward(y.reshape((-1, 1)), x0, None, None), s0) gs = jacobian(lambda y: self.transition(y.reshape((-1, 1)), x0, None, None, None, estar), s0) # Reshape to ensure conformability ds, dx = self.dims['ds', 'dx'] f0.shape = 1, 1 s0.shape = ds, 1 x0.shape = dx, 1 fs.shape = 1, ds fx.shape = 1, dx fss.shape = ds, ds fxs.shape = dx, ds fxx.shape = dx, dx g0.shape = ds, 1 gx.shape = ds, dx gs.shape = ds, ds fsx = fxs.T f0 += - fs @ s0 - fx @ x0 + 0.5 * s0.T @ fss @ s0 + s0.T @ fsx @ x0 + 0.5 * x0.T @ fxx @ x0 fs += - s0.T @ fss - x0.T @ fxs fx += - s0.T @ fsx - x0.T @ fxx g0 += - gs @ s0 - gx @ x0 return LQmodel(f0, fs, fx, fss, fsx, fxx, g0, gs, gx, delta,self.labels.s, self.labels.x)
def check_derivatives(self): ni, nj, ds, ns = self.dims['ni', 'nj', 'ds', 'ns'] nij = ni * nj idx = pd.MultiIndex.from_product([np.arange(ni), np.arange(nj)], names=['i', 'j']) F = pd.DataFrame({ 'fx': np.zeros(nij) + np.nan, 'fxx': np.zeros(nij) + np.nan}, index=idx) idy = pd.MultiIndex.from_product([['gx', 'gxx'], np.arange(ds)], names=['func', 's']) G = pd.DataFrame(np.zeros((nij,2 * ds)), index=idx, columns=idy) s = self.Value.nodes e = np.tile(self.random.e[:, 0], ns) for k, (i, j) in enumerate(indices(ni,nj).T): xl, xu = self.bounds(s, i , j) xlinf = np.isinf(xl) xuinf = np.isinf(xu) xm = (xl + xu) / 2 xm[xlinf] = xu[xlinf] xm[xuinf] = xl[xuinf] xm[xuinf & xlinf] = 0.0 f, fx, fxx = self.reward(s,xm,i,j, derivative=True) fxa = jacobian(lambda z: self.reward(s, z, i, j), xm) fxxa = hessian(lambda z: self.reward(s, z, i, j), xm) #FIXME: HESSIAN SEEMS TO BE THROWING WRONG DIMENSIONS!! F.values[k, 0] = np.linalg.norm(fx - fxa, np.inf) F.values[k, 1] = np.linalg.norm(fxx - fxxa, np.inf)
def ss2(x, r, tau): tmp = lambda x: ss(x, r, tau) return jacobian(tmp, x)[0]
def lqapprox(self, s0, x0, steady=False): assert ( self.dims.ni * self.dims.nj < 2 ), 'Linear-Quadratic not implemented for models with discrete state or choice' s0, x0 = np.atleast_1d(s0, x0) assert s0.size == self.dims.ds, 's0 must have %d values' % self.dims.ds assert x0.size == self.dims.dx, 'x0 must have %d values' % self.dims.dx s0, x0 = s0.astype(float), x0.astype(float) s0.shape = -1, 1 x0.shape = -1, 1 delta = self.time.discount # Fix shock at mean estar = self.random.w @ self.random.e.T estar.shape = -1, 1 # Get derivatives f0, fx, fxx = self.reward(s0, x0, None, None, True) g0, gx, gxx = self.transition(s0, x0, None, None, None, estar, derivative=True) fs = jacobian( lambda y: self.reward(y.reshape((-1, 1)), x0, None, None), s0) fxs = jacobian( lambda y: self.reward(y.reshape((-1, 1)), x0, None, None, True)[1], s0) fss = hessian( lambda y: self.reward(y.reshape((-1, 1)), x0, None, None), s0) gs = jacobian( lambda y: self.transition(y.reshape( (-1, 1)), x0, None, None, None, estar), s0) # Reshape to ensure conformability ds, dx = self.dims['ds', 'dx'] f0.shape = 1, 1 s0.shape = ds, 1 x0.shape = dx, 1 fs.shape = 1, ds fx.shape = 1, dx fss.shape = ds, ds fxs.shape = dx, ds fxx.shape = dx, dx g0.shape = ds, 1 gx.shape = ds, dx gs.shape = ds, ds fsx = fxs.T f0 += -fs @ s0 - fx @ x0 + 0.5 * s0.T @ fss @ s0 + s0.T @ fsx @ x0 + 0.5 * x0.T @ fxx @ x0 fs += -s0.T @ fss - x0.T @ fxs fx += -s0.T @ fsx - x0.T @ fxx g0 += -gs @ s0 - gx @ x0 # Solve Riccati equation using QZ decomposition dx2ds = dx + 2 * ds A = np.zeros((dx2ds, dx2ds)) A[:ds, :ds] = np.identity(ds) A[ds:-ds, -ds:] = -delta * gx.T A[-ds:, -ds:] = delta * gs.T B = np.zeros_like(A) B[:ds, :-ds] = np.c_[gs, gx] B[ds:-ds, :-ds] = np.c_[fsx.T, fxx] B[-ds:] = np.c_[-fss, -fsx, np.identity(ds)] S, T, Q, Z = qzordered(A, B) C = np.real(np.linalg.solve(Z[:ds, :ds].T, Z[ds:, :ds].T)).T X = C[:dx] P = C[dx:, :] # Compute steady-state state, action, and shadow price t0 = np.r_[np.c_[fsx.T, fxx, delta * gx.T], np.c_[fss, fsx, delta * gs.T - np.eye(ds)], np.c_[gs - np.eye(ds), gx, np.zeros((ds, ds))]] t1 = np.r_[-fx.T, -fs.T, -g0] t = np.linalg.solve(t0, t1) sstar, xstar, pstar = np.split(t, [ds, ds + dx]) vstar = (f0 + fs @ sstar + fx @ xstar + 0.5 * sstar.T @ fss @ sstar + sstar.T @ fsx @ xstar + 0.5 * xstar.T @ fxx @ xstar) / (1 - delta) # Compute lq-approximation optimal policy and shadow price functions at state nodes s = self.Value.nodes.T.copy() sstar = sstar.T xstar = xstar.T pstar = pstar.T s -= sstar # hopefully broadcasting works here (np.ones(ns,1),:) #todo make sure!! xlq = xstar + s @ X.T #(np.ones(1,ns),:) plq = pstar + s @ P.T #(np.ones(1,ns),:) vlq = vstar + s @ pstar.T + 0.5 * np.sum( s * (s @ P.T), axis=1, keepdims=True) self.Value[:] = vlq.T[:] self.Value_j[:] = vlq.T[:] self.Policy[:] = xlq.T[:] self.Policy_j[:] = xlq.T[:] #MAKE PANDAS DATAFRAME ni, nj, dx = self.dims['ni', 'nj', 'dx'] sr = self.Value.nodes.copy() discrete_indices = np.indices(vlq.shape)[:2].reshape(2, -1) data = np.vstack((discrete_indices, np.tile(sr, ni * nj), vlq.T.flatten())) columns = ["i", "j" ] + self.labels.s + ['value_j' if nj > 1 else 'value'] # Add continuous action if dx: xlq = np.rollaxis(xlq, -2) xlq.shape = (dx, -1) data = np.vstack((data, xlq)) columns = columns + list(self.labels.x) data = pd.DataFrame(data.T, columns=columns) # Add value if nj > 1: data['value'] = np.nan data.value[data.j == 0] = vlq.flatten() # eliminate singleton dimensions, label non-singleton dimensions if ni > 1: data['i'] = self.__as_categorical(data.i, True) else: del data['i'] if nj > 1: data['j'] = self.__as_categorical(data.j, False) else: del data['j'] if steady: ss0 = {a: b for a, b in zip(self.labels.s, sstar)} ss0.update({a: b for a, b in zip(self.labels.x, xstar)}) ss0['value'] = vstar ss0['shadow'] = pstar return (data, ss0) if steady else data