(* :Title: GMM *)

(* :Author: Mark Fisher *)

(* :Summary:
This package provides functions to estimate GMM
(Generalized Method of Moments).
*)

(* :Notes to myself:
	Need to deal with
	ELMomentObject.
	Estimating functions.
*)

(* :Discussion:

This package remains in a somewhat unfinished state.

It does not establish its own Context to encapsulate its functions.

You should think of the GMM code here as an adjunct to my package
EmpiricalLikelihood, which creates the tools to deal with moment-based
estimating equations.

In particular, in order to use the GMM (the main engine here) one must use
ELMakeMomentObject and ELMakeGradientObject (or ELMakeBothObjects) to
produce the ELMomentObjects that contain the appropriate moment and
gradient information that GMM requires as input. These functions are
defined in EmpiricalLikelihood. (That package further requires my package
MetropolisMCMC.) You should read the usage notes for the functions
(and perhaps the discussion at the top of the package itself).

Here is a simple example that illustrates the basic usage.
In this example, the list of moment conditions is

{x - m, (x - m)^2 - v}

where {x} is list of symbols that represent columns in the data matrix, and
{m, v} is the list of symbols that represent the parameters to be
estimated.

Clear[m, v, x]
{mobj, gobj} = ELMakeBothObjects[{x - m, (x - m)^2 - v}, {x}, {m, v}];
data = Table[Random[], {30}, {1}]; (* a matrix with one column *)
GMM[mobj, gobj, data, {.5, .1}]

*)

(* :Mathematica Version: 5.0 or greater *)

Needs["EmpiricalLikelihood`"]
Needs["Utilities`FilterOptions`"]

