| 1 |
# svpmc: Stochastic Volatility Inference via Population Monte Carlo |
|---|
| 2 |
# |
|---|
| 3 |
# Copyright (C) 2007-2008 by Edwin A. Suominen, http://www.eepatents.com |
|---|
| 4 |
# |
|---|
| 5 |
# This program is free software; you can redistribute it and/or modify it under |
|---|
| 6 |
# the terms of the GNU General Public License as published by the Free Software |
|---|
| 7 |
# Foundation; either version 2 of the License, or (at your option) any later |
|---|
| 8 |
# version. |
|---|
| 9 |
# |
|---|
| 10 |
# This program is distributed in the hope that it will be useful, but WITHOUT |
|---|
| 11 |
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
|---|
| 12 |
# FOR A PARTICULAR PURPOSE. See the file COPYING for more details. |
|---|
| 13 |
# |
|---|
| 14 |
# You should have received a copy of the GNU General Public License along with |
|---|
| 15 |
# this program; if not, write to the Free Software Foundation, Inc., 51 |
|---|
| 16 |
# Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA |
|---|
| 17 |
|
|---|
| 18 |
""" |
|---|
| 19 |
Model parameters |
|---|
| 20 |
""" |
|---|
| 21 |
|
|---|
| 22 |
import os.path |
|---|
| 23 |
import scipy as s |
|---|
| 24 |
from scipy import linalg |
|---|
| 25 |
from scipy.stats import distributions |
|---|
| 26 |
|
|---|
| 27 |
from twisted_goodies.pybywire.params import Parameterized |
|---|
| 28 |
|
|---|
| 29 |
|
|---|
| 30 |
SHAPE_MISMATCH = ValueError( |
|---|
| 31 |
"shape mismatch: objects cannot be broadcast to a single shape") |
|---|
| 32 |
|
|---|
| 33 |
|
|---|
| 34 |
class FlexArray(object): |
|---|
| 35 |
""" |
|---|
| 36 |
I am an array of arbitrary objects, accessible in much the same manner as the |
|---|
| 37 |
elements of a SciPy array object. Initially, all my objects are C{None}. |
|---|
| 38 |
|
|---|
| 39 |
Call an instance of me with a hash array and you'll get a new instance with |
|---|
| 40 |
just the subset of objects referenced by the array. Call the instance with |
|---|
| 41 |
a single object ID and you'll get that object, just like you'd get a |
|---|
| 42 |
numeric scalar by addressing a single element of a SciPy array. In either |
|---|
| 43 |
case, you can supply a raveled list of the new values (objects, numeric |
|---|
| 44 |
scalars, whatever) to be used instead of the instance's current objects. |
|---|
| 45 |
|
|---|
| 46 |
You can add two like-dimensioned instances of me. Each element of the sum |
|---|
| 47 |
will be the sum of the respective objects of the added L{FlexArray} |
|---|
| 48 |
instances. |
|---|
| 49 |
|
|---|
| 50 |
You can call the L{call} method on an instance of me with a method name and |
|---|
| 51 |
zero or more arguments. The result will be a numeric array of the same |
|---|
| 52 |
shape as the instance, each element of which will have the numeric result |
|---|
| 53 |
of a call of the named method on each of the corresponding objects in the |
|---|
| 54 |
instance. Any argument that is an array (or L{FlexArray} instance) of the |
|---|
| 55 |
same shape as my instance will have each the value of each element |
|---|
| 56 |
corresponding to the called object's element extracted and used as the |
|---|
| 57 |
method argument instead of the original array argument. |
|---|
| 58 |
|
|---|
| 59 |
You can get and set an attribute of an instance's objects in array fashion |
|---|
| 60 |
by simply setting or getting the named attribute of the instance. |
|---|
| 61 |
|
|---|
| 62 |
B{Caveat}: Unlike the behavior of SciPy arrays, Setting elements of a |
|---|
| 63 |
subset or raveled version of an instance of me does I{not} set the |
|---|
| 64 |
corresponding element of the original instance. |
|---|
| 65 |
""" |
|---|
| 66 |
@classmethod |
|---|
| 67 |
def concatenate(cls, FA_list): |
|---|
| 68 |
""" |
|---|
| 69 |
Returns an instance of me that is a concatentation of the instances in |
|---|
| 70 |
the supplied list I{FA_list}. Analogous to SciPy's C{concatenate} |
|---|
| 71 |
function. |
|---|
| 72 |
""" |
|---|
| 73 |
length = 0 |
|---|
| 74 |
shape = None |
|---|
| 75 |
for FA in FA_list: |
|---|
| 76 |
length += len(FA) |
|---|
| 77 |
if shape is None: |
|---|
| 78 |
shape = FA.shape |
|---|
| 79 |
elif FA.shape[1:] != shape[1:]: |
|---|
| 80 |
raise ValueError( |
|---|
| 81 |
"FlexArray dimensions must agree except for d_0") |
|---|
| 82 |
shape = list(shape) |
|---|
| 83 |
shape[0] = length |
|---|
| 84 |
newVersion = cls(*shape) |
|---|
| 85 |
k = 0 |
|---|
| 86 |
for FA in FA_list: |
|---|
| 87 |
for FA_sub in FA: |
|---|
| 88 |
newVersion[k] = FA_sub |
|---|
| 89 |
k += 1 |
|---|
| 90 |
return newVersion |
|---|
| 91 |
|
|---|
| 92 |
def __init__(self, *shape): |
|---|
| 93 |
self._shape = tuple([int(x) for x in shape]) |
|---|
| 94 |
self._N = 1 |
|---|
| 95 |
for dim in self._shape: |
|---|
| 96 |
self._N *= dim |
|---|
| 97 |
# Object list |
|---|
| 98 |
self._O = [None] * self._N |
|---|
| 99 |
# Index array |
|---|
| 100 |
self._I = s.arange(self._N).reshape(*self._shape) |
|---|
| 101 |
|
|---|
| 102 |
def __call__(self, I, valueList=None): |
|---|
| 103 |
if valueList is None: |
|---|
| 104 |
valueList = self._O |
|---|
| 105 |
if isinstance(I, int): |
|---|
| 106 |
return valueList[I] |
|---|
| 107 |
newVersion = self.__class__(*I.shape) |
|---|
| 108 |
for j, k in enumerate(I.ravel()): |
|---|
| 109 |
newVersion._O[j] = valueList[k] |
|---|
| 110 |
return newVersion |
|---|
| 111 |
|
|---|
| 112 |
def __len__(self): |
|---|
| 113 |
return self.shape[0] |
|---|
| 114 |
|
|---|
| 115 |
def __iter__(self): |
|---|
| 116 |
self._iterator = iter(self._I) |
|---|
| 117 |
return self |
|---|
| 118 |
|
|---|
| 119 |
def next(self): |
|---|
| 120 |
return self(self._iterator.next()) |
|---|
| 121 |
|
|---|
| 122 |
def __getitem__(self, key): |
|---|
| 123 |
return self(self._I[key]) |
|---|
| 124 |
|
|---|
| 125 |
def __setitem__(self, key, value): |
|---|
| 126 |
I = self._I[key] |
|---|
| 127 |
if isinstance(I, int): |
|---|
| 128 |
self._O[I] = value |
|---|
| 129 |
elif I.shape == s.shape(value): |
|---|
| 130 |
if not isinstance(value, (list, tuple)): |
|---|
| 131 |
value = value.ravel() |
|---|
| 132 |
for j, k in enumerate(I.ravel()): |
|---|
| 133 |
self._O[k] = value[j] |
|---|
| 134 |
else: |
|---|
| 135 |
raise SHAPE_MISMATCH |
|---|
| 136 |
|
|---|
| 137 |
def ravel(self): |
|---|
| 138 |
return self(self._I.ravel()) |
|---|
| 139 |
|
|---|
| 140 |
def reshape(self, *shape): |
|---|
| 141 |
return self(self._I.reshape(*shape)) |
|---|
| 142 |
|
|---|
| 143 |
def __add__(self, other): |
|---|
| 144 |
if other.shape != self.shape: |
|---|
| 145 |
raise SHAPE_MISMATCH |
|---|
| 146 |
newVersion = self(self._I) |
|---|
| 147 |
for k, thisObj in enumerate(newVersion._O): |
|---|
| 148 |
thisObj += other._O[k] |
|---|
| 149 |
return newVersion |
|---|
| 150 |
|
|---|
| 151 |
def __getattr__(self, name): |
|---|
| 152 |
""" |
|---|
| 153 |
For each of my objects, get the specified numeric attribute. |
|---|
| 154 |
|
|---|
| 155 |
Returns the result of each attribute value in an array of the same |
|---|
| 156 |
shape as my object array. |
|---|
| 157 |
|
|---|
| 158 |
If any value is not a numeric scalar, the result will be another |
|---|
| 159 |
instance of me with the results (of whatever type) as its objects. |
|---|
| 160 |
""" |
|---|
| 161 |
if name == 'shape': |
|---|
| 162 |
return self._shape |
|---|
| 163 |
x = [] |
|---|
| 164 |
allNumeric = True |
|---|
| 165 |
for k in self._I.ravel(): |
|---|
| 166 |
thisValue = getattr(self._O[k], name) |
|---|
| 167 |
if allNumeric and not isinstance(thisValue, (int, long, float)): |
|---|
| 168 |
allNumeric = False |
|---|
| 169 |
x.append(thisValue) |
|---|
| 170 |
if allNumeric: |
|---|
| 171 |
return s.array(x).reshape(*self._shape) |
|---|
| 172 |
return self(self._I, x) |
|---|
| 173 |
|
|---|
| 174 |
def __setattr__(self, name, value): |
|---|
| 175 |
""" |
|---|
| 176 |
For each of my objects, sets the specified numeric attribute to the |
|---|
| 177 |
supplied value. |
|---|
| 178 |
|
|---|
| 179 |
If the value is an array (or L{FlexArray}) with the same shape as me |
|---|
| 180 |
(not a list or tuple), each object's attribute value will be set to the |
|---|
| 181 |
value of the corresponding array element. |
|---|
| 182 |
""" |
|---|
| 183 |
if name.startswith('_'): |
|---|
| 184 |
object.__setattr__(self, name, value) |
|---|
| 185 |
else: |
|---|
| 186 |
if hasattr(value, 'shape') and value.shape == self.shape: |
|---|
| 187 |
values = value.ravel() |
|---|
| 188 |
else: |
|---|
| 189 |
values = [value] * self._N |
|---|
| 190 |
for k, thisValue in enumerate(values): |
|---|
| 191 |
setattr(self._O[k], name, thisValue) |
|---|
| 192 |
|
|---|
| 193 |
def call(self, methodName, *args, **kw): |
|---|
| 194 |
""" |
|---|
| 195 |
For each of my objects, call the method specified by I{methodName} with |
|---|
| 196 |
any further args and keywords supplied. |
|---|
| 197 |
|
|---|
| 198 |
For any argument that is an array (or L{FlexArray}) with the same shape |
|---|
| 199 |
as me (not a list or tuple), only the corresponding array element will |
|---|
| 200 |
be supplied as the argument to each object's method call. You can force |
|---|
| 201 |
an array argument to be supplied to the call in its entirety (not |
|---|
| 202 |
element-by-element) even if its shape matches mine by including its |
|---|
| 203 |
position index (with 0 as the first argument after the method name) in |
|---|
| 204 |
a sequence defined via the keyword I{arrayArgs}. For example, you can |
|---|
| 205 |
force the first argument (after the method name) to be an array |
|---|
| 206 |
argument to all method calls with C{arrayArgs=(1,)}. |
|---|
| 207 |
|
|---|
| 208 |
Returns the result of each call in an array of the same shape as my |
|---|
| 209 |
object array. The results must all be numeric scalars. |
|---|
| 210 |
|
|---|
| 211 |
If the result of any call is not a numeric scalar, the result will be |
|---|
| 212 |
another instance of me with the results (of whatever type) as its |
|---|
| 213 |
objects. |
|---|
| 214 |
""" |
|---|
| 215 |
indicesOfArrayArgs = [ |
|---|
| 216 |
k for k, arg in enumerate(args) |
|---|
| 217 |
if hasattr(arg, 'shape') and \ |
|---|
| 218 |
arg.shape == self.shape and \ |
|---|
| 219 |
k not in kw.get('arrayArgs', [])] |
|---|
| 220 |
# This is a shallow list copy |
|---|
| 221 |
theseArgs = [arg for arg in args] |
|---|
| 222 |
x = [] |
|---|
| 223 |
allNumeric = True |
|---|
| 224 |
for j in self._I.ravel(): |
|---|
| 225 |
for k in indicesOfArrayArgs: |
|---|
| 226 |
theseArgs[k] = args[k].ravel()[j] |
|---|
| 227 |
thisValue = getattr(self._O[j], methodName)(*theseArgs) |
|---|
| 228 |
if allNumeric and not isinstance(thisValue, (int, long, float)): |
|---|
| 229 |
allNumeric = False |
|---|
| 230 |
x.append(thisValue) |
|---|
| 231 |
if allNumeric: |
|---|
| 232 |
return s.array(x).reshape(*self._shape) |
|---|
| 233 |
return self(self._I, x) |
|---|
| 234 |
|
|---|
| 235 |
|
|---|
| 236 |
class ParameterContainer(Parameterized): |
|---|
| 237 |
""" |
|---|
| 238 |
I am a container for arrays of model parameters. |
|---|
| 239 |
|
|---|
| 240 |
In addition to the parameter arrays, I house as key attributes: |
|---|
| 241 |
|
|---|
| 242 |
- A scalar I{Lx} with the log-likelihood of the model's data given my |
|---|
| 243 |
parameters and some externally supplied normal variates underlying |
|---|
| 244 |
sets of simulated log-volatilites. |
|---|
| 245 |
|
|---|
| 246 |
- A scalar I{Lp} with the prior log-probability density of the |
|---|
| 247 |
parameters. |
|---|
| 248 |
|
|---|
| 249 |
- A scalar I{Lj} with the log-probability density of the proposal jump |
|---|
| 250 |
resulting in my parameters. |
|---|
| 251 |
|
|---|
| 252 |
""" |
|---|
| 253 |
keyAttrs = {'Lx':None, 'Lp':None, 'Lj':None} |
|---|
| 254 |
|
|---|
| 255 |
|
|---|
| 256 |
class Prior(Parameterized): |
|---|
| 257 |
""" |
|---|
| 258 |
You must define the name of an underlying dist object from the scipy.stats |
|---|
| 259 |
L{distributions} module via my parameter I{dname}. |
|---|
| 260 |
|
|---|
| 261 |
For all underlying dist objects, you must also specify I{loc} and I{scale} as |
|---|
| 262 |
additional parameters. Some of the objects also require I{a} and I{b}. |
|---|
| 263 |
|
|---|
| 264 |
The exception is that you can use 'constant' as a distribution name. Then |
|---|
| 265 |
the only pertinent parameter is the constant, unvarying I{loc}. |
|---|
| 266 |
|
|---|
| 267 |
""" |
|---|
| 268 |
paramNames = ['dname', 'loc', 'scale', 'a', 'b'] |
|---|
| 269 |
N_draw = 1000 |
|---|
| 270 |
|
|---|
| 271 |
class Constant(object): |
|---|
| 272 |
def __init__(self, loc): |
|---|
| 273 |
self.loc = loc |
|---|
| 274 |
def pdf(self, x): |
|---|
| 275 |
return 1.0 * (x == self.loc) |
|---|
| 276 |
def rvs(self, N): |
|---|
| 277 |
return self.loc * s.ones(N) |
|---|
| 278 |
def ppf(self, *args): |
|---|
| 279 |
return 0.0 |
|---|
| 280 |
|
|---|
| 281 |
class Jump(object): |
|---|
| 282 |
__slots__ = ('x', 'px', 'pJump') |
|---|
| 283 |
def __init__(self, x, px, pJump): |
|---|
| 284 |
self.x = x |
|---|
| 285 |
self.px = px |
|---|
| 286 |
self.pJump = pJump |
|---|
| 287 |
|
|---|
| 288 |
def _get_distObj(self): |
|---|
| 289 |
if 'dist' not in self.cache: |
|---|
| 290 |
if self.dname == 'constant': |
|---|
| 291 |
self.cache['dist'] = self.Constant(self.loc) |
|---|
| 292 |
elif not hasattr(distributions, self.dname): |
|---|
| 293 |
raise ImportError( |
|---|
| 294 |
"No distribution '%s' in scipy.stats.distributions" \ |
|---|
| 295 |
% self.dname) |
|---|
| 296 |
else: |
|---|
| 297 |
distClass = getattr(distributions, self.dname) |
|---|
| 298 |
args = [] |
|---|
| 299 |
for xName in ('a', 'b'): |
|---|
| 300 |
xValue = getattr(self, xName, None) |
|---|
| 301 |
if xValue is not None: |
|---|
| 302 |
args.append(xValue) |
|---|
| 303 |
kw = {'loc':self.loc, 'scale':self.scale} |
|---|
| 304 |
self.cache['dist'] = distClass(*args, **kw) |
|---|
| 305 |
return self.cache['dist'] |
|---|
| 306 |
distObj = property(_get_distObj) |
|---|
| 307 |
|
|---|
| 308 |
def _get_rdsObj(self): |
|---|
| 309 |
if 'rds' not in self.cache: |
|---|
| 310 |
width = s.diff(self.distObj.ppf([0.15, 0.85]))[0] |
|---|
| 311 |
self.cache['rds'] = distributions.norm(0, width) |
|---|
| 312 |
return self.cache['rds'] |
|---|
| 313 |
rdsObj = property(_get_rdsObj) |
|---|
| 314 |
|
|---|
| 315 |
def _newDraw(self): |
|---|
| 316 |
rJumps = self.rdsObj.rvs(self.N_draw) |
|---|
| 317 |
pJumps = self.rdsObj.pdf(rJumps) |
|---|
| 318 |
return {'k':-1, 'N':self.N_draw, 'R':rJumps, 'P':pJumps} |
|---|
| 319 |
|
|---|
| 320 |
def _rJumpDraw(self): |
|---|
| 321 |
if 'draw' not in self.cache: |
|---|
| 322 |
draw = self.cache['draw'] = self._newDraw() |
|---|
| 323 |
else: |
|---|
| 324 |
draw = self.cache['draw'] |
|---|
| 325 |
draw['k'] += 1 |
|---|
| 326 |
if draw['k'] >= draw['N']: |
|---|
| 327 |
draw = self.cache['draw'] = self._newDraw() |
|---|
| 328 |
k = draw['k'] |
|---|
| 329 |
return draw['R'][k], draw['P'][k] |
|---|
| 330 |
|
|---|
| 331 |
def pdf(self, x): |
|---|
| 332 |
""" |
|---|
| 333 |
Returns probability density of the supplied parameter value I{x}. |
|---|
| 334 |
""" |
|---|
| 335 |
if x.shape: |
|---|
| 336 |
return self.distObj.pdf(x) |
|---|
| 337 |
return float(self.distObj.pdf(x)) |
|---|
| 338 |
|
|---|
| 339 |
def rvs(self): |
|---|
| 340 |
""" |
|---|
| 341 |
Returns a parameter value drawn from my distribution. |
|---|
| 342 |
""" |
|---|
| 343 |
return self.distObj.rvs(1)[0] |
|---|
| 344 |
|
|---|
| 345 |
def rJump(self, startValue, v, N_attempts=200): |
|---|
| 346 |
""" |
|---|
| 347 |
Call this method with a I{startValue} and the jump deviation I{v} to |
|---|
| 348 |
use for the jump. |
|---|
| 349 |
|
|---|
| 350 |
Draws a jump value from a normal distribution with a standard deviation |
|---|
| 351 |
that is set to approximate the 70% probability width of my prior |
|---|
| 352 |
distribution, scaled by the jump deviation. (The 70% width is the +/- 1 |
|---|
| 353 |
standard deviation width of a normal distribution.) |
|---|
| 354 |
|
|---|
| 355 |
Returns a tiny object with three attributes, conveniently accessible as |
|---|
| 356 |
part of a L{FlexArray}: |
|---|
| 357 |
|
|---|
| 358 |
- B{x}: the value after the jump, and |
|---|
| 359 |
|
|---|
| 360 |
- B{px}: the probability density of I{x}, given the prior |
|---|
| 361 |
distribution. |
|---|
| 362 |
|
|---|
| 363 |
- B{pj}: the probability density of the jump, given the jump |
|---|
| 364 |
deviation used. |
|---|
| 365 |
|
|---|
| 366 |
For my Gaussian-PDF deviate generator, the probability density of a |
|---|
| 367 |
jump is inversely proportional to the scaling to its standard deviation |
|---|
| 368 |
imparted by the jump deviation. Thus, the unit-normal PDF is divided by |
|---|
| 369 |
each possible jump deviation in the I{p} array of the result. |
|---|
| 370 |
""" |
|---|
| 371 |
vInverse = 1.0 / v |
|---|
| 372 |
for k in xrange(N_attempts): |
|---|
| 373 |
rJump, pJump = self._rJumpDraw() |
|---|
| 374 |
rJump *= v |
|---|
| 375 |
pJump *= vInverse |
|---|
| 376 |
x = startValue + rJump |
|---|
| 377 |
px = self.pdf(x) |
|---|
| 378 |
if px > 0: |
|---|
| 379 |
break |
|---|
| 380 |
else: |
|---|
| 381 |
print "Oops" |
|---|
| 382 |
# Could't come up with a jump from this point, fall back to an |
|---|
| 383 |
# independent draw from the prior distribution |
|---|
| 384 |
x = self.rvs() |
|---|
| 385 |
px = self.pdf(x) |
|---|
| 386 |
pJump = 1.0 # No jump involved |
|---|
| 387 |
return self.Jump(x, px, pJump) |
|---|
| 388 |
|
|---|
| 389 |
|
|---|
| 390 |
class PriorContainer(object): |
|---|
| 391 |
""" |
|---|
| 392 |
I am a container for array-like instances of L{FlexArray} that contain |
|---|
| 393 |
L{Prior} objects for the array elements of my model's parameters. |
|---|
| 394 |
|
|---|
| 395 |
@ivar n: The number of samples. |
|---|
| 396 |
|
|---|
| 397 |
@ivar p: The number of time series. |
|---|
| 398 |
|
|---|
| 399 |
@ivar primaryParamNames: A list of the primary parameter names in sequence. |
|---|
| 400 |
|
|---|
| 401 |
@ivar derivedParamNames: A list of the derived parameter names in sequence. |
|---|
| 402 |
|
|---|
| 403 |
""" |
|---|
| 404 |
typeChecking = True |
|---|
| 405 |
N_attempts = 200 |
|---|
| 406 |
|
|---|
| 407 |
def __init__(self, p, n): |
|---|
| 408 |
self.p = p |
|---|
| 409 |
self.n = n |
|---|
| 410 |
|
|---|
| 411 |
def priorArray(self, paramName, *shape): |
|---|
| 412 |
array = getattr(self, paramName, None) |
|---|
| 413 |
if array is None: |
|---|
| 414 |
array = FlexArray(*shape) |
|---|
| 415 |
setattr(self, paramName, array) |
|---|
| 416 |
return array |
|---|
| 417 |
|
|---|
| 418 |
def covarMatrix(self, cs, cr): |
|---|
| 419 |
""" |
|---|
| 420 |
Returns a covariance matrix from the vector or sequence of I{cs} |
|---|
| 421 |
standard deviations and I{cr} cross-correlations. |
|---|
| 422 |
|
|---|
| 423 |
The I{cs} argument is in the form s0, s1,..., sp. It must have C{p} |
|---|
| 424 |
elements. |
|---|
| 425 |
|
|---|
| 426 |
The I{cr} argument must have C{0.5*(p**2 + p)} elements and is in the |
|---|
| 427 |
form r00, r01, r02,..., r0p, r11, r12,..., r1p,..., rpp |
|---|
| 428 |
""" |
|---|
| 429 |
i = 0 |
|---|
| 430 |
p = len(cs) |
|---|
| 431 |
cv = s.zeros((p, p)) |
|---|
| 432 |
for j in xrange(p): |
|---|
| 433 |
cv[j,j] = cs[j]**2 |
|---|
| 434 |
for k in xrange(j+1, p): |
|---|
| 435 |
cv[j, k] = cv[k, j] = cr[i] * cs[j] * cs[k] |
|---|
| 436 |
i += 1 |
|---|
| 437 |
return cv |
|---|
| 438 |
|
|---|
| 439 |
def derivedParams(self, paramContainer): |
|---|
| 440 |
""" |
|---|
| 441 |
Sets derived parameters in the supplied I{paramContainer} based on its |
|---|
| 442 |
primary parameters, unless there is an error in generating the derived |
|---|
| 443 |
parameters. |
|---|
| 444 |
|
|---|
| 445 |
Returns C{True} if everything went OK, C{False} if not. |
|---|
| 446 |
""" |
|---|
| 447 |
# Concurrent cross-correlations between volatility shocks and inverse |
|---|
| 448 |
# of concurrent cross-correlations between innovation shocks |
|---|
| 449 |
try: |
|---|
| 450 |
rz = linalg.cholesky(self.covarMatrix( |
|---|
| 451 |
paramContainer.fs, paramContainer.fr), lower=True) |
|---|
| 452 |
ri = linalg.inv(linalg.cholesky(self.covarMatrix( |
|---|
| 453 |
s.ones(self.p), paramContainer.cr), lower=True)) |
|---|
| 454 |
except linalg.LinAlgError: |
|---|
| 455 |
return False |
|---|
| 456 |
paramContainer.rz = rz |
|---|
| 457 |
paramContainer.ri = ri |
|---|
| 458 |
# Scaling constants for multivariate normal pdf |
|---|
| 459 |
sc = s.empty((self.p, 2)) |
|---|
| 460 |
sc[:,0] = 1.0 / s.sqrt(1.0 - paramContainer.g**2) |
|---|
| 461 |
sc[:,1] = s.log(s.sqrt(2*s.pi) / sc[:,0]) |
|---|
| 462 |
paramContainer.sc = sc |
|---|
| 463 |
return True |
|---|
| 464 |
|
|---|
| 465 |
def new(self): |
|---|
| 466 |
""" |
|---|
| 467 |
Returns a new parameter container with values intialized from a random |
|---|
| 468 |
draw of my priors. |
|---|
| 469 |
|
|---|
| 470 |
The method will generate derived parameters based on the primary |
|---|
| 471 |
parameter values, repeating as needed to get primary values that |
|---|
| 472 |
work. The derived values are included in the returned parameter |
|---|
| 473 |
container. |
|---|
| 474 |
""" |
|---|
| 475 |
paramContainer = ParameterContainer( |
|---|
| 476 |
paramNames=self.primaryParamNames+self.derivedParamNames) |
|---|
| 477 |
for k in xrange(self.N_attempts): |
|---|
| 478 |
Lp = 0 |
|---|
| 479 |
for name in self.primaryParamNames: |
|---|
| 480 |
priorFlexArray = getattr(self, name) |
|---|
| 481 |
paramArray = priorFlexArray.call('rvs') |
|---|
| 482 |
setattr(paramContainer, name, paramArray) |
|---|
| 483 |
Lp += s.log(priorFlexArray.call('pdf', paramArray)).sum() |
|---|
| 484 |
if self.derivedParams(paramContainer): |
|---|
| 485 |
break |
|---|
| 486 |
else: |
|---|
| 487 |
raise ValueError( |
|---|
| 488 |
"Couldn't generate a valid set of derived parameters") |
|---|
| 489 |
paramContainer.Lp = Lp |
|---|
| 490 |
paramContainer.Lj = 0 # There was no jump |
|---|
| 491 |
return paramContainer |
|---|
| 492 |
|
|---|
| 493 |
def proposal(self, paramContainer, v): |
|---|
| 494 |
""" |
|---|
| 495 |
Returns a new instance of L{ParameterContainer} with a random-walk |
|---|
| 496 |
proposal based on the supplied I{paramContainer}, my priors, and the |
|---|
| 497 |
supplied jump deviation I{v}. |
|---|
| 498 |
|
|---|
| 499 |
The returned parameter container object will have new values of I{Lp} |
|---|
| 500 |
and I{Lj} corresponding to the proposal. |
|---|
| 501 |
|
|---|
| 502 |
The value of I{Lj} is computed for each parameter element from a normal |
|---|
| 503 |
jump distribution whose width is proportional to the jump deviation |
|---|
| 504 |
I{v} and the width of the parameter's prior distribution. |
|---|
| 505 |
|
|---|
| 506 |
The method will generate derived parameters based on the primary |
|---|
| 507 |
parameter values of the proposal, repeating as needed to get primary |
|---|
| 508 |
values that work. The derived values are included in the returned |
|---|
| 509 |
parameter container. |
|---|
| 510 |
""" |
|---|
| 511 |
if self.typeChecking and \ |
|---|
| 512 |
not isinstance(paramContainer, ParameterContainer): |
|---|
| 513 |
raise TypeError( |
|---|
| 514 |
"You must supply a ParameterContainer as the first "+\ |
|---|
| 515 |
"argument, not a '%s'" % type(paramContainer)) |
|---|
| 516 |
newVersion = ParameterContainer( |
|---|
| 517 |
paramNames=self.primaryParamNames + self.derivedParamNames) |
|---|
| 518 |
for k in xrange(self.N_attempts): |
|---|
| 519 |
Lp, Lj = 0, 0 |
|---|
| 520 |
# Define a 1-D array holding the probability density for each jump, |
|---|
| 521 |
# joint across all parameters. Since the densities will be scaled |
|---|
| 522 |
# by the weight for each jump deviation, just start the array with |
|---|
| 523 |
# the weights. |
|---|
| 524 |
for name in self.primaryParamNames: |
|---|
| 525 |
priorsFA = getattr(self, name) |
|---|
| 526 |
startValues = getattr(paramContainer, name) |
|---|
| 527 |
jumpsFA = priorsFA.call( |
|---|
| 528 |
'rJump', startValues, v, N_attempts=self.N_attempts) |
|---|
| 529 |
setattr(newVersion, name, jumpsFA.x) |
|---|
| 530 |
Lp += s.log(jumpsFA.px).sum() |
|---|
| 531 |
Lj += s.log(jumpsFA.pJump).sum() |
|---|
| 532 |
if self.derivedParams(newVersion): |
|---|
| 533 |
break |
|---|
| 534 |
else: |
|---|
| 535 |
raise ValueError( |
|---|
| 536 |
"Couldn't generate a valid set of derived parameters") |
|---|
| 537 |
newVersion.Lp = Lp |
|---|
| 538 |
newVersion.Lj = Lj |
|---|
| 539 |
return newVersion |
|---|