Reconstructing Figure A.1

Aizenman, Hutchison and Noy (2011) | Appendix A | Completed for Jamel Saadaoui | decimal formatting v4

Result: a close numerical reconstruction of both published panels is available. It requires an inferred objective that differs from the stated structural model. Both implementations, their exact optima, and all assumptions are provided below.

Source: Aizenman, J., Hutchison, M., and Noy, I. (2011). Inflation targeting and real exchange rates in emerging markets. World Development 39(5), 712-724, Appendix A, pp. 722-723. DOI: 10.1016/j.worlddev.2010.11.005. Based on the supplied PDF and Mario's Jamel-Aizenman.nb. Prepared 6 September 2026.

Model equations and loss function

The model is static: adjustment to the shocks takes place within the period. The four equations jointly determine output, inflation, the real exchange rate, and the real interest rate.

({{Output / IS curve, y=-βr-δe+ϵ, (A.3a)}, {Inflation / Phillips curve, π=αy-γe+η, (A.3b)}, {Real exchange rate, e=θr+ν, (A.3c)}, {Monetary policy rule, r=aπ+by+ce, (A.4)}, {Policy loss, L=V(π)+μV(y)+ϕV(e), (A.2)}})

Here y is the output deviation from its reference level, Ï is inflation, e is the real exchange rate, and r is the real interest rate. A higher e means an appreciation. All four variables are expressed as deviations from their average levels.

The shocks Îµ, Î·, and Î½ represent demand, inflation, and exchange-rate disturbances. They have zero means and are mutually uncorrelated. V(Â·) denotes variance. The positive structural parameters Î±, Î², Î³, Î´, and Î¸ describe the transmission mechanism. The coefficients a, b, and c specify the policy rule; Î¼ and Ï weight output and exchange-rate variability in the loss function.

The notebook first derives the solution to these equations. It then distinguishes the structurally correct loss from the inferred formula that reproduces the published Figure A.1.

Independent numerical reference preview. The executable cells regenerate native Mathematica figures.

1. Setup and reproducibility

Evaluate the notebook from top to bottom using Evaluation > Evaluate Notebook. It is self-contained and needs no external data or Wolfram packages. Version 4 retains the working plain-text InputForm cells and displays axis ticks and contour labels as decimals. Exact arithmetic is retained for derivations and validation. The companion .wl contains the same executable code. Output files are created in an output subfolder beside the notebook. All computations use exact rational inputs before plotting.
Validation status: Jamel's exported PDF confirms that version 3 executed in Mathematica, reproduced all four numerical benchmarks, and completed the guarded exports. Version 4 changes plot-label formatting only; its computational expressions and exact benchmark checks are unchanged. Version 4 has not been run in a native Mathematica kernel in the preparation environment. The opening reference preview was generated independently with Python.

ClearAll["Global`*"];
symbolicChecksPassed = False;
figuresReady = False;
assertTrue[test_, label_String] := If[!TrueQ[test], Print["FAILED: " <> label]; Abort[]];
baseDirectory = Quiet[Check[NotebookDirectory[], $Failed]];
If[!StringQ[baseDirectory], baseDirectory = If[StringQ[$InputFileName] && StringLength[$InputFileName] > 0, DirectoryName[ExpandFileName[$InputFileName]], Directory[]]];
outputDirectory = FileNameJoin[{baseDirectory, "output"}];
If[!DirectoryQ[outputDirectory], CreateDirectory[outputDirectory, CreateIntermediateDirectories -> True]];

2. Structural equations A.3-A.4

The variables are deviations from means. An increase in e is an appreciation. The three shocks are eps (demand), eta (inflation), and nu (exchange rate). Their covariances are zero, as required for the paper's variance formulas. Use muWeight for the output weight to avoid confusing it with the inflation shock.
First substitute e = theta*r + nu into the IS curve: y = eps - B*r - delta*nu, with B = beta + delta*theta. Substitution into the Phillips curve gives pi = alpha*eps + eta - (alpha*B + gamma*theta)*r - (alpha*delta + gamma)*nu. Substituting both expressions into the policy rule determines r and the denominator H below. The code solves the original simultaneous equations independently.