GMM::usage = "GMM[mobj, gobj, data, start] computes the GMM estimator of
the parameters in the moment conditions (and gradients) in the
ELMomentObjects given the data and the starting values. GMM returns a list
containing the chi-square value and the estimated parameters. GMM takes the
option MaxFixedPointSteps. For a two-step estimator use MaxFixedPointSteps
-> 2. The fixed-point loop iterates on the pair {parvals, chol}, where
parvals are the parameter values and chol is the Cholesky decomposition of
the weighting matrix. Convergence is controlled by the option SameTest. The
default setting is SameTest -> (Max[Abs[#1[[1]] - #2[[1]]]] < 10^(-6) &),
which ignors chol."

MaxFixedPointSteps::usage = "MaxFixedPointSteps is an option for GMM which
specifies the maximum number of steps for the fixed point. The default
setting is MaxFixedPointSteps -> 100. For a two-step estimator use
MaxFixedPointSteps -> 2."

InitialWeightingMatrix::usage = "InitialWeightingMatrix is an option for
GMM. The default setting is InitialWeightingMatrix -> IdentityMatrix. The
other valid setting is Automatic which computes the \"optimal\" weighting
matrix from the starting values."

NeweyWestLagLength::usage = "NeweyWestLagLength is an option for GMM which
specifies the number of lags to include in the Newey-West correction. The
default setting is NeweyWestLagLength -> 0."

Options[GMM] = {
	InitialWeightingMatrix -> IdentityMatrix,
	MaxFixedPointSteps -> 100,
	SameTest -> (Max[Abs[#1[[1]] - #2[[1]]]] < 10^(-6) &),
	NeweyWestLagLength -> 0,
	Center -> False
	}

GMM::maxsteps = "MaxFixedPointSteps must be a positive integer."

(* returns the (uncentered) chi-square value *)
GMM[mobj_ELMomentObject, gobj_ELMomentObject, data_?(MatrixQ[#, NumericQ]&),
		start_?(VectorQ[#, NumericQ]&), opts___?OptionQ] :=
	Module[{fmopts, sameQ, maxsteps, w, lags, center, parmsyms, mnum, chol,
		fp, parvals, residual},
	fmopts = FilterOptions[FindMinimum, opts];
	{sameQ, maxsteps, w, lags, center} = {SameTest, MaxFixedPointSteps,
		InitialWeightingMatrix, NeweyWestLagLength, Center} /.
		{opts} /. Options[GMM];
	center = TrueQ[center];
	If[maxsteps < 1 || !IntegerQ[maxsteps],
		Message[GMM::maxsteps]; Return[$Failed]];
	parmsyms = ELParameters[mobj];
	mnum = ELMomentDimensions[mobj][[1]];
	Switch[w,
		IdentityMatrix,
			chol = IdentityMatrix[mnum],
		Automatic,
			vals = mobj[data, start];
			If[center, vals = vals - (Mean /@ vals)];
			w = Inverse[ GMMNeweyWest[vals, lags] ];
			chol = CholeskyDecomposition[(w + Transpose[w])/2],
		_,
			chol = CholeskyDecomposition[(w + Transpose[w])/2]
		];
	fp = FixedPointList[GMMLoop[#,
			mobj, gobj, parmsyms, data, lags, center, fmopts] &,
		{start, chol}, maxsteps, SameTest -> sameQ];
	parvals = fp[[-1, 1]];
	chol = fp[[-2, 2]]; (* use previous iteration *)
	residual = GMMResidual[{parvals, chol}, mobj, data];
	{residual.residual, parvals}
	]

(* 	Note: GMMFunction is not needed as long as the LevenbergMarquardt
	method is used and the Residual is computed. *)
GMMLoop[{parvals_, chol_},
		mobj_, gobj_, parmsyms_, data_, lags_, center_, opts___?OptionQ] :=
	Module[{parmsol, vals, w},
	parmsol = parmsyms /. Last @
		FindMinimum[Null, (* GMMFunction[{parmsyms, chol}, mobj, data] *)
			Transpose[{parmsyms, parvals}],
			Method -> {"LevenbergMarquardt",
				"Residual" -> GMMResidual[{parmsyms, chol}, mobj, data],
				"Jacobian" -> GMMJacobian[{parmsyms, chol}, gobj, data],
				"StepControl" -> {
					"TrustRegion", (* required step control *)
					(* these are the defaults; see advanced documentation *)
					"StartingScaledStepSize" -> Automatic,
					"MaxScaledStepSize" -> Infinity,
					"AcceptableStepRatio" -> 1/10000}},
				opts];
	vals = mobj[data, parmsol];
	If[center, vals = vals - (Mean /@ vals)];
	w = Inverse[ GMMNeweyWest[vals, lags] ];
	{parmsol, CholeskyDecomposition[(w + Transpose[w])/2]}
	]

GMMResidual[{parvals_?(VectorQ[#, NumericQ]&), chol_}, mobj_, data_] :=
	chol.(Tr /@ mobj[data, parvals])

GMMJacobian[{parvals_?(VectorQ[#, NumericQ]&), chol_}, gobj_, data_] :=
	chol.Map[Tr, Transpose[gobj[data, parvals], {2, 3, 1}], {2}]

GMMFunction[{parvals_?(VectorQ[#, NumericQ]&), chol_}, mobj_, data_] :=
	With[{residual = chol.(Tr /@ mobj[data, parvals])},
	(1/2) * residual.residual]

GMMNeweyWest[vals_, 0] := vals.Transpose[vals]

GMMNeweyWest[vals_, q_] := vals.Transpose[vals] +
	Sum[With[{t = (Drop[#, j]& /@ vals).Transpose[Drop[#, -j]& /@ vals]},
	(1 - j/(q + 1))(t + Transpose[t])], {j, 1, q}]

(* not a standard estimator *)
GMMOptimize[mobj_ELMomentObject, data_?(MatrixQ[#, NumericQ] &),
		start_?(VectorQ[#, NumericQ] &), opts___?OptionQ] :=
	Module[{parmsyms, fm},
	parmsyms = ELParameters[mobj];
	fm = FindMinimum[Null,
		Transpose[{parmsyms, start}],
		Evaluate[FilterOptions[FindMinimum, opts]],
		Method -> {"LevenbergMarquardt",
			"Residual" -> GMMOptimizeResidual[mobj, data, parmsyms, opts]}];
	{fm[[1]], parmsyms /. fm[[2]]}
	]

GMMOptimizeResidual[mobj_ELMomentObject, data_?(MatrixQ[#, NumericQ] &),
		parvals_?(VectorQ[#, NumericQ] &), opts___?OptionQ] :=
	Module[{center, lags, vals, g, w},
	{center, lags} = {Center, NeweyWestLagLength} /. {opts} /.
		Options[GMMChiSquareValue];
	vals = mobj[data, parvals];
	g = Tr /@ vals;
	If[TrueQ[center], vals = vals - g/Length[data]];
	w = Inverse[ GMMNeweyWest[vals, lags] ];
	Sqrt[2] * CholeskyDecomposition[(w + Transpose[w])/2].g
	]

Options[GMMCovarianceMatrix] = {UsePseudoInverse -> False}

(* can use SandwichEstimator with Likelihood -> GMM *)
GMMCovarianceMatrix[mobj_ELMomentObject, gobj_ELMomentObject,
		data_?(MatrixQ[#, NumericQ]&), parvals_?(VectorQ[#, NumericQ]&),
		opts___?OptionQ] :=
	Module[{pseudo, invfun, vals, G, Omega},
	pseudo = UsePseudoInverse /. {opts} /. Options[GMMCovarianceMatrix];
	invfun = If[TrueQ[pseudo], PseudoInverse, Inverse];
	vals = mobj[data, parvals];
	Omega = vals.Transpose[vals];
	G = Transpose[gobj[data, parvals], {1, 3, 2}].Table[1, {Length[data]}];
	invfun[G.invfun[Omega].Transpose[G]]
	]