structuralEquations = {
 yy == -beta*rr - delta*ee + eps,
 pp == alpha*yy - gamma*ee + eta,
 ee == theta*rr + nu,
 rr == a*pp + b*yy + c*ee
};
modelSolution = First[Solve[structuralEquations, {yy, pp, ee, rr}]];
shockVector = {eps, eta, nu};
Bexpr = beta + delta*theta;
qexpr = b + a*alpha;
Hexpr = 1 + Bexpr*qexpr + theta*(a*gamma - c);
rateExpected = (qexpr*eps + a*eta + (c - a*(alpha*delta + gamma) - b*delta)*nu)/Hexpr;
assertTrue[FullSimplify[(rr /. modelSolution) - rateExpected] === 0, "interest-rate solution"];
residuals = FullSimplify[(Subtract @@@ structuralEquations) /. modelSolution];
assertTrue[residuals === {0, 0, 0, 0}, "four structural-equation residuals"];

3. Reduced form and variance formulas

Rows of the loading matrix are {y, pi, e}; columns are {eps, eta, nu}. Every entry is divided by H. Squaring the loadings and multiplying by the shock variances yields V(y), V(pi), and V(e).
This derivation corrects two locations in printed A.5: the output-variance expression and the common denominator require a*gamma, not a*alpha. The paper labels the inflation-shock variance V(mu), although A.3 calls that shock eta. The inflation-variance term delta*alpha is correct in the paper; Mario's transcription uses delta*a.

loadingExpected = {
 {1 + theta*(a*gamma-c), -a*Bexpr, beta*(a*gamma-c)-delta},
 {alpha*(1-c*theta)-b*gamma*theta, 1-c*theta+b*Bexpr, -(beta*(alpha*c+b*gamma)+delta*alpha+gamma)},
 {theta*qexpr, a*theta, 1+beta*qexpr}
}/Hexpr;
loadingSolved = Table[Coefficient[Expand[(Part[{yy,pp,ee},i] /. modelSolution)], Part[shockVector,j]], {i,3}, {j,3}];
assertTrue[FullSimplify[loadingSolved-loadingExpected] === ConstantArray[0,{3,3}], "complete shock-loading matrix"];
shockCovariance = DiagonalMatrix[{sigmaEps2, sigmaEta2, sigmaNu2}];
varianceVector = Together /@ Diagonal[loadingExpected . shockCovariance . Transpose[loadingExpected]];
{varY, varPi, varE} = varianceVector;
lossStructural = Together[varPi + muWeight*varY + phi*varE];
Print["Structural shock-loading matrix (rows y, pi, e; columns eps, eta, nu):"];
Print[MatrixForm[loadingExpected]];
Print["Common denominator H: ", Hexpr];

4. What reproduces the published figure?

The supplied paper does not report gamma or the welfare weight mu. Its simulation paragraph gives V(mu)=0.3, which, read literally as a shock variance, implies V(eta)=0.3. A close numerical reconstruction of Figure A.1 instead uses gamma=0.2, muWeight=0.3, and unit variances for all three shocks.
One further alteration is required: replace the inflation-shock loading 1-c*theta+b*B with 1-a*theta+b*B. This candidate reproduces the displayed loss levels, contour geometry, and both first-order-condition curves. It does NOT solve the stated structural equations. This is an inferred explanation of the figure, not verification of the authors' unavailable source code; other implementations can yield the same objective.
The notebook keeps both objectives explicit. L_R is the inferred reconstruction; L_S is the structurally correct loss under the SAME inferred calibration. A separate stated-variance option uses V(eta)=0.3. No parameter is silently changed.

loadingReconstructed = ReplacePart[loadingExpected, {2,2} -> (1-a*theta+b*Bexpr)/Hexpr];
varianceReconstructed = Together /@ Diagonal[loadingReconstructed . shockCovariance . Transpose[loadingReconstructed]];
lossReconstructed = Together[{muWeight,1,phi} . varianceReconstructed];
lossDifference = sigmaEta2*((1-a*theta+b*Bexpr)^2-(1-c*theta+b*Bexpr)^2)/Hexpr^2;
assertTrue[FullSimplify[lossReconstructed-lossStructural-lossDifference] === 0, "isolated reconstruction alteration"];
commonCalibration = {alpha->2/5, beta->3/5, delta->1/5, theta->2, gamma->1/5, muWeight->3/10, phi->1, sigmaEps2->1, sigmaNu2->1};
calibration = Join[commonCalibration, {sigmaEta2->1}];
statedVarianceCalibration = Join[commonCalibration, {sigmaEta2->3/10}];
lossR = Together[lossReconstructed /. calibration];
lossS = Together[lossStructural /. calibration];
lossStatedVariance = Together[lossStructural /. statedVarianceCalibration];
Hcal = Hexpr /. calibration;
polynomialR = (56562*a^2-3200*a*b+34590*b^2-(20590+3270*c)*a+(18420+4360*c)*b+15940-10210*c+12535*c^2)/(6250*(1+4*a/5+b-2*c)^2);
polynomialS = (31562*a^2+21800*a*b+34590*b^2+(4410-3270*c)*a+(18420-20640*c)*b+15940-35210*c+37535*c^2)/(6250*(1+4*a/5+b-2*c)^2);
assertTrue[Together[lossR-polynomialR] === 0, "reconstruction polynomial"];
assertTrue[Together[lossS-polynomialS] === 0, "structural polynomial"];
Print["Reconstructed objective: ", lossR];
Print["Correct structural objective: ", lossS];

5. Exact optimal coefficients and global checks

For either objective at fixed c, write L=(z'Qz)/(h'z)^2, with z={a,b,1}. Q is positive definite here. The Cauchy-Schwarz inequality gives L >= 1/(h'Q^(-1)h), with equality at z proportional to Q^(-1)h. Normalizing the third element to one gives exact optimal coefficients. This supplies a global check, not just a local numerical solution. All four main optima fall inside the plotted unit square, and H is positive throughout that square.
The two lines in each figure solve dL/da=0 and dL/db=0. Their intersection is the optimum. These are conditional best-response curves, not independent simulations of the shocks.

exactMinimum[objective_, cc_] := Module[{lc,n,qm,h,u,z,minimum,curvature},
 lc = Together[objective /. c->cc];
 n = Expand[Cancel[lc*(Hcal /. c->cc)^2]];
 qm = {
  {D[n,{a,2}]/2,D[n,a,b]/2,(D[n,a] /. {a->0,b->0})/2},
  {D[n,a,b]/2,D[n,{b,2}]/2,(D[n,b] /. {a->0,b->0})/2},
  {(D[n,a] /. {a->0,b->0})/2,(D[n,b] /. {a->0,b->0})/2,n /. {a->0,b->0}}
 };
 h = {4/5,1,1-2*cc};
 assertTrue[And @@ ((# > 0 & ) /@ Eigenvalues[N[qm,30]]), "positive quadratic form"];
 u = LinearSolve[qm,h]; z = u/Last[u]; minimum = 1/(h.u);
 assertTrue[FullSimplify[(lc /. {a->Part[z,1],b->Part[z,2]})-minimum] === 0, "global minimum attained"];
 assertTrue[FullSimplify[({D[lc,a],D[lc,b]} /. {a->Part[z,1],b->Part[z,2]})] === {0,0}, "zero gradient at minimum"];
 {Part[z,1],Part[z,2],minimum}
];
minimumR = exactMinimum[lossR,#]& /@ {0,-1};
minimumS = exactMinimum[lossS,#]& /@ {0,-1};
expectedR = {{51623/137804,75423/551216,735519373/494895375},{15175/45669,30803/182676,45051361/78423875}};
expectedS = {{79/235,3/20,1697899/833625},{97/156,15/104,976451/710125}};
assertTrue[minimumR === expectedR, "reconstruction rational benchmarks"];
assertTrue[minimumS === expectedS, "structural rational benchmarks"];
assertTrue[And @@ ((0 < # < 1 &) /@ Flatten[Part[Join[minimumR,minimumS],All,1;;2]]), "optima inside unit square"];
benchmarkTable = Join[{{"Objective","c","a*","b*","Minimum loss"}},
 Table[Join[{"Reconstructed",Part[{0,-1},j]},N[Part[minimumR,j],12]],{j,2}],
 Table[Join[{"Structural",Part[{0,-1},j]},N[Part[minimumS,j],12]],{j,2}]];
symbolicChecksPassed = True;
Print[Grid[benchmarkTable,Frame->All,Alignment->Left]];
Print["Apparent reconstructed loss reduction (%): ", N[100*(1-Part[minimumR,2,3]/Part[minimumR,1,3]),10]];
Print["Correct-model loss reduction at this calibration (%): ", N[100*(1-Part[minimumS,2,3]/Part[minimumS,1,3]),10]];

6. Rebuild Figure A.1 and the structural comparison

The reconstruction uses 0<=a<=1 and 0<=b<=1, with c=0 and c=-1. Its left contours begin at 1.5 in steps of 0.1; its right contours begin at 0.58 in steps of 0.02. The first-order-condition curves are calculated from the same objective as the corresponding contours. Axis ticks display one decimal place and contour labels display two decimal places. Label positions may differ from the published figure.
The reconstructed minima are (0.374612,0.136830) and (0.332282,0.168621). Under the correct structural model at the same calibration, they become (0.336170,0.150000) and (0.621795,0.144231). The apparent loss reduction changes from 61.35% to 32.49%. These are illustrative theoretical losses, not empirical estimates or consumption-equivalent welfare measures.

(* Exact tick positions; decimal text only. Model arithmetic stays exact. *)
decimalTicks = Table[{t, NumberForm[N[t], {2,1}]}, {t, Range[1,4]/5}];
panel[objective_, cc_, levels_List] := Module[{lc,ga,gb,contours,focs},
 lc = Together[objective /. c->cc];
 ga = Numerator[Together[D[lc,a]]]; gb = Numerator[Together[D[lc,b]]];
 contours = ContourPlot[Evaluate[lc],{a,0,1},{b,0,1},Contours->levels,
  ContourShading->None,ContourStyle->Directive[Black,AbsoluteThickness[0.85]],
  ContourLabels->(Text[Style[NumberForm[N[#3],{4,2},NumberPadding->{"","0"}],9,Black,Background->White],{#1,#2}]&),
  PlotPoints->100,MaxRecursion->3,Frame->{{True,False},{True,False}},
  FrameLabel->{Style["a",14,Italic],Style["b",14,Italic]},
  FrameTicks->{{decimalTicks,None},{decimalTicks,None}},
  PlotRange->{{0,1},{0,1}},PlotRangePadding->None,AspectRatio->6/5,
  BaseStyle->{FontFamily->"Palatino Linotype",FontSize->11},ImageSize->470,
  ImagePadding->{{42,10},{45,12}}];
 focs = ContourPlot[Evaluate[{ga==0,gb==0}],{a,0,1},{b,0,1},
  ContourStyle->{Directive[Black,AbsoluteThickness[1.3]],Directive[Black,AbsoluteThickness[1.3]]},
  PlotPoints->100,MaxRecursion->3,ContourShading->None];
 Labeled[Show[contours,focs],Style[Row[{"c = ",cc}],16],Bottom]
];
figureR = GraphicsRow[{panel[lossR,0,Range[15,28]/10],panel[lossR,-1,Range[58,92,2]/100]},ImageSize->1000,Spacings->20];
figureS = GraphicsRow[{panel[lossS,0,Range[205,270,5]/100],panel[lossS,-1,Range[138,178,2]/100]},ImageSize->1000,Spacings->20];
figuresReady = MatchQ[figureR,_Graphics] && MatchQ[figureS,_Graphics];
assertTrue[figuresReady,"both native figures created"];
Print["Figure A.1: inferred reconstruction of the published simulation"];
Print[figureR];
Print["Correct structural model under the same inferred calibration"];
Print[figureS];

7. Stated-variance and printed-equation diagnostics

The following comparison retains the text's stated inflation-shock variance 0.3. Gamma=0.2 and muWeight=0.3 remain explicitly inferred because the paper does not report them. A literal implementation of printed A.5 uses a*alpha in the two erroneous locations and is included only as a diagnostic. It is not used to generate the reconstructed Figure A.1.

Hprinted = 1+Bexpr*qexpr+theta*(a*alpha-c);
varYprinted = ((1+theta*(a*alpha-c))^2*sigmaEps2+(a*Bexpr)^2*sigmaEta2+(beta*(a*alpha-c)-delta)^2*sigmaNu2)/Hprinted^2;
varEprinted = varE*Hexpr^2/Hprinted^2;
varPiprinted = varPi*Hexpr^2/Hprinted^2;
lossPrinted = Together[(varPiprinted+muWeight*varYprinted+phi*varEprinted) /. statedVarianceCalibration];
Print["Correct model with stated V(eta)=0.3 (gamma and mu still inferred):"];
Print[N[exactMinimum[lossStatedVariance,#]& /@ {0,-1},12]];
Print["Literal printed A.5, constrained to the unit square:"];
Print[Table[NMinimize[{lossPrinted /. c->cc,0<=a<=1 && 0<=b<=1},{a,b},WorkingPrecision->30],{cc,{0,-1}}]];

8. Export all native Mathematica outputs

The main PNG is exported at 600 dpi; the PDF retains vector contours. Executing this section also exports the optimum table and the two explicit loss expressions. The prepared package already contains independently generated PNG/PDF versions and a CSV of graphical checkpoints used to assess the match.

If[!TrueQ[symbolicChecksPassed] || !TrueQ[figuresReady] || !StringQ[outputDirectory],
 Print["Export stopped: evaluate sections 1-6 successfully first."]; Abort[]];
If[!DirectoryQ[outputDirectory], Print["Export stopped: output directory does not exist."]; Abort[]];
exportResults = {
Export[FileNameJoin[{outputDirectory,"Figure_A1_Reconstructed.png"}],figureR,"PNG",ImageResolution->600],
Export[FileNameJoin[{outputDirectory,"Figure_A1_Reconstructed.pdf"}],figureR,"PDF"],
Export[FileNameJoin[{outputDirectory,"Figure_A1_Structural_Corrected.png"}],figureS,"PNG",ImageResolution->600],
Export[FileNameJoin[{outputDirectory,"Figure_A1_Structural_Corrected.pdf"}],figureS,"PDF"],
Export[FileNameJoin[{outputDirectory,"optimal_coefficients_mathematica.csv"}],benchmarkTable,"CSV"],
Export[FileNameJoin[{outputDirectory,"objectives.txt"}],"Reconstruction: "<>ToString[lossR,InputForm]<>"\n\nStructural: "<>ToString[lossS,InputForm],"Text"]
};
If[!AllTrue[exportResults, StringQ[#] && FileExistsQ[#]&],
 Print["Export failed: at least one requested file was not created."]; Abort[]];
Print["Figures, table, and objectives exported successfully to: ",outputDirectory];

9. Optional sensitivity analysis

This optional control changes the structurally correct model while keeping the stated structural parameters and unit demand/exchange-rate shock variances. The inflation-shock variance is an explicit control. Positive ranges preserve the maintained interpretation of parameters and loss weights. The reconstruction itself remains fixed and documented above.

Manipulate[
 Module[{lc},
 lc = Together[lossStructural /. {alpha->2/5,beta->3/5,delta->1/5,theta->2,gamma->gammaChoice,muWeight->muChoice,phi->1,sigmaEps2->1,sigmaEta2->etaVarianceChoice,sigmaNu2->1}];
 ContourPlot[Evaluate[lc /. c->cChoice],{a,0,1},{b,0,1},Contours->15,ContourShading->False,
  FrameTicks->{{decimalTicks,None},{decimalTicks,None}},
  ContourLabels->(Text[Style[NumberForm[N[#3],{4,2},NumberPadding->{"","0"}],9,Black,Background->White],{#1,#2}]&),
  FrameLabel->{"a","b"},PlotLabel->"Correct structural loss",ImageSize->600]
 ],
 {{gammaChoice,1/5,"gamma"},1/100,1,1/100},
 {{muChoice,3/10,"output weight"},0,2,1/100},
 {{etaVarianceChoice,1,"inflation-shock variance"},1/100,2,1/100},
 {{cChoice,0,"c"},-2,0,1/20},TrackedSymbols:>{gammaChoice,muChoice,etaVarianceChoice,cChoice}
]

Audit and interpretation

Audit of Mario's notebook: (1) B was entered as beta*delta*theta instead of beta+delta*theta; (2) V(mu) was entered as 1/3 instead of the printed 0.3; (3) delta*a was used instead of delta*alpha; (4) repeated substitutions and Union created two incompatible B-calibration routes; (5) the two FOC curves were absent; (6) negative gamma/mu slider ranges were allowed; (7) the final cell contains a stray multiplier s. These implementation issues are separate from the paper's own formula/calibration discrepancies. The original notebook is preserved, unchanged, in original/Mario_original.nb.
Graphical validation: 79 first-order-condition checkpoints and 10 labeled contour checkpoints were digitized from the supplied figure. Coordinate RMSE is about 0.002-0.005 on unit axes; contour-label loss RMSE is about 0.0051 in the left panel and 0.0025 in the right panel. These errors are compatible with reading a raster figure. They support the reconstruction but do not identify the authors' actual source code uniquely.
Validation files: validate_replication.py independently solves the four structural equations, checks loss/gradient formulas and exact-minimum benchmarks numerically, and recreates the figures. validation_results.json records its results. This validation is not a native Mathematica execution test.