diff --git a/lectures/_static/lecture_specific/phillips_drifts_volatilities/NEWQDATA.csv b/lectures/_static/lecture_specific/phillips_drifts_volatilities/NEWQDATA.csv new file mode 100644 index 000000000..66f11168e --- /dev/null +++ b/lectures/_static/lecture_specific/phillips_drifts_volatilities/NEWQDATA.csv @@ -0,0 +1,212 @@ +date,y3,ur,dp +1948.25,0.0025062656641603454,0.03666666666666667,0.02935221201252737 +1948.5,0.0025062656641603454,0.03766666666666667,0.008230499136515444 +1948.75,0.0028078620136382515,0.03833333333333333,-0.012371291802546718 +1949.0,0.0029335807236166023,0.04666666666666667,-0.00833338155914441 +1949.25,0.0029335807236166023,0.05866666666666666,0.0 +1949.5,0.0025565191237657547,0.067,-0.004192878260035927 +1949.75,0.0026319087604962377,0.06966666666666667,-0.008438868645864595 +1950.0,0.0026821748176371063,0.064,0.0 +1950.25,0.0028832894571892975,0.05566666666666668,0.012631746905900564 +1950.5,0.0029084344599337175,0.04633333333333334,0.01659789140903783 +1950.75,0.0032857608668388494,0.042333333333333334,0.028399474521697957 +1951.0,0.003361260221742768,0.035,0.035367143837291344 +1951.25,0.003688555441246466,0.031000000000000003,0.0 +1951.5,0.003915269551249878,0.03166666666666667,0.0038535693159899723 +1951.75,0.0038648797871807528,0.033666666666666664,0.01904819497069441 +1952.0,0.003940466330346615,0.030666666666666672,-0.0037807228399060443 +1952.25,0.003940466330346615,0.029666666666666668,0.0037807228399061523 +1952.5,0.004545568698359981,0.03233333333333333,0.0037664827954768648 +1952.75,0.004369005172500273,0.028333333333333332,0.003752349618550372 +1953.0,0.00492412822831878,0.027,-0.0037523496185503527 +1953.25,0.005505140645031492,0.025666666666666667,0.007490671729157659 +1953.5,0.005126143330987931,0.027333333333333338,0.0037243990909822727 +1953.75,0.003461943705785009,0.037,0.0 +1954.0,0.0029587282483325428,0.05266666666666667,0.0 +1954.25,0.002430894920181448,0.057999999999999996,0.0 +1954.5,0.0018032458425165654,0.05966666666666666,-0.003724399090982328 +1954.75,0.0024560172422434245,0.05333333333333334,0.0 +1955.0,0.003059330959426232,0.04733333333333334,0.0 +1955.25,0.0039908636831405,0.044,-0.0037383221106072153 +1955.5,0.004016064257028162,0.040999999999999995,0.007462721201589594 +1955.75,0.0056062548709052695,0.042333333333333334,0.0 +1956.0,0.0060615206619885065,0.04033333333333334,0.0 +1956.25,0.006542526421741357,0.042,0.011090686694158138 +1956.5,0.005808544343584243,0.04133333333333333,0.007326040092072881 +1956.75,0.007302946361118057,0.041333333333333326,0.007272759329079878 +1957.0,0.007835924311522025,0.03933333333333334,0.010810916104215456 +1957.25,0.0077343612223816915,0.041,0.007142887512380204 +1957.5,0.007962906965023775,0.042333333333333334,0.007092228309491837 +1957.75,0.009030825891731098,0.04933333333333333,0.007042282625412951 +1958.0,0.006137438374081983,0.063,0.013937507843781678 +1958.25,0.0028330032341363776,0.07366666666666667,0.0 +1958.5,0.0022801874263951305,0.07333333333333333,0.0 +1958.75,0.006618516747615599,0.06366666666666668,0.0034542348680876036 +1959.0,0.007100055390502957,0.058333333333333334,0.0 +1959.25,0.00742979473617944,0.051000000000000004,0.0034423441909729197 +1959.5,0.008064516129032251,0.05266666666666667,0.006849341845574768 +1959.75,0.010228564212653168,0.056,0.0034071583216141346 +1960.0,0.010994565904208331,0.051333333333333335,0.0 +1960.25,0.008140736446808017,0.052333333333333336,0.006779686985378769 +1960.5,0.00578325370882582,0.05533333333333334,0.0 +1960.75,0.00578325370882582,0.06266666666666666,0.0067340321813441194 +1961.0,0.005631536604987941,0.06799999999999999,0.0 +1961.25,0.005757964345880051,0.07,0.0 +1961.5,0.005631536604987941,0.06766666666666667,0.006688988150796489 +1961.75,0.00578325370882582,0.062000000000000006,0.0 +1962.0,0.00684655658477662,0.056333333333333326,0.006644542718668511 +1962.25,0.006871900722430491,0.05533333333333334,0.0 +1962.5,0.00735368187770713,0.05566666666666668,0.006600684031352093 +1962.75,0.0068972461360319315,0.05533333333333334,0.0 +1963.0,0.00732831348057128,0.05766666666666667,0.0032840752011900187 +1963.25,0.007302946361118057,0.05733333333333334,0.0032733253449691085 +1963.5,0.00801370898644227,0.055,0.0032626456348163694 +1963.75,0.008700037826251394,0.05566666666666666,0.0064935293105483115 +1964.0,0.008878127522195411,0.05466666666666666,0.0 +1964.25,0.00875091418051599,0.05199999999999999,0.00323102058144654 +1964.5,0.008725475361880397,0.05,0.003220614700042157 +1964.75,0.009005372953611035,0.049666666666666665,0.006410278360919019 +1965.0,0.009616598096872764,0.048999999999999995,0.0 +1965.25,0.009922488449011446,0.04666666666666666,0.00953902304675891 +1965.5,0.009693053311793376,0.04366666666666667,0.0 +1965.75,0.010177538702426991,0.041,0.009448889197932289 +1966.0,0.011608204142535605,0.03866666666666667,0.009360442759563893 +1966.25,0.011684961303050079,0.03833333333333333,0.00619197024792098 +1966.5,0.012145748987854255,0.03766666666666667,0.012270092591814401 +1966.75,0.013556315722792478,0.037000000000000005,0.003044142381228273 +1967.0,0.011940902651285112,0.03833333333333333,0.003034903695154111 +1967.25,0.009693053311793376,0.03833333333333333,0.009049835519917856 +1967.5,0.010636953940220906,0.03799999999999999,0.008968669982760536 +1967.75,0.011531458628363245,0.039,0.011834457647002798 +1968.0,0.012658227848101111,0.03733333333333333,0.008784829555732811 +1968.25,0.013633368810501256,0.035666666666666666,0.011594332780919446 +1968.5,0.01345359649345057,0.035333333333333335,0.011461443519006598 +1968.75,0.013556315722792478,0.033999999999999996,0.014144507386164743 +1969.0,0.015589295688823501,0.033999999999999996,0.013947227480850552 +1969.25,0.015511944959252677,0.034333333333333334,0.013755375068485419 +1969.5,0.01781170483460559,0.035666666666666666,0.013568729206068796 +1969.75,0.01781170483460559,0.035666666666666666,0.016043124840575684 +1970.0,0.02006987478642297,0.041666666666666664,0.015789801732635073 +1970.25,0.01654425779562385,0.04766666666666666,0.012970350442627405 +1970.5,0.016389277093126653,0.051666666666666666,0.010256500167189282 +1970.75,0.014996574386561434,0.058333333333333334,0.01519016549397502 +1971.0,0.011224592982101322,0.059333333333333335,0.005012541823544414 +1971.25,0.009744029888423267,0.05900000000000001,0.01242251999855711 +1971.5,0.013684744044602182,0.060333333333333336,0.00738010729762246 +1971.75,0.01127572432623758,0.059333333333333335,0.0073260400920731016 +1972.0,0.00852201099288985,0.05766666666666667,0.007272759329079657 +1972.25,0.009361830982361541,0.057,0.007220247973487097 +1972.5,0.010049997474875116,0.05566666666666666,0.00954661188357991 +1972.75,0.011992106461569563,0.05366666666666666,0.009456335242035444 +1973.0,0.013710433614638085,0.04933333333333333,0.02095536517598809 +1973.25,0.01589881647787883,0.04933333333333333,0.018265347977293404 +1973.5,0.020434194749866075,0.047999999999999994,0.022372297754532984 +1973.75,0.018381791333570874,0.04766666666666666,0.024044874254002868 +1974.0,0.01980980547127964,0.051333333333333335,0.03188367840522198 +1974.25,0.02126790410294377,0.052,0.024794658613216274 +1974.5,0.01923811950566945,0.056333333333333326,0.032131278182793196 +1974.75,0.0190044326692822,0.066,0.02536721387842295 +1975.0,0.01589881647787883,0.08266666666666665,0.01719240054037277 +1975.25,0.014224498592763535,0.08866666666666667,0.013170463189745227 +1975.5,0.015563510803056957,0.08466666666666667,0.020352228848898517 +1975.75,0.015125367982945948,0.083,0.01814931850567727 +1976.0,0.012325057575987675,0.07733333333333334,0.007168489478612497 +1976.25,0.012299438173811739,0.07566666666666667,0.01242251999855711 +1976.5,0.013248220482812822,0.07733333333333334,0.015748356968139112 +1976.75,0.012453174040700565,0.07766666666666668,0.01379332213233577 +1977.0,0.011684961303050079,0.075,0.020339684237122783 +1977.25,0.011480301421129768,0.07133333333333333,0.014987790965491708 +1977.5,0.013145563688862882,0.06899999999999999,0.013136477905369981 +1977.75,0.01564086938858411,0.06666666666666667,0.016181582851241695 +1978.0,0.016363451570281518,0.06333333333333334,0.01750243564977276 +1978.25,0.015976226156307893,0.06,0.024923408452456934 +1978.5,0.017837604010280117,0.060333333333333336,0.022814677766171264 +1978.75,0.020382133108849176,0.059,0.020834086902842053 +1979.0,0.023934468194035663,0.05866666666666667,0.02902961467511403 +1979.25,0.02422287089670716,0.056999999999999995,0.03237439665901583 +1979.5,0.023646227863650227,0.05866666666666667,0.030015895940265568 +1979.75,0.030131341746072682,0.05966666666666666,0.03304993467255219 +1980.0,0.030927835051546504,0.063,0.04076997756271521 +1980.25,0.03412616339193386,0.07333333333333333,0.029522439266321834 +1980.5,0.02056437209777018,0.07666666666666666,0.0168273201325253 +1980.75,0.029919151346619266,0.074,0.02936206233684953 +1981.0,0.039015013766948936,0.07433333333333333,0.025144181801025164 +1981.25,0.03543786078537958,0.074,0.021217993094845188 +1981.5,0.03882612647708106,0.07400000000000001,0.02832433357714079 +1981.75,0.03503596749987059,0.08233333333333334,0.01068386230831261 +1982.0,0.031672340864541404,0.08833333333333333,0.006355953600698727 +1982.25,0.03279111799638512,0.09433333333333335,0.023996978311350333 +1982.5,0.02920365367297051,0.099,0.007190580545354209 +1982.75,0.019653827525555112,0.10666666666666667,0.0 +1983.0,0.020043861886061,0.10366666666666667,0.004085807522580354 +1983.25,0.020955103499323613,0.10133333333333333,0.01316474709121107 +1983.5,0.023227258774173754,0.09366666666666666,0.010010093595100425 +1983.75,0.022076860179885527,0.08533333333333334,0.00991088389945396 +1984.0,0.02275632830478136,0.07866666666666668,0.014684551682921099 +1984.25,0.024826420025108265,0.07433333333333333,0.007744472395477762 +1984.5,0.025956704627064786,0.07433333333333333,0.009597002641009484 +1984.75,0.024957720494029667,0.07300000000000001,0.007611835039630074 +1985.0,0.019783805833163415,0.07233333333333333,0.012246973609973294 +1985.25,0.02027802576202009,0.073,0.006532921041623095 +1985.5,0.01801893515219377,0.07200000000000001,0.005565877077444956 +1985.75,0.0182262498727217,0.07033333333333334,0.012867824611392983 +1986.0,0.017993026747766816,0.07033333333333334,-0.0036596564175303986 +1986.25,0.015383053256841084,0.07166666666666667,0.002745997148855844 +1986.5,0.014790572595580587,0.06966666666666667,0.005469475804535255 +1986.75,0.013119902740489353,0.06833333333333333,0.007246408520767253 +1987.0,0.013761816661175486,0.06599999999999999,0.013447087431214033 +1987.25,0.014301653311694862,0.06266666666666666,0.011509644542653354 +1987.5,0.014430270599274708,0.06,0.010507977598415165 +1987.75,0.015563510803056957,0.058333333333333334,0.0078091503141649475 +1988.0,0.01473908521271472,0.057,0.007748639716761098 +1988.25,0.014996574386561434,0.05466666666666666,0.012782449286924565 +1988.5,0.017112924962493947,0.05466666666666666,0.012621118313214792 +1988.75,0.01871896090665981,0.05333333333333334,0.009983443984183205 +1989.0,0.021111479845812076,0.052000000000000005,0.012340757192412188 +1989.25,0.022102976874920133,0.05233333333333333,0.014610649517434854 +1989.5,0.020095889013567403,0.05233333333333333,0.0064257249209704916 +1989.75,0.01947191354878175,0.05366666666666667,0.01193806458122503 +1990.0,0.01947191354878175,0.053,0.01725533009076184 +1990.25,0.01980980547127964,0.05333333333333334,0.010827638652063393 +1990.5,0.019419950048422452,0.057,0.01980262729617973 +1990.75,0.01825217014993763,0.061333333333333344,0.012739025777429932 +1991.0,0.015795621920869474,0.066,0.004457659684518188 +1991.25,0.014327374160010287,0.06833333333333333,0.008856146443710062 +1991.5,0.014147355610770296,0.06866666666666667,0.007320676910848397 +1991.75,0.012632591579960106,0.07100000000000001,0.008714652102443909 +1992.0,0.009591115598182709,0.07366666666666667,0.006486509229606632 +1992.25,0.009463722397476282,0.076,0.007158226700170449 +1992.5,0.008089921621008633,0.07633333333333332,0.007107350458652018 +1992.75,0.007201490658206078,0.07366666666666667,0.008462673918733728 +1993.0,0.007556675062972307,0.07133333333333333,0.006997929187644637 +1993.25,0.007226852667892247,0.07066666666666667,0.006949298293205944 +1993.5,0.00765820233776715,0.06799999999999999,0.0048359334314163165 +1993.75,0.007607436142878621,0.06633333333333333,0.008919441637069146 +1994.0,0.007505919097274605,0.06566666666666666,0.0054496047675644645 +1994.25,0.00928542591844983,0.062000000000000006,0.0054200674693393345 +1994.5,0.010943462986832353,0.06,0.009414998934601883 +1994.75,0.012530059486141054,0.056333333333333326,0.0053404666313172835 +1995.0,0.014481726647898707,0.05466666666666666,0.0072968814642759275 +1995.25,0.014327374160010287,0.056666666666666664,0.00789997525315593 +1995.5,0.013736124486795953,0.056666666666666664,0.00457966125902451 +1995.75,0.01337657073368459,0.05566666666666666,0.005857485019579088 +1996.0,0.012658227848101111,0.05533333333333334,0.009686869418221236 +1996.25,0.012530059486141054,0.055,0.007682496172016431 +1996.5,0.013042927694060902,0.05266666666666667,0.006357300495466498 +1996.75,0.012632591579960106,0.05333333333333334,0.008832864998508664 +1997.0,0.012735144441349933,0.05266666666666667,0.004387346429626051 +1997.25,0.013068584743187062,0.05,0.002498439775598719 +1997.5,0.012786428661855975,0.048666666666666664,0.006218925515656017 +1997.75,0.012581322937498474,0.04666666666666667,0.003712875552426268 +1998.0,0.012760785902369731,0.04666666666666667,0.0018512809977230173 +1998.25,0.012530059486141054,0.044333333333333336,0.004306373926687039 +1998.5,0.012555690562980892,0.045,0.0036764747293086273 +1998.75,0.00999899000101001,0.044333333333333336,0.006097579868118538 +1999.0,0.010969013799727145,0.043,0.003034903695154111 +1999.25,0.010841272649162237,0.04300000000000001,0.006644542718668511 +1999.5,0.011505879377923911,0.042333333333333334,0.010182777413856646 +1999.75,0.012299438173811739,0.040999999999999995,0.006534029780648527 +2000.0,0.013479274348839443,0.04033333333333333,0.013525639893887215 +2000.25,0.014353096312826441,0.04033333333333333,0.005824128285988713 +2000.5,0.015125367982945948,0.04,0.008097210232619303 +2000.75,0.015511944959252677,0.03966666666666667,0.005743841179252093 diff --git a/lectures/_static/quant-econ.bib b/lectures/_static/quant-econ.bib index e5138d638..8174125a7 100644 --- a/lectures/_static/quant-econ.bib +++ b/lectures/_static/quant-econ.bib @@ -4104,3 +4104,680 @@ @article{Campbell1987 pages = {1249--1273}, year = {1987} } + +@book{Sargent1999, + author = {Sargent, Thomas J.}, + title = {The Conquest of American Inflation}, + publisher = {Princeton University Press}, + address = {Princeton, New Jersey}, + year = {1999} +} + +@article{Phelps1967, + author = {Phelps, Edmund S.}, + title = {Phillips Curves, Expectations of Inflation and Optimal Unemployment over Time}, + journal = {Economica}, + volume = {34}, + number = {135}, + pages = {254--281}, + year = {1967} +} + +@article{KingWatson1994, + author = {King, Robert G. and Watson, Mark W.}, + title = {The Post-War {U.S.} Phillips Curve: A Revisionist Econometric History}, + journal = {Carnegie-Rochester Conference Series on Public Policy}, + volume = {41}, + pages = {157--219}, + year = {1994} +} + +@book{KushnerClark1978, + author = {Kushner, Harold J. and Clark, Dean S.}, + title = {Stochastic Approximation Methods for Constrained and Unconstrained Systems}, + publisher = {Springer-Verlag}, + address = {New York}, + year = {1978} +} + +@article{SamuelsonSolow1960, + author = {Samuelson, Paul A. and Solow, Robert M.}, + title = {Analytical Aspects of Anti-Inflation Policy}, + journal = {American Economic Review}, + volume = {50}, + number = {2}, + pages = {177--194}, + year = {1960} +} + +@article{BaxterKing1999, + author = {Baxter, Marianne and King, Robert G.}, + title = {Measuring Business Cycles: Approximate Band-Pass Filters for Economic Time Series}, + journal = {Review of Economics and Statistics}, + volume = {81}, + number = {4}, + pages = {575--593}, + year = {1999} +} + +@article{Sims1988, + author = {Sims, Christopher A.}, + title = {Projecting Policy Effects with Statistical Models}, + journal = {Revista de An\'alisis Econ\'omico}, + volume = {3}, + number = {1}, + pages = {3--20}, + year = {1988} +} + +@phdthesis{Chung1990, + author = {Chung, Heetaik}, + title = {Did Policy Makers Really Believe in the Phillips Curve? An Econometric Test}, + school = {University of Minnesota}, + year = {1990} +} + +@article{CooleyPrescott1973, + author = {Cooley, Thomas F. and Prescott, Edward C.}, + title = {An Adaptive Regression Model}, + journal = {International Economic Review}, + volume = {14}, + number = {2}, + pages = {364--371}, + year = {1973} +} + +@article{HansenSargent1993, + author = {Hansen, Lars Peter and Sargent, Thomas J.}, + title = {Seasonality and Approximation Errors in Rational Expectations Models}, + journal = {Journal of Econometrics}, + volume = {55}, + number = {1--2}, + pages = {21--55}, + year = {1993} +} + +@article{Granger1966, + author = {Granger, C. W. J.}, + title = {The Typical Spectral Shape of an Economic Variable}, + journal = {Econometrica}, + volume = {34}, + number = {1}, + pages = {150--161}, + year = {1966} +} + +@incollection{Kreps1998, + author = {Kreps, David M.}, + title = {Anticipated Utility and Dynamic Choice}, + booktitle = {Frontiers of Research in Economic Theory: The Nancy L. Schwartz Memorial Lectures, 1983--1997}, + editor = {Jacobs, Donald P. and Kalai, Ehud and Kamien, Morton I.}, + publisher = {Cambridge University Press}, + address = {Cambridge}, + pages = {242--274}, + year = {1998} +} + +@incollection{Lucas1972, + author = {Lucas, Robert E., Jr.}, + title = {Econometric Testing of the Natural Rate Hypothesis}, + booktitle = {The Econometrics of Price Determination}, + editor = {Eckstein, Otto}, + publisher = {Board of Governors of the Federal Reserve System}, + address = {Washington, D.C.}, + pages = {50--59}, + year = {1972} +} + +@incollection{Solow1968, + author = {Solow, Robert M.}, + title = {Recent Controversy on the Theory of Inflation: An Eclectic View}, + booktitle = {Proceedings of a Symposium on Inflation: Its Causes, Consequences, and Control}, + editor = {Rousseaus, Stephen W.}, + publisher = {New York University}, + address = {New York}, + year = {1968} +} + +@incollection{Tobin1968, + author = {Tobin, James}, + title = {Discussion}, + booktitle = {Proceedings of a Symposium on Inflation: Its Causes, Consequences, and Control}, + editor = {Rousseaus, Stephen W.}, + publisher = {New York University}, + address = {New York}, + year = {1968} +} + +@article{ChoWilliamsSargent2002, + author = {Cho, In-Koo and Williams, Noah and Sargent, Thomas J.}, + title = {Escaping {Nash} Inflation}, + journal = {Review of Economic Studies}, + volume = {69}, + number = {1}, + pages = {1--40}, + year = {2002} +} + +@article{SargentWilliams2005, + author = {Sargent, Thomas J. and Williams, Noah}, + title = {Impacts of Priors on Convergence and Escapes from {Nash} Inflation}, + journal = {Review of Economic Dynamics}, + volume = {8}, + number = {2}, + pages = {360--391}, + year = {2005} +} + +@article{Kasa2004, + author = {Kasa, Kenneth}, + title = {Learning, Large Deviations, and Recurrent Currency Crises}, + journal = {International Economic Review}, + volume = {45}, + number = {1}, + pages = {141--173}, + year = {2004} +} + +@article{RobbinsMonro1951, + author = {Robbins, Herbert and Monro, Sutton}, + title = {A Stochastic Approximation Method}, + journal = {Annals of Mathematical Statistics}, + volume = {22}, + number = {3}, + pages = {400--407}, + year = {1951} +} + +@article{KieferWolfowitz1952, + author = {Kiefer, Jack and Wolfowitz, Jacob}, + title = {Stochastic Estimation of the Maximum of a Regression Function}, + journal = {Annals of Mathematical Statistics}, + volume = {23}, + number = {3}, + pages = {462--466}, + year = {1952} +} + +@book{BenvenisteMetivierPriouret1990, + author = {Benveniste, Albert and M\'etivier, Michel and Priouret, Pierre}, + title = {Adaptive Algorithms and Stochastic Approximations}, + publisher = {Springer-Verlag}, + address = {Berlin}, + year = {1990} +} + +@book{KushnerYin2003, + author = {Kushner, Harold J. and Yin, G. George}, + title = {Stochastic Approximation and Recursive Algorithms and Applications}, + edition = {2nd}, + publisher = {Springer-Verlag}, + address = {New York}, + year = {2003} +} + +@book{FreidlinWentzell1998, + author = {Freidlin, Mark I. and Wentzell, Alexander D.}, + title = {Random Perturbations of Dynamical Systems}, + edition = {2nd}, + publisher = {Springer-Verlag}, + address = {New York}, + year = {1998} +} + +@article{DupuisKushner1987, + author = {Dupuis, Paul and Kushner, Harold J.}, + title = {Asymptotic Behavior of Constrained Stochastic Approximations via the Theory of Large Deviations}, + journal = {Probability Theory and Related Fields}, + volume = {75}, + number = {2}, + pages = {223--244}, + year = {1987} +} + +@article{DupuisKushner1989, + author = {Dupuis, Paul and Kushner, Harold J.}, + title = {Stochastic Approximation and Large Deviations: Upper Bounds and w.p.1 Convergence}, + journal = {SIAM Journal on Control and Optimization}, + volume = {27}, + number = {5}, + pages = {1108--1135}, + year = {1989} +} + +@book{DupuisEllis1997, + author = {Dupuis, Paul and Ellis, Richard S.}, + title = {A Weak Convergence Approach to the Theory of Large Deviations}, + publisher = {John Wiley and Sons}, + address = {New York}, + year = {1997} +} + +@article{Woodford1990, + author = {Woodford, Michael}, + title = {Learning to Believe in Sunspots}, + journal = {Econometrica}, + volume = {58}, + number = {2}, + pages = {277--307}, + year = {1990} +} + +@article{BrockHommes1997, + author = {Brock, William A. and Hommes, Cars H.}, + title = {A Rational Route to Randomness}, + journal = {Econometrica}, + volume = {65}, + number = {5}, + pages = {1059--1095}, + year = {1997} +} + +@article{KandoriMailathRob1993, + author = {Kandori, Michihiro and Mailath, George J. and Rob, Rafael}, + title = {Learning, Mutation, and Long Run Equilibria in Games}, + journal = {Econometrica}, + volume = {61}, + number = {1}, + pages = {29--56}, + year = {1993} +} + +@article{Williams2019, + author = {Williams, Noah}, + title = {Escape Dynamics in Learning Models}, + journal = {Review of Economic Studies}, + volume = {86}, + number = {2}, + pages = {882--912}, + year = {2019} +} + +@article{Bullard1994, + author = {Bullard, James}, + title = {Learning Equilibria}, + journal = {Journal of Economic Theory}, + volume = {64}, + number = {2}, + pages = {468--485}, + year = {1994} +} + +@book{FudenbergLevine1998, + author = {Fudenberg, Drew and Levine, David K.}, + title = {The Theory of Learning in Games}, + publisher = {MIT Press}, + address = {Cambridge, Massachusetts}, + year = {1998} +} + +@article{SargentWilliamsZha2006, + author = {Sargent, Thomas J. and Williams, Noah and Zha, Tao}, + title = {Shocks and Government Beliefs: The Rise and Fall of American Inflation}, + journal = {American Economic Review}, + volume = {96}, + number = {4}, + pages = {1193--1224}, + year = {2006} +} + +@article{CogleySargent2005, + author = {Cogley, Timothy and Sargent, Thomas J.}, + title = {Drifts and Volatilities: Monetary Policies and Outcomes in the Post {WWII} {US}}, + journal = {Review of Economic Dynamics}, + volume = {8}, + number = {2}, + pages = {262--302}, + year = {2005} +} + +@article{CogleySargent2001, + author = {Cogley, Timothy and Sargent, Thomas J.}, + title = {Evolving Post-World War {II} {U.S.} Inflation Dynamics}, + journal = {NBER Macroeconomics Annual}, + volume = {16}, + pages = {331--373}, + year = {2001} +} + +@article{Primiceri2005, + author = {Primiceri, Giorgio E.}, + title = {Time Varying Structural Vector Autoregressions and Monetary Policy}, + journal = {Review of Economic Studies}, + volume = {72}, + number = {3}, + pages = {821--852}, + year = {2005} +} + +@article{Jacquier1994, + author = {Jacquier, Eric and Polson, Nicholas G. and Rossi, Peter E.}, + title = {Bayesian Analysis of Stochastic Volatility Models}, + journal = {Journal of Business and Economic Statistics}, + volume = {12}, + number = {4}, + pages = {371--389}, + year = {1994} +} + +@article{CarterKohn1994, + author = {Carter, C. K. and Kohn, R.}, + title = {On {Gibbs} Sampling for State Space Models}, + journal = {Biometrika}, + volume = {81}, + number = {3}, + pages = {541--553}, + year = {1994} +} + +@article{BernankeMihov1998, + author = {Bernanke, Ben S. and Mihov, Ilian}, + title = {Measuring Monetary Policy}, + journal = {Quarterly Journal of Economics}, + volume = {113}, + number = {3}, + pages = {869--902}, + year = {1998} +} + +@article{SimsZha2006, + author = {Sims, Christopher A. and Zha, Tao}, + title = {Were There Regime Switches in {U.S.} Monetary Policy?}, + journal = {American Economic Review}, + volume = {96}, + number = {1}, + pages = {54--81}, + year = {2006} +} + +@article{Sims2001comment, + author = {Sims, Christopher A.}, + title = {Comment on {Sargent} and {Cogley's} `Evolving Post World War {II} {US} Inflation Dynamics'}, + journal = {NBER Macroeconomics Annual}, + volume = {16}, + pages = {373--379}, + year = {2001} +} + +@article{Stock2001comment, + author = {Stock, James H.}, + title = {Discussion of {Cogley} and {Sargent} `Evolving Post World War {II} {US} Inflation Dynamics'}, + journal = {NBER Macroeconomics Annual}, + volume = {16}, + pages = {379--387}, + year = {2001} +} + +@article{Andrews1993, + author = {Andrews, Donald W. K.}, + title = {Tests for Parameter Instability and Structural Change with Unknown Change Point}, + journal = {Econometrica}, + volume = {61}, + number = {4}, + pages = {821--856}, + year = {1993} +} + +@article{Nyblom1989, + author = {Nyblom, Jukka}, + title = {Testing for the Constancy of Parameters over Time}, + journal = {Journal of the American Statistical Association}, + volume = {84}, + number = {405}, + pages = {223--230}, + year = {1989} +} + +@article{Whittle1953, + author = {Whittle, Peter}, + title = {The Analysis of Multiple Stationary Time Series}, + journal = {Journal of the Royal Statistical Society, Series B}, + volume = {15}, + number = {1}, + pages = {125--139}, + year = {1953} +} + +@article{Clarida2000, + author = {Clarida, Richard and Gal\'{i}, Jordi and Gertler, Mark}, + title = {Monetary Policy Rules and Macroeconomic Stability: Evidence and Some Theory}, + journal = {Quarterly Journal of Economics}, + volume = {115}, + number = {1}, + pages = {147--180}, + year = {2000} +} + +@article{KimNelson1999, + author = {Kim, Chang-Jin and Nelson, Charles R.}, + title = {Has the {U.S.} Economy Become More Stable? A {Bayesian} Approach Based on a {Markov}-Switching Model of the Business Cycle}, + journal = {Review of Economics and Statistics}, + volume = {81}, + number = {4}, + pages = {608--616}, + year = {1999} +} + +@article{McConnellPerezQuiros2000, + author = {McConnell, Margaret M. and Perez-Quiros, Gabriel}, + title = {Output Fluctuations in the {United States}: What Has Changed Since the Early 1980's?}, + journal = {American Economic Review}, + volume = {90}, + number = {5}, + pages = {1464--1476}, + year = {2000} +} + +@incollection{DeLong1997, + author = {DeLong, J. Bradford}, + title = {America's Peacetime Inflation: The 1970s}, + booktitle = {Reducing Inflation: Motivation and Strategy}, + editor = {Romer, Christina D. and Romer, David H.}, + publisher = {University of Chicago Press}, + pages = {247--280}, + year = {1997} +} + +@incollection{Taylor1997comment, + author = {Taylor, John B.}, + title = {Comment on `America's Peacetime Inflation: The 1970s'}, + booktitle = {Reducing Inflation: Motivation and Strategy}, + editor = {Romer, Christina D. and Romer, David H.}, + publisher = {University of Chicago Press}, + pages = {276--280}, + year = {1997} +} + +@book{Perko1996, + author = {Perko, Lawrence}, + title = {Differential Equations and Dynamical Systems}, + edition = {2nd}, + publisher = {Springer-Verlag}, + address = {New York}, + year = {1996} +} + +@article{StockWatson1998, + author = {Stock, James H. and Watson, Mark W.}, + title = {Median Unbiased Estimation of Coefficient Variance in a Time-Varying Parameter Model}, + journal = {Journal of the American Statistical Association}, + volume = {93}, + number = {441}, + pages = {349--358}, + year = {1998} +} + +@article{FudenbergLevine1993, + author = {Fudenberg, Drew and Levine, David K.}, + title = {Self-Confirming Equilibrium}, + journal = {Econometrica}, + volume = {61}, + number = {3}, + pages = {523--545}, + year = {1993} +} + +@article{ChoMatsui1995, + author = {Cho, In-Koo and Matsui, Akihiko}, + title = {Induction and the {Ramsey} Policy}, + journal = {Journal of Economic Dynamics and Control}, + volume = {19}, + number = {5--7}, + pages = {1113--1140}, + year = {1995} +} + +@book{DemboZeitouni1998, + author = {Dembo, Amir and Zeitouni, Ofer}, + title = {Large Deviations Techniques and Applications}, + edition = {2nd}, + publisher = {Springer-Verlag}, + address = {New York}, + year = {1998} +} + +@article{Rogoff1985, + author = {Rogoff, Kenneth}, + title = {The Optimal Degree of Commitment to an Intermediate Monetary Target}, + journal = {Quarterly Journal of Economics}, + volume = {100}, + number = {4}, + pages = {1169--1189}, + year = {1985} +} + +@article{Sims1980, + author = {Sims, Christopher A.}, + title = {Macroeconomics and Reality}, + journal = {Econometrica}, + volume = {48}, + number = {1}, + pages = {1--48}, + year = {1980} +} + +@book{Friedman1957, + author = {Friedman, Milton}, + title = {A Theory of the Consumption Function}, + publisher = {Princeton University Press}, + address = {Princeton, New Jersey}, + year = {1957} +} + +@article{EllisonYates2007, + author = {Ellison, Martin and Yates, Tony}, + title = {Escaping Volatile Inflation}, + journal = {Journal of Money, Credit and Banking}, + volume = {39}, + number = {4}, + pages = {981--993}, + year = {2007} +} + +@article{CarboniEllison2009, + author = {Carboni, Giacomo and Ellison, Martin}, + title = {The Great Inflation and the {Greenbook}}, + journal = {Journal of Monetary Economics}, + volume = {56}, + number = {6}, + pages = {831--841}, + year = {2009} +} + +@article{Primiceri2006, + author = {Primiceri, Giorgio E.}, + title = {Why Inflation Rose and Fell: Policymakers' Beliefs and {U.S.} Postwar Stabilization Policy}, + journal = {Quarterly Journal of Economics}, + volume = {121}, + number = {3}, + pages = {867--901}, + year = {2006} +} + +@article{Sargent2008, + author = {Sargent, Thomas J.}, + title = {Evolution and Intelligent Design}, + journal = {American Economic Review}, + volume = {98}, + number = {1}, + pages = {5--37}, + year = {2008} +} + +@article{PhelpsTaylor1977, + author = {Phelps, Edmund S. and Taylor, John B.}, + title = {Stabilizing Powers of Monetary Policy under Rational Expectations}, + journal = {Journal of Political Economy}, + volume = {85}, + number = {1}, + pages = {163--190}, + year = {1977} +} + +@article{SargentWilliams2025, + author = {Sargent, Thomas J. and Williams, Noah}, + title = {Rationalizing {Fed} Interest Rate Decisions in the 2020s}, + journal = {Journal of Political Economy: Macroeconomics}, + year = {2025}, + note = {Forthcoming} +} + +@article{Orphanides2001, + author = {Orphanides, Athanasios}, + title = {Monetary Policy Rules Based on Real-Time Data}, + journal = {American Economic Review}, + volume = {91}, + number = {4}, + pages = {964--985}, + year = {2001} +} + +@incollection{McLeayTenreyro2019, + author = {McLeay, Michael and Tenreyro, Silvana}, + title = {Optimal Inflation and the Identification of the Phillips Curve}, + booktitle = {NBER Macroeconomics Annual 2018, Volume 33}, + editor = {Eichenbaum, Martin and Hurst, Erik and Parker, Jonathan A.}, + publisher = {University of Chicago Press}, + address = {Chicago}, + pages = {199--255}, + year = {2019} +} + +@article{CogleySargentConquest2005, + author = {Cogley, Timothy and Sargent, Thomas J.}, + title = {The Conquest of {US} Inflation: Learning and Robustness to Model Uncertainty}, + journal = {Review of Economic Dynamics}, + volume = {8}, + number = {2}, + pages = {528--563}, + year = {2005} +} + +@book{Bernanke2022, + author = {Bernanke, Ben S.}, + title = {21st Century Monetary Policy: The {Federal} {Reserve} from the Great Inflation to {COVID-19}}, + publisher = {W. W. Norton and Company}, + address = {New York}, + year = {2022} +} + +@article{StockWatson2007, + author = {Stock, James H. and Watson, Mark W.}, + title = {Why Has {U.S.} Inflation Become Harder to Forecast?}, + journal = {Journal of Money, Credit and Banking}, + volume = {39}, + number = {S1}, + pages = {3--33}, + year = {2007} +} + +@inproceedings{MurrayAdamsMacKay2010, + author = {Murray, Iain and Adams, Ryan P. and MacKay, David J. C.}, + title = {Elliptical Slice Sampling}, + booktitle = {Proceedings of the Thirteenth International Conference on + Artificial Intelligence and Statistics}, + series = {Proceedings of Machine Learning Research}, + volume = {9}, + pages = {541--548}, + year = {2010} +} diff --git a/lectures/_toc.yml b/lectures/_toc.yml index 3789a67a6..e16fd96a3 100644 --- a/lectures/_toc.yml +++ b/lectures/_toc.yml @@ -117,6 +117,19 @@ parts: - file: lq_bewley_complete_markets - file: lq_robust_smoothing - file: lq_inventories +- caption: Phillips Curve Tradeoffs + numbered: true + chapters: + - file: phillips_two_stories + - file: phillips_credibility + - file: phillips_adaptive + - file: phillips_misspecified + - file: phillips_self_confirming + - file: phillips_learning + - file: phillips_escaping_nash + - file: phillips_priors + - file: phillips_lost_conquest + - file: phillips_drifts_volatilities - caption: Optimal Growth numbered: true chapters: diff --git a/lectures/phillips_adaptive.md b/lectures/phillips_adaptive.md new file mode 100644 index 000000000..d67d9b4b2 --- /dev/null +++ b/lectures/phillips_adaptive.md @@ -0,0 +1,463 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.16.7 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(phillips_adaptive)= +```{raw} jupyter +
+ + QuantEcon + +
+``` + +# Adaptive Expectations and the Phelps Problem + +```{contents} Contents +:depth: 2 +``` + +In addition to what's in Anaconda, this lecture will use the following library: + +```{code-cell} ipython3 +:tags: [hide-output] + +!pip install quantecon +``` + +## Overview + +> They cannot look out far. +> They cannot look in deep. +> But when was that ever a bar +> To any watch they keep? +> +> -- Robert Frost + +This lecture continues the study of Phillips curve tradeoffs begun in {doc}`phillips_credibility`. + +It follows chapter 5 of {cite}`Sargent1999`. + +We describe + +* the Cagan-Friedman adaptive expectations hypothesis, +* how {cite}`Phelps1967` used it to formulate a government control problem, and +* the role of adaptive expectations in early econometric tests of the natural-rate hypothesis. + +The key object is the **Phelps problem**: a government that is rational solves an optimal control problem while the public forecasts inflation with a fixed, mechanical adaptive rule. + +Unlike the one-period model of {doc}`phillips_credibility`, the government now takes into account that the economy lasts more than one period, and that today's inflation shapes tomorrow's expectations. + +This intertemporal link can improve outcomes and, in a limiting case, even sustain the Ramsey outcome. + +The Phelps problem is a linear-quadratic (LQ) control problem, so we solve it with the {doc}`LQ control ` tools from QuantEcon. + +Let's start with some imports: + +```{code-cell} ipython3 +import matplotlib.pyplot as plt +import numpy as np +import quantecon as qe +``` + +## Adaptive expectations + +{cite}`Phelps1967` formulated a control problem for a natural-rate model. + +He dropped rationality for the public but not for the government, and assigned the public a particular mechanical forecasting rule that is known to the government. + +The public uses the adaptive expectations scheme of Milton Friedman and {cite}`Cagan`: + +```{math} +:label: pa_adaptive + +x_t - x_{t-1} = (1 - \lambda)(y_{t-1} - x_{t-1}), \qquad \lambda \in (0, 1), +``` + +where $x_t$ is the public's expectation of inflation and $y_t$ is inflation. + +Rearranging, $x_t = \lambda x_{t-1} + (1 - \lambda) y_{t-1}$, so expected inflation is a geometric distributed lag of past inflation, + +```{math} +:label: pa_geom + +x_t = (1 - \lambda) \sum_{i=1}^{\infty} \lambda^{i-1} y_{t-i} . +``` + +Notice that {eq}`pa_adaptive` is a *constant gain* version of the least squares learning algorithm from {doc}`phillips_credibility`, with the constant $(1 - \lambda)$ playing the role that the decreasing gain $t^{-1}$ played there. + +Equation {eq}`pa_geom` possesses an **induction property**: if the government keeps repeating a constant $y_t = \tilde y$ policy, eventually the public comes to set $x_t \approx \tilde y$. + +The weights $(1 - \lambda)\lambda^{i-1}$ sum to one, so a permanently maintained inflation rate is eventually expected. + +Let's confirm the induction property numerically. + +```{code-cell} ipython3 +def adaptive_forecast(y, λ, x0=0.0): + "Simulate x_t = λ x_{t-1} + (1-λ) y_{t-1}." + T = len(y) + x = np.empty(T) + x[0] = x0 + for t in range(1, T): + x[t] = λ * x[t - 1] + (1 - λ) * y[t - 1] + return x + +T = 60 +y_const = np.full(T, 10.0) # a constant inflation policy + +fig, ax = plt.subplots(figsize=(9, 4.5)) +for λ in [0.7, 0.9]: + x = adaptive_forecast(y_const, λ) + ax.plot(x, lw=1.5, label=rf'$\lambda = {λ}$') +ax.axhline(10.0, color='k', ls='--', lw=1, label='policy $\\tilde y$') +ax.set_xlabel('$t$') +ax.set_ylabel('expected inflation $x_t$') +ax.legend() +plt.show() +``` + +Under a constant policy the public's expectation converges to the policy, more slowly the larger is $\lambda$. + +Solow and Tobin exploited this induction property when they tested the natural-rate hypothesis, as we discuss below. + +## The Phelps problem + +The economy repeats forever, and the government evaluates outcome sequences with the discounted criterion + +```{math} +:label: pa_criterion + +V^g = (1 - \delta) \sum_{t=1}^{\infty} \delta^{t-1} p(U_t, y_t), +\qquad p(U_t, y_t) = - \frac{1}{2}(U_t^2 + y_t^2), +\qquad \delta \in (0, 1] . +``` + +When $\delta = 1$ we interpret {eq}`pa_criterion` in the limit-of-means (Cesàro) sense. + +The government maximizes {eq}`pa_criterion` by choice of a rule for inflation $y_t$, subject to the adaptive expectations scheme {eq}`pa_adaptive` and the expectations-augmented Phillips curve + +```{math} +:label: pa_phillips + +U_t = U^* - \theta(y_t - x_t) . +``` + +Because expected inflation $x_t$ is predetermined at the start of period $t$, it is the only endogenous state variable. + +The government's problem is therefore a discounted LQ control problem with + +* state $s_t = \begin{bmatrix} 1 & x_t \end{bmatrix}'$, +* control $y_t$, and +* transition $x_{t+1} = \lambda x_t + (1 - \lambda) y_t$. + +### Casting the problem in LQ form + +Write $U_t = a' s_t - \theta y_t$ with $a = \begin{bmatrix} U^* & \theta \end{bmatrix}'$. + +The per-period loss $\tfrac{1}{2}(U_t^2 + y_t^2)$ then equals + +$$ +\frac{1}{2}\left[ s_t'(a a') s_t + (\theta^2 + 1) y_t^2 - 2 \theta \, y_t \, (a' s_t) \right] . +$$ + +Matching this to the QuantEcon LQ loss $s_t' R s_t + y_t' Q y_t + 2 y_t' N s_t$, and matching the transition to $s_{t+1} = A s_t + B y_t$, gives + +$$ +R = \tfrac{1}{2} a a', \quad +Q = \tfrac{1}{2}(\theta^2 + 1), \quad +N = -\tfrac{1}{2}\theta\, a', \quad +A = \begin{bmatrix} 1 & 0 \\ 0 & \lambda \end{bmatrix}, \quad +B = \begin{bmatrix} 0 \\ 1 - \lambda \end{bmatrix} . +$$ + +The discount factor is $\beta = \delta$; the scaling $(1 - \delta)$ in {eq}`pa_criterion` does not affect the optimal policy. + +```{code-cell} ipython3 +class PhelpsProblem: + """ + The Phelps optimal-control problem: a rational government facing a + public that forecasts inflation adaptively with parameter λ. + """ + + def __init__(self, θ=1.0, U_star=5.0, λ=0.7, δ=0.96): + self.θ, self.U_star, self.λ, self.δ = θ, U_star, λ, δ + + a = np.array([[U_star], [θ]]) + R = 0.5 * (a @ a.T) + Q = np.array([[0.5 * (θ**2 + 1)]]) + N = -0.5 * θ * a.T + A = np.array([[1.0, 0.0], [0.0, λ]]) + B = np.array([[0.0], [1 - λ]]) + + # δ = 1 (limit of means) is handled as the limit δ → 1 + β = min(δ, 1 - 1e-7) + self.lq = qe.LQ(Q, R, A, B, N=N, beta=β) + P, F, d = self.lq.stationary_values() + + # optimal rule y_t = f1 + f2 x_t + self.f1, self.f2 = -F[0, 0], -F[0, 1] + + def simulate(self, x0=12.0, T=60): + "Disinflation path (U_t, y_t) starting from expectation x0." + θ, U_star, λ = self.θ, self.U_star, self.λ + U, y, x = np.empty(T), np.empty(T), x0 + for t in range(T): + y[t] = self.f1 + self.f2 * x + U[t] = U_star - θ * (y[t] - x) + x = λ * x + (1 - λ) * y[t] + return U, y +``` + +The optimal rule takes the form $y_t = f_1 + f_2 x_t$ with $f_1 \neq 0$ and $f_2 \neq 1$. + +These inequalities reflect that the public does not use an optimal forecasting rule; if instead $f_1 = 0$ and $f_2 = 1$, we would have $y_t = x_t$ for all histories. + +```{code-cell} ipython3 +pp = PhelpsProblem(θ=1.0, U_star=5.0, λ=0.7, δ=0.96) +print(f"optimal rule: y_t = {pp.f1:.3f} + {pp.f2:.3f} x_t") +``` + +### A proposition + +The reason the Phelps problem is interesting is the following result. + +```{prf:proposition} δ = 1 eventually sustains Ramsey +:label: pa_prop + +In the absence of discounting ($\delta = 1$), the government drives $y_t$ to $0$, the Ramsey outcome. +``` + +When $\delta = 1$, $\lambda$ governs the speed of convergence to the Ramsey outcome. + +When $\delta < 1$, the limit point of $y_t$ depends on a comparison of $\lambda$ with $\delta$. + +For $\lambda < \delta$ and $\delta$ close to $1$, the government's policy eventually approximates the Ramsey outcome. + +The public's expectations are wrong along the transition path but are correct in the steady state, by virtue of the induction property. + +## Disinflation paths + +We now reproduce the disinflation experiments of chapter 5 of {cite}`Sargent1999`. + +Set $\theta = 1$ and $U^* = 5$, and start the government's problem from late-1970s initial conditions $x_{-1} = y_{-1} = 12$, which imply $U = U^* = 5$. + +The following tables record paths of unemployment $U$ and inflation $y$ at selected lags, for two discount factors $\delta \in \{0.96, 1\}$ and two adaptation parameters $\lambda \in \{0.7, 0.9\}$. + +```{code-cell} ipython3 +def disinflation_table(δ, lags=(1, 5, 20, 50)): + rows = [] + for λ in [0.7, 0.9]: + U, y = PhelpsProblem(λ=λ, δ=δ).simulate(x0=12.0, T=max(lags) + 1) + for lag in lags: + rows.append((λ, lag, U[lag - 1], y[lag - 1])) + return rows + +for δ in [0.96, 1.0]: + print(f"\n δ = {δ}") + print(f" {'λ':>4} {'lag':>4} {'U':>7} {'y':>7}") + for λ, lag, U, y in disinflation_table(δ): + print(f" {λ:>4} {lag:>4} {U:>7.1f} {y:>7.1f}") +``` + +For each parameter setting the government engineers a major recession and immediately brings inflation down more than halfway toward its eventual limiting value. + +In the discounted case ($\delta = 0.96$), inflation settles at a positive level, and the government accepts a longer but milder recession when $\lambda = 0.9$ than when $\lambda = 0.7$. + +In the undiscounted case ($\delta = 1$), inflation is driven all the way to the Ramsey value of zero, more slowly the larger is $\lambda$. + +Let's plot the full disinflation paths. + +```{code-cell} ipython3 +fig, axes = plt.subplots(1, 2, figsize=(11, 4.5)) + +for δ, ax in zip([0.96, 1.0], axes): + for λ in [0.7, 0.9]: + U, y = PhelpsProblem(λ=λ, δ=δ).simulate(x0=12.0, T=60) + ax.plot(y, lw=1.5, label=rf'$\lambda = {λ}$') + ax.set_title(rf'$\delta = {δ}$') + ax.set_xlabel('$t$') + ax.set_ylabel('inflation $y_t$') + ax.legend() + +plt.tight_layout() +plt.show() +``` + +The undiscounted problem drives inflation to the Ramsey outcome; the discounted problem stops short of it. + +## The general Phelps problem + +For the government's control problem, what matters is the *reduced form* of the Phillips curve, not the underlying structure that identifies $x_t$. + +It is useful to state a more general version of the Phelps problem in which the government's model is a reduced-form distributed lag Phillips curve. + +Define the vectors + +$$ +X_{U,t} = \begin{bmatrix} U_{t-1} & \cdots & U_{t-m_U} \end{bmatrix}', +\qquad +X_{y,t} = \begin{bmatrix} y_{t-1} & \cdots & y_{t-m_y} \end{bmatrix}', +$$ + +and the state vector $X_t = \begin{bmatrix} X_{U,t}' & X_{y,t}' & 1 \end{bmatrix}'$, which collects information dated $t-1$ and earlier. + +We can write two reduced-form Phillips curves that differ only in their *direction of fit*: + +$$ +\text{Classical:} \quad U_t = \gamma' X_{C,t} + \varepsilon_{C,t}, +\qquad X_{C,t} = \begin{bmatrix} y_t & X_{t-1}' \end{bmatrix}', +$$ + +$$ +\text{Keynesian:} \quad y_t = \beta' X_{K,t} + \varepsilon_{K,t}, +\qquad X_{K,t} = \begin{bmatrix} U_t & X_{t-1}' \end{bmatrix}' . +$$ + +The subscripts $C$ and $K$ stand for *Classical* (regress $U$ on $y$) and *Keynesian* (regress $y$ on $U$). + +The general **Phelps problem** is to choose a control law $\hat y_t = h X_{t-1}$ to maximize the expected value of {eq}`pa_criterion` subject to the government's believed Phillips curve and to $y_t = \hat y_t + v_{2t}$, where $v_{2t}$ is a control error. + +This induces a mapping from the government's beliefs $\gamma$ to its decision rule $h$: + +```{math} +:label: pa_map + +h = h(\gamma) . +``` + +The specific problem solved above is the special case in which $\gamma$ is restricted by substituting the adaptive expectations hypothesis {eq}`pa_adaptive` into the Phillips curve {eq}`pa_phillips`. + +Once that substitution is made, the state variable $x_t$ disappears from view. + +These objects — $\gamma$, $\beta$, $h(\gamma)$, and the two directions of fit — are exactly the ingredients we will need to define **self-confirming equilibria** in {doc}`phillips_self_confirming`. + +```{note} +The **induction hypothesis** is the restriction that, in the Keynesian Phillips curve $y_t = \beta' X_{K,t} + \varepsilon_{K,t}$, the weights on lagged $y$'s sum to unity (equivalently, in the classical form the weights on current and lagged $y$'s sum to zero). Under adaptive expectations this holds because the weights in {eq}`pa_geom` sum to one. +``` + +## Testing the natural-rate hypothesis + +Robert Solow and James Tobin {cite}`Solow1968,Tobin1968` exploited the induction hypothesis to test the natural-rate hypothesis. + +Substituting the geometric distributed lag {eq}`pa_geom` into an inverted Phillips curve gives + +```{math} +:label: pa_invphill + +y_t = (1 - \lambda) \sum_{i=1}^{\infty} \lambda^{i-1} y_{t-i} + + \theta^{-1}(U^* - U_t) . +``` + +They proposed to test the natural-rate hypothesis by running the regression + +```{math} +:label: pa_tobin + +y_t = b_0 + b_1 (1 - \lambda) \sum_{i=1}^{\infty} \lambda^{i-1} y_{t-i} + + b_2 U_t + \varepsilon_t , +``` + +and interpreting a finding of $b_1 < 1$ as evidence of a long-run tradeoff between inflation and unemployment of slope $b_1 - 1$. + +Early implementations found $b_1 < 1$ and so rejected the natural-rate hypothesis in favor of a long-run tradeoff. + +{cite}`KingWatson1994` and others later argued that the pattern of rejections and non-rejections is consistent with the tendency of inflation to have a unit root after the 1960s but not before, so the unit-sum restriction $b_1 = 1$ is *compatible* with rational expectations when $y_t$ has a unit root. + +From the viewpoint of Phelps's control problem, it is incidental whether the natural-rate hypothesis holds: the Phelps problem imparts interesting dynamics to the inflation-unemployment choice whether or not $b_1 = 1$. + +## Sacrifice ratios and the subversion of the Phelps model + +Despite its encouraging implications for sustaining the Ramsey outcome under the induction hypothesis, Phelps's control problem carries a tattered past. + +In the Phelps problem, for a fixed $\delta < 1$ it is always possible to find a $\lambda$ close enough to 1 that high inflationary expectations will make a government want to avoid disinflating. + +In the late 1970s, models with long expectations-adjustment lags were used to recommend *against* reducing inflation. + +Large *sacrifice ratios* — estimated amounts of foregone GDP required to bring inflation down one percentage point — circulated widely. + +The lesson to carry forward is that activating the induction hypothesis can eventually lead to better outcomes, but in the form that Phelps, Tobin, and Solow used it, the induction hypothesis retreats from rational expectations. + +In {doc}`phillips_misspecified` and {doc}`phillips_self_confirming` we impute more symmetry to the government and the public, applying updating schemes like {eq}`pa_adaptive` to functions rather than to numbers, and we turn $\lambda$ from a free parameter into an equilibrium outcome. + +The LQ Phelps problem solved here returns again in {doc}`phillips_learning`, {doc}`phillips_escaping_nash`, and {doc}`phillips_priors`, where a learning government re-solves it every period — and where activating the induction hypothesis is exactly what triggers the Volcker-like stabilizations. + +## Exercises + +```{exercise-start} +:label: pa_ex1 +``` + +The proposition above states that as $\delta \to 1$ the government drives inflation to the Ramsey value $0$. + +For $\lambda = 0.8$, compute the limiting inflation rate $y_\infty$ (the value that $y_t$ settles at after a long simulation) for a grid of discount factors $\delta \in \{0.90, 0.92, \ldots, 0.99\}$. + +Plot $y_\infty$ against $\delta$ and confirm that it declines toward zero as $\delta \to 1$. + +```{exercise-end} +``` + +```{solution-start} pa_ex1 +:class: dropdown +``` + +```{code-cell} ipython3 +δ_grid = np.arange(0.90, 0.995, 0.01) +y_inf = [] +for δ in δ_grid: + U, y = PhelpsProblem(λ=0.8, δ=δ).simulate(x0=12.0, T=400) + y_inf.append(y[-1]) + +fig, ax = plt.subplots(figsize=(8, 4.5)) +ax.plot(δ_grid, y_inf, 'o-') +ax.set_xlabel(r'discount factor $\delta$') +ax.set_ylabel(r'limiting inflation $y_\infty$') +plt.show() +``` + +As $\delta$ rises toward one, the government becomes willing to accept the transitional recession needed to reap the long-run benefit of low expected inflation, so the limiting inflation rate falls toward the Ramsey value of zero. + +```{solution-end} +``` + +```{exercise-start} +:label: pa_ex2 +``` + +Verify the induction property of the optimal policy directly. + +Take the discounted problem with $\delta = 0.96$ and $\lambda = 0.7$, and simulate a long disinflation path. + +Check that in the steady state the public's expectation $x_t$ equals actual inflation $y_t$ (so the public is *not* fooled in the limit), even though expectations are wrong along the transition. + +```{exercise-end} +``` + +```{solution-start} pa_ex2 +:class: dropdown +``` + +```{code-cell} ipython3 +pp = PhelpsProblem(θ=1.0, U_star=5.0, λ=0.7, δ=0.96) +U, y = pp.simulate(x0=12.0, T=200) + +# reconstruct the expectation path implied by the adaptive rule +x = adaptive_forecast(np.concatenate([[12.0], y]), λ=0.7, x0=12.0)[1:] + +print(f"steady-state inflation y_∞ = {y[-1]:.4f}") +print(f"steady-state expectation x_∞ = {x[-1]:.4f}") +print(f"gap = {y[-1] - x[-1]:.2e}") +``` + +In the steady state expected and actual inflation coincide, confirming that the induction property makes the public's forecast correct in the limit. + +```{solution-end} +``` diff --git a/lectures/phillips_credibility.md b/lectures/phillips_credibility.md new file mode 100644 index 000000000..3f712f695 --- /dev/null +++ b/lectures/phillips_credibility.md @@ -0,0 +1,498 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.16.7 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(phillips_credibility)= +```{raw} jupyter +
+ + QuantEcon + +
+``` + +# The Credibility Problem + +```{contents} Contents +:depth: 2 +``` + +## Overview + +This lecture describes a basic expectational Phillips curve model of the sort studied by {cite}`KydlandPrescott1977` and Robert Barro and David Gordon. + +It is the first in a suite of lectures based on chapters of {cite}`Sargent1999`. + +Those chapters formalize + +* the temptation to inflate that is unleashed by the discovery of a Phillips curve, +* the value of a commitment technology for resisting that temptation, and +* the fragility of reputational mechanisms as substitutes for commitment. + +Throughout, rational expectations is the only equilibrium concept that we use. + +Alterations in the *timing* of decisions by a government and a private sector induce different economies with distinct outcomes. + +A government faces a **credibility problem** whenever it wishes to make decisions sooner than it must. + +We shall compare outcomes under two timing protocols: + +* In one, the government chooses inflation *before* the private sector sets its expectations, so the government takes into account how its choice shapes those expectations. +* In the other, the government chooses inflation *after* the private sector has set its expectations. + +The deterioration in outcomes under the second protocol measures the loss from an inability to commit. + +We also study two *out-of-equilibrium* dynamics that converge to the no-commitment (Nash) outcome: + +* best response dynamics, and +* least squares learning. + +Let's start with some standard imports: + +```{code-cell} ipython3 +import matplotlib.pyplot as plt +import numpy as np +``` + +## A one-period economy + +Although credibility problems are intrinsically dynamic, it is possible to describe them in a one-period model under different patterns of within-period timing. + +This prepares the way for the multi-period analyses in subsequent lectures. + +We describe a version of the one-period model of {cite}`KydlandPrescott1977` in the terms used by Nancy Stokey {cite}`stokey1989reputation`. + +Let $(U, y, x)$ be the unemployment rate, the inflation rate, and the public's expectation of the inflation rate, respectively. + +The government's one-period payoff is + +```{math} +:label: pc_payoff + +- \frac{1}{2} \left( U^2 + y^2 \right) . +``` + +Unemployment is determined by an expectations-augmented Phillips curve + +```{math} +:label: pc_phillips + +U = U^* - \theta (y - x), \qquad \theta > 0 . +``` + +The equation asserts that unemployment deviates from $U^*$, the natural rate, only when there is surprise inflation or deflation. + +Substituting {eq}`pc_phillips` into {eq}`pc_payoff` expresses the government's payoff as a function $r(x, y)$: + +```{math} +:label: pc_r + +r(x, y) = - \frac{1}{2} \left[ \left(U^* - \theta (y - x)\right)^2 + y^2 \right] . +``` + +We work with the following objects. + +**Rational expectations equilibrium:** a triple $(U, x, y)$ satisfying {eq}`pc_phillips` and $y = x$. + +**Government (one-period) best response:** given the public's expectation $x$, a decision rule $B(x) = \operatorname{argmax}_y r(x, y)$ for setting $y$. + +**Nash equilibrium:** a pair $(x, y)$ satisfying (i) $x = y$, and (ii) $y = B(x)$. + +**Ramsey problem:** $\max_y r(y, y)$. The *Ramsey outcome* is the value of $y$ that attains the maximum. + +**Best response dynamics:** the dynamical system $y_t = B(y_{t-1})$, $y_0$ given. + +A rational expectations equilibrium is a $(U, x, y)$ triple that lies on the Phillips curve and for which private agents are not fooled, given $x$. + +Substituting $x = y$ into the Phillips curve {eq}`pc_phillips` shows that $U = U^*$ in any rational expectations equilibrium. + +This identifies $U^*$ as the natural rate of unemployment. + +## Nash and Ramsey outcomes + +A Nash equilibrium builds in a best response by the government, taking the state of expectations $x$ as given, together with a response $x = y$ by the market, i.e., rational expectations for a given $y$. + +Maximizing {eq}`pc_r` with respect to $y$ for a fixed $x$ gives the government's best response function + +```{math} +:label: pc_B + +y = B(x) = \frac{\theta}{\theta^2 + 1} U^* + \frac{\theta^2}{\theta^2 + 1} x . +``` + +The Nash equilibrium sets $x = y = B(x)$, which gives + +$$ +y^N = x^N = \theta U^*, \qquad U = U^* . +$$ + +The Ramsey problem instead imposes $x = y$ *before* maximizing, so it maximizes $r(y, y) = -\tfrac{1}{2}(U^{*2} + y^2)$, which yields the Ramsey outcome + +$$ +y^R = x^R = 0, \qquad U = U^* . +$$ + +Thus $r(x^R, y^R) = -\tfrac{1}{2} U^{*2}$ while $r(x^N, y^N) = -\tfrac{1}{2}(1 + \theta^2) U^{*2}$. + +Both outcomes deliver the natural rate $U^*$, but the Nash equilibrium delivers it with positive inflation and hence a strictly lower payoff. + +Let's collect these formulas in a class. + +```{code-cell} ipython3 +class CredibilityModel: + """ + A one-period expectational Phillips curve economy. + """ + + def __init__(self, θ=1.0, U_star=5.0): + self.θ, self.U_star = θ, U_star + + def phillips(self, y, x): + "Unemployment implied by inflation y and expected inflation x." + return self.U_star - self.θ * (y - x) + + def r(self, x, y): + "Government one-period payoff." + U = self.phillips(y, x) + return -0.5 * (U**2 + y**2) + + def B(self, x): + "Government best response to expected inflation x." + θ = self.θ + return θ / (θ**2 + 1) * self.U_star + θ**2 / (θ**2 + 1) * x + + def nash(self): + "Nash equilibrium inflation (= expected inflation)." + return self.θ * self.U_star + + def ramsey(self): + "Ramsey inflation (= expected inflation)." + return 0.0 +``` + +```{code-cell} ipython3 +cm = CredibilityModel() + +y_N, y_R = cm.nash(), cm.ramsey() +print(f"Nash inflation y^N = {y_N:.2f}") +print(f"Ramsey inflation y^R = {y_R:.2f}") +print(f"Nash payoff r = {cm.r(y_N, y_N):.3f}") +print(f"Ramsey payoff r = {cm.r(y_R, y_R):.3f}") +``` + +The Nash payoff is worse than the Ramsey payoff. + +The government would prefer the Ramsey outcome, but it cannot attain it without a technology for committing to $y = 0$ before the public forms its expectations. + +The Nash equilibrium is supported by a timing protocol in which the government decides *after* the private sector sets its expectations. + +The Ramsey outcome is associated with a timing protocol in which the government chooses *first*, knowing that in a rational expectations equilibrium the public's expectations will move with its choice because $y = x$. + +### A picture of the two outcomes + +The government's indifference curves are circles centered at the origin in $(U, y)$ space, because the payoff {eq}`pc_payoff` depends only on $U^2 + y^2$. + +For a given expectation $x$, the Phillips curve {eq}`pc_phillips` is a downward-sloping line in $(U, y)$ space. + +The government's best response for $y$, given $x$, occurs where an indifference curve is tangent to the Phillips curve indexed by $x$. + +```{code-cell} ipython3 +fig, ax = plt.subplots(figsize=(7, 6)) + +U_grid = np.linspace(0, 12, 200) + +# a family of Phillips curves indexed by expected inflation x +for x in [0.0, y_N / 2, y_N]: + # U = U_star - θ (y - x) => y = x + (U_star - U) / θ + y_line = x + (cm.U_star - U_grid) / cm.θ + ax.plot(U_grid, y_line, 'C0', lw=1) + +# government indifference curves (circles U^2 + y^2 = const) +ξ = np.linspace(0, 2 * np.pi, 200) +for R in [y_R, np.hypot(cm.U_star, y_N)]: + if R > 0: + ax.plot(R * np.cos(ξ), R * np.sin(ξ), 'C1--', lw=1) + +ax.plot(cm.U_star, y_N, 'ko') +ax.annotate('Nash', (cm.U_star, y_N), (cm.U_star + 0.4, y_N + 0.4)) +ax.plot(cm.U_star, y_R, 'ko') +ax.annotate('Ramsey', (cm.U_star, y_R), (cm.U_star + 0.4, y_R + 0.4)) + +ax.set_xlim(0, 12) +ax.set_ylim(0, 10) +ax.set_xlabel('unemployment $U$') +ax.set_ylabel('inflation $y$') +plt.show() +``` + +Solid lines are Phillips curves for expected inflation $x \in \{0, y^N/2, y^N\}$; dashed circles are government indifference curves. + +The Nash outcome $(U^*, y^N)$ lies on a larger circle (lower payoff) than the Ramsey outcome $(U^*, 0)$. + +## Best response dynamics + +Best response dynamics convert the one-period model into a dynamic one by positing an adaptive mechanism in which expected inflation equals last period's inflation, $x_t = y_{t-1}$. + +This leads to the dynamics + +$$ +y_t = B(y_{t-1}), \qquad y_0 \text{ given} . +$$ + +Because $B$ is an affine map with slope $\theta^2 / (\theta^2 + 1) \in (0, 1)$, iterating on it converges to the fixed point $y^N = \theta U^*$ from any starting point. + +Let's plot the best response function against the 45-degree line and simulate the dynamics. + +```{code-cell} ipython3 +def best_response_path(cm, y0, T=20): + "Iterate y_{t+1} = B(y_t)." + y = np.empty(T + 1) + y[0] = y0 + for t in range(T): + y[t + 1] = cm.B(y[t]) + return y + +y_path = best_response_path(cm, y0=0.0, T=20) +``` + +```{code-cell} ipython3 +fig, ax = plt.subplots(figsize=(6, 6)) + +x_grid = np.linspace(0, y_N + 1, 100) +ax.plot(x_grid, cm.B(x_grid), 'C0', label='$B(x)$') +ax.plot(x_grid, x_grid, 'k--', lw=1, label='45 degrees') + +# cobweb of the best response dynamics +for t in range(len(y_path) - 1): + ax.plot([y_path[t], y_path[t]], [y_path[t], y_path[t + 1]], 'C1', lw=0.8) + ax.plot([y_path[t], y_path[t + 1]], [y_path[t + 1], y_path[t + 1]], + 'C1', lw=0.8) + +ax.plot(y_N, y_N, 'ko') +ax.annotate('Nash', (y_N, y_N), (y_N - 1.5, y_N + 0.3)) +ax.set_xlabel('$x$') +ax.set_ylabel('$B(x)$') +ax.legend() +plt.show() +``` + +Starting from $x = 0$ (the Ramsey inflation rate), the government sets $y = B(0) > 0$. + +This provokes the public to raise its expectation, which leads the government to raise inflation further. + +The limit of this process is the Nash outcome $y = x = y^N$, a self-confirming situation. + +Thus best response dynamics converge to the Nash equilibrium and reinforce $(U^*, y^N)$ as the prediction of the model without a commitment technology. + +## Least squares learning converges to Nash + +A version of the best response dynamics also emerges from least squares learning. + +Least squares learning plays a key role throughout this suite of lectures, and this simple example introduces analytical elements that reappear later in more complex settings. + +Following {cite}`Bray1982`, assume that expected inflation $x_t$ is the average of past inflation rates, + +$$ +x_t = \frac{1}{t - 1} \sum_{s=1}^{t-1} y_s , +$$ + +which can be represented recursively as + +```{math} +:label: pc_expect1 + +x_t = x_{t-1} + \frac{1}{t-1} (y_{t-1} - x_{t-1}), \qquad x_1 = 0 . +``` + +Actual inflation is a disturbed version of the best response mapping evaluated at $x_t$, + +```{math} +:label: pc_expect2 + +y_t = B(x_t) + \eta_t , +``` + +where $\eta_t$ is an i.i.d. mean-zero term that represents the government's imperfect control of inflation. + +Substituting {eq}`pc_expect2` into {eq}`pc_expect1` gives the stochastic recursion + +```{math} +:label: pc_expect3 + +x_t = x_{t-1} + \frac{1}{t-1} \left[ B(x_{t-1}) - x_{t-1} + \eta_t \right] . +``` + +By the theory of stochastic approximation, the limiting behavior of $x_t$ is described by the associated ordinary differential equation (ODE) + +```{math} +:label: pc_ode + +\frac{d x}{d t} = B(x) - x . +``` + +The rest point of this ODE satisfies $x = B(x)$, which is the Nash equilibrium inflation rate $x = \theta U^*$. + +Because the map $B$ is affine, the ODE is linear with slope + +$$ +\mathcal{M} = \frac{d}{d x}\left( B(x) - x \right) = B'(x) - 1 = - \frac{1}{\theta^2 + 1} . +$$ + +Since $\mathcal{M} < 0$, the ODE is stable about its rest point, and theorems of {cite}`MarcetSargent1989` give conditions under which $x_t$ converges to $y^N$ globally. + +Let's simulate the recursion {eq}`pc_expect3` and confirm convergence to the Nash outcome. + +```{code-cell} ipython3 +def ls_learning(cm, T=2000, σ_η=1.0, seed=0): + "Simulate least squares learning of expected inflation." + rng = np.random.default_rng(seed) + x = np.empty(T + 1) + y = np.empty(T + 1) + x[0] = 0.0 + y[0] = cm.B(x[0]) + for t in range(1, T + 1): + η = σ_η * rng.standard_normal() + y[t] = cm.B(x[t - 1]) + η + gain = 1.0 / (t + 1) # decreasing gain + x[t] = x[t - 1] + gain * (cm.B(x[t - 1]) - x[t - 1] + η) + return x, y +``` + +```{code-cell} ipython3 +x, y = ls_learning(cm, T=2000, σ_η=1.0) + +fig, ax = plt.subplots(figsize=(9, 5)) +ax.plot(x, 'C0', lw=1, label='expected inflation $x_t$') +ax.axhline(y_N, color='k', ls='--', lw=1, label='Nash $y^N$') +ax.axhline(y_R, color='C2', ls=':', lw=1, label='Ramsey $y^R$') +ax.set_xlabel('$t$') +ax.set_ylabel('inflation') +ax.legend() +plt.show() +``` + +The least squares dynamics confirm the pessimism of the best response dynamics. + +Given an initial condition in the form of a low, gold-standard value of $x$, the best response or least squares dynamics can explain an *acceleration* of inflation. + +But they cannot explain a Volcker-style *stabilization* that brings inflation back down. + +Later lectures reformulate versions of least squares learning in ways designed to moderate this pessimism. + +## More foresight + +Best response and least squares learning are out-of-equilibrium dynamics tacked onto a one-period economy. + +They force all movement through expectation formation: in choosing inflation, the government forgets that the economy lasts more than one period. + +Better outcomes can occur if the government plans for the future. + +Subsequent lectures describe three ways of modeling foresight, which impute varying amounts of rationality and predict different qualities of outcomes: + +1. A reputational approach that attributes rational expectations to both the government and the public. Many outcomes are sustainable, ranging from repetition of the Ramsey outcome to paths worse than repetition of the Nash outcome. +2. An approach that keeps the government rational but gives the public *adaptive* expectations in the original Cagan-Friedman sense. This is the subject of {doc}`phillips_adaptive`. Depending on a comparison between a discount factor and an adaptation parameter, this setup can improve outcomes and possibly sustain repetition of the Ramsey outcome. +3. An approach that attributes adaptive behavior to both the government and the public. This is the subject of {doc}`phillips_misspecified` and {doc}`phillips_self_confirming`. + +## Appendix: stochastic approximation + +Here we sketch why the ODE {eq}`pc_ode` governs the tail behavior of the stochastic difference equation {eq}`pc_expect3`. + +The argument, due to {cite}`KushnerClark1978`, has two key components: a shift in time scale and a liberal application of averaging. + +Let $\{a_n\}_{n \geq 0}$ be a positive sequence of gains satisfying + +$$ +\lim_{n \to \infty} a_n = 0, \qquad \sum_n a_n = +\infty, \qquad \sum_n a_n^2 < +\infty . +$$ + +The choice $a_n = 1 / (n + 1)$ satisfies these assumptions. + +Rewrite the recursion as + +```{math} +:label: pc_sa1 + +x_{n+1} = x_n + a_n \left[ B(x_n) - x_n + \eta_n \right] , +``` + +where $\eta_n$ is i.i.d. with mean zero and finite variance. + +Introduce the transformed time scale $t_0 = 0$, $t_n = \sum_{i=0}^{n-1} a_i$, and interpolate the discrete sequence $\{x_n\}$ into a continuous-time process $x^0(t)$. + +Kushner and Clark show that on this transformed time scale the interpolated process is well approximated by the integral equation + +$$ +x^0(t) = x^0(0) + \int_0^t \left[ B(x^0(s)) - x^0(s) \right] d s + R(t) , +$$ + +where the approximation error $R(t)$ has two components — one from approximating a distributed lag in $B(x) - x$ by an integral, and one from a distributed lag in $\eta_s$. + +Studying a sequence of left-shifted versions of the process, they show that both error components can be driven to zero as $n \to \infty$. + +The key step for the noise component is to note that the relevant partial sums form a martingale with variance proportional to $\sum_i a_i^2$, which converges because $\sum_i a_i^2 < \infty$. + +The remaining error is sent to zero because $a_i \to 0$ shrinks the mesh of the Riemann sum used to approximate the integral. + +In the limit, the stochastic difference equation {eq}`pc_sa1` shares the behavior of the non-stochastic ODE + +$$ +\frac{d}{d t} \tilde x(t) = B(\tilde x(t)) - \tilde x(t) , +$$ + +which is said to describe the *mean dynamics* of the original system. + +Later lectures study systems like {eq}`pc_sa1` in which $a_i$ does *not* approach zero as $i$ grows — so-called constant gain algorithms. + +The mean dynamics {eq}`pc_ode` and these constant-gain algorithms become the central tools of {doc}`phillips_learning`, {doc}`phillips_escaping_nash`, and {doc}`phillips_priors`, where the scalar expectation $x$ studied here grows into a whole vector of drifting Phillips-curve coefficients. + +## Exercises + +```{exercise-start} +:label: pc_ex1 +``` + +The convergence rate of least squares learning depends on the slope $\mathcal{M} = -1/(\theta^2 + 1)$ of the associated ODE. + +A necessary condition for convergence at the usual $\sqrt{t}$ rate is $\mathcal{M} < -1/2$, which requires $\theta < 1$. + +Simulate least squares learning for $\theta \in \{0.5, 1.0, 2.0\}$ (holding $U^*$ fixed) and compare how quickly $x_t$ settles down near its Nash value $\theta U^*$. + +Plot the three paths of $x_t - \theta U^*$ on one figure. + +```{exercise-end} +``` + +```{solution-start} pc_ex1 +:class: dropdown +``` + +```{code-cell} ipython3 +fig, ax = plt.subplots(figsize=(9, 5)) + +for θ in [0.5, 1.0, 2.0]: + cm_θ = CredibilityModel(θ=θ, U_star=5.0) + x, _ = ls_learning(cm_θ, T=3000, σ_η=1.0, seed=1) + ax.plot(x - cm_θ.nash(), lw=1, label=rf'$\theta = {θ}$') + +ax.axhline(0, color='k', lw=0.8) +ax.set_xlabel('$t$') +ax.set_ylabel('$x_t - \\theta U^*$') +ax.legend() +plt.show() +``` + +Smaller $\theta$ (a steeper $\mathcal{M}$) produces faster and tighter convergence to the Nash inflation rate. + +Larger $\theta$ leaves $x_t$ wandering more persistently around its limit. + +```{solution-end} +``` diff --git a/lectures/phillips_drifts_volatilities.md b/lectures/phillips_drifts_volatilities.md new file mode 100644 index 000000000..becf74ee7 --- /dev/null +++ b/lectures/phillips_drifts_volatilities.md @@ -0,0 +1,3419 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.16.7 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(phillips_drifts_volatilities)= +```{raw} jupyter +
+ + QuantEcon + +
+``` + +# Drifts and Volatilities + +```{contents} Contents +:depth: 2 +``` + +## Overview + +The lectures in this section have told a story about how a government's *model* +of the Phillips curve, and the *policy* it induces, can drift over time. + +In {doc}`phillips_learning` and {doc}`phillips_escaping_nash` a government that +fits and refits an approximating Phillips curve is repeatedly pushed away from a +bad {doc}`self-confirming equilibrium ` along an +*escape route*, while {doc}`phillips_priors` and {doc}`phillips_lost_conquest` +use drifting beliefs to interpret the rise and fall of American inflation. + +Those lectures were mostly about *theory*. + +This lecture turns to the *data*. + +It studies {cite:t}`CogleySargent2005`, which asks a deceptively simple +question: + +> When we look at postwar U.S. time series on inflation, unemployment, and +> interest rates, do we see evidence that the dynamics have *drifted*? + +Tim Cogley and Thomas Sargent began this work as an empirical companion to the +*Conquest* book {cite}`Sargent1999` and the escape-route papers +{cite}`ChoWilliamsSargent2002`. + +It is also a response to searching comments by {cite:t}`Sims2001comment` and +{cite:t}`Stock2001comment` on an earlier paper {cite}`CogleySargent2001`, and it +grew into a friendly debate with {cite:t}`SimsZha2006` and +{cite:t}`BernankeMihov1998` about a question that organizes this whole section: + +*Was the Great Inflation of the 1970s and its conquest in the 1980s a story of +bad policy, or of bad luck?* + +To let the data speak to that question we need a statistical model flexible +enough to accommodate *both* answers. + +That model is a *Bayesian vector autoregression whose coefficients drift as +random walks and whose shock variances evolve as stochastic volatilities*. + +Fitting it requires a Markov chain Monte Carlo algorithm that combines the +{doc}`Kalman filter `, the forward-filter/backward-sample smoother of +{cite:t}`CarterKohn1994`, and the stochastic-volatility sampler of +{cite:t}`Jacquier1994`. + +Readers who want background will find companion-form vector autoregressions in +{doc}`var_dmd`, the Kalman smoother in {doc}`kalman_2`, and Bayesian inference +for state-space models by MCMC in {doc}`ar1_bayes` and {doc}`ar1_turningpts`. + +We work through the data transformation, prior, sampler, and main empirical +results. + +Let's start with some imports and the path to the data. + +```{code-cell} ipython3 +from pathlib import Path +import time + +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +from IPython.display import display, Math +from scipy import linalg +from scipy.special import expit +from scipy.stats import invwishart + + +def locate_data_assets(): + """Find assets from either a MyST build or the repository root.""" + relative = Path('_static/lecture_specific/phillips_drifts_volatilities') + candidates = (relative, Path('lectures') / relative) + for candidate in candidates: + if (candidate / 'NEWQDATA.csv').is_file(): + return candidate + searched = ', '.join(str(path.resolve()) for path in candidates) + raise FileNotFoundError(f'NEWQDATA.csv was not found; searched {searched}') + + +asset_path = locate_data_assets() +data_path = asset_path / 'NEWQDATA.csv' +``` + +## Bad policy or bad luck? + +Two respectable views compete to explain the American Great Inflation — the same +two stories, triumph versus vindication, that open {doc}`phillips_two_stories`. + +The **bad policy** view is the one dramatized throughout this section and in the +*Conquest* book {cite}`Sargent1999`. + +Something about Arthur Burns's *model* of the economy, his *patience*, or his +inability to *commit* to a better rule led the Federal Reserve to administer +monetary policy in a way that produced the greatest peacetime inflation in U.S. +history, while an improved model, more patience, or greater discipline led Paul +Volcker to conquer it {cite}`DeLong1997,Taylor1997comment`. + +On this view, what changed between the 1970s and the 1980s was the *systematic +part* of policy, namely the way the Fed's interest-rate setting responded to +inflation and unemployment. + +The **bad luck** view says something quite different. + +What distinguished the Burns and Volcker eras was not their models or policies, +but the *shocks* that hit the economy. + +On this view the *coefficients* of a reduced-form description of the economy +were essentially constant, and what changed was the *size* of the disturbances, +namely the *volatility*. + +{cite:t}`BernankeMihov1998` and {cite:t}`SimsZha2006` marshaled evidence for +this second view, in part by applying classical tests that *failed to reject* +the hypothesis that VAR coefficients were time invariant. + +How can we discriminate? + +A constant-coefficient, constant-volatility VAR can generate unusually large +realized shocks, but it cannot represent systematic changes in their variance +over time. + +A model with drifting coefficients but constant volatility can also mistake +changing volatility for coefficient drift. + +So Cogley and Sargent build a model that has room for *both* channels at once, +and they let a Bayesian posterior sort out how much of each the data call for. + +## A VAR with drifting coefficients and stochastic volatility + +Let the variables be ordered as nominal interest, transformed unemployment, and +inflation, + +$$ +y_t = \begin{bmatrix} i_t & u_t & \pi_t \end{bmatrix}'. +$$ + +(Here $u_t$ is not the raw unemployment rate but its logit, we define this transformation in the data section below.) + +The measurement equation is a VAR with two lags and date-specific coefficients, + +```{math} +:label: csdv_measurement +y_t = X_t'\theta_t + \varepsilon_t, +\qquad +X_t' = I_3 \otimes \begin{bmatrix} 1 & y_{t-1}' & y_{t-2}' \end{bmatrix}. +``` + +Each equation has an intercept and six lag coefficients, so $\theta_t$ contains +$3(1+2\times 3)=21$ elements. + +A two-lag VAR can be rewritten as a one-lag system by stacking $y_t$ and +$y_{t-1}$ into a single vector; the matrix that multiplies this stacked vector +is the **companion matrix**, and the rewritten system is the VAR in +**companion form**. + +The following function builds the companion matrix from the stacked +coefficients. + +```{code-cell} ipython3 +n_variables = 3 +n_lags = 2 +n_regressors = 1 + n_variables * n_lags +n_coefficients = n_variables * n_regressors + + +def companion_matrix(θ): + """Return the intercept and companion matrix for one coefficient vector.""" + equation_rows = np.asarray(θ, dtype=float).reshape( + n_variables, n_regressors + ) + intercept = np.r_[equation_rows[:, 0], np.zeros(n_variables)] + companion = np.zeros((n_variables * n_lags, n_variables * n_lags)) + companion[:n_variables] = equation_rows[:, 1:] + companion[n_variables:, :n_variables] = np.eye(n_variables) + return intercept, companion + + +def design_matrix(regressors): + """Return the observation matrix X_t prime for one date.""" + return np.kron(np.eye(n_variables), np.asarray(regressors, dtype=float)) +``` + +The coefficient vector follows a driftless random walk, + +```{math} +:label: csdv_transition +\theta_t = \theta_{t-1} + v_t, +\qquad +v_t \sim N(0,Q). +``` + +A prior over how fast coefficients drift plays a mirror-image role in +{doc}`phillips_priors`: there it is the *government's* prior about a drifting +Phillips curve that shapes the policy it chooses, whereas here it is the +*econometrician's* prior in a posterior about drifting reduced-form dynamics. + +The companion system is stable when every companion-matrix eigenvalue lies +strictly inside the unit circle. + +For an AR(1), this is $|\rho|<1$; equivalently, the zero of $1-\rho z$ lies +outside the unit circle. + +Cogley and Sargent rule out explosive paths by retaining a path only when the +companion matrix is stable at every date and using the truncated prior + +```{math} +:label: csdv_stability +p(\theta^T,Q) \propto I(\theta^T) f(\theta^T \mid Q) f(Q), +``` + +where $I(\theta^T)=1$ denotes a stable path. + +This restriction encodes the belief that the economy did not in fact follow an +explosive path. + +This restriction also tilts the marginal prior for $Q$ toward values that are +less likely to generate explosive coefficient paths. + +The code below applies the stability restriction to an entire trajectory + +```{code-cell} ipython3 +def companion_roots(θ_path): + """Return all companion roots along a path with shape (21, T).""" + θ_path = np.asarray(θ_path, dtype=float) + if θ_path.ndim == 1: + θ_path = θ_path[:, None] + companions = np.stack( + [ + companion_matrix(θ_path[:, t])[1] + for t in range(θ_path.shape[1]) + ] + ) + return np.linalg.eigvals(companions) + + +def is_stable(θ_path): + """Test whether every companion root is strictly inside the unit circle.""" + return bool(np.max(np.abs(companion_roots(θ_path))) < 1) +``` + +The reduced-form innovation covariance changes over time according to + +```{math} +:label: csdv_covariance +\varepsilon_t = R_t^{1/2}\xi_t, +\qquad +\xi_t \sim N(0,I_3), +\qquad +R_t = B^{-1} H_t B^{-1\prime}, +``` + +where + +$$ +B = +\begin{bmatrix} +1 & 0 & 0 \\ +\beta_{21} & 1 & 0 \\ +\beta_{31} & \beta_{32} & 1 +\end{bmatrix}, +\qquad +H_t = \operatorname{diag}(h_{1t},h_{2t},h_{3t}). +$$ + +The diagonal elements $h_{it}$ let the size of each orthogonalized shock wax and +wane over time. + +The next two functions construct the triangular factor and the reduced-form +innovation covariance. + +```{code-cell} ipython3 +def b_matrix(β): + """Construct B from β_21, β_31, and β_32.""" + matrix = np.eye(n_variables) + matrix[1, 0], matrix[2, 0], matrix[2, 1] = np.asarray(β, dtype=float) + return matrix + + +def innovation_covariance(h, β): + """Construct R_t from one vector of orthogonalized variances.""" + inverse = np.linalg.inv(b_matrix(β)) + return inverse @ np.diag(h) @ inverse.T +``` + +Each diagonal volatility is a geometric random walk, + +```{math} +:label: csdv_volatility +\log h_{it} = \log h_{i,t-1} + \sigma_i \eta_{it}, +\qquad +\eta_{it} \sim N(0,1). +``` + +The standardized measurement innovations, coefficient innovations, and +volatility innovations are mutually independent. + +Setting $Q=0$ produces constant coefficients with drifting volatility, while +holding $H_t$ fixed produces drifting coefficients with constant volatility. + +The posterior contains the full paths $\theta^T$ and $H^T$ together with $Q$, +$\beta$, and $(\sigma_1,\sigma_2,\sigma_3)$. + +This posterior has thousands of dimensions, so later sections build a sampler +that updates one group of parameters at a time, holding the rest fixed. + +## The data + +We begin with {cite:t}`CogleySargent2005`'s quarterly U.S. dataset ending in 2000Q4. + +Inflation is the log difference of the seasonally adjusted CPI for all urban +consumers, point sampled in the third month of each quarter. + +Unemployment is the quarterly average of the seasonally adjusted civilian +unemployment rate and enters the VAR as $0.01\log[u/(1-u)]$, a logit +transformation that maps a bounded rate into an unconstrained variable. + +The nominal interest rate is the log of one plus the three-month Treasury-bill +rate, averaged over daily observations in the first month of each quarter and +expressed as a quarterly fraction. + +The data starts in 1948Q2 because its first inflation observation is +already differenced, so two VAR lags make 1948Q4 the first usable regression +date. + +The following cell performs every transformation and constructs the VAR(2) data +directly from the series. + +```{code-cell} ipython3 +def prepare_data(source, ordering=('i', 'u', 'pi')): + """Transform a quarterly table and construct the VAR data.""" + if isinstance(source, (str, Path)): + table = pd.read_csv(source) + else: + table = source.copy() + variables = { + 'i': table['y3'].to_numpy(dtype=float), + 'u': 0.01 * np.log( + table['ur'].to_numpy(dtype=float) + / (1 - table['ur'].to_numpy(dtype=float)) + ), + 'pi': table['dp'].to_numpy(dtype=float), + } + if sorted(ordering) != ['i', 'pi', 'u']: + raise ValueError("ordering must be a permutation of ('i', 'u', 'pi')") + raw_y = np.column_stack([variables[name] for name in ordering]) + raw_dates = table['date'].to_numpy(dtype=float) + regressors = np.ones((len(table) - n_lags, n_regressors)) + for lag in range(1, n_lags + 1): + left = 1 + n_variables * (lag - 1) + regressors[:, left:left + n_variables] = raw_y[n_lags-lag:-lag] + targets = raw_y[n_lags:] + dates = raw_dates[n_lags:] + n_training = 4 * 11 - n_lags - 1 + return { + 'raw_dates': raw_dates, + 'raw_y': raw_y, + 'prior_dates': dates[:n_training], + 'prior_y': targets[:n_training], + 'prior_x': regressors[:n_training], + 'dates': dates[n_training:], + 'y': targets[n_training:], + 'x': regressors[n_training:], + } + + +data = prepare_data(data_path) + +data_summary = pd.Series( + { + 'ordering': 'interest, unemployment, inflation', + 'prior sample': '1948Q4--1958Q4', + 'prior observations': len(data['prior_dates']), + 'posterior sample': '1959Q1--2000Q4', + 'posterior observations': len(data['dates']), + 'VAR lags': n_lags, + 'coefficient dimension': n_coefficients, + }, + name='value', +) + +data_summary.to_frame() +``` + +The early observations calibrate the prior, while the remaining observations +form the posterior sample. + +Let's view the data in familiar economic units. + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: Observed $\pi_t$, $u_t$, and $i_t$, 1948Q2--2000Q4 + name: fig-csdv-historical-data +--- +dates_raw = data['raw_dates'] +interest = 400 * np.expm1(data['raw_y'][:, 0]) +unemployment = 100 * expit(100 * data['raw_y'][:, 1]) +inflation = 400 * data['raw_y'][:, 2] + +fig, axes = plt.subplots(3, 1, figsize=(9, 7), sharex=True) +axes[0].plot(dates_raw, inflation, lw=2) +axes[0].set_ylabel('inflation (annual %)') +axes[1].plot(dates_raw, unemployment, lw=2) +axes[1].set_ylabel('unemployment (%)') +axes[2].plot(dates_raw, interest, lw=2) +axes[2].set_ylabel('interest (annual %)') +axes[2].set_xlabel('year') +plt.tight_layout() +plt.show() +``` + +Inflation and nominal interest rates rise together through the 1970s and peak +around 1980, unemployment peaks only after disinflation begins, and all three +series are calmer in the 1990s. + +These observations locate the episode but cannot tell whether changed +systematic dynamics or unusually large shocks produced it, which is the +distinction the model is built to examine. + +## Priors + +The priors are independent across parameters and deliberately weak so that, in +Cogley and Sargent's phrase, "the data are free to speak." + +They are calibrated from a time-invariant VAR fitted to the short 1948--1958 +training sample. + +The initial coefficient prior is a stable, truncated Gaussian, + +$$ +p(\theta_0) \propto I(\theta_0)N(\bar\theta,\bar P), +$$ + +where $\bar\theta$ and $\bar P$ come from a constant-coefficient seemingly +unrelated regression fitted to the 1948--1958 training sample. + +Because all three equations have identical regressors, the SUR coefficient +estimates equal equation-by-equation OLS estimates, which lets us implement the +calibration compactly. + +```{code-cell} ipython3 +def sur_prior(y, x): + """Calibrate the Gaussian coefficient prior from a constant VAR.""" + xx_inverse = np.linalg.inv(x.T @ x) + coefficients = xx_inverse @ x.T @ y + residuals = y - x @ coefficients + residual_covariance = np.cov(residuals, rowvar=False, ddof=1) + θ = coefficients.T.reshape(-1) + covariance = np.kron(residual_covariance, xx_inverse) + return θ, covariance, residual_covariance + + +θ_bar, p_bar, r_bar = sur_prior(data['prior_y'], data['prior_x']) + +assert is_stable(θ_bar) +``` + +The coefficient-drift covariance has the inverse-Wishart prior + +```{math} +:label: csdv_q_prior +Q \sim IW_{21}\left(T_0,T_0\bar Q\right), +\qquad +T_0 = 22, +\qquad +\bar Q = \gamma^2 \bar P, +\qquad +\gamma^2 = 3.5\times 10^{-4}. +``` + +The convention in {eq}`csdv_q_prior` lists degrees of freedom first and the +inverse-Wishart scale matrix second. + +Because $T_0$ is only one greater than the dimension of $\theta_t$, the prior is +proper but has no finite mean. + +The matrix $\bar Q$ is therefore a conservative scale calibration rather than +the expectation of $Q$. + +This calibration favors slow coefficient drift before the posterior sees the +main sample. + +The remaining priors are + +$$ +\begin{aligned} +\log h_{i0} &\sim N(\log \bar R_{ii},10), \\ +\beta &\sim N(0,10000I_3), \\ +\sigma_i^2 &\sim IG\left(\frac{1}{2},\frac{0.01^2}{2}\right), +\end{aligned} +$$ + +where $\bar R$ is the residual covariance from the training-sample regression. + +The next function gathers these hyperparameters. + +```{code-cell} ipython3 +def calibrate_prior(model_data, γ_squared=3.5e-4): + """Return every calibrated prior for one variable ordering.""" + θ_mean, θ_covariance, residual_covariance = sur_prior( + model_data['prior_y'], model_data['prior_x'] + ) + degrees_freedom = n_coefficients + 1 + q_center = γ_squared * θ_covariance + return { + 'θ_mean': θ_mean, + 'θ_covariance': θ_covariance, + 'q_center': q_center, + 'q_scale': degrees_freedom * q_center, + 'q_degrees_freedom': degrees_freedom, + 'log_h_mean': np.log(np.diag(residual_covariance)), + 'log_h_variance': 10.0, + 'β_mean': np.zeros(3), + 'β_variance': 10000.0, + 'σ_degrees_freedom': 1.0, + 'σ_scale': 0.01**2, + 'γ_squared': γ_squared, + } + + +prior = calibrate_prior(data) +q_bar = prior['q_center'] + +prior_summary = pd.Series( + { + 'dim(θ)': n_coefficients, + 'T0': prior['q_degrees_freedom'], + 'γ squared': prior['γ_squared'], + 'trace(Q bar)': np.trace(q_bar), + 'log-h prior variance': prior['log_h_variance'], + 'β prior variance': prior['β_variance'], + 'σ-squared IG shape': prior['σ_degrees_freedom'] / 2, + 'σ-squared IG scale': prior['σ_scale'] / 2, + }, + name='value', +) + +prior_summary.to_frame() +``` + +## A Metropolis-within-Gibbs sampler + +We simulate the posterior by cycling through five parameter blocks used by +{cite:t}`CogleySargent2005`. + +One pass through all five blocks is called a sweep, and the sampler runs many +sweeps to build up the posterior sample. + +Cogley and Sargent simulate the unrestricted posterior and then discard a +complete MCMC realization whenever its coefficient path is explosive. + +But in this case only stable sweeps contribute realizations +to the retained restricted-posterior sample. + +We instead impose stability inside the coefficient-path block with the +elliptical slice sampler of {cite:t}`MurrayAdamsMacKay2010`. + +This changes the transition kernel, but not its target posterior. + +1. An auxiliary Gaussian coefficient path is drawn by a Carter--Kohn + forward-filter/backward-sample step and used to update the stable path by + elliptical slice sampling. + +2. The drift covariance $Q$ is drawn from an inverse-Wishart distribution + conditional on the coefficient innovations. + +3. The volatility innovation variances $\sigma_i^2$ are drawn from inverse-gamma + distributions conditional on the volatility increments. + +4. The covariance parameters $\beta$ are drawn from two transformed Gaussian + regressions among the VAR residuals. + +5. The volatility paths $H^T$ are drawn one date at a time by a + Jacquier--Polson--Rossi Metropolis step. + +This ordering matters: every block in a sweep is paired with the values on which +it was actually conditioned. + +### Coefficient path + +Conditional on $R^T$ and $Q$, a forward Kalman filter followed by the +Carter--Kohn backward simulator draws the entire coefficient path +{cite}`CarterKohn1994`. + +The forward pass is a Kalman filter, while the backward pass samples +$\theta_T,\theta_{T-1},\ldots,\theta_0$ in reverse with each state conditioned +on the draw that follows it. + +Sampling $\theta_0$ is essential. + +It supplies all $T$ random-walk increments to the conjugate update for $Q$. + +```{code-cell} ipython3 +def covariance_root(matrix): + """Return a numerically stable lower covariance factor.""" + matrix = 0.5 * (matrix + matrix.T) + scale = max(1.0, np.max(np.abs(np.diag(matrix)))) + return np.linalg.cholesky(matrix + 1e-12 * scale * np.eye(len(matrix))) + + +def draw_coefficient_path( + y, x, q, h, β, prior, rng, return_mean=False +): + """Draw θ_0,...,θ_T and optionally return its smoothing mean.""" + periods = len(y) + filtered_mean = np.empty((periods + 1, n_coefficients)) + filtered_covariance = np.empty( + (periods + 1, n_coefficients, n_coefficients) + ) + predicted_covariance = np.empty_like(filtered_covariance) + filtered_mean[0] = prior['θ_mean'] + filtered_covariance[0] = prior['θ_covariance'] + predicted_covariance[0] = prior['θ_covariance'] + for t in range(1, periods + 1): + observation = design_matrix(x[t - 1]) + prediction_covariance = filtered_covariance[t - 1] + q + r_t = innovation_covariance(h[t], β) + forecast_covariance = ( + observation @ prediction_covariance @ observation.T + r_t + ) + gain = linalg.solve( + forecast_covariance, + (prediction_covariance @ observation.T).T, + assume_a='pos', + ).T + mean = filtered_mean[t - 1] + mean = mean + gain @ (y[t - 1] - observation @ mean) + covariance = ( + prediction_covariance + - gain @ observation @ prediction_covariance + ) + covariance = 0.5 * (covariance + covariance.T) + filtered_mean[t] = mean + filtered_covariance[t] = covariance + predicted_covariance[t] = prediction_covariance + path = np.empty((n_coefficients, periods + 1)) + if not return_mean: + path[:, -1] = ( + filtered_mean[-1] + + covariance_root(filtered_covariance[-1]) + @ rng.standard_normal(n_coefficients) + ) + for t in range(periods - 1, -1, -1): + smoother = linalg.solve( + predicted_covariance[t + 1], + filtered_covariance[t].T, + assume_a='pos', + ).T + mean = filtered_mean[t] + smoother @ ( + path[:, t + 1] - filtered_mean[t] + ) + covariance = ( + filtered_covariance[t] + - smoother @ predicted_covariance[t + 1] @ smoother.T + ) + path[:, t] = mean + covariance_root(covariance) @ ( + rng.standard_normal(n_coefficients) + ) + return path + + smoothed_mean = np.empty_like(path) + centered_draw = np.empty_like(path) + smoothed_mean[:, -1] = filtered_mean[-1] + centered_draw[:, -1] = covariance_root(filtered_covariance[-1]) @ ( + rng.standard_normal(n_coefficients) + ) + for t in range(periods - 1, -1, -1): + smoother = linalg.solve( + predicted_covariance[t + 1], + filtered_covariance[t].T, + assume_a='pos', + ).T + smoothed_mean[:, t] = filtered_mean[t] + smoother @ ( + smoothed_mean[:, t + 1] - filtered_mean[t] + ) + covariance = ( + filtered_covariance[t] + - smoother @ predicted_covariance[t + 1] @ smoother.T + ) + centered_draw[:, t] = ( + smoother @ centered_draw[:, t + 1] + + covariance_root(covariance) @ rng.standard_normal(n_coefficients) + ) + return smoothed_mean + centered_draw, smoothed_mean + + +def draw_stable_coefficient_path( + current, y, x, q, h, β, prior, rng, max_contractions=100 +): + """Elliptical-slice update of the stability-truncated Gaussian path.""" + if not is_stable(current): + raise ValueError('the elliptical-slice update needs a stable path') + gaussian_draw, mean = draw_coefficient_path( + y, x, q, h, β, prior, rng, return_mean=True + ) + current_centered = current - mean + innovation = gaussian_draw - mean + angle = rng.uniform(0, 2 * np.pi) + lower = angle - 2 * np.pi + upper = angle + for contractions in range(max_contractions + 1): + proposal = ( + mean + + current_centered * np.cos(angle) + + innovation * np.sin(angle) + ) + if is_stable(proposal): + return proposal, contractions + if angle < 0: + lower = angle + else: + upper = angle + angle = rng.uniform(lower, upper) + raise RuntimeError('elliptical-slice stability bracket did not contract') +``` + +### Drift covariance + +Conditional on the coefficient increments, $Q$ has an inverse-Wishart full +conditional. + +The retained simulation draws $Q$ from a scale matrix formed by its prior scale +and all $T$ squared increments from $\theta_0$ through $\theta_T$. + +```{code-cell} ipython3 +def draw_q(θ_path, prior, rng): + """Draw Q conditional on the sampled coefficient path.""" + increments = np.diff(θ_path, axis=1) + scale = prior['q_scale'] + increments @ increments.T + degrees_freedom = prior['q_degrees_freedom'] + increments.shape[1] + return invwishart.rvs(df=degrees_freedom, scale=scale, random_state=rng) +``` + +### Volatility parameters and paths + +Conditional on the volatility increments, each $\sigma_i^2$ has an inverse-gamma +full conditional. + +Conditional on the VAR residuals and $H^T$, the free elements of $B$ are drawn +from two Gaussian regressions. + +Conditional on the orthogonalized residuals, each volatility state is updated +with the single-site Metropolis step of {cite:t}`Jacquier1994`. + +The random-walk neighbors determine the Gaussian proposal for a log volatility, +while the corresponding orthogonalized residual determines whether that proposal +is accepted. + +The following code implements the conditional updates, including the different +endpoint proposals. + +```{code-cell} ipython3 +def var_residuals(y, x, θ_path): + """Return residuals with shape (T, 3).""" + if θ_path.shape[1] == len(y) + 1: + θ_path = θ_path[:, 1:] + if θ_path.shape[1] != len(y): + raise ValueError('θ_path must contain T or T + 1 states') + coefficients = θ_path.T.reshape(len(y), n_variables, n_regressors) + fitted = np.einsum('tk,tnk->tn', x, coefficients) + return y - fitted + + +def draw_σ(h, prior, rng): + """Draw the three log-volatility innovation standard deviations.""" + increments = np.diff(np.log(h), axis=0) + shape = (prior['σ_degrees_freedom'] + increments.shape[0]) / 2 + scales = (prior['σ_scale'] + np.sum(increments**2, axis=0)) / 2 + σ_squared = scales / rng.gamma(shape, 1.0, size=n_variables) + return np.sqrt(σ_squared) + + +def draw_β(residuals, h, prior, rng): + """Draw the free elements of B from transformed Gaussian regressions.""" + β = np.empty(3) + offset = 0 + for equation in range(1, n_variables): + standardized = residuals / np.sqrt(h[1:, equation])[:, None] + dependent = standardized[:, equation] + regressors = -standardized[:, :equation] + prior_precision = np.eye(equation) / prior['β_variance'] + covariance = np.linalg.inv(prior_precision + regressors.T @ regressors) + prior_slice = prior['β_mean'][offset:offset + equation] + mean = covariance @ ( + prior_precision @ prior_slice + regressors.T @ dependent + ) + β[offset:offset + equation] = ( + mean + covariance_root(covariance) @ rng.standard_normal(equation) + ) + offset += equation + return β + + +def accept_volatility(proposal, current, residual, rng): + """Apply the Jacquier--Polson--Rossi likelihood acceptance step.""" + log_ratio = ( + -0.5 * np.log(proposal) + - residual**2 / (2 * proposal) + + 0.5 * np.log(current) + + residual**2 / (2 * current) + ) + return proposal if np.log(rng.random()) <= min(0.0, log_ratio) else current + + +def draw_volatility_path(h, residuals, β, σ, prior, rng): + """Update all stochastic-volatility states one date at a time.""" + periods = len(residuals) + orthogonalized = (b_matrix(β) @ residuals.T).T + updated = np.empty_like(h) + for equation in range(n_variables): + variance = σ[equation]**2 + initial_variance = ( + prior['log_h_variance'] * variance + / (variance + prior['log_h_variance']) + ) + initial_mean = initial_variance * ( + prior['log_h_mean'][equation] / prior['log_h_variance'] + + np.log(h[1, equation]) / variance + ) + updated[0, equation] = np.exp( + initial_mean + np.sqrt(initial_variance) * rng.standard_normal() + ) + for t in range(1, periods): + mean = 0.5 * ( + np.log(updated[t - 1, equation]) + np.log(h[t + 1, equation]) + ) + proposal = np.exp( + mean + np.sqrt(variance / 2) * rng.standard_normal() + ) + updated[t, equation] = accept_volatility( + proposal, + h[t, equation], + orthogonalized[t - 1, equation], + rng, + ) + proposal = np.exp( + np.log(updated[-2, equation]) + + σ[equation] * rng.standard_normal() + ) + updated[-1, equation] = accept_volatility( + proposal, + h[-1, equation], + orthogonalized[-1, equation], + rng, + ) + return updated +``` + +### Complete sampler + +The restricted posterior assigns zero density to coefficient paths that are +explosive at any date, including $\theta_0$. + +Stack the whole coefficient path $\theta_0,\ldots,\theta_T$ into $z$, and +collect the other parameter blocks in $\lambda=(Q,H^T,\beta,\sigma)$. + +Conditional on $\lambda$ and the data $Y^T$, the unrestricted Carter--Kohn +distribution is Gaussian with smoothing mean $m$ and covariance $C$, + +$$ +z\mid \lambda,Y^T \sim N(m,C). +$$ + +Let $\mathcal A$ be the set of paths whose companion roots are strictly inside +the unit circle at every date, so that the restricted full conditional is this +Gaussian truncated to $\mathcal A$, + +$$ +\pi_{\mathcal A}(z\mid\lambda,Y^T) += \frac{N(z;m,C)\,\mathbb{1}_{\mathcal A}(z)} + {\Pr(z\in\mathcal A\mid\lambda,Y^T)}. +$$ + +That normalizing probability is difficult to compute, but the elliptical +transition never evaluates it. + +Cogley and Sargent instead simulate the unrestricted joint posterior and keep +a realization only when its whole path is stable, which is valid because +conditioning the unrestricted posterior on $z\in\mathcal A$ reproduces the +restricted posterior exactly. + +If $a$ denotes the unrestricted probability that a path is stable, this +rejection scheme needs roughly $1/a$ complete sweeps, including the four other +parameter updates, for every retained draw. + +The elliptical slice sampler avoids that waste by moving within the stable +region instead of restarting from an arbitrary draw. + +It starts from the current stable path $z^{(c)}$ and a fresh Carter--Kohn +draw $\widetilde z\sim N(m,C)$, whose centered version +$\nu=\widetilde z-m$ traces an ellipse together with $z^{(c)}$, + +$$ +z(\phi) +=m+(z^{(c)}-m)\cos\phi+\nu\sin\phi, +\qquad 0\leq\phi<2\pi, +$$ + +so that $z(0)=z^{(c)}$ and $z(\pi/2)=\widetilde z$. + +The algorithm draws an angle uniformly from the full circle and, whenever +$z(\phi)$ is explosive, shrinks the bracket to the side containing the +known-stable angle $\phi=0$ before drawing again. + +Because companion roots vary continuously with the coefficients, a nonzero +interval around $\phi=0$ is always stable, so this bracket search always +terminates. + +Each rejected angle costs only a linear combination and a companion-root +check, far cheaper than another Kalman filter and backward simulation. + +The transition is valid because rotating the pair $(z^{(c)}-m,\nu)$ by any +angle leaves their joint Gaussian density unchanged, so the search moves +along a fixed orbit on which every point is equally likely under the +unrestricted density. + +The stability indicator plays the role of the likelihood in a standard +elliptical slice update, and since it equals one at the current point, every +accepted angle is automatically a stable one and no separate slice-height +draw is needed. + +Marginalizing out the auxiliary path shows that this transition leaves the +truncated Gaussian $N(z;m,C)\mathbb{1}_{\mathcal A}(z)$ invariant, exactly the +property a valid transition kernel needs. + +The other four blocks require no such adjustment. + +Conditional on a stable $z$, the stability indicator is constant in +$Q,H^T,\beta$, and $\sigma$, so it cancels from each of their full +conditionals and leaves the same updates as the unrestricted sampler. + +$Q$'s conditional is unchanged in form, but its marginal posterior still tilts +toward less explosive drift because every draw of $Q$ is conditioned on a +stable coefficient path. + +Together, the elliptical transition for $z$ and the unchanged updates for +$Q,H^T,\beta$, and $\sigma$ target the same stability-restricted posterior as +Cogley and Sargent's original rejection sampler, at a fraction of the cost. + +The next function composes these five blocks and includes a +stochastic-volatility warm-up. + +```{code-cell} ipython3 +def initial_volatilities(y, prior): + """Construct the sampler's initial volatility path.""" + changes = np.diff(y, axis=0) + centered = changes - changes.mean(axis=0) + log_h = np.empty((len(y) + 1, n_variables)) + log_h[:2] = prior['log_h_mean'] + log_h[2:] = np.log(np.maximum(centered**2, np.finfo(float).tiny)) + return np.exp(log_h) + + +def run_sampler( + y, + x, + prior, + n_sweeps=1_000, + burn=500, + thin=1, + seed=42, + warmup=200, + max_contractions=100, + stable=True, + fixed_q=None, + retain=('S0D', 'SD', 'QD', 'HD', 'CD', 'VD', 'stable_draw'), + progress_every=0, +): + """Run a Gibbs sampler for the unrestricted or stable posterior. + + For the stable posterior, an elliptical-slice transition updates the FFBS + path inside its stability-truncated Gaussian full conditional. Passing + ``fixed_q`` holds Q at that value (for example a matrix of zeros) instead of + drawing it, which nests the constant-coefficient model. + """ + if not (0 <= burn < n_sweeps and thin >= 1): + raise ValueError('require 0 <= burn < n_sweeps and thin >= 1') + if (n_sweeps - burn) % thin: + raise ValueError('(n_sweeps - burn) must be divisible by thin') + valid_retain = {'S0D', 'SD', 'QD', 'HD', 'CD', 'VD', 'stable_draw'} + unknown = set(retain) - valid_retain + if unknown: + raise ValueError(f'unknown retained arrays: {sorted(unknown)}') + + started = time.perf_counter() + rng = np.random.default_rng(seed) + h = initial_volatilities(y, prior) + β = prior['β_mean'].copy() + warm_θ = np.repeat(prior['θ_mean'][:, None], len(y), axis=1) + warm_residuals = var_residuals(y, x, warm_θ) + for _ in range(warmup): + σ = draw_σ(h, prior, rng) + β = draw_β(warm_residuals, h, prior, rng) + h = draw_volatility_path( + h, warm_residuals, β, σ, prior, rng + ) + + q = ( + prior['q_center'].copy() + if fixed_q is None + else np.array(fixed_q, dtype=float) + ) + θ = np.repeat( + prior['θ_mean'][:, None], len(y) + 1, axis=1 + ) + if stable and not is_stable(θ): + raise ValueError('the prior mean does not provide a stable start') + slice_contractions = 0 + maximum_slice_contractions = 0 + + retained = {name: [] for name in retain} + saved_stability = [] + for sweep in range(1, n_sweeps + 1): + if stable: + θ, contractions = draw_stable_coefficient_path( + θ, + y, + x, + q, + h, + β, + prior, + rng, + max_contractions=max_contractions, + ) + else: + θ = draw_coefficient_path(y, x, q, h, β, prior, rng) + contractions = 0 + slice_contractions += contractions + maximum_slice_contractions = max( + maximum_slice_contractions, contractions + ) + if fixed_q is None: + q = draw_q(θ, prior, rng) + residuals = var_residuals(y, x, θ) + σ = draw_σ(h, prior, rng) + β = draw_β(residuals, h, prior, rng) + h = draw_volatility_path( + h, residuals, β, σ, prior, rng + ) + + if sweep > burn and (sweep - burn) % thin == 0: + path_is_stable = is_stable(θ) + saved_stability.append(path_is_stable) + values = { + 'S0D': θ[:, 0], + 'SD': θ[:, 1:], + 'QD': q, + 'HD': h, + 'CD': β, + 'VD': σ, + 'stable_draw': path_is_stable, + } + for name in retained: + retained[name].append(np.asarray(values[name]).copy()) + + if progress_every and sweep % progress_every == 0: + elapsed = time.perf_counter() - started + print( + f'{sweep:,}/{n_sweeps:,} sweeps; ' + f'{slice_contractions:,} slice contractions; ' + f'{elapsed / 60:.1f} minutes', + flush=True, + ) + + stack_axis = { + 'S0D': 1, + 'SD': 2, + 'QD': 2, + 'HD': 2, + 'CD': 1, + 'VD': 1, + 'stable_draw': 0, + } + result = { + name: np.stack(values, axis=stack_axis[name]) + for name, values in retained.items() + } + result['diagnostics'] = { + 'sampler_version': 3, + 'seed': int(seed), + 'stable_restriction': bool(stable), + 'n_sweeps': int(n_sweeps), + 'burn': int(burn), + 'thin': int(thin), + 'warmup': int(warmup), + 'retained_draws': int((n_sweeps - burn) // thin), + 'slice_contractions': int(slice_contractions), + 'mean_slice_contractions': float(slice_contractions / n_sweeps), + 'maximum_slice_contractions': int(maximum_slice_contractions), + 'retained_stability_rate': float(np.mean(saved_stability)), + 'elapsed_seconds': float(time.perf_counter() - started), + } + return result +``` + +The executable version below uses 1,000 sweeps, discards the first 500, and +retains the remaining 500. + +It uses the complete historical sample and the ordering $(i,u,\pi)$. + +Instead of running a large MCMC experiment, we intentionally keep the sampler +run small so that it finishes in a reasonable time for a lecture. + +This short run illustrates the method but not a numerical replication. + +However the main qualitative features of the posterior are close to those reported by Cogley and Sargent. + +```{code-cell} ipython3 +posterior = run_sampler( + data['y'], + data['x'], + prior, + n_sweeps=1_000, + burn=500, + thin=1, + seed=42, + warmup=200, + stable=True, + progress_every=0, +) + +def validate_posterior_arrays(result, periods): + """Check posterior shapes, finiteness, positivity, and stability.""" + draws = result['diagnostics']['retained_draws'] + expected = { + 'S0D': (n_coefficients, draws), + 'SD': (n_coefficients, periods, draws), + 'QD': (n_coefficients, n_coefficients, draws), + 'HD': (periods + 1, n_variables, draws), + 'CD': (3, draws), + 'VD': (3, draws), + 'stable_draw': (draws,), + } + assert {name: result[name].shape for name in expected} == expected + assert all(np.all(np.isfinite(result[name])) for name in expected) + assert np.all(result['HD'] > 0) + assert np.all(result['VD'] > 0) + assert np.all(result['stable_draw']) + return expected + + +expected_shapes = validate_posterior_arrays(posterior, len(data['dates'])) +``` + +## What the data say + +We summarize the posterior by its mean coefficient path $E(\theta_t\mid T)$ and +mean covariance path $E(R_t\mid T)$, and then interpret them +in the context of the question we asked. + +### The rate and structure of drift + +The trace of $Q$ measures the total rate of coefficient drift, with +$\operatorname{tr}(Q)=0$ corresponding to constant coefficients. + +The histogram shows the retained $Q$ draws and the prior scale. + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: Posterior $\operatorname{tr}(Q)$ and prior $\operatorname{tr}(\bar Q)$ + name: fig-csdv-drift-rate +--- +trace_q = np.trace(posterior['QD'], axis1=0, axis2=1) +fig, ax = plt.subplots() +ax.hist(trace_q, bins=30, histtype='step', lw=2) +ax.axvline(np.trace(q_bar), color='C1', lw=2, + label=r'prior $\mathrm{tr}(\bar Q)$') +ax.set_xlabel(r'$\mathrm{tr}(Q)$') +ax.set_ylabel('frequency') +ax.legend() +plt.show() +``` + +The posterior drift rate lies well above the conservative prior calibration, +indicating more coefficient variation than that calibration anticipated. + +This is not a formal comparison with a fixed-coefficient model because the +continuous prior assigns no point mass to $Q=0$. + +Within the fitted TVP-VAR, this variation is attributed to changing systematic +relationships; it does not identify policy as the cause. + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: Posterior mean VAR coefficients $E(\theta_t\mid T)$ + name: fig-csdv-coefficient-paths +--- +θ_mean = posterior['SD'].mean(axis=2) +mean_path_root_modulus = np.max( + np.abs(companion_roots(θ_mean)), axis=1 +) +assert np.all(mean_path_root_modulus < 1) +coefficient_labels = ( + 'constant', + r'$i_{t-1}$', + r'$u_{t-1}$', + r'$\pi_{t-1}$', + r'$i_{t-2}$', + r'$u_{t-2}$', + r'$\pi_{t-2}$', +) +equation_labels = ( + 'interest equation', + 'unemployment equation', + 'inflation equation', +) + + +def plot_equation_coefficients(axes, dates, θ_path): + """Plot seven labeled coefficients for each VAR equation.""" + first_lines = None + for equation, (ax, label) in enumerate(zip(axes, equation_labels)): + start = equation * len(coefficient_labels) + stop = start + len(coefficient_labels) + lines = ax.plot(dates, θ_path[start:stop].T, lw=2) + for line, coefficient_label in zip(lines, coefficient_labels): + line.set_label(coefficient_label) + if first_lines is None: + first_lines = lines + ax.axhline(0, color='0.65', lw=1) + ax.set_xlabel('year') + ax.set_ylabel(f'{label} coefficient') + return first_lines + + +fig, axes = plt.subplots(1, 3, figsize=(10, 4), sharex=True) +coefficient_lines = plot_equation_coefficients( + axes, + data['dates'], + θ_mean, +) +fig.legend( + coefficient_lines, + coefficient_labels, + loc='lower center', + ncol=4, +) +plt.tight_layout(rect=(0, 0.17, 1, 1)) +plt.show() +``` + +The unemployment equation is comparatively stable, whereas several inflation +equation coefficients move strongly through the 1970s and turn around near +1980. + +The drift is therefore concentrated in how inflation propagates rather than +spread evenly across the VAR, and individual lag coefficients should not be +given structural interpretations because paired lags can offset one another. + +We summarize drift for the ordering $(i,u,\pi)$, which has the smallest stable +posterior mean $\operatorname{tr}(Q)$. + +```{code-cell} ipython3 +trace_q = np.trace(posterior['QD'], axis1=0, axis2=1) +q_mean = posterior['QD'].mean(axis=2) +drift_summary = { + r'\text{Posterior mean } \operatorname{tr}(Q)': np.trace(q_mean), + r'\text{Posterior mean largest eigenvalue}': ( + np.linalg.eigvalsh(q_mean)[-1] + ), + r'\text{Prior } \operatorname{tr}(\bar Q)': np.trace(q_bar), +} +drift_rows = ' \\\\\n'.join( + f'{label} & {value:.4f}' for label, value in drift_summary.items() +) +display(Math(rf''' +\begin{{array}}{{lr}} +\text{{Quantity}} & \text{{Estimate}} \\ +\hline +{drift_rows} +\end{{array}} +''')) +``` + +Cogley and Sargent estimated every ordering. + +Their posterior means show that +the ordering changes magnitudes but does not remove drift: + +| Ordering | Stable $\operatorname{tr}(Q)$ | Stable $\max(\lambda)$ | Unrestricted $\operatorname{tr}(Q)$ | Unrestricted $\max(\lambda)$ | +|---|---:|---:|---:|---:| +| $(i,\pi,u)$ | 0.055 | 0.025 | 0.056 | 0.027 | +| $(i,u,\pi)$ | 0.047 | 0.023 | 0.059 | 0.031 | +| $(\pi,i,u)$ | 0.064 | 0.031 | 0.082 | 0.044 | +| $(\pi,u,i)$ | 0.062 | 0.031 | 0.088 | 0.051 | +| $(u,i,\pi)$ | 0.057 | 0.026 | 0.051 | 0.028 | +| $(u,\pi,i)$ | 0.055 | 0.024 | 0.072 | 0.035 | + +For the minimum-$Q$ ordering, removing the stability restriction raises the +posterior mean drift rate, as the table above shows. + +The analysis that follows adopts the $(i,u,\pi)$ ordering, which places the +nominal interest rate first and inflation last. + +Diagonalizing the posterior mean of $Q$ reveals that the drift is low +dimensional. + +The following eigendecomposition summarizes the posterior mean of $Q$. + +```{code-cell} ipython3 +q_mean = posterior['QD'].mean(axis=2) +q_eigenvalues = np.linalg.eigvalsh(q_mean)[::-1] +q_cumulative = np.cumsum(q_eigenvalues) / q_eigenvalues.sum() + +drift_structure = pd.DataFrame( + { + 'eigenvalue': q_eigenvalues[:3], + 'cumulative share': q_cumulative[:3], + }, + index=pd.Index(range(1, 4), name='principal component'), +) +drift_structure.round(4) +``` + +The first three principal components account for the large majority of total +coefficient drift even though the VAR contains 21 coefficients. + +### The evolution of volatility + +We first ask how the *size* of the shocks changed. + +Equation {eq}`csdv_covariance` can be averaged over draws without constructing a +four-dimensional covariance array. + +```{code-cell} ipython3 +def mean_innovation_covariance(h_draws, β_draws): + """Compute E(R_t | T) with working memory proportional to T times D.""" + n_draws = h_draws.shape[2] + matrices = np.broadcast_to(np.eye(3), (n_draws, 3, 3)).copy() + matrices[:, 1, 0] = β_draws[0] + matrices[:, 2, 0] = β_draws[1] + matrices[:, 2, 1] = β_draws[2] + inverses = np.linalg.solve( + matrices, + np.broadcast_to(np.eye(3), matrices.shape), + ) + h = h_draws[1:] + mean = np.empty((h.shape[0], 3, 3)) + for row in range(3): + for column in range(row + 1): + value = np.zeros(h.shape[0]) + for shock in range(3): + weights = inverses[:, row, shock] * inverses[:, column, shock] + value += h[:, shock, :] @ weights + mean[:, row, column] = value / n_draws + mean[:, column, row] = mean[:, row, column] + return mean + + +r_mean = mean_innovation_covariance(posterior['HD'], posterior['CD']) +``` + +The next plot shows the innovation standard deviations and correlations. + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: Standard deviations and correlations implied by $E(R_t\mid T)$ + name: fig-csdv-volatility-correlation +--- +variances = ((0, 'Nominal interest'), (2, 'Inflation'), (1, 'Unemployment')) +correlations = ( + (0, 1, 'Interest--unemployment'), + (0, 2, 'Interest--inflation'), + (2, 1, 'Inflation--unemployment'), +) +fig, axes = plt.subplots(3, 2, figsize=(9, 8), sharex=True) +for row, (index, label) in enumerate(variances): + axes[row, 0].plot( + data['dates'], 10000 * np.sqrt(r_mean[:, index, index]), lw=2 + ) + axes[row, 0].set_title(label) +for row, (left, right, label) in enumerate(correlations): + scale = np.sqrt(r_mean[:, left, left] * r_mean[:, right, right]) + axes[row, 1].plot(data['dates'], r_mean[:, left, right] / scale, lw=2) + axes[row, 1].set_title(label) +axes[1, 0].set_ylabel( + r'innovation standard deviation $\times 10^4$' +) +axes[1, 1].set_ylabel('correlation') +axes[-1, 0].set_xlabel('year') +axes[-1, 1].set_xlabel('year') +plt.tight_layout() +plt.show() +``` + +The interest-rate and inflation innovation standard deviations peak sharply +around 1980, whereas unemployment innovation volatility declines more gradually +toward the end of the sample. + +All three innovation correlations also move most abruptly around 1980, so both +the size and the joint composition of reduced-form shocks changed. + +These movements give the changing-shocks, or bad-luck, explanation an important +role, although the interest-rate innovation is not itself a structural +monetary-policy shock. + +The log determinant of the posterior mean covariance matrix summarizes the +generalized one-step innovation variance {cite}`Whittle1953`. + +The following transformation summarizes generalized innovation variance. + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: Generalized innovation variance $\log |E(R_t\mid T)|$ + name: fig-csdv-total-variance +--- +sign, logdet_r = np.linalg.slogdet(r_mean) +assert np.all(sign > 0) +fig, ax = plt.subplots() +ax.plot(data['dates'], logdet_r, lw=2) +ax.set_xlabel('year') +ax.set_ylabel(r'$\log |E(R_t\mid T)|$') +plt.show() +``` + +Because a less negative log determinant means greater joint innovation +variance, the two-step rise to an exceptional 1981 peak and the long subsequent +decline mark a large shock episode followed by the Great Moderation documented +by {cite:t}`KimNelson1999` and {cite:t}`McConnellPerezQuiros2000`. + +This is the clearest aggregate evidence for changing luck, but it cannot explain +the coefficient-based changes in inflation dynamics examined next. + +### Core inflation and the natural rate + +To study the systematic dynamics, write the VAR at date $t$ in companion form as + +$$ +z_t = \mu_{t\mid T} + A_{t\mid T}z_{t-1} + e_t. +$$ + +At date $t$, the local mean $m_t$ is the fixed point to which the companion-form +VAR would converge if its coefficients remained fixed at their posterior means +and future innovations were zero. + +```{math} +:label: csdv_local_means +m_t = (I-A_{t\mid T})^{-1}\mu_{t\mid T}, +\qquad +\bar\pi_t = 4s_\pi m_t, +\qquad +\bar u_t = \frac{\exp(100s_u m_t)}{1+\exp(100s_u m_t)}. +``` + +Here $s_\pi$ and $s_u$ select inflation and unemployment from $m_t$, the factor +four annualizes inflation, and the inverse-logit transformation returns +unemployment to its observed rate. + +These are date-specific steady states of frozen local systems rather than +unconditional means of the globally drifting process. + +Core inflation is the long-horizon inflation forecast implied by freezing the +date-$t$ coefficients, while the natural rate is the corresponding long-run +unemployment anchor rather than a natural interest rate or a forecast of next +quarter's unemployment. + +Freezing the current coefficients and projecting forward is exactly the +*anticipated-utility* device used by the learning governments of +{doc}`phillips_learning` and {doc}`phillips_escaping_nash`, who act as if their +current beliefs will never be revised. + +The following implementation annualizes core inflation and reverses the +archive's unemployment transformation. + +```{code-cell} ipython3 +def local_means(θ_path): + """Compute local core inflation and the natural unemployment rate.""" + θ_path = np.asarray(θ_path, dtype=float) + if θ_path.ndim == 1: + θ_path = θ_path[:, None] + core = np.empty(θ_path.shape[1]) + natural = np.empty(θ_path.shape[1]) + for t in range(θ_path.shape[1]): + intercept, companion = companion_matrix(θ_path[:, t]) + mean = np.linalg.solve(np.eye(6) - companion, intercept) + core[t] = 4 * mean[2] + natural[t] = expit(100 * mean[1]) + return core, natural + + +core_inflation, natural_rate = local_means(θ_mean) +``` + +We plot the fourth-quarter observation from each year. + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: Local means $\bar\pi_t$ and $\bar u_t$ + name: fig-csdv-local-means +--- +def annual_indices(dates, start=1960): + """Return the final observation in each year from a start date.""" + year_values = np.floor(dates + 1e-8).astype(int) + return np.array( + [ + np.flatnonzero(year_values == year)[-1] + for year in np.unique(year_values) + if year >= start + ] + ) + + +years = np.floor(data['dates'] + 1e-8).astype(int) +annual = annual_indices(data['dates']) + +fig, ax = plt.subplots() +ax.plot(data['dates'][annual], 100 * core_inflation[annual], 'o-', lw=2, + markersize=3, label='core inflation') +ax.plot(data['dates'][annual], 100 * natural_rate[annual], '+-', lw=2, + markersize=5, label='natural rate') +ax.set_xlabel('year') +ax.set_ylabel('percent') +ax.legend() +plt.show() +``` + +```{code-cell} ipython3 +core_summary = pd.Series( + { + 'early-1960s mean core inflation (%)': ( + 100 * core_inflation[(years >= 1960) & (years <= 1964)].mean() + ), + 'peak core inflation (%)': 100 * core_inflation[annual].max(), + '1985--2000 mean core inflation (%)': ( + 100 * core_inflation[(years >= 1985) & (years <= 2000)].mean() + ), + }, + name='estimate', +) +core_summary.to_frame().round(2) +``` + +Core inflation climbs from a low level in the early 1960s to a high peak near +1980 and then falls back, while the natural unemployment rate rises more +smoothly from about 5 to 6.5 percent before returning toward 4 percent. + +Because both lines are determined by the fitted coefficients rather than by +realized shocks, their persistent shifts point to a changing systematic +component. + +The following calculation summarizes their comovement over the full posterior +sample. + +```{code-cell} ipython3 +core_natural_correlation = np.corrcoef(core_inflation, natural_rate)[0, 1] + +core_natural_correlation +``` + +The strong positive quarterly correlation between $\bar\pi_t$ and $\bar u_t$ +says that the model-implied long-run inflation and unemployment anchors share +a broad cycle, not that current inflation and current unemployment must move +together. + +Because $(I-A_t)^{-1}$ amplifies small coefficient changes when the largest root +is close to one, long-run means are intrinsically more sensitive than +short-horizon forecasts. + +### Inflation persistence + +We now ask whether the *systematic* dynamics drifted on top of the moving +volatilities. + +The main summary is inflation persistence, measured by the normalized spectrum +of inflation at frequency zero. + +The spectral density of inflation at date $t$ is + +```{math} +:label: csdv_spectrum +f_{\pi\pi}(\omega,t) += +\frac{1}{2\pi} +s_\pi +(I-A_{t\mid T}e^{-i\omega})^{-1} +\mathcal R_t +(I-A_{t\mid T}'e^{i\omega})^{-1} +s_\pi', +``` + +where $\mathcal R_t$ embeds $E(R_t\mid T)$ in the companion system. + +Low-frequency power depends on both the autoregressive coefficients and the +innovation covariance. + +The next function evaluates inflation power at any frequency measured in cycles +per quarter and also returns inflation variance at date $t$. + +```{code-cell} ipython3 +def inflation_spectrum(θ, covariance, frequencies): + """Compute inflation power and its variance-normalized counterpart.""" + _, companion = companion_matrix(θ) + innovation = np.zeros((6, 6)) + innovation[:3, :3] = covariance + selector = np.zeros(6) + selector[2] = 1 + stationary = linalg.solve_discrete_lyapunov(companion, innovation) + variance = float(selector @ stationary @ selector) + power = np.empty(len(frequencies)) + for index, frequency in enumerate(frequencies): + phase = np.exp(-2j * np.pi * frequency) + transfer = np.linalg.solve(np.eye(6) - companion * phase, np.eye(6)) + power[index] = np.real( + selector @ transfer @ innovation @ transfer.conj().T @ selector + ) / (2 * np.pi) + return power, power / variance + +``` + +The normalized spectrum divides by inflation variance at date $t$, + +```{math} +:label: csdv_normalized_spectrum +g_{\pi\pi}(\omega,t) += +\frac{f_{\pi\pi}(\omega,t)} +{\int_{-\pi}^{\pi}f_{\pi\pi}(\omega,t)d\omega}, +``` + +so $g_{\pi\pi}(0,t)$ is an autocorrelation-based persistence measure. + +The normalization removes a common scale factor from $R_t$, but it can still +depend on the relative variances and covariances in $R_t$. + +The first figure isolates frequency zero as a one-dimensional persistence +summary. + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: Normalized zero-frequency spectrum $g_{\pi\pi}(0,t)$ + name: fig-csdv-inflation-persistence +--- +zero_frequency = np.array([0.0]) +inflation_persistence = np.array([ + inflation_spectrum(θ_mean[:, t], r_mean[t], zero_frequency)[1][0] + for t in range(len(data['dates'])) +]) + +fig, ax = plt.subplots() +ax.plot( + data['dates'][annual], + inflation_persistence[annual], + 'o-', + lw=2, + markersize=3, +) +ax.set_xlabel('year') +ax.set_ylabel(r'$g_{\pi\pi}(0,t)$') +plt.show() +``` + +```{code-cell} ipython3 +persistence_summary = pd.Series( + { + '1960--64 mean': inflation_persistence[ + (years >= 1960) & (years <= 1964) + ].mean(), + '1970--79 mean': inflation_persistence[ + (years >= 1970) & (years <= 1979) + ].mean(), + '1985--2000 mean': inflation_persistence[ + (years >= 1985) & (years <= 2000) + ].mean(), + 'peak': inflation_persistence[annual].max(), + 'peak year': years[annual][np.argmax(inflation_persistence[annual])], + }, + name='estimate', +) +persistence_summary.to_frame().round(3) +``` + +Normalized zero-frequency power rises sharply from a low level in the early +1960s to a high peak around 1980 and then falls back below one for most of the +remaining sample. + +The post-1980 collapse cannot be explained by a proportional rescaling of all +innovations, although the normalized statistic can still depend on the +composition of $R_t$. + +For comparison, an $AR(1)$ with coefficient $\rho$ has normalized zero-frequency +power $(1+\rho)/[2\pi(1-\rho)]$. + +Values between 2 and 10 correspond to $\rho$ between approximately $0.85$ and +$0.97$. + +The zero-frequency path omits the rest of the frequency distribution. + +The following heatmaps show how raw and variance-normalized inflation power move +over both time and frequency. + +```{code-cell} ipython3 +def inflation_spectrum_surface(θ_path, covariance_path, frequencies): + """Evaluate the inflation spectrum at each date on a frequency grid.""" + raw = np.empty((len(frequencies), θ_path.shape[1])) + normalized = np.empty_like(raw) + for date in range(θ_path.shape[1]): + raw[:, date], normalized[:, date] = inflation_spectrum( + θ_path[:, date], + covariance_path[date], + frequencies, + ) + return raw, normalized + + +spectrum_frequencies = np.linspace(0, 0.5, 41) +raw_spectrum, normalized_spectrum = inflation_spectrum_surface( + θ_mean, + r_mean, + spectrum_frequencies, +) +``` + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: Inflation spectra $f_{\pi\pi}(\omega,t)$ and $g_{\pi\pi}(\omega,t)$ + name: fig-csdv-inflation-spectra +--- +spectrum_start = data['dates'] >= 1960 +fig, axes = plt.subplots(1, 2, figsize=(9, 4), sharey=True) +surfaces = ( + (raw_spectrum, 'raw spectrum', 'log10 power'), + (normalized_spectrum, 'normalized spectrum', 'log10 normalized power'), +) +for ax, (surface, title, color_label) in zip(axes, surfaces): + image = ax.pcolormesh( + data['dates'][spectrum_start], + spectrum_frequencies, + np.log10(surface[:, spectrum_start]), + shading='auto', + ) + ax.set_xlabel('year') + ax.set_ylabel(f'{title}\ncycles per quarter') + fig.colorbar(image, ax=ax, label=color_label) +plt.tight_layout() +plt.show() +``` + +The raw spectrum is brightest near frequency zero around 1980 because shocks +and persistence are both elevated, while the normalized spectrum retains a +broad low-frequency ridge through the 1970s that recedes after 1980. + +The ridge that survives normalization shows that the Great Inflation was not +only a high-volatility episode because inflation shocks were also propagated +more persistently. + +Point estimates do not reveal how strongly the data locate these paths. + +We therefore compute the same annual features from every retained draw. + +```{code-cell} ipython3 +def posterior_feature_draws(result, indices): + """Compute selected local means and persistence for retained draws.""" + n_draws = result['SD'].shape[2] + shape = (len(indices), n_draws) + core = np.empty(shape) + natural = np.empty(shape) + persistence = np.empty(shape) + for draw in range(n_draws): + core[:, draw], natural[:, draw] = local_means( + result['SD'][:, indices, draw] + ) + for row, date in enumerate(indices): + covariance = innovation_covariance( + result['HD'][date + 1, :, draw], + result['CD'][:, draw], + ) + persistence[row, draw] = inflation_spectrum( + result['SD'][:, date, draw], + covariance, + zero_frequency, + )[1][0] + return { + 'core': 100 * core, + 'natural': 100 * natural, + 'persistence': persistence, + } + + +historical_feature_draws = posterior_feature_draws(posterior, annual) +``` + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: Posterior medians and pointwise 90 percent intervals for $\bar\pi_t$, $\bar u_t$, and $g_{\pi\pi}(0,t)$ + name: fig-csdv-feature-uncertainty +--- +fig, axes = plt.subplots(3, 1, figsize=(9, 8), sharex=True) +feature_specs = ( + ('core', 'core inflation (%)'), + ('natural', 'natural rate (%)'), + ('persistence', r'$g_{\pi\pi}(0,t)$'), +) +for ax, (key, ylabel) in zip(axes, feature_specs): + lower, median, upper = np.quantile( + historical_feature_draws[key], + (0.05, 0.5, 0.95), + axis=1, + ) + line, = ax.plot(data['dates'][annual], median, lw=2) + ax.fill_between( + data['dates'][annual], + lower, + upper, + color=line.get_color(), + alpha=0.2, + ) + ax.set_ylabel(ylabel) +axes[-1].set_xlabel('year') +plt.tight_layout() +plt.show() +``` + +The solid curves are posterior medians, and the shaded regions are pointwise 90 +percent intervals rather than simultaneous bands for entire paths. + +The median core-inflation and persistence paths preserve the rise and +post-1980 fall, but their right-skewed bands widen markedly through the 1970s +and around 1980. + +The natural-rate median moves more smoothly, with broad uncertainty around 1980 +and again at the sample endpoint, so the direction of the historical movement +is clearer than its exact magnitude. + +The following plot compares the timing of core inflation and persistence. + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: $\bar\pi_t$ and $g_{\pi\pi}(0,t)$ + name: fig-csdv-core-persistence +--- +core_persistence_correlation = np.corrcoef( + core_inflation, inflation_persistence +)[0, 1] + +fig, ax = plt.subplots() +ax.plot(data['dates'][annual], 100 * core_inflation[annual], 'o-', + lw=2, markersize=3, label='core inflation (%)') +ax.plot(data['dates'][annual], inflation_persistence[annual], 'x-', + lw=2, markersize=4, label='normalized spectrum at zero') +ax.set_xlabel('year') +ax.legend() +plt.show() +``` + +Core inflation and normalized persistence rise together through the late 1960s +and 1970s and collapse almost simultaneously after 1980, although core remains +positive after persistence falls below one. + +Their quarterly correlation of 0.909 summarizes common timing rather than a +causal relationship because the two measures have different units and are +nonlinear summaries of the same fitted coefficients. + +Within the fitted TVP-VAR, the joint movement supports an important role for +changing propagation as well as changing shock volatility. + +A direct comparison with fixed coefficients requires fitting the restricted +$Q=0$ model. + +The fall in persistence during the Volcker disinflation conflicts with +escape-route models in which persistence grows along a transition from high to +low inflation {cite}`Sargent1999,ChoWilliamsSargent2002`. + +That tension helped motivate later learning models in which policymakers became +reluctant to disinflate during the 1970s and then changed course. + +### Monetary policy activism + +Cogley and Sargent summarize systematic policy with a forward-looking Taylor +rule, + +```{math} +:label: csdv_policy_rule +i_t = \beta_0 ++ \beta_1 E_t\bar\pi_{t,t+h_\pi} ++ \beta_2 E_t\bar u_{t,t+h_u} ++ \beta_3 i_{t-1} ++ \nu_t. +``` + +They define the activism coefficient as $\mathcal A_t=\beta_1/(1-\beta_3)$ and +call policy active when $\mathcal A_t\geq 1$. + +At each date, population two-stage least squares projections implied by the +local VAR produce the policy-rule coefficients. + +The benchmark horizons are $h_\pi=4$ quarters and $h_u=2$ quarters, reflecting +conventional views about monetary-policy lags. + +The following function projects the short rate on the model-implied inflation +and unemployment forecasts using the stationary second moments of each local +VAR. + +```{code-cell} ipython3 +def policy_rule_coefficients(θ, covariance, h_pi=4, h_u=2): + """Return the local policy-rule coefficients.""" + _, companion = companion_matrix(θ) + innovation = np.zeros((6, 6)) + innovation[:3, :3] = covariance + stationary_covariance = linalg.solve_discrete_lyapunov( + companion, innovation + ) + selectors = np.eye(6) + companion_power = np.eye(6) + inflation_loading = np.zeros(6) + unemployment_loading = np.zeros(6) + for horizon in range(1, max(h_pi, h_u) + 1): + companion_power = companion_power @ companion + if horizon <= h_pi: + inflation_loading += selectors[2] @ companion_power + if horizon <= h_u: + unemployment_loading += selectors[1] @ companion_power + inflation_loading /= h_pi + unemployment_loading /= h_u + loadings = np.vstack( + (inflation_loading, unemployment_loading, selectors[0]) + ) + regressor_covariance = loadings @ stationary_covariance @ loadings.T + cross_covariance = ( + loadings @ stationary_covariance @ companion.T @ selectors[0] + ) + return np.linalg.solve(regressor_covariance, cross_covariance) + + +policy_coefficients = np.array( + [ + policy_rule_coefficients(θ_mean[:, t], r_mean[t]) + for t in range(len(data['dates'])) + ] +) +inflation_response = policy_coefficients[:, 0] +interest_persistence = policy_coefficients[:, 2] +policy_margin = np.where( + np.abs(interest_persistence) < 1, + inflation_response + interest_persistence - 1, + np.nan, +) +``` + +For $|\beta_3|<1$, the policy margin +$\mathcal{M}_t=\beta_{1t}+\beta_{3t}-1$ is nonnegative exactly when +$\mathcal A_t\geq1$ and avoids division by $1-\beta_3$. + +Because $\beta_3$ multiplies the lagged interest rate, the long-run response +sums $\beta_1(1+\beta_3+\beta_3^2+\cdots)$ and exists only when +$|\beta_3|<1$. + +The figures leave other dates blank because $\mathcal A_t$ has no finite +long-run interpretation there. + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: Policy margin $\mathcal{M}_t=\beta_{1t}+\beta_{3t}-1$ + name: fig-csdv-policy-activism +--- +fig, ax = plt.subplots() +ax.plot(data['dates'], policy_margin, lw=2) +ax.axhline(0, color='0.45', lw=1) +ax.set_xlabel('year') +ax.set_ylabel(r'policy margin $\mathcal{M}_t$') +plt.show() +``` + +The policy margin falls below zero through much of the 1970s and then moves +decisively above zero after the early 1980s. + +This timing is consistent with a policy-regime contribution to the Great +Inflation. + +Blank intervals mark dates at which the policy margin is omitted by the rule +above. + +The following scatter plots use fourth-quarter observations. + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: $\mathcal{M}_t$ versus $\bar\pi_t$ and $g_{\pi\pi}(0,t)$ + name: fig-csdv-activism-correlations +--- +displayed_annual = annual[np.isfinite(policy_margin[annual])] + +fig, axes = plt.subplots(1, 2, figsize=(9, 4)) +pairs = ( + (100 * core_inflation, 'core inflation (%)'), + (inflation_persistence, 'normalized spectrum at zero'), +) +for ax, (feature, label) in zip(axes, pairs): + ax.scatter( + policy_margin[displayed_annual], + feature[displayed_annual], + s=18, + ) + ax.axvline(0, color='0.45', lw=1) + ax.set_xlabel(r'policy margin $\mathcal{M}_t$') + ax.set_ylabel(label) +plt.tight_layout() +plt.show() +``` + +High core-inflation and persistence observations cluster near or below the zero +margin, whereas positive policy margins cluster at low values of both measures. + +The displayed fourth-quarter observations establish a historical association +rather than a causal policy effect. + +The policy-rule coefficients are weakly identified at some dates, so a path +based on posterior mean inputs understates uncertainty. + +We therefore calculate activism from every retained draw in 1975, 1985, and +1995. + +```{code-cell} ipython3 +selected_years = (1975, 1985, 1995) +selected_dates = [np.flatnonzero(years == year)[-1] for year in selected_years] + +activism_draws = { + year: np.empty(posterior['SD'].shape[2]) for year in selected_years +} +stable_response_by_year = { + year: np.empty(posterior['SD'].shape[2], dtype=bool) + for year in selected_years +} +for draw in range(posterior['SD'].shape[2]): + for year, date in zip(selected_years, selected_dates): + covariance = innovation_covariance( + posterior['HD'][date + 1, :, draw], + posterior['CD'][:, draw], + ) + rule = policy_rule_coefficients( + posterior['SD'][:, date, draw], covariance + ) + activism_draws[year][draw] = rule[0] / (1 - rule[2]) + stable_response_by_year[year][draw] = np.abs(rule[2]) < 1 +``` + +These draws give posterior probabilities of active policy at each date and of a +rise in activism after 1975, conditional on $|\beta_3|<1$. + +```{code-cell} ipython3 +activism_events = ( + activism_draws[1975] > 1, + activism_draws[1985] > 1, + activism_draws[1995] > 1, + activism_draws[1985] > activism_draws[1975], + activism_draws[1995] > activism_draws[1975], +) +activism_conditions = ( + stable_response_by_year[1975], + stable_response_by_year[1985], + stable_response_by_year[1995], + stable_response_by_year[1985] & stable_response_by_year[1975], + stable_response_by_year[1995] & stable_response_by_year[1975], +) +assert all(condition.any() for condition in activism_conditions) +activism_probability_values = np.array( + [ + event[condition].mean() + for event, condition in zip(activism_events, activism_conditions) + ] +) + +activism_probability_index = ( + 'P(A_1975 > 1)', + 'P(A_1985 > 1)', + 'P(A_1995 > 1)', + 'P(A_1985 > A_1975)', + 'P(A_1995 > A_1975)', +) +activism_probabilities = pd.DataFrame( + {'conditional estimate': activism_probability_values}, + index=activism_probability_index, +) +stable_response_shares = pd.Series( + {year: draws.mean() for year, draws in stable_response_by_year.items()}, + name='share with |beta_3| < 1', +) + +display(activism_probabilities.round(3)) +stable_response_shares.to_frame().round(3) +``` + +The first three probabilities condition on $|\beta_3|<1$ at that date, while +the comparisons require this condition at both dates. + +The central draw distributions expose the overlap and skewness behind the +probability estimates. + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: Central posterior draws of $\mathcal A_t$ conditional on $|\beta_3|<1$ in 1975, 1985, and 1995 + name: fig-csdv-activism-distributions +--- +stable_activism_draws = { + year: activism_draws[year][stable_response_by_year[year]] + for year in selected_years +} +pooled_activism = np.concatenate(tuple(stable_activism_draws.values())) +activism_limits = np.quantile(pooled_activism, (0.05, 0.95)) +activism_bins = np.linspace(*activism_limits, 31) + +fig, ax = plt.subplots() +for year in selected_years: + central_draws = stable_activism_draws[year] + central_draws = central_draws[ + (central_draws >= activism_limits[0]) + & (central_draws <= activism_limits[1]) + ] + ax.hist( + central_draws, + bins=activism_bins, + histtype='step', + lw=2, + label=str(year), + ) +ax.axvline(1, color='0.45', lw=1) +ax.set_xlabel('activism coefficient') +ax.set_ylabel('retained draws') +ax.legend() +plt.show() +``` + +The 1975 distribution is concentrated around or below one, while the 1985 and +1995 distributions shift substantially to the right but remain broad, skewed, +and overlapping. + +The posterior therefore favors a passive-to-active shift after 1975 but does +not sharply distinguish 1985 from 1995. + +The figure plots draws with $|\beta_3|<1$ inside the pooled 5th and 95th +percentiles, while the probability calculations use every such draw. + +(csdv-updated-evidence)= +## Another quarter-century of evidence + +The sample ends in 2000Q4, so it misses the financial crisis, the +zero-interest-rate period, the pandemic, and the 2021--2022 inflation surge. + +The question is whether these episodes alter the earlier evidence about drift, +volatility, inflation persistence, and systematic policy. + +### The new observations + +To examine those observations, we append current data after 2000Q4 while leaving pre-2000Q4 sample unchanged. + +This splice prevents revisions to pre-2001 CPI and unemployment data from being +mistaken for information in the additional quarter-century. + +We download seasonally adjusted [CPI][fred-cpi], seasonally adjusted +[unemployment][fred-unemployment], and the [three-month Treasury-bill +rate][fred-interest] from FRED. + +[fred-cpi]: https://fred.stlouisfed.org/series/CPIAUCSL +[fred-unemployment]: https://fred.stlouisfed.org/series/UNRATE +[fred-interest]: https://fred.stlouisfed.org/series/TB3MS + +The transformations and within-quarter timing remain unchanged: CPI comes from +the third month, unemployment is a three-month average, and the interest rate +comes from the first month. + +```{code-cell} ipython3 +fred_url = ( + 'https://fred.stlouisfed.org/graph/fredgraph.csv?' + 'id=CPIAUCSL%2CUNRATE%2CTB3MS' +) +fred_monthly = pd.read_csv( + fred_url, + parse_dates=['observation_date'], +).set_index('observation_date') + +``` + +The [BLS notes](https://www.bls.gov/web/empsit/cpsee_e12.pdf) that the October +2025 unemployment observation was not collected during the federal government +shutdown, leaving 2025Q4 without a complete three-month average. + +The code below therefore ends the updated sample at the last quarter whose three +monthly unemployment readings are all present, detected automatically rather +than hard-coded; at the time of writing that quarter is 2025Q3. + +```{code-cell} ipython3 +def fred_quarterly_table(unemployment_monthly): + """Construct transformed quarterly observations from current FRED data.""" + interest = fred_monthly.loc[ + fred_monthly.index.month.isin((1, 4, 7, 10)), 'TB3MS' + ].copy() + interest.index = interest.index.to_period('Q').start_time + + cpi = fred_monthly.loc[ + fred_monthly.index.month.isin((3, 6, 9, 12)), 'CPIAUCSL' + ].copy() + cpi.index = cpi.index.to_period('Q').start_time + + unemployment = unemployment_monthly.resample('QS').mean() + quarterly = pd.concat( + { + 'interest': interest, + 'unemployment': unemployment, + 'cpi': cpi, + }, + axis=1, + ) + quarterly['y3'] = np.log1p(quarterly['interest'] / 400) + quarterly['ur'] = quarterly['unemployment'] / 100 + quarterly['dp'] = np.log(quarterly['cpi']).diff() + quarterly['date'] = ( + quarterly.index.year + (quarterly.index.quarter - 1) / 4 + ) + columns = ['date', 'y3', 'ur', 'dp'] + return quarterly.loc[:, columns].dropna() + + +unemployment_monthly = fred_monthly['UNRATE'] +unemployment_counts = unemployment_monthly.resample('QS').count() +latest_quarterly = fred_quarterly_table(unemployment_monthly) + +# Extend the archived sample dynamically: append post-2000Q4 quarters only up +# to the first one missing any of its three monthly unemployment readings, so +# no endpoint is hard-coded. An isolated missing month -- for example October +# 2025, which the federal shutdown left uncollected -- caps the sample at the +# preceding complete quarter, and a normally incomplete current quarter caps it +# at the last finished one. +quarterly_unfilled = latest_quarterly.loc['2001-01-01':] +month_counts = unemployment_counts.reindex(quarterly_unfilled.index) +incomplete_quarters = month_counts.index[month_counts < 3] +if len(incomplete_quarters): + first_incomplete_quarter = incomplete_quarters[0] + complete_extension = quarterly_unfilled.loc[ + quarterly_unfilled.index < first_incomplete_quarter + ] +else: + complete_extension = quarterly_unfilled + +cs_sample = pd.read_csv(data_path) +overlap_date = pd.Timestamp('2000-10-01') +cs_sample_overlap = cs_sample.iloc[-1] +latest_overlap = latest_quarterly.loc[overlap_date] + + +def scaled_observation(row): + """Return observable units for a transformed quarterly row.""" + return pd.Series( + { + 'interest rate (annual %)': 400 * np.expm1(row['y3']), + 'unemployment (%)': 100 * row['ur'], + 'inflation (annual %)': 400 * row['dp'], + } + ) + + +cs_sample_scaled = scaled_observation(cs_sample_overlap) +latest_scaled = scaled_observation(latest_overlap) +splice_audit = pd.DataFrame( + { + 'Cogley-Sargent (2005) 2000Q4': cs_sample_scaled, + 'latest revised 2000Q4 data': latest_scaled, + 'current minus Cogley-Sargent (2005)': ( + latest_scaled - cs_sample_scaled + ), + 'first appended 2001Q1': scaled_observation( + complete_extension.iloc[0] + ), + } +) +extended_observations = pd.concat( + (cs_sample, complete_extension.reset_index(drop=True)), + ignore_index=True, +) + + +def quarter_label(timestamp): + """Format a timestamp as year and quarter.""" + return str(timestamp.to_period('Q')) + + +display(splice_audit.round(3)) +``` + +No missing value is filled or otherwise treated as observed. + +The overlap table shows any break created by joining the archived data to the +newly downloaded data. + +The appended 2001Q1 inflation rate uses the latest revised CPI level for both +2000Q4 and 2001Q1, so one log difference never combines observations from two +data releases. + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: Observed $\pi_t$, $u_t$, and $i_t$, 1959Q1--2025Q3 + name: fig-csdv-updated-data +--- +fig, axes = plt.subplots(3, 1, figsize=(9, 7), sharex=True) +extended_plot = extended_observations[ + extended_observations['date'] >= 1959 +] +updated_sample_end = complete_extension['date'].iloc[-1] +axes[0].plot( + extended_plot['date'], + 400 * extended_plot['dp'], + lw=2, +) +axes[0].set_ylabel('inflation (annual %)') +axes[1].plot( + extended_plot['date'], + 100 * extended_plot['ur'], + lw=2, +) +axes[1].set_ylabel('unemployment (%)') +axes[2].plot( + extended_plot['date'], + 400 * np.expm1(extended_plot['y3']), + lw=2, +) +axes[2].set_ylabel('interest (annual %)') +axes[2].set_xlabel('year') +for index, ax in enumerate(axes): + cs_sample_label = 'Cogley-Sargent (2005)' if index == 0 else None + sample_end_label = 'updated sample end' if index == 0 else None + ax.axvline( + 2000.75, + color='0.65', + ls='--', + lw=1, + label=cs_sample_label, + ) + ax.axvline( + updated_sample_end, + color='0.45', + ls=':', + lw=1, + label=sample_end_label, + ) +axes[0].legend() +plt.tight_layout() +plt.show() +``` + +```{code-cell} ipython3 +endpoint_observation = complete_extension.iloc[-1] +pd.Series( + { + 'annualized quarterly inflation (%)': ( + 400 * endpoint_observation['dp'] + ), + 'unemployment (%)': 100 * endpoint_observation['ur'], + 'three-month interest rate (%)': ( + 400 * np.expm1(endpoint_observation['y3']) + ), + }, + name=quarter_label(complete_extension.index[-1]), +).to_frame().round(3) +``` + +The extension adds the financial-crisis contraction, a pandemic quarterly +unemployment spike to 13.0 percent, and a short 2021--2022 inflation surge +alongside two long stretches of near-zero interest rates. + +Because inflation is annualized from quarterly changes, isolated movements look +especially large in this panel, but the recent surge is still visibly much +shorter than the sustained 1970s rise. + +These observations provide a demanding test of whether the model assigns recent +extremes to shock volatility or persistent dynamics, while the near-zero rate +also weakens short-rate measures of policy after 2008. + +We fit the stable TVP-VAR through 2025Q3, the last complete quarter, using the +Treasury-bill measure above. + +```{code-cell} ipython3 +def append_extension(extension): + """Append a transformed FRED extension to the Cogley-Sargent sample.""" + extension = extension.reset_index(drop=True) + table = pd.concat((cs_sample, extension), ignore_index=True) + assert table['date'].is_unique + assert np.allclose(np.diff(table['date']), 0.25) + return table + + +def fit_updated_model(table): + """Calibrate and fit the stable drifting VAR to one table.""" + model_data = prepare_data(table) + model_prior = calibrate_prior(model_data) + result = run_sampler( + model_data['y'], + model_data['x'], + model_prior, + n_sweeps=5_000, + burn=2_500, + thin=5, + seed=42, + warmup=500, + stable=True, + progress_every=500, + ) + validate_posterior_arrays(result, len(model_data['dates'])) + trace = np.trace(result['QD'], axis1=0, axis2=1) + return { + 'data': model_data, + 'prior': model_prior, + 'posterior': result, + 'trace': trace, + } + + +updated_fit = fit_updated_model(append_extension(complete_extension)) + +latest_data_table = latest_quarterly.loc[ + (latest_quarterly.index >= pd.Timestamp('1948-04-01')) + & (latest_quarterly.index <= complete_extension.index[-1]) +].reset_index(drop=True) +assert np.allclose(np.diff(latest_data_table['date']), 0.25) +latest_data_fit = fit_updated_model(latest_data_table) +``` + +### Did coefficient drift continue? + +```{code-cell} ipython3 +def updated_drift_summary(fit): + """Summarize the updated coefficient-drift distribution.""" + trace = fit['trace'] + q_mean = fit['posterior']['QD'].mean(axis=2) + eigenvalues = np.linalg.eigvalsh(q_mean)[::-1] + return { + 'posterior mean tr(Q)': trace.mean(), + 'share in first three eigen-directions': ( + eigenvalues[:3].sum() / eigenvalues.sum() + ), + } + + +updated_label = quarter_label(complete_extension.index[-1]) +updated_summary = pd.DataFrame( + { + 'Cogley-Sargent (2005) + extension': ( + updated_drift_summary(updated_fit) + ), + 'latest revised data for full sample': updated_drift_summary( + latest_data_fit + ), + } +) +updated_summary.round(3) +``` + +The drift-rate distributions and coefficient paths provide the same views used +for the {cite:t}`CogleySargent2005` sample. + +The dashed vertical line in updated time-series figures marks the +{cite:t}`CogleySargent2005` sample's 2000Q4 endpoint, but each updated path +comes from a full re-estimation rather than from attaching new points to an +unchanged historical estimate. + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: Posterior $\operatorname{tr}(Q)$ and prior $\operatorname{tr}(\bar Q)$ through 2025Q3 + name: fig-csdv-updated-drift-rate +--- +fig, ax = plt.subplots() +ax.hist(updated_fit['trace'], bins=30, histtype='step', lw=2) +ax.axvline( + np.trace(updated_fit['prior']['q_center']), + color='C1', + lw=2, + label=r'prior $\mathrm{tr}(\bar Q)$', +) +ax.set_xlabel(r'$\mathrm{tr}(Q)$') +ax.set_ylabel('frequency') +ax.legend() +plt.show() +``` + +In both data constructions, the full-sample posterior drift rate remains above +its conservative prior calibration. + +Within the TVP-VAR this indicates non-negligible full-sample drift, but it is +not a formal comparison of $Q=0$ with $Q>0$. + +Its magnitude depends on whether the historical observations come from the +archived dataset or the latest revisions. + +Because $Q$ is a single variance parameter for the full 1959--2025 path, this +histogram does not by itself show that drift accelerated after 2000. + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: Posterior mean VAR coefficients $E(\theta_t\mid T)$ through 2025Q3 + name: fig-csdv-updated-coefficient-paths +--- +fig, axes = plt.subplots(1, 3, figsize=(10, 4), sharex=True) +updated_dates = updated_fit['data']['dates'] +updated_coefficients = updated_fit['posterior']['SD'].mean(axis=2) +coefficient_lines = plot_equation_coefficients( + axes, + updated_dates, + updated_coefficients, +) +for ax in axes: + ax.axvline(2000.75, color='0.65', ls='--', lw=1) +fig.legend( + coefficient_lines, + coefficient_labels, + loc='lower center', + ncol=4, +) +plt.tight_layout(rect=(0, 0.17, 1, 1)) +plt.show() +``` + +Several coefficient paths continue moving gradually after 2000, with the +largest changes again concentrated in the inflation equation rather than +appearing as abrupt financial-crisis or pandemic breaks. + +The opposing movements of some paired lag coefficients also show why their +combined dynamic implications are more informative than any single line. + +The first three eigen-directions account for the large majority of the +posterior mean drift variance, so the estimated movement remains low +dimensional. + +The stability restriction now applies to a longer path, which makes the +posterior drift rate a property of the full 1959--2025 sample. + +### Volatility after the Great Moderation + +We next separate changes in the sizes and correlations of innovations from +changes in the VAR dynamics. + +```{code-cell} ipython3 +def model_features(fit): + """Compute features at posterior mean parameters.""" + result = fit['posterior'] + θ = result['SD'].mean(axis=2) + mean_root_modulus = np.max( + np.abs(companion_roots(θ)), axis=1 + ) + if not np.all(mean_root_modulus < 1): + raise ValueError('mean coefficient path is unstable') + covariance = mean_innovation_covariance(result['HD'], result['CD']) + core, natural = local_means(θ) + persistence = np.array([ + inflation_spectrum( + θ[:, t], covariance[t], zero_frequency + )[1][0] + for t in range(len(fit['data']['dates'])) + ]) + sign, logdet = np.linalg.slogdet(covariance) + assert np.all(sign > 0) + policy_coefficients = np.array( + [ + policy_rule_coefficients(θ[:, t], covariance[t]) + for t in range(len(fit['data']['dates'])) + ] + ) + inflation_response = policy_coefficients[:, 0] + interest_persistence = policy_coefficients[:, 2] + policy_margin = np.where( + np.abs(interest_persistence) < 1, + inflation_response + interest_persistence - 1, + np.nan, + ) + return { + 'θ': θ, + 'maximum_companion_root': mean_root_modulus.max(), + 'covariance': covariance, + 'core': core, + 'natural': natural, + 'persistence': persistence, + 'logdet': logdet, + 'policy_margin': policy_margin, + } + + +updated_features = model_features(updated_fit) +latest_data_features = model_features(latest_data_fit) + +pd.Series( + { + 'Cogley-Sargent (2005) + extension': ( + updated_features['maximum_companion_root'] + ), + 'latest revised data for full sample': ( + latest_data_features['maximum_companion_root'] + ), + }, + name='maximum companion-root modulus of mean path', +).to_frame().round(4) +``` + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: Standard deviations and correlations implied by $E(R_t\mid T)$ through 2025Q3 + name: fig-csdv-updated-volatility-correlation +--- +fig, axes = plt.subplots(3, 2, figsize=(9, 8), sharex=True) +dates = updated_fit['data']['dates'] +covariance = updated_features['covariance'] +for row, (index, _) in enumerate(variances): + axes[row, 0].plot( + dates, + 10000 * np.sqrt(covariance[:, index, index]), + lw=2, + ) +for row, (left, right, _) in enumerate(correlations): + scale = np.sqrt( + covariance[:, left, left] * covariance[:, right, right] + ) + axes[row, 1].plot( + dates, + covariance[:, left, right] / scale, + lw=2, + ) +for row, (_, label) in enumerate(variances): + axes[row, 0].set_title(label) +for row, (_, _, label) in enumerate(correlations): + axes[row, 1].set_title(label) +for ax in axes.flat: + ax.axvline(2000.75, color='0.65', ls='--', lw=1) +axes[1, 0].set_ylabel( + r'innovation standard deviation $\times 10^4$' +) +axes[1, 1].set_ylabel('correlation') +axes[-1, 0].set_xlabel('year') +axes[-1, 1].set_xlabel('year') +plt.tight_layout() +plt.show() +``` + +The Volcker transition still dominates interest-rate innovation volatility, the +financial crisis produces the largest inflation-volatility spike, and the +pandemic uniquely dominates unemployment volatility. + +The pandemic also drives a sharp fall in the inflation--unemployment +correlation, so recent episodes changed the mix of reduced-form shocks as well +as their size. + +This is direct evidence for a bad-luck component, although reduced-form +innovations do not establish that the underlying disturbances were structurally +exogenous. + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: $\log |E(R_t\mid T)|$ through 2025Q3 + name: fig-csdv-updated-total-variance +--- +fig, ax = plt.subplots() +ax.plot( + updated_fit['data']['dates'], + updated_features['logdet'], + lw=2, +) +ax.axvline(2000.75, color='0.65', ls='--', lw=1) +ax.set_xlabel('year') +ax.set_ylabel(r'$\log |E(R_t\mid T)|$') +plt.show() +``` + +Joint innovation variance rises during the financial crisis, reaches a deep +Great Moderation trough in the 2010s, and then jumps in 2020 above even its 1981 +peak before falling rapidly. + +```{code-cell} ipython3 +def decimal_quarter_label(value): + """Format a decimal quarterly date.""" + year = int(np.floor(value + 1e-8)) + quarter = int(round(4 * (value - year))) + 1 + return f'{year}Q{quarter}' + + +def volatility_summary(fit, features): + """Summarize innovation-volatility peaks and endpoints.""" + dates = fit['data']['dates'] + standard_deviation = 10000 * np.sqrt( + np.diagonal(features['covariance'], axis1=1, axis2=2) + ) + names = ('interest', 'unemployment', 'inflation') + return pd.DataFrame( + { + 'peak quarter': [ + decimal_quarter_label( + dates[np.argmax(standard_deviation[:, index])] + ) + for index in range(n_variables) + ], + }, + index=names, + ) + + +updated_volatility = volatility_summary(updated_fit, updated_features) +updated_volatility +``` + +All three innovation standard deviations are well below their peaks at the +2025Q3 endpoint, which supports a large but transient pandemic-shock +interpretation inside this model. + +### Core inflation and the natural rate after 2000 + +The same local-mean calculation distinguishes temporary inflation from a shift +in the model's long-horizon forecast. + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: Local means $\bar\pi_t$ and $\bar u_t$ through 2025Q3 + name: fig-csdv-updated-local-means +--- +updated_dates = updated_fit['data']['dates'] +updated_annual = annual_indices(updated_dates) +fig, ax = plt.subplots() +ax.plot( + updated_dates[updated_annual], + 100 * updated_features['core'][updated_annual], + 'o-', + lw=2, + markersize=3, + label='core inflation', +) +ax.plot( + updated_dates[updated_annual], + 100 * updated_features['natural'][updated_annual], + '+-', + lw=2, + markersize=5, + label='natural rate', +) +ax.axvline(2000.75, color='0.65', ls='--', lw=1) +ax.set_xlabel('year') +ax.set_ylabel('percent') +ax.legend() +plt.show() +``` + +The 1970s core-inflation peak is lower here than in the +{cite:t}`CogleySargent2005` figure because later observations revise the +smoothed history, so values on both sides of the dashed line belong to one +updated fit. + +After 2000 the core rate stays mostly between about 2 and 3 percent and +rises only modestly after 2020, even though observed inflation moves much more +sharply. + +The natural-rate line is the model's locally implied long-run unemployment +anchor, which is why quarterly unemployment can jump to 13.0 percent in 2020 +while this line remains near 5 percent. + +The monthly peak was 14.8 percent. + +The final annual point represents 2025Q3 because the sample ends before the +fourth quarter. + +```{code-cell} ipython3 +def endpoint_features(features): + """Return economically scaled endpoint features.""" + return pd.Series( + { + 'core inflation (%)': 100 * features['core'][-1], + 'natural rate (%)': 100 * features['natural'][-1], + 'normalized persistence': features['persistence'][-1], + 'log generalized innovation variance': features['logdet'][-1], + } + ) + + +updated_endpoints = endpoint_features(updated_features).to_frame( + name=updated_label +).T +latest_data_endpoints = endpoint_features( + latest_data_features +).to_frame(name=updated_label).T +data_revision_sensitivity = pd.concat( + { + 'Cogley-Sargent (2005) + extension': updated_endpoints, + 'latest revised data for full sample': latest_data_endpoints, + } +) +data_revision_sensitivity.round(3) +``` + +The endpoint core-inflation, natural-rate, and persistence summaries are similar +across the two data constructions. + +The draw-wise annual paths show how uncertainty evolves inside the updated fit. + +```{code-cell} ipython3 +updated_feature_draws = posterior_feature_draws( + updated_fit['posterior'], + updated_annual, +) +``` + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: Posterior medians and pointwise 90 percent intervals for $\bar\pi_t$, $\bar u_t$, and $g_{\pi\pi}(0,t)$ through 2025Q3 + name: fig-csdv-updated-feature-uncertainty +--- +fig, axes = plt.subplots(3, 1, figsize=(9, 8), sharex=True) +for ax, (key, ylabel) in zip(axes, feature_specs): + lower, median, upper = np.quantile( + updated_feature_draws[key], + (0.05, 0.5, 0.95), + axis=1, + ) + line, = ax.plot(updated_dates[updated_annual], median, lw=2) + ax.fill_between( + updated_dates[updated_annual], + lower, + upper, + color=line.get_color(), + alpha=0.2, + ) + ax.axvline(2000.75, color='0.65', ls='--', lw=1) + ax.set_ylabel(ylabel) +axes[-1].set_xlabel('year') +plt.tight_layout() +plt.show() +``` + +The solid lines are posterior medians, and the shaded regions are pointwise 90 +percent intervals rather than simultaneous whole-path bands. + +After 2000 the core-inflation median is comparatively flat and the persistence +median remains low, whereas the natural-rate band is broad and widens again at +the endpoint. + +The endpoint rows summarize uncertainty in the three nonlinear features. + +```{code-cell} ipython3 +def endpoint_feature_intervals(draws): + """Summarize draw-wise uncertainty in endpoint model features.""" + labels = { + 'core': 'core inflation (%)', + 'natural': 'natural rate (%)', + 'persistence': 'normalized persistence', + } + rows = {} + for key, label in labels.items(): + values = draws[key][-1] + rows[label] = { + 'median': np.median(values), + '5th percentile': np.quantile(values, 0.05), + '95th percentile': np.quantile(values, 0.95), + } + return pd.DataFrame.from_dict(rows, orient='index') + + +updated_endpoint_intervals = endpoint_feature_intervals( + updated_feature_draws +) +updated_endpoint_intervals.round(3) +``` + +At 2025Q3 both the core-inflation and natural-rate medians remain +historically moderate, as the table above shows. + +Their intervals remain broad, and normalized persistence retains a substantial +upper tail, so the classification of recent inflation as temporary is not +certain. + +### Did inflation become persistent again? + +Here persistence means propagation of an inflation innovation into future +inflation, rather than the number of quarters in which observed inflation +remains high. + +The normalized spectrum reduces sensitivity to a common change in shock scale, +although it still depends on the relative variances and covariances in $R_t$. + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: $g_{\pi\pi}(0,t)$ through 2025Q3 + name: fig-csdv-updated-persistence +--- +fig, ax = plt.subplots() +ax.plot( + updated_dates[updated_annual], + updated_features['persistence'][updated_annual], + 'o-', + lw=2, + markersize=3, +) +ax.axvline(2000.75, color='0.65', ls='--', lw=1) +ax.set_xlabel('year') +ax.set_ylabel(r'$g_{\pi\pi}(0,t)$') +plt.show() +``` + +The estimated $g_{\pi\pi}(0,t)$ path recreates the rise to a 1980 peak and the +subsequent collapse, but it stays near 0.2--0.5 after 2000 and rises only +slightly after 2020. + +A sequence of large reduced-form innovations can keep observed inflation high +for several quarters without generating the strong propagation estimated for +the 1970s. + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: $\bar\pi_t$ and $g_{\pi\pi}(0,t)$ through 2025Q3 + name: fig-csdv-updated-core-persistence +--- +fig, axes = plt.subplots( + 2, + 1, + figsize=(9, 6), + sharex=True, +) +axes[0].plot( + updated_dates[updated_annual], + 100 * updated_features['core'][updated_annual], + 'o-', + lw=2, + markersize=3, +) +axes[1].plot( + updated_dates[updated_annual], + updated_features['persistence'][updated_annual], + 'x-', + lw=2, + markersize=4, +) +for ax in axes: + ax.axvline(2000.75, color='0.65', ls='--', lw=1) +axes[0].set_ylabel('core inflation (%)') +axes[1].set_ylabel('normalized spectrum at zero') +axes[1].set_xlabel('year') +plt.tight_layout() +plt.show() +``` + +Core inflation recovers from its mid-2010s low toward its earlier level, while +persistence remains in its low post-1980 range instead of rising with it. + +This divergence separates a modest rise in the model's long-run inflation rate +from a return to 1970s-style propagation. + +The full spectrum shows where the difference comes from. + +```{code-cell} ipython3 +updated_raw_spectrum, updated_normalized_spectrum = ( + inflation_spectrum_surface( + updated_features['θ'], + updated_features['covariance'], + spectrum_frequencies, + ) +) +``` + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: $f_{\pi\pi}(\omega,t)$ and $g_{\pi\pi}(\omega,t)$ through 2025Q3 + name: fig-csdv-updated-spectra +--- +fig, axes = plt.subplots( + 1, + 2, + figsize=(9, 4), + sharey=True, + constrained_layout=True, +) +updated_surfaces = ( + (updated_raw_spectrum, 'raw spectrum', 'log10 power'), + ( + updated_normalized_spectrum, + 'normalized spectrum', + 'log10 normalized power', + ), +) +for ax, (surface, title, color_label) in zip(axes, updated_surfaces): + image = ax.pcolormesh( + updated_dates, + spectrum_frequencies, + np.log10(surface), + shading='auto', + ) + ax.axvline(2000.75, color='0.85', ls='--', lw=1) + ax.set_xlabel('year') + ax.set_ylabel(f'{title}\ncycles per quarter') + fig.colorbar(image, ax=ax, label=color_label) +plt.show() +``` + +The financial crisis and pandemic appear as bright, broad bands in +$f_{\pi\pi}(\omega,t)$ because reduced-form innovation variance increased. + +The normalized spectrum $g_{\pi\pi}(\omega,t)$ lacks a post-2000 low-frequency +ridge comparable to the 1970s. + +Together, the panels weigh against a return to 1970s-style persistence without +making the normalized statistic independent of $R_t$. + +```{code-cell} ipython3 +def episode_summary(fit, features): + """Average selected features over economically distinct episodes.""" + years = np.floor(fit['data']['dates'] + 1e-8).astype(int) + periods = { + '1970--1979': (years >= 1970) & (years <= 1979), + '1985--2000': (years >= 1985) & (years <= 2000), + '2001--2019': (years >= 2001) & (years <= 2019), + '2020--2022': (years >= 2020) & (years <= 2022), + '2023--2025Q3': years >= 2023, + } + rows = {} + for label, mask in periods.items(): + rows[label] = { + 'core inflation (%)': 100 * features['core'][mask].mean(), + 'normalized persistence': features['persistence'][mask].mean(), + 'log generalized innovation variance': ( + features['logdet'][mask].mean() + ), + } + return pd.DataFrame.from_dict(rows, orient='index') + + +updated_episodes = episode_summary(updated_fit, updated_features) +updated_episodes.round(3) +``` + +The episode averages contrast the high volatility of 2020--2022 with the lower +normalized persistence of the post-2000 decades. + +### Can recent policy activism be measured? + +When $|\beta_3|<1$, the policy margin gives the same active-policy +classification after 2000 without division by $1-\beta_3$. + +In the following figures, the gray line marks the active-policy threshold +$\mathcal{M}_t=0$. + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: Policy margin $\mathcal{M}_t$ through 2025Q3 + name: fig-csdv-updated-activism +--- +fig, ax = plt.subplots() +updated_policy_margin = updated_features['policy_margin'] +ax.plot(updated_dates, updated_policy_margin, lw=2) +ax.axhline(0, color='0.45', lw=1) +ax.axvline(2000.75, color='0.65', ls='--', lw=1) +ax.set_xlabel('year') +ax.set_ylabel(r'policy margin $\mathcal{M}_t$') +plt.show() +``` + +Among the displayed post-2000 dates, the margin is mostly positive and moves +toward zero in the mid-2010s and around 2020. + +Blank intervals omit dates for which the fitted interest-rate response does not +settle down. + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: Post-2000 $\mathcal{M}_t$ versus $\bar\pi_t$ and $g_{\pi\pi}(0,t)$ + name: fig-csdv-updated-activism-correlations +--- +fig, axes = plt.subplots(1, 2, figsize=(9, 4)) +recent_annual = annual_indices(updated_dates, start=2001) +displayed_recent = recent_annual[ + np.isfinite(updated_policy_margin[recent_annual]) +] +axes[0].scatter( + updated_policy_margin[displayed_recent], + 100 * updated_features['core'][displayed_recent], + s=18, +) +axes[1].scatter( + updated_policy_margin[displayed_recent], + updated_features['persistence'][displayed_recent], + s=18, +) +for ax in axes: + ax.axvline(0, color='0.45', lw=1) + ax.set_xlabel(r'policy margin $\mathcal{M}_t$') +axes[0].set_ylabel('core inflation (%)') +axes[1].set_ylabel('normalized spectrum at zero') +plt.tight_layout() +plt.show() +``` + +The remaining post-2000 observations do not establish a stable causal relation +between the policy margin, core inflation, and persistence. + +We also examine the central draw distribution at the 2025Q3 endpoint. + +```{code-cell} ipython3 +def endpoint_policy_margin_draws(fit): + """Return endpoint margins and stability indicators.""" + result = fit['posterior'] + margins = np.empty(result['SD'].shape[2]) + stable_response = np.empty(result['SD'].shape[2], dtype=bool) + for draw in range(len(margins)): + covariance = innovation_covariance( + result['HD'][-1, :, draw], + result['CD'][:, draw], + ) + rule = policy_rule_coefficients( + result['SD'][:, -1, draw], covariance + ) + margins[draw] = rule[0] + rule[2] - 1 + stable_response[draw] = np.abs(rule[2]) < 1 + return margins, stable_response + + +updated_margin_draws, stable_response_draws = ( + endpoint_policy_margin_draws(updated_fit) +) +assert np.any(stable_response_draws) +stable_margin_draws = updated_margin_draws[stable_response_draws] +updated_margin_summary = pd.Series( + { + 'median M': np.median(stable_margin_draws), + '5th percentile of M': np.quantile(stable_margin_draws, 0.05), + '95th percentile of M': np.quantile(stable_margin_draws, 0.95), + 'P(M >= 0 | |beta_3| < 1)': np.mean(stable_margin_draws >= 0), + 'share with |beta_3| < 1': stable_response_draws.mean(), + }, + name=updated_label, +).to_frame().T +updated_margin_summary.round(3) +``` + +```{code-cell} ipython3 +--- +mystnb: + figure: + caption: Central 90 percent of posterior draws for $\mathcal{M}_t$ conditional on $|\beta_3|<1$ at 2025Q3 + name: fig-csdv-updated-activism-distributions +--- +updated_margin_limits = np.quantile( + stable_margin_draws, + (0.05, 0.95), +) +updated_margin_bins = np.linspace(*updated_margin_limits, 31) + +fig, ax = plt.subplots() +central_draws = stable_margin_draws[ + (stable_margin_draws >= updated_margin_limits[0]) + & (stable_margin_draws <= updated_margin_limits[1]) +] +ax.hist( + central_draws, + bins=updated_margin_bins, + histtype='step', + lw=2, +) +ax.axvline(0, color='0.45', lw=1) +ax.set_xlabel(r'policy margin $\mathcal{M}_t$') +ax.set_ylabel('draw count') +plt.show() +``` + +The table reports the share of draws with a stable interest-rate response and +the active-policy probability among those draws. + +An interval spanning zero indicates that the endpoint classification remains +uncertain. + +The plot displays only draws between the 5th and 95th percentiles, and the zero +lower bound and unconventional policy further weaken the interpretation of this +short-rate projection after 2008. + +### What the additional observations change + +The extra quarter-century adds a dramatic volatility episode without a +post-2000 low-frequency ridge comparable to the 1970s. + +The pandemic is the dominant aggregate uncertainty episode, but the recent +inflation surge does not reproduce the 1970s low-frequency persistence ridge. + +Within the updated TVP-VAR, the full-sample drift-rate posterior remains above +its conservative prior calibration; a fixed-coefficient comparison would +require a separate $Q=0$ model. + +The 2025Q3 natural-rate and policy-margin estimates remain imprecise, especially +because not every posterior draw satisfies $|\beta_3|<1$. + +## Bad policy or bad luck? A verdict + +The Bayesian VAR delivers a nuanced answer to the question that opened this +lecture. + +- *Volatilities drifted:* the size of the shocks changed enormously, with a + Volcker-era spike and a subsequent Great Moderation, so the bad-luck story + captures something real. + +- *The fitted TVP-VAR attributes variation to coefficients too:* inflation + persistence and core inflation rose through the 1970s and fell in the 1980s, + although a formal fixed-versus-drifting comparison requires a separate $Q=0$ + model. + +- *The new observations do not overturn that distinction:* the pandemic + produces an extreme volatility episode, while the recent inflation surge does + not recreate the persistence of the 1970s. + +The reduced-form VAR cannot by itself prove that changes in Federal Reserve +beliefs caused the coefficient drift, because private behavior and other omitted +mechanisms can also change reduced-form dynamics. + +There is one more twist, and it loops us back to the theory of this section. + +The escape-route models of {doc}`phillips_learning` and +{doc}`phillips_escaping_nash` predict that inflation persistence should *grow* +along a disinflation as a learning government becomes reluctant to abandon a +high-inflation self-confirming equilibrium. + +The data show the opposite because persistence fell as inflation came down after +1980. + +It helped motivate later learning models, including +{cite:t}`CogleySargentConquest2005` and {cite:t}`Primiceri2006`, in which +policymakers' reluctance to disinflate in the 1970s and their eventual +conversion generate persistence that first rises and then falls. + +The friendly debate with Sims, Zha, Bernanke, and Mihov thus did more than +adjudicate a historical question. + +It sharpened the theoretical models of learning and drift that run through this +section, from the {doc}`self-confirming equilibria ` +of the *Conquest* book to the +{doc}`drifting Fed beliefs ` used to interpret later +inflation. + +## Exercises + +```{exercise} +:label: csdv_ex1 + +For an $AR(1)$ process, normalized zero-frequency power is + +$$ +g(0)=\frac{1+\rho}{2\pi(1-\rho)}. +$$ + +Compute $g(0)$ for $\rho=0$, $0.85$, and $0.97$, and use the persistence +path above to interpret how inflation dynamics changed around 1980. +``` + +```{solution-start} csdv_ex1 +:class: dropdown +``` + +```{code-cell} ipython3 +ρ = np.array([0.0, 0.85, 0.97]) +g0 = (1 + ρ) / (2 * np.pi * (1 - ρ)) +pd.Series(g0, index=ρ, name='normalized power at zero').to_frame() +``` + +White noise has $g(0)=1/(2\pi)$, while values between roughly 2 and 10 +correspond to highly persistent autoregressions with coefficients between about +$0.85$ and $0.97$. + +Thus, the rise in zero-frequency power during the Great Inflation and its +decline after 1980 represent a large change in persistence. + +```{solution-end} +``` + +```{exercise} +:label: csdv_ex2 + +Throughout this lecture we noted that a formal contrast between drifting and +constant coefficients requires refitting the model with $Q=0$, the pure +"bad luck" special case in which the VAR coefficients are frozen and only the +stochastic volatilities $H_t$ move. + +The sampler already supports this: pass +`fixed_q=np.zeros((n_coefficients, n_coefficients))` to `run_sampler` to hold +$Q$ at zero instead of drawing it. + +Fit this constant-coefficient model to the Cogley--Sargent sample and plot its +normalized zero-frequency spectrum $g_{\pi\pi}(0,t)$ against the +drifting-coefficient path from {numref}`fig-csdv-inflation-persistence`. + +What happens to the 1970s rise and post-1980 fall in measured persistence, and +what does that tell you about whether drifting *volatility* alone can account +for the persistence dynamics? +``` + +```{solution-start} csdv_ex2 +:class: dropdown +``` + +With $Q=0$ the elliptical-slice update returns a coefficient path that is +constant across time, so $A_{t\mid T}$ no longer moves and the only remaining +source of time variation in $g_{\pi\pi}(0,t)$ is the drifting covariance $R_t$. + +```{code-cell} ipython3 +constant_posterior = run_sampler( + data['y'], + data['x'], + prior, + n_sweeps=1_000, + burn=500, + thin=1, + seed=42, + warmup=200, + stable=True, + fixed_q=np.zeros((n_coefficients, n_coefficients)), +) + +constant_θ = constant_posterior['SD'].mean(axis=2) +constant_R = mean_innovation_covariance( + constant_posterior['HD'], constant_posterior['CD'] +) +constant_persistence = np.array([ + inflation_spectrum(constant_θ[:, t], constant_R[t], zero_frequency)[1][0] + for t in range(len(data['dates'])) +]) + +fig, ax = plt.subplots() +ax.plot(data['dates'][annual], inflation_persistence[annual], 'o-', + lw=2, markersize=3, label='drifting coefficients') +ax.plot(data['dates'][annual], constant_persistence[annual], 's-', + lw=2, markersize=3, label=r'constant coefficients ($Q=0$)') +ax.set_xlabel('year') +ax.set_ylabel(r'$g_{\pi\pi}(0,t)$') +ax.legend() +plt.show() +``` + +With a fixed $A$ the persistence measure still moves, because the *composition* +of $R_t$ — the relative sizes of the three orthogonal shocks — changes even +after its overall scale is normalized out. + +In fact the constant-coefficient path also climbs to a peak around 1980, as +inflation innovations grow large relative to the others, so the bad-luck channel +alone can manufacture much of the *rise*. + +What it cannot reproduce is the *fall*: after 1980 the constant-coefficient +persistence stays elevated, around 2 to 3, for the rest of the sample, whereas +the drifting-coefficient path collapses back below one. + +Freezing $A$ at its full-sample average leaves inflation propagating almost as +strongly in the 1990s as in the 1970s. + +So drifting volatility alone accounts for part of the run-up but none of the +Volcker-era disinflation of persistence — the post-1980 collapse is evidence +about the *systematic* dynamics, which is exactly why both channels are needed +to answer the bad-policy-or-bad-luck question. + +```{solution-end} +``` diff --git a/lectures/phillips_escaping_nash.md b/lectures/phillips_escaping_nash.md new file mode 100644 index 000000000..79917d436 --- /dev/null +++ b/lectures/phillips_escaping_nash.md @@ -0,0 +1,565 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.16.7 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(phillips_escaping_nash)= +```{raw} jupyter + +``` + +# Escaping Nash Inflation + +```{contents} Contents +:depth: 2 +``` + +## Overview + +> If an unlikely event occurs, it is very likely to occur in the most likely way. +> +> -- Michael Harrison + +This lecture is the analytical completion of {doc}`phillips_learning`. + +It follows {cite}`ChoWilliamsSargent2002` (CWS), which turns the *simulated* escape dynamics of chapter 8 of {cite}`Sargent1999` into a *deterministic characterization*. + +In {doc}`phillips_learning` we watched a constant-gain government recurrently escape the Nash self-confirming equilibrium and spend long spells near the Ramsey outcome — but we described those escapes only informally and by simulation. + +CWS show that the escapes are governed by their own ordinary differential equation, obtained from the **theory of large deviations**. + +The picture that emerges has two deterministic pieces: + +* the **mean dynamics**, an ODE that pulls the government's beliefs *toward* the self-confirming equilibrium; and +* the **escape dynamics**, a second ODE that — driven by a "most likely unlikely" sequence of shocks — pushes beliefs *away* from it, toward the beliefs that support the Ramsey outcome. + +The remarkable finding is that the escape has a *dominant path*: conditional on escaping, the government's beliefs follow a nearly deterministic route along which it temporarily learns a version of the natural-rate hypothesis and cuts inflation. + +This lecture is a technical extension of {doc}`phillips_learning`; the next lecture, {doc}`phillips_priors`, builds on it by asking how the government's *prior* about parameter drift reshapes both the mean dynamics and these escape dynamics. + +We work with the analytically tractable **static** model. + +Let's start with our imports: + +```{code-cell} ipython3 +import matplotlib.pyplot as plt +import numpy as np +from scipy.integrate import solve_ivp +``` + +## The model + +The true economy is the natural-rate model of {cite}`KydlandPrescott1977`, + +```{math} +:label: en_truth + +U_n = u - \theta(\pi_n - \hat x_n) + \sigma_1 W_{1n}, +\qquad +\pi_n = x_n + \sigma_2 W_{2n}, +\qquad +\hat x_n = x_n, +``` + +with $\theta, u > 0$ and $W_n = (W_{1n}, W_{2n})'$ i.i.d. standard Gaussian. + +The government does not know {eq}`en_truth`. + +In the static model it fits a non-expectational Phillips curve by regressing unemployment on inflation and a constant, + +```{math} +:label: en_belief + +U_n = \gamma_1 \pi_n + \gamma_{-1} + \eta_n , +``` + +with beliefs $\gamma = (\gamma_1, \gamma_{-1})$ (slope and intercept), and it treats $\eta_n$ as exogenous. + +Believing {eq}`en_belief`, the government solves the {doc}`Phelps problem `, whose static best response sets inflation to the constant + +```{math} +:label: en_bestresp + +x(\gamma) = -\frac{\gamma_{-1}\, \gamma_1}{1 + \gamma_1^2} . +``` + +Three beliefs are worth naming, following CWS: + +* **Belief 1 (Nash):** $\gamma_1 = -\theta$ with an intercept that makes $x = \theta u$ — the time-consistent outcome of {cite}`KydlandPrescott1977`. +* **Belief 2 (Ramsey):** $\gamma_1 = 0$, so the government perceives no tradeoff and sets $x = 0$. +* **Belief 3 (induction):** coefficients on inflation summing to zero, which for a patient government also sends inflation to $0$. + +```{code-cell} ipython3 +class EscapeModel: + "CWS 2002 static model. γ = (γ₁ slope, γ₋₁ intercept), regressors Φ = (π, 1)." + + def __init__(self, θ=1.0, u=5.0, σ1=0.3, σ2=0.3): + self.θ, self.u, self.σ1, self.σ2 = θ, u, σ1, σ2 + + def x(self, γ): + γ1, γm1 = γ + return -γm1 * γ1 / (1 + γ1**2) + + def M(self, γ): + "E[ΦΦ'] with Φ = (π, 1)." + x = self.x(γ) + return np.array([[x**2 + self.σ2**2, x], [x, 1.0]]) + + def g_bar(self, γ): + "Mean-dynamics forcing E[Φ(U − Φ'γ)] = M(T(γ) − γ)." + x = self.x(γ) + E_ΦU = np.array([x * self.u - self.θ * self.σ2**2, self.u]) + return E_ΦU - self.M(γ) @ γ +``` + +## The self-confirming equilibrium + +A self-confirming equilibrium is a belief that reproduces itself: the population regression coefficients of {eq}`en_belief`, computed on the data the government generates by acting on $\gamma$, equal $\gamma$. + +Writing $T(\gamma)$ for those population coefficients, CWS show + +$$ +\bar g(\gamma) \equiv E\left[\Phi(U - \Phi'\gamma)\right] = \bar M \left(T(\gamma) - \gamma\right), +$$ + +so a self-confirming equilibrium solves $\bar g(\gamma) = 0$. + +For the static model the equilibrium is the intersection of the line $\gamma_1 = -\theta$ with the parabola $\gamma_{-1} = u(1 + \gamma_1^2)$ — a unique point that supports the Nash outcome of {doc}`phillips_credibility`. + +It is the same self-confirming equilibrium constructed in {doc}`phillips_self_confirming`, written here in the static (constant-plus-slope) special case that {doc}`phillips_priors` also uses. + +```{code-cell} ipython3 +model = EscapeModel(θ=1.0, u=5.0, σ1=0.3, σ2=0.3) +γ_sce = np.array([-model.θ, model.u * (1 + model.θ**2)]) + +print(f"self-confirming beliefs γ = {γ_sce} (slope -θ, intercept u(1+θ²))") +print(f"self-confirming inflation x = {model.x(γ_sce):.2f} (= Nash = θu)") +print(f"check g_bar = {model.g_bar(γ_sce)}") +``` + +```{code-cell} ipython3 +γ1_grid = np.linspace(-2, 1, 200) +fig, ax = plt.subplots(figsize=(7, 6)) +ax.plot(γ1_grid, model.u * (1 + γ1_grid**2), label=r'$\gamma_{-1} = u(1+\gamma_1^2)$') +ax.axvline(-model.θ, color='C1', ls='--', label=r'$\gamma_1 = -\theta$') +ax.plot(γ_sce[0], γ_sce[1], 'ko', ms=8) +ax.annotate('SCE (Nash)', γ_sce, (γ_sce[0] + 0.1, γ_sce[1] + 1)) +ax.set_xlabel(r'slope $\gamma_1$') +ax.set_ylabel(r'intercept $\gamma_{-1}$') +ax.set_ylim(0, 25) +ax.legend() +ax.set_title('The unique self-confirming equilibrium') +plt.show() +``` + +## Adaptation and the mean dynamics + +We make the government adaptive: each period it updates $\gamma$ by constant-gain recursive least squares and acts on its current estimate — an *anticipated-utility* model in the sense of {cite}`Kreps1998`. + +The literature on least squares learning ({cite}`MarcetSargent1989`, {cite}`Woodford1990`, {cite}`EvansHonkapohja2001`) shows that, as the gain $\varepsilon \to 0$, the beliefs are approximated by the **mean-dynamics** ODE + +```{math} +:label: en_mean + +\dot\gamma = R^{-1} \bar g(\gamma), +\qquad +\dot R = \bar M(\gamma) - R . +``` + +A rest point of {eq}`en_mean` is a self-confirming equilibrium, and CWS show this ODE is *globally stable* about it. + +So under the mean dynamics alone, the adaptive government is drawn to Nash inflation. + +```{code-cell} ipython3 +def mean_ode(t, z, model): + γ, R = z[:2], z[2:].reshape(2, 2) + return np.concatenate([np.linalg.inv(R) @ model.g_bar(γ), + (model.M(γ) - R).ravel()]) + +z0 = np.concatenate([γ_sce + np.array([0.4, -3.0]), model.M(γ_sce).ravel()]) +sol = solve_ivp(lambda t, z: mean_ode(t, z, model), [0, 60], z0, + max_step=0.1, rtol=1e-9, atol=1e-11) + +fig, ax = plt.subplots(figsize=(8, 4.5)) +ax.plot(sol.t, -sol.y[1] * sol.y[0] / (1 + sol.y[0]**2)) +ax.axhline(model.θ * model.u, color='k', ls='--', lw=1, label='Nash') +ax.set_xlabel('time') +ax.set_ylabel('inflation $x$') +ax.set_title('Mean dynamics: from a perturbed start, beliefs return to Nash') +ax.legend() +plt.show() +``` + +The mean dynamics cannot, on their own, explain the recurrent visits to low inflation in the simulations of {doc}`phillips_learning`. + +For that we need a second force. + +## Escape dynamics as a control problem + +Although the impact of the noise vanishes as $\varepsilon \to 0$, for a fixed positive gain *rare* sequences of shocks can push beliefs a long way from the self-confirming equilibrium. + +The theory of large deviations characterizes the *most likely* such rare event. + +For a candidate belief path $\gamma(\cdot)$, one defines a log-moment-generating (H-) functional of the least-squares innovations, its Legendre transform $L$, and an **action functional** $S(T, \gamma) = \int_0^T L\,ds$ that measures how "costly" — how improbable — the path is. + +The **dominant escape path** minimizes the action subject to leaving a neighbourhood $G$ of $\bar\gamma$. + +Drawing on {cite}`Williams2019`, CWS reduce this to a clean control problem: the H-functional becomes a quadratic form with a normalizing matrix $Q$ (a fourth-moment matrix obtained from Lyapunov equations), and the escape path solves + +```{math} +:label: en_control + +\bar S = \inf_{v(\cdot),\, T} \; \frac12 \int_0^T v(s)' Q(\gamma(s), R(s))^{-1} v(s)\, ds +``` + +subject to the *perturbed* mean dynamics + +```{math} +:label: en_perturbed + +\dot\gamma = R^{-1}\bar g(\gamma) + v, +\qquad +\dot R = \bar M(\gamma) - R, +\qquad +\gamma(0) = \bar\gamma, \; \gamma(T) \notin G . +``` + +Read {eq}`en_control` as a least squares problem: $v$ is the extra "forcing" that the mean dynamics would need to escape, $Q$ plays the role of a covariance matrix, and the least-cost forcing is the most likely unusual shock sequence. + +Two consequences (their Theorem 5.3) tie the control problem to the stochastic model: + +* the probability of an escape on a bounded interval is $\approx \exp(-\bar S/\varepsilon)$, so the **mean time between escapes** is $\approx \exp(\bar S/\varepsilon)$; and +* conditional on escaping, beliefs follow the dominant escape path with probability approaching one. + +The escape dynamics, like the mean dynamics, are *deterministic*. + +## The dominant escape path + +For the static model with binomial shocks, CWS solve the control problem in closed form (their Section 7). + +The escape forcing is strikingly simple: + +```{math} +:label: en_force + +v = R^{-1} \begin{bmatrix} \sigma_1 \sigma_2 \\ 0 \end{bmatrix} , +``` + +so the dominant escape path solves + +```{math} +:label: en_escape + +\dot\gamma = R^{-1}\left( \bar g(\gamma) + \begin{bmatrix} \sigma_1 \sigma_2 \\ 0 \end{bmatrix} \right), +\qquad +\dot R = \bar M(\gamma) - R . +``` + +Let's integrate {eq}`en_escape` from the self-confirming equilibrium until beliefs leave a circle of radius 5. + +```{code-cell} ipython3 +def escape_ode(t, z, model): + γ, R = z[:2], z[2:].reshape(2, 2) + force = np.array([model.σ1 * model.σ2, 0.0]) + return np.concatenate([np.linalg.inv(R) @ (model.g_bar(γ) + force), + (model.M(γ) - R).ravel()]) + +def left_circle(t, z): + "Terminal event: beliefs leave the radius-5 circle around the SCE." + return 5.0 - np.linalg.norm(z[:2] - γ_sce) +left_circle.terminal = True +left_circle.direction = -1 + +z0 = np.concatenate([γ_sce, model.M(γ_sce).ravel()]) +esc = solve_ivp(lambda t, z: escape_ode(t, z, model), [0, 200], z0, + events=left_circle, max_step=0.02, rtol=1e-9, atol=1e-11) + +slope, intercept = esc.y[0], esc.y[1] +infl = -intercept * slope / (1 + slope**2) +``` + +```{code-cell} ipython3 +fig, axes = plt.subplots(1, 2, figsize=(12, 5)) + +axes[0].plot(esc.t, intercept, label='intercept $\\gamma_{-1}$') +axes[0].plot(esc.t, slope, label='slope $\\gamma_1$') +axes[0].axhline(0, color='C3', ls=':', lw=1, label='induction ($\\gamma_1 = 0$)') +axes[0].set_xlabel('time') +axes[0].set_ylabel('coefficient') +axes[0].set_title('Dominant escape path (cf. CWS Figure 4)') +axes[0].legend() + +axes[1].plot(esc.t, infl) +axes[1].axhline(model.θ * model.u, color='k', ls='--', lw=1, label='Nash') +axes[1].axhline(0, color='C2', ls=':', lw=1, label='Ramsey') +axes[1].set_xlabel('time') +axes[1].set_ylabel('inflation $x$') +axes[1].set_title('Inflation along the escape') +axes[1].legend() + +plt.tight_layout() +plt.show() +``` + +```{code-cell} ipython3 +print(f"slope : {slope[0]:.2f} → {slope[-1]:.3f} (induction hypothesis at 0)") +print(f"intercept : {intercept[0]:.1f} → {intercept[-1]:.1f}") +print(f"inflation : {infl[0]:.2f} → {infl[-1]:.2f} (Nash {model.θ*model.u:.0f} → Ramsey 0)") +``` + +Along the dominant escape path the slope rises from its self-confirming value of $-1$ toward zero — the **induction hypothesis** — and inflation falls from the Nash value toward Ramsey. + +This is exactly the temporary stabilization we saw by simulation in {doc}`phillips_learning`, now derived as a deterministic path: the government, by chance, generates enough inflation *experiments* to discover a good-enough version of the natural-rate hypothesis, and acts on it. + +## The race of four ODEs + +Where does the escape forcing {eq}`en_force` come from? + +With binomial shocks $W_{in} \in \{-1, +1\}$, the shock pair realized in any period lies in one of four groups. + +CWS show that the most likely escape uses the *same* unusual pair repeatedly, so there are four candidate escape ODEs — one per pair — and the dominant escape is the one that reaches the boundary fastest. + +Let's compute the instantaneous velocity of each candidate at the self-confirming equilibrium. + +```{code-cell} ipython3 +R0 = model.M(γ_sce) +R0_inv = np.linalg.inv(R0) +σ1σ2 = model.σ1 * model.σ2 + +candidates = { + "{(1,1),(-1,-1)} → Ramsey": np.array([σ1σ2, 0.0]), + "{(1,-1),(-1,1)} → higher π": np.array([-σ1σ2, 0.0]), + "{(1,1),(1,-1)}": R0 @ (R0_inv @ np.array([model.x(γ_sce) * model.σ1, model.σ1])), + "{(-1,1),(-1,-1)}": R0 @ (R0_inv @ np.array([-model.x(γ_sce) * model.σ1, -model.σ1])), +} + +for name, force in candidates.items(): + v = R0_inv @ force + print(f" {name:28s} |velocity| = {np.linalg.norm(v):.3f}") +``` + +The pair $\{(1,1), (-1,-1)\}$ produces a velocity far larger than the last two, and it points *toward* Ramsey (rising slope, falling intercept). + +Its mirror image $\{(1,-1),(-1,1)\}$ has the same speed but points the wrong way — toward *higher* inflation — where the mean dynamics oppose it and quickly pull it back. + +So the winner of the race is the Ramsey-ward path, and the escape forcing it induces is the $R^{-1}(\sigma_1\sigma_2, 0)'$ of {eq}`en_force`. + +## Mean dynamics reinforce the escape + +Why does the Ramsey-ward escape succeed while its mirror image fails? + +Near the self-confirming equilibrium the mean dynamics point *toward* it, opposing any escape. + +But CWS (their Figures 8-9) show that once beliefs have moved a little way out along the Ramsey-ward direction, the mean dynamics themselves start pointing *toward* Ramsey, reinforcing the escape. + +We can see this by plotting the mean-dynamics vector field in belief space. + +```{code-cell} ipython3 +gs = np.linspace(-1.2, 0.1, 16) # slope +gi = np.linspace(4.5, 10.5, 16) # intercept +GS, GI = np.meshgrid(gs, gi) +DS, DI = np.zeros_like(GS), np.zeros_like(GI) + +for i in range(GS.shape[0]): + for j in range(GS.shape[1]): + γ = np.array([GS[i, j], GI[i, j]]) + d = np.linalg.inv(model.M(γ)) @ model.g_bar(γ) + DS[i, j], DI[i, j] = d + +fig, ax = plt.subplots(figsize=(8, 6)) +ax.quiver(GS, GI, DS, DI, angles='xy', alpha=0.7) +ax.plot(*γ_sce, 'ko', ms=8, label='SCE (Nash)') +ax.plot(0, model.u, 'C2s', ms=8, label='Ramsey belief') +ax.plot(slope, intercept, 'C3', lw=2, label='escape path') +ax.set_xlabel(r'slope $\gamma_1$') +ax.set_ylabel(r'intercept $\gamma_{-1}$') +ax.set_title('Mean dynamics (arrows) and the escape path') +ax.legend() +plt.show() +``` + +Near the self-confirming equilibrium the arrows push back toward Nash, but away from it — along the escape route — they sweep toward the Ramsey belief. + +The escape dynamics only need to *start* the departure; the mean dynamics finish the job. + +This is the sense in which the mean dynamics trace a *circuitous* route: pushed off the equilibrium along the escape path, the system travels near Ramsey before the residual short-run Phillips curve is rediscovered and the mean dynamics eventually carry beliefs back to Nash. + +## The experimentation trap + +The escape has a compelling behavioural interpretation. + +Within its approximating model, the government can only detect the natural-rate hypothesis if there is enough *dispersion* in inflation. + +But inside a self-confirming equilibrium the government sets a constant systematic inflation rate, so it generates no such dispersion — it is caught in an **experimentation trap**. + +Only an unusual run of shocks makes the government vary inflation enough to steepen its estimated Phillips curve; a steeper perceived curve leads it (through the best response) to cut inflation, which generates further influential observations that steepen the curve further. + +This self-reinforcing process halts when the perceived Phillips curve is vertical — the induction hypothesis — and inflation is near Ramsey. + +The system cannot stay there forever: in truth there *is* a short-run Phillips curve, which the government eventually rediscovers, rekindling the mean dynamics that carry it back to Nash. + +## Escape frequency and model richness + +The minimized action $\bar S$ governs how often escapes occur: the mean escape time grows like $\exp(\bar S/\varepsilon)$. + +A striking finding of CWS is that escapes are *more frequent* when the government's model is richer. + +The full dynamic model of {doc}`phillips_learning` — with lagged unemployment and inflation — has a much smaller $\bar S$ than the static model, even though the two share the same self-confirming equilibrium. + +A richer model lets the government detect the subtler distributed-lag ("induction-hypothesis") version of the natural-rate hypothesis, so it escapes toward Ramsey more readily. + +```{note} +The escape dynamics inherit the same "near determinism" that makes the mean dynamics useful: for small gains, the stochastic simulations of {doc}`phillips_learning` hug the deterministic escape path derived here. The next lecture, {doc}`phillips_priors`, shows that the government's *prior* about how its coefficients drift reshapes both dynamics — and can even make the escape a deterministic *cycle*. +``` + +## Escaping volatile inflation + +The escape delivers a fall in the *level* of inflation. + +{cite}`EllisonYates2007` extend the model to explain a second post-war fact: inflation *volatility* rose and fell together with the level. + +Their device is to give the government a reason to stabilize. + +Following {cite}`PhelpsTaylor1977`, they add an unemployment shock $W_3$ that the government — but not the price-setting private sector — can react to. + +Now a government that believes in an exploitable Phillips curve is tempted to *lean against* $W_3$ by varying inflation, so the perceived effectiveness of policy, $|\gamma_1|$, drives the volatility of inflation as well as its level. + +In their model the expected inflation volatility a private agent faces is + +```{math} +:label: en_vol + +E(\sigma_\pi \mid \gamma) = \left[ \sigma_2^2 + \left(\frac{\gamma_1}{1 + \gamma_1^2}\right)^2 \sigma_3^2 \right]^{1/2} . +``` + +At the self-confirming equilibrium $\gamma_1 = -\theta$, the government believes policy is effective and leans against $W_3$ aggressively, so inflation is *volatile*. + +Along an escape, $\gamma_1 \to 0$: the government stops believing it can exploit the Phillips curve, abandons stabilization, and the volatility term collapses to the control error $\sigma_2$. + +Applying {eq}`en_vol` to the belief path we already computed shows the level and volatility of inflation escaping *in tandem*. + +```{code-cell} ipython3 +σ3 = 0.9 # size of the stabilizable shock +infl_vol = np.sqrt(model.σ2**2 + (slope / (1 + slope**2))**2 * σ3**2) + +fig, axes = plt.subplots(1, 2, figsize=(12, 4.5)) +axes[0].plot(esc.t, infl) +axes[0].axhline(model.θ * model.u, color='k', ls='--', lw=1, label='Nash') +axes[0].axhline(0, color='C2', ls=':', lw=1, label='Ramsey') +axes[0].set_xlabel('time'); axes[0].set_ylabel('inflation level') +axes[0].set_title('level escapes'); axes[0].legend() + +axes[1].plot(esc.t, infl_vol, 'C1') +axes[1].axhline(model.σ2, color='k', ls='--', lw=1, label=r'control error $\sigma_2$') +axes[1].set_xlabel('time'); axes[1].set_ylabel('inflation volatility') +axes[1].set_title('volatility escapes too'); axes[1].legend() + +plt.tight_layout() +plt.show() +``` + +Both fall as beliefs escape, because both spring from the same source: the government's belief in an exploitable tradeoff. + +{cite}`EllisonYates2007` draw a further, subtler lesson about the *timing* of escapes. + +A larger stabilizable shock $\sigma_3$ makes an escape *harder to trigger*: to create the illusion that inflation moves while unemployment stays put, an unusual sequence of shocks must now offset not only the control errors but also the government's own stabilizing reaction to $W_3$. + +The more shocks the government can offset, the more complex the escape-triggering sequence, and the longer the wait. + +Taken literally, this says an economy is more likely to escape to low inflation precisely when there are *few* shocks to stabilize — a suggestive link between the arrival of the mid-1980s calm and the disinflation that accompanied it. + +## Exercises + +```{exercise-start} +:label: en_ex1 +``` + +The escape forcing {eq}`en_force` scales with $\sigma_1 \sigma_2$ — the product of the two shock standard deviations. + +Integrate the dominant escape ODE {eq}`en_escape` for a grid of $\sigma_1 = \sigma_2 = \sigma \in \{0.2, 0.3, 0.4, 0.5\}$ and report, for each, the *exit time* from the radius-5 circle. + +How does a noisier economy affect how quickly beliefs travel along the escape route? + +```{exercise-end} +``` + +```{solution-start} en_ex1 +:class: dropdown +``` + +```{code-cell} ipython3 +for σ in [0.2, 0.3, 0.4, 0.5]: + m = EscapeModel(θ=1.0, u=5.0, σ1=σ, σ2=σ) + γ0 = np.array([-m.θ, m.u * (1 + m.θ**2)]) + + def leave(t, z, m=m, γ0=γ0): + return 5.0 - np.linalg.norm(z[:2] - γ0) + leave.terminal = True + leave.direction = -1 + + z0 = np.concatenate([γ0, m.M(γ0).ravel()]) + s = solve_ivp(lambda t, z: escape_ode(t, z, m), [0, 500], z0, + events=leave, max_step=0.02, rtol=1e-9, atol=1e-11) + print(f"σ = {σ}: exit time along the escape path = {s.t[-1]:.2f}") +``` + +A larger $\sigma$ makes the escape forcing $R^{-1}(\sigma_1\sigma_2, 0)'$ stronger, so beliefs travel the escape route faster (a shorter exit *time* along the deterministic path). + +Note that this is distinct from the *frequency* of escapes, which is governed by the action $\bar S$ and the gain $\varepsilon$; a noisier economy travels a given escape route more quickly once the escape is under way. + +```{solution-end} +``` + +```{exercise-start} +:label: en_ex2 +``` + +Make the reinforcement in the vector-field plot quantitative. + +At several points *along* the escape path, evaluate the mean-dynamics drift $R^{-1}\bar g(\gamma)$ and measure its cosine alignment with the direction from the current beliefs toward the Ramsey belief $(0, u)$. + +A cosine near $+1$ means the mean dynamics are pushing beliefs *toward* Ramsey — reinforcing the escape. + +```{exercise-end} +``` + +```{solution-start} en_ex2 +:class: dropdown +``` + +```{code-cell} ipython3 +γ_ramsey = np.array([0.0, model.u]) # Belief 2 + +def cosine_toward_ramsey(γ, R): + drift = np.linalg.inv(R) @ model.g_bar(γ) + to_ramsey = γ_ramsey - γ + denom = np.linalg.norm(drift) * np.linalg.norm(to_ramsey) + return np.nan if denom < 1e-9 else drift @ to_ramsey / denom + +for frac in [0.0, 0.25, 0.5, 0.75, 0.95]: + k = min(int(frac * len(esc.t)), len(esc.t) - 1) + γ, R = esc.y[:2, k], esc.y[2:, k].reshape(2, 2) + print(f"frac {frac:.2f}: γ = ({γ[0]:+.2f}, {γ[1]:.1f}), " + f"cos(drift, →Ramsey) = {cosine_toward_ramsey(γ, R):+.2f}") +``` + +Right at the self-confirming equilibrium the drift vanishes (the cosine is undefined), so the mean dynamics neither help nor hinder. + +But once beliefs have moved even slightly along the escape route, the mean-dynamics drift points almost exactly toward the Ramsey belief (cosine $\approx +1$): the mean dynamics reinforce the escape all the way to Ramsey. + +The opposition that CWS emphasize is confined to a *tiny* neighbourhood of the equilibrium — the escape dynamics only need to nudge beliefs out of it, after which the mean dynamics finish the job. + +```{solution-end} +``` diff --git a/lectures/phillips_learning.md b/lectures/phillips_learning.md new file mode 100644 index 000000000..460e3c83e --- /dev/null +++ b/lectures/phillips_learning.md @@ -0,0 +1,775 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.16.7 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(phillips_learning)= +```{raw} jupyter + +``` + +# Adaptive Learning and Escape Dynamics + +```{contents} Contents +:depth: 2 +``` + +## Overview + +This lecture is the culmination of the *Phillips curve tradeoffs* suite. + +It follows chapter 8 of {cite}`Sargent1999`, the most ambitious chapter of the book. + +In {doc}`phillips_self_confirming` a government held *fixed* beliefs about the Phillips curve — beliefs that were confirmed by the data those beliefs generated. + +Here we make the government a real-time econometrician. + +Each period it + +* re-estimates its Phillips curve by recursive least squares from the data seen so far, and +* sets inflation at the first-period recommendation of the {doc}`Phelps problem ` for its *current* estimate. + +We ask whether such an adaptive government converges to a self-confirming equilibrium. + +The answer depends on a single parameter — the **gain** that governs how fast old data are discounted: + +* With a *decreasing* gain that implements least squares, the mean dynamics pull the economy to a self-confirming equilibrium, and we get nothing new: the system is stuck near the Nash outcome. +* With a *constant* gain, agents discount past data, convergence is arrested, and **new outcomes emerge**. The system recurrently *escapes* the self-confirming equilibrium toward the Ramsey (zero-inflation) outcome — spontaneous stabilizations that resemble the arrival of Volcker. + +These escapes are the heart of the *vindication of econometric policy evaluation* story from {doc}`phillips_two_stories`: an adaptive government, learning a Solow-Tobin distributed-lag version of the natural-rate hypothesis, is led by chance observations to stabilize inflation. + +This lecture replicates, analyzes, and reinterprets simulations like those of Christopher Sims and Heetaik Chung {cite}`Sims1988,Chung1990`. + +Here we study the escapes by *simulation*; {doc}`phillips_escaping_nash` then characterizes them analytically as a second, deterministic ODE, and {doc}`phillips_priors` asks how the government's prior about coefficient drift reshapes both forces. + +Let's import what we need: + +```{code-cell} ipython3 +import matplotlib.pyplot as plt +import numpy as np +from scipy.linalg import solve_discrete_are +``` + +## A primer on recursive algorithms + +This section is a self-contained primer. + +It develops the two analytical objects — **mean dynamics** and **escape routes** — that organize everything that follows, and it explains the sense in which one and the same recursion can be read either as an *algorithm* for computing a self-confirming equilibrium or as a *model* of a government adapting in real time. + +The material is technical, and readers who want the punchline can skip to the simulations below and refer back as needed. + +### Beliefs, moment conditions, and self-confirming equilibria + +A self-confirming equilibrium under the classical identification is pinned down by the government's beliefs about some population moments and the regression coefficients they imply. + +Under the classical identification these beliefs are measured by the triple $(\gamma, \, E X_{C} X_{C}', \, E U X_{C})$, where $\gamma$ is the vector of Phillips-curve coefficients. + +In the adaptive models of this lecture the *time-$t$ values* of these objects are among the economy's state variables; they disappear as state variables in a self-confirming equilibrium only because there they are constants. + +A self-confirming equilibrium under the classical identification satisfies the moment conditions + +```{math} +:label: pl_scemoments + +\begin{aligned} +E\, R_{XC}^{-1}(\gamma)\left[ U_t X_{Ct}' - \left(X_{Ct} X_{Ct}'\right)\gamma \right] &= 0, \\ +E\, X_{Ct} X_{Ct}' - R_{XC}(\gamma) &= 0, +\end{aligned} +``` + +where the mathematical expectation is taken with respect to a distribution of $(U_t, X_{Ct})$ that depends on $\gamma$ through the solution $h(\gamma)$ of the Phelps problem. + +Self-reference surfaces precisely in this dependence of the distribution on $\gamma$: the government's beliefs shape its policy, which shapes the data on which the beliefs are then checked. + +The first line of {eq}`pl_scemoments` is the least squares normal equations for $\gamma$, pre-multiplied by the inverse second-moment matrix; the second line defines $R_{XC}$ as the second-moment matrix of the regressors. + +It is convenient to assemble all the unknowns into a single vector + +```{math} +:label: pl_phivec + +\phi = \begin{bmatrix} \gamma \\ \operatorname{col}(R_{XC}) \end{bmatrix}, +``` + +where $\operatorname{col}(R_{XC})$ stacks the columns of $R_{XC}$. + +The moment conditions {eq}`pl_scemoments` then take the compact form + +```{math} +:label: pl_bdef + +E\left[F(\phi, \zeta)\right] = 0, +\qquad +b(\phi) \equiv E\left[F(\phi, \zeta)\right], +``` + +where $\zeta$ is a random vector and the expectation is over its distribution (which, again, depends on $\phi$). + +A self-confirming equilibrium is a set of beliefs $\phi_f$ that is a zero of $b$, + +```{math} +:label: pl_scezero + +b(\phi_f) = 0 . +``` + +The rest of this section describes recursive algorithms for finding such a zero, and a change of perspective that converts each computational algorithm into a model of real-time adaptation. + +### Iteration + +The simplest algorithm computes a sequence $\{\phi_k\}$ of estimates from + +```{math} +:label: pl_iterate + +\phi_{k+1} = \phi_k + a\, b(\phi_k), +``` + +where the distribution used to evaluate the expectation defining $b(\phi_k)$ in {eq}`pl_bdef` is itself evaluated at the current estimate $\phi_k$, and $a > 0$ is a step size. + +This is the relaxation algorithm used to compute self-confirming equilibria in {doc}`phillips_self_confirming`. + +Each step requires evaluating the mathematical expectation $b(\phi) = E[F(\phi, \zeta)]$ — which is exactly why we needed the moment (Lyapunov) formulas there. + +### Stochastic approximation + +A random version of {eq}`pl_iterate` is obtained by replacing the mean $b(\phi_n)$ by a single random draw $F(\phi_n, \zeta_n)$ and letting the *step size* do the averaging: + +```{math} +:label: pl_sa + +\phi_{n+1} = \phi_n + a_n F(\phi_n, \zeta_n), +\qquad +a_n > 0, \quad \sum_{n=0}^\infty a_n = +\infty . +``` + +To study the limiting behavior of {eq}`pl_sa`, define **artificial time** + +```{math} +:label: pl_artificial + +t_n = \sum_{k=0}^n a_k , +``` + +form the sampled process $\phi(t_n) = \phi_n$, and interpolate (typically piecewise-linearly) to obtain a continuous-time process $\phi^o(t)$. + +One then approximates $\phi^o(t)$ by a continuous-time process as $n \to \infty$ and uses it to characterize the tail of the original sequence. + +Different rates of decrease of the gain sequence $\{a_n\}$ produce different approximating processes, because they change the mapping {eq}`pl_artificial` from real time $n$ to artificial time $t_n$. + +```{note} +Recursive stochastic approximation originates with {cite}`RobbinsMonro1951`, who devised {eq}`pl_sa` to find the root of a regression function observed with noise, and with {cite}`KieferWolfowitz1952`, who adapted it to find the maximum of a regression function (the "K-W" algorithms referred to below). The "ODE method" for analyzing such recursions — approximating the interpolated process by the solution of a differential equation — is due to {cite}`Ljung1977`; book-length treatments are {cite}`BenvenisteMetivierPriouret1990` and {cite}`KushnerYin2003`. Its use to study learning in self-referential macroeconomic models is developed by {cite}`MarcetSargent1989` and, comprehensively, by {cite}`EvansHonkapohja2001`. +``` + +### Mean dynamics + +The classic stochastic approximation algorithms of {cite}`KushnerClark1978` and {cite}`Ljung1977` set the gain to decrease like $a_n \sim 1/n$ (at least for $t \geq N$ for some $N > 0$). + +This permits strong statements about the almost sure convergence of {eq}`pl_sa` to a zero of $b(\phi)$. + +For $a_n \sim 1/n$, as $n \to \infty$ the interpolated process $\phi^o(t)$ approaches the solution of the ordinary differential equation + +```{math} +:label: pl_ode + +\frac{d \phi^o(t)}{dt} = b\left(\phi^o(t)\right), +``` + +which we call the **mean dynamics** — the vector generalization of the scalar least-squares-learning ODE derived in the appendix of {doc}`phillips_credibility`. + +A law of large numbers makes the random term in the continuous-time approximation vanish fast enough that the mean dynamics {eq}`pl_ode` describe the *tail* behavior of the stochastic process {eq}`pl_sa`. + +Consequently: + +* if the algorithm converges (almost surely), it converges to a zero of the mean dynamics, $b(\phi) = 0$ — a self-confirming equilibrium; and +* the ODE {eq}`pl_ode` carries the information about local and global stability of the algorithm. + +Local stability is governed by the eigenvalues of the Jacobian of $b$ at a rest point: if all eigenvalues have negative real parts the rest point is locally stable, and, under conditions on the gain, the eigenvalue of largest real part governs the *rate* of convergence (the usual $\sqrt{T}$ rate requires that eigenvalue to be below $-\tfrac12$). + +We will see below that for the classical model at our parameters this eigenvalue sits exactly at the $-\tfrac12$ boundary, so convergence is marginal — a fact that turns out to matter for how readily the system escapes. + +```{note} +{cite}`BrockHommes1997` build models whose global behavior is driven by stable mean dynamics far from rational expectations equilibria together with local instability of adaptation near them — a complementary mechanism for generating endogenous fluctuations from learning. +``` + +### Constant gain and convergence in distribution + +We are equally interested in versions of {eq}`pl_sa` with a *constant* gain $a_n = \epsilon > 0$ for all $n$. + +Limit theorems for constant-gain algorithms use a weaker notion of convergence — convergence *in distribution* — than the almost-sure convergence available when $a_n \sim 1/n$. + +They concern small-noise limits, taken as $\epsilon \to 0$ and $n\epsilon \to +\infty$ simultaneously. + +Again using artificial time {eq}`pl_artificial`, form the family of processes + +```{math} +:label: pl_cgain + +\phi_{n+1}^\epsilon = \phi_n^\epsilon + \epsilon\, F(\phi_n^\epsilon, \zeta_n), +``` + +interpolate to obtain $\phi^\epsilon(t)$, and study its small-$\epsilon$ limit. + +Dupuis and Kushner, {cite}`KushnerYin2003`, and others verified conditions under which, as $\epsilon \to 0$ and $\epsilon n \to \infty$, the process $\phi_n^\epsilon$ converges *in distribution* to the zeros of the same mean dynamics {eq}`pl_ode`; the restrictions on the mean dynamics needed for convergence match those from the classic $a_n \sim 1/n$ theory. + +Unlike the decreasing-gain algorithm, a constant-gain algorithm does not settle down: $(\gamma, R_{XC})$ converges to a *stationary stochastic process*, perpetually fluctuating around — and occasionally far from — the self-confirming equilibrium. + +### Escape routes and the theory of large deviations + +The feature of the constant-gain apparatus that matters most for this lecture is not convergence toward $\phi_f$ but the *excursions away from it*. + +We are as interested in movements away from a self-confirming equilibrium as in those toward one, because the recurrent stabilizations in the simulations are precisely such excursions. + +The **theory of large deviations** characterizes these excursions through three objects. + +First, the log moment generating function of (an averaged version of) the innovation process $F(\phi_n, \zeta_n)$: for a vector $\theta$ conformable to $F$, + +```{math} +:label: pl_mgf + +H(\theta, \phi) = \log E \exp\left(\theta' F(\phi, \zeta)\right), +``` + +where the expectation is over the distribution of $\zeta$. + +```{note} +Equation {eq}`pl_mgf` is a heuristic shorthand. The object that actually enters the theory is a *time-averaged* limit; {cite}`DupuisKushner1987` and {cite}`KushnerYin2003` assume that for each $\delta > 0$ the following limit exists uniformly in $\phi_i, \alpha_i$ on any compact set: +$$ +\sum_{i=0}^{T/\delta - 1} \delta\, H(\alpha_i, \phi_i) += \lim_{N \to \infty} \frac{\delta}{N} + \log E \exp \sum_{i=0}^{T/\delta - 1} \alpha_i' + \sum_{j=iN}^{iN+N-1} F(\phi_i, \zeta_j) . +$$ +The inner sum averages the innovations over a block of length $N$; the double limit lets us treat serially dependent innovations. +``` + +Second, the **Legendre transform** of $H$, which plays the role of a rate function: + +```{math} +:label: pl_legendre + +L(\beta, \phi) = \sup_\theta \left[ \theta'\beta - H(\theta, \phi) \right] . +``` + +Third, the **action functional**, which measures the "cost" of a candidate escape path $\phi(\cdot)$: + +```{math} +:label: pl_action + +S(T, \phi) = +\begin{cases} +\displaystyle \int_0^T L\!\left(\tfrac{d}{ds}\phi(s),\, \phi(s)\right) ds + & \text{if } \phi(s) \text{ is absolutely continuous and } \phi(0) = \phi_f, \\[2mm] +\infty & \text{otherwise.} +\end{cases} +``` + +Dupuis and Kushner turn the search for the most likely escape into a *deterministic control problem*. + +Let $D$ be a compact set containing $\phi_f$, with boundary $\partial D$, and let $C[0,T]$ be the continuous functions on $[0,T]$. + +The escape route is the path $\tilde\phi(\cdot)$ that solves + +```{math} +:label: pl_escapeproblem + +\inf_{T > 0} \; \inf_{\phi \in A} S(T, \phi), +\qquad +A = \left\{ \phi(\cdot) \in C[0,T] : \phi(T) \in \partial D \right\} . +``` + +Assuming the minimizer $\tilde\phi(\cdot)$ is unique, and letting $t_D^\epsilon$ be the first time the constant-gain process $\phi^\epsilon(t)$ leaves $D$, {cite}`DupuisKushner1987` show that for every $\delta > 0$ + +```{math} +:label: pl_escapelim + +\lim_{\epsilon \to 0} \operatorname{Prob}\left( +\left| \phi^\epsilon(t_D^\epsilon) - \tilde\phi(T) \right| > \delta +\right) = 0 . +``` + +In words: *conditional on escaping the set $D$, the system leaves it near the terminal point of the least-action path* — so the escape has a deterministic direction and shape, even though it is triggered by chance. + +This is the sense in which the escapes below, though they require no large shock, "seem purposeful": they follow the least-action route dictated by {eq}`pl_escapeproblem`. + +A crucial contrast with the mean dynamics: the mean dynamics {eq}`pl_ode` do *not* depend on the noise around them, whereas the escape routes *do* — the noise not only adds random fluctuations around {eq}`pl_ode`, it also carves out this second family of paths. + +```{note} +The mathematical foundations are the large-deviation theory for randomly perturbed dynamical systems of {cite}`FreidlinWentzell1998` (especially their chapter 4), specialized to stochastic approximation by {cite}`DupuisKushner1987` and {cite}`DupuisKushner1989`; a weak-convergence treatment is {cite}`DupuisEllis1997`. +``` + +### A tractable action functional + +The escape-route calculation promises cheap information about central tendencies of the algorithm, but the action functional {eq}`pl_action` is generally hard to compute. + +An important special case simplifies it dramatically. + +Suppose the innovation is additive and Gaussian, + +$$ +F(\phi, \zeta) = b(\phi) + \sigma(\phi)\, \zeta, +$$ + +where $\zeta_n$ is stationary and Gaussian but not necessarily serially uncorrelated, and define $R = \sum_j E\, \zeta_t \zeta_{t-j}'$. + +Then the action functional takes the quadratic form + +```{math} +:label: pl_action2 + +S(T, \phi) = \frac{1}{2} \int_0^T +\left(\tfrac{d}{ds}\phi - b(\phi)\right)' +\left[\sigma(\phi)\, R\, \sigma(\phi)'\right]^{+} +\left(\tfrac{d}{ds}\phi - b(\phi)\right) +h(s)\, ds , +``` + +where $(\cdot)^{+}$ is the Moore-Penrose generalized inverse (used to handle possible stochastic singularity of $\sigma R \sigma'$). + +The weight $h(s)$ depends on the gain: $h(s) = \exp(s)$ when $\gamma = 1$ in $a_n = a_0 / n^\gamma$, and $h(s) = 1$ when $\gamma < 1$. + +Read {eq}`pl_action2` as a cost that penalizes departures of the *realized drift* $\tfrac{d}{ds}\phi$ from the *mean drift* $b(\phi)$, weighting each direction by the inverse of the local noise covariance $\sigma R \sigma'$. + +The least-action escape therefore threads the beliefs through regions where the mean dynamics are weak and the noise is informative — which, in our model, is the direction of the *induction hypothesis*. + +```{note} +This quadratic action functional is precisely the object minimized in {cite}`ChoWilliamsSargent2002`, the published treatment of the model of this lecture. They solve the control problem {eq}`pl_escapeproblem` for the Nash self-confirming equilibrium and show, analytically, that the least-action escape drives the sum of weights on inflation toward the value that activates the induction hypothesis — that is, toward the Ramsey outcome. {cite}`SargentWilliams2005` study how the government's prior (equivalently, the covariance structure of the gain algorithm, our $P_0$ and forgetting factor) reshapes the escape, and {cite}`Kasa2004` applies the same large-deviation machinery to recurrent currency crises. +``` + +### From computation to adaptation + +The preceding recursions were introduced as *algorithms* to approximate a self-confirming equilibrium. + +The same mathematics tells us what happens when we instead *modify* our self-confirming-equilibrium models to incorporate real-time adaptation — simply by reading $\phi_n$ as the government's time-$n$ beliefs rather than as the $n$-th iterate of a solver. + +Two facts organize everything below: + +1. gain sequences that implement least squares (decreasing like $1/t$) make the mean dynamics pull the economy *toward* self-confirming equilibria; while +2. gain sequences that fall off more slowly — in the limit, constant gains that discount the past — *arrest* that pull and increase the frequency with which the escape dynamics influence outcomes. + +```{note} +A brief intellectual history. {cite}`Lucas_Prescott_1971` dismissed iterating on the moment conditions {eq}`pl_scezero` as a computational strategy, but {cite}`Townsend1983` used it. {cite}`Woodford1990` and {cite}`MarcetSargent1989` used the mean dynamics {eq}`pl_ode` to establish conditions for the convergence of least squares learning to rational expectations in models with self-reference, both requiring continuity of $b(\phi)$. In-Koo Cho studied problems with *discontinuous* $b(\phi)$ inherited from discontinuous decision rules (trigger strategies in credibility and search problems); to make least squares learning approach rational expectations he used gains satisfying $\tfrac{1}{\log n} < a_n < \tfrac{1}{\sqrt n}$, which yield a *diffusion* approximation to {eq}`pl_sa` that promotes enough experimentation to discover an equilibrium. {cite}`KandoriMailathRob1993` use related mathematics to select long-run equilibria in games via mutation, and Roger Myerson applied an escape-route calculation to a voting problem. The modern synthesis of these learning methods is {cite}`EvansHonkapohja2001`. +``` + +## The adaptive model + +We now build the classical adaptive model. + +### Government beliefs and behavior + +The government believes in a distributed-lag Phillips curve + +```{math} +:label: pl_belief + +U_t = \gamma' X_{C,t} + \varepsilon_{C,t}, +\qquad +X_{C,t} = \begin{bmatrix} y_t & U_{t-1} & U_{t-2} & y_{t-1} & y_{t-2} & 1 \end{bmatrix}' . +``` + +Arriving at time $t$ with an estimate $\gamma_{t-1}$, it sets the systematic part of inflation by solving the Phelps problem *as if* $\gamma_{t-1}$ will govern the Phillips curve forever: + +```{math} +:label: pl_rule + +y_t = h(\gamma_{t-1}) X_{t-1} + v_{2t}, +\qquad +X_{t-1} = \begin{bmatrix} U_{t-1} & U_{t-2} & y_{t-1} & y_{t-2} & 1 \end{bmatrix}' . +``` + +It then updates its beliefs by **recursive least squares** (RLS): + +```{math} +:label: pl_rls + +\begin{aligned} +\gamma_t &= \gamma_{t-1} + g_t R_{XC,t}^{-1} X_{C,t}\left(U_t - \gamma_{t-1}' X_{C,t}\right), \\ +R_{XC,t} &= R_{XC,t-1} + g_t\left(X_{C,t} X_{C,t}' - R_{XC,t-1}\right), +\end{aligned} +``` + +where $\{g_t\}$ is the gain sequence. + +Least squares sets $g_t = 1/t$; a constant-gain algorithm sets $g_t = g_0 > 0$ and discounts past observations, which is sensible if the government suspects the Phillips curve wanders over time. + +The public is assumed to know the government's rule, so its inflation forecast is $x_t = h(\gamma_{t-1}) X_{t-1}$, the systematic part of {eq}`pl_rule`. + +Unemployment is generated by the actual Phillips curve of {doc}`phillips_self_confirming` with $\rho_1 = \rho_2 = 0$: + +$$ +U_t = U^* - \theta(y_t - x_t) + v_{1t} = U^* - \theta v_{2t} + v_{1t} . +$$ + +### The Phelps problem with lags + +Given a belief $\gamma$, the decision rule $h(\gamma)$ solves an LQ control problem. + +Write the believed Phillips curve as $U_t = \gamma_0 y_t + c' s_t$, where $\gamma_0$ is the coefficient on current inflation and $c$ collects the coefficients on the state $s_t = X_{t-1}$. + +The government minimizes $E\sum_t \delta^t (U_t^2 + y_t^2)$, so the per-period loss is $s_t' (cc') s_t + (\gamma_0^2 + 1) y_t^2 + 2\gamma_0\, y_t\, c' s_t$, and the state evolves as $s_{t+1} = A s_t + B y_t$ with + +$$ +s_{t+1} = \begin{bmatrix} U_t \\ U_{t-1} \\ y_t \\ y_{t-1} \\ 1 \end{bmatrix}, +\qquad +U_t = c' s_t + \gamma_0 y_t . +$$ + +We solve the discounted LQ problem with `scipy`'s discrete algebraic Riccati equation. + +```{code-cell} ipython3 +class AdaptivePhillips: + """ + Classical adaptive Phillips curve model: the government re-estimates a + distributed-lag Phillips curve by recursive least squares and each period + acts on the first-period recommendation of the Phelps problem. + """ + + def __init__(self, θ=1.0, U_star=5.0, σ1=0.3, σ2=0.3, δ=0.98): + self.θ, self.U_star, self.σ1, self.σ2, self.δ = θ, U_star, σ1, σ2, δ + + # classical self-confirming belief: U = -θ y + (θ²+1)U* + self.γ_sce = np.array([-θ, 0.0, 0.0, 0.0, 0.0, (θ**2 + 1) * U_star]) + + # self-confirming moment matrix M = E[X_C X_C'] and residual variance + self.M = self._sce_moments() + self.σC2 = σ1**2 # var(U | X_C) at the SCE + + def _sce_moments(self): + "E[X_C X_C'] at the serially-uncorrelated classical SCE." + θ, σ1, σ2 = self.θ, self.σ1, self.σ2 + μU, μy = self.U_star, self.θ * self.U_star + Σ = {('U', 'U'): θ**2 * σ2**2 + σ1**2, ('y', 'y'): σ2**2, + ('U', 'y'): -θ * σ2**2, ('y', 'U'): -θ * σ2**2} + # regressors: (time, type) for [y_t, U_{t-1}, U_{t-2}, y_{t-1}, y_{t-2}, 1] + regs = [(0, 'y'), (1, 'U'), (2, 'U'), (1, 'y'), (2, 'y'), (None, 'c')] + mean = {'U': μU, 'y': μy, 'c': 1.0} + M = np.zeros((6, 6)) + for i, (ti, tyi) in enumerate(regs): + for j, (tj, tyj) in enumerate(regs): + if tyi == 'c' or tyj == 'c' or ti != tj: + M[i, j] = mean[tyi] * mean[tyj] + else: + M[i, j] = Σ[(tyi, tyj)] + mean[tyi] * mean[tyj] + return M + + def phelps_h(self, γ): + "Government decision rule ŷ_t = h(γ)·X_{t-1} for belief γ." + δ, γ0, c = self.δ, γ[0], γ[1:] + R = np.outer(c, c) + Q = np.array([[γ0**2 + 1.0]]) + N = (γ0 * c).reshape(1, -1) + A = np.zeros((5, 5)); B = np.zeros((5, 1)) + A[0, :] = c; B[0, 0] = γ0 # U_t + A[1, 0] = 1.0 # U_{t-1} + B[2, 0] = 1.0 # y_t + A[3, 2] = 1.0 # y_{t-1} + A[4, 4] = 1.0 # constant + sb = np.sqrt(δ) + Ad, Bd = sb * A, sb * B + P = solve_discrete_are(Ad, Bd, R, Q, s=sb * N.T) + F = np.linalg.solve(Q + Bd.T @ P @ Bd, Bd.T @ P @ Ad + N) + return -F.ravel() +``` + +We use the Kalman-filter implementation of RLS from Appendix A of {cite}`Sargent1999`. + +A forgetting factor $\lambda \in (0, 1]$ maps to the gain: $\lambda = 1$ gives least squares ($g_t \to 1/t$), while $\lambda < 1$ gives a constant gain $g_0 = 1 - \lambda$. + +The prior is initialized as if the government had already seen $T$ periods of self-confirming-equilibrium data, through $P_0 = (\sigma_C^2 / T)\, M^{-1}$; larger $T$ means a tighter prior. + +```{code-cell} ipython3 +def simulate(model, λ, T_prior, n=1000, seed=0): + "Simulate the adaptive system. λ=1 is least squares; λ<1 is constant gain." + rng = np.random.default_rng(seed) + θ, U_star, σ1, σ2 = model.θ, model.U_star, model.σ1, model.σ2 + + γ = model.γ_sce.copy() + P = (model.σC2 / T_prior) * np.linalg.inv(model.M) + R2 = model.σC2 + g0 = 1 - λ + + U1 = U2 = U_star + y1 = y2 = θ * U_star + y_path, U_path, sumweights, constant = (np.empty(n) for _ in range(4)) + + for t in range(n): + h = model.phelps_h(γ) + X_lag = np.array([U1, U2, y1, y2, 1.0]) + yhat = h @ X_lag + + v2, v1 = σ2 * rng.standard_normal(), σ1 * rng.standard_normal() + y = yhat + v2 + U = U_star - θ * (y - yhat) + v1 + + φ = np.array([y, U1, U2, y1, y2, 1.0]) # X_C,t + denom = R2 + φ @ P @ φ + gain = P @ φ / denom + γ = γ + gain * (U - γ @ φ) + R1 = (g0 / (1 - g0)) * P if λ < 1 else 0.0 # constant vs decreasing + P = P - np.outer(P @ φ, φ @ P) / denom + R1 + + y_path[t], U_path[t] = y, U + sumweights[t] = γ[0] + γ[3] + γ[4] # weights on current+lagged y + constant[t] = γ[5] + U2, U1 = U1, U + y2, y1 = y1, y + + return dict(y=y_path, U=U_path, sumweights=sumweights, constant=constant) +``` + +```{code-cell} ipython3 +model = AdaptivePhillips() +h_sce = model.phelps_h(model.γ_sce) +print("decision rule at the self-confirming belief:") +print(f" h(γ_sce) = {np.round(h_sce, 3)} (a constant rule of " + f"{h_sce[-1]:.1f} = Nash inflation)") +``` + +At the self-confirming belief the Phelps rule is a constant equal to the Nash inflation rate, and the beliefs reproduce themselves — the adaptive system's rest point is the self-confirming equilibrium of {doc}`phillips_self_confirming`. + +## Least squares learning converges + +We follow {cite}`Sargent1999` in setting the true data-generating parameters to the classical example at the end of {doc}`phillips_self_confirming`: $U^* = 5$, $\theta = 1$, $\sigma_1 = \sigma_2 = 0.3$, $\rho_1 = \rho_2 = 0$, $\delta = 0.98$. + +The classical self-confirming equilibrium has serially uncorrelated $(U, y)$ fluctuating around means $(5, 5)$. + +First, least squares (a decreasing gain). + +```{code-cell} ipython3 +ls = simulate(model, λ=1.0, T_prior=5000, n=1000, seed=1) + +fig, ax = plt.subplots(figsize=(9, 4.5)) +ax.plot(ls['y'], lw=0.8) +ax.axhline(5, color='k', ls='--', lw=1, label='self-confirming (Nash)') +ax.axhline(0, color='C2', ls=':', lw=1, label='Ramsey') +ax.set_xlabel('$t$') +ax.set_ylabel('inflation $y_t$') +ax.set_title('Figure 8.1: classical adaptive model, least squares') +ax.legend() +plt.show() +``` + +Under least squares the mean dynamics dominate: inflation hugs the self-confirming value of 5, and the simulation looks like a draw from the self-confirming equilibrium itself. + +We get nothing new — the government is stuck near the Nash outcome. + +## Constant gain and escape dynamics + +Now give the government a *constant* gain, $\lambda = 0.975$, so it discounts past data. + +```{code-cell} ipython3 +cg = simulate(model, λ=0.975, T_prior=300, n=1000, seed=1) + +fig, ax = plt.subplots(figsize=(9, 4.5)) +ax.plot(cg['y'], lw=0.8) +ax.axhline(5, color='k', ls='--', lw=1, label='self-confirming (Nash)') +ax.axhline(0, color='C2', ls=':', lw=1, label='Ramsey') +ax.set_xlabel('$t$') +ax.set_ylabel('inflation $y_t$') +ax.set_title('Figure 8.2: classical adaptive model, constant gain ' + r'($\lambda = 0.975$)') +ax.legend() +plt.show() +``` + +The picture is completely different. + +Inflation starts near the self-confirming value of 5, then drops almost to zero and stays there for a long time, before slowly heading back toward 5 only to be propelled toward zero again. + +The mean dynamics that pull the system toward the self-confirming equilibrium are opposed by a recurrent force that sends inflation close to the Ramsey outcome. + +Crucially, no large shocks trigger these stabilizations: they are an *endogenous* feature of the constant-gain learning dynamics. + +```{code-cell} ipython3 +print(f"constant-gain inflation: mean {cg['y'].mean():.2f}, " + f"fraction of periods near Ramsey (y<2): {(cg['y'] < 2).mean():.0%}") +``` + +## The escape route and the induction hypothesis + +Why does the system escape toward Ramsey rather than in some other direction? + +The answer is the **induction hypothesis** of {doc}`phillips_adaptive`: when the sum of the weights on current and lagged inflation in the estimated Phillips curve approaches zero, the Phelps problem advises the government to *reduce* inflation. + +Let's plot inflation together with that sum of weights. + +```{code-cell} ipython3 +fig, axes = plt.subplots(2, 1, figsize=(9, 7), sharex=True) + +axes[0].plot(cg['y'], lw=0.8) +axes[0].axhline(0, color='C2', ls=':', lw=1) +axes[0].set_ylabel('inflation $y_t$') + +axes[1].plot(cg['sumweights'], lw=0.8, color='C1') +axes[1].axhline(-1, color='k', ls='--', lw=1, label='self-confirming value') +axes[1].axhline(0, color='C3', ls=':', lw=1, label='induction hypothesis') +axes[1].set_xlabel('$t$') +axes[1].set_ylabel('sum of weights on $y$') +axes[1].legend() + +fig.suptitle('Escape route: stabilizations coincide with the sum of ' + 'weights rising toward zero') +plt.tight_layout() +plt.show() +``` + +Every stabilization coincides with the sum of weights jumping from its self-confirming value of $-1$ toward zero. + +When it reaches zero, the induction hypothesis is (temporarily) satisfied, the Phelps problem calls for near-Ramsey inflation, and the resulting data briefly *reinforce* the induction hypothesis — a situation that is not self-confirming in the technical sense but is nonetheless self-reinforcing. + +We can see the escape route directly by plotting the joint path of the constant and the sum of weights in the estimated Phillips curve. + +```{code-cell} ipython3 +fig, ax = plt.subplots(figsize=(8, 6)) +sc = ax.scatter(cg['constant'], cg['sumweights'], c=np.arange(len(cg['y'])), + cmap='viridis', s=6) +ax.axhline(0, color='C3', ls=':', lw=1.5, label='induction hypothesis') +ax.axhline(-1, color='k', ls='--', lw=1, label='self-confirming value') +ax.set_xlabel('constant in estimated Phillips curve') +ax.set_ylabel('sum of weights on $y$') +ax.legend() +plt.colorbar(sc, label='time $t$') +plt.show() +``` + +The beliefs spend most of their time near the self-confirming value (sum of weights $\approx -1$) but repeatedly shoot up toward the induction line (sum of weights $= 0$) — the escape route along which the government learns a Solow-Tobin version of the natural-rate hypothesis and stabilizes inflation. + +## Relation to equilibria under forecast misspecification + +The near-Ramsey episodes are reminiscent of the equilibria with optimal misspecified forecasts of {doc}`phillips_misspecified` and {doc}`phillips_self_confirming`. + +There, a forecasting model *without a constant but with a unit root* could closely approximate a true model that *includes* a constant. + +Here an approximation with a similar flavor operates during the near-Ramsey episodes: the government's estimated Phillips curve, by driving the sum of weights toward zero, uses the induction hypothesis to approximate a constant — except that the approximated model is not fixed but changes as the government's own beliefs feed back through the Phelps problem. + +## Role of the discount factor + +The recurrent stabilizations toward Ramsey depend on the discount factor $\delta$ being near one. + +Lowering $\delta$ raises the inflation rate observed during the low-inflation episodes, consistent with the workings of the Phelps problem under the induction hypothesis. + +```{code-cell} ipython3 +fig, ax = plt.subplots(figsize=(9, 4.5)) +for δ in [0.90, 0.95, 0.98]: + m = AdaptivePhillips(δ=δ) + sim = simulate(m, λ=0.975, T_prior=300, n=1000, seed=1) + ax.plot(sim['y'], lw=0.7, label=rf'$\delta = {δ}$') +ax.axhline(0, color='k', lw=0.5) +ax.set_xlabel('$t$') +ax.set_ylabel('inflation $y_t$') +ax.legend() +ax.set_title('Escapes toward Ramsey deepen as the government becomes patient') +plt.show() +``` + +## Anticipated utility + +The adaptive model is an example of what David Kreps calls an **anticipated utility** model {cite}`Kreps1998`. + +The government adapts a temporarily misspecified model — a Phillips curve with fixed coefficients — to incorporate the most recent observations, and reoptimizes along the way. + +In forming decisions at $t$, it acts as if its current estimate $\gamma_{t-1}$ will govern the Phillips curve forever, using the same policy functional $h(\cdot)$ that would be optimal if $\gamma$ were truly time-invariant. + +This is a small departure from rational expectations: calendar time enters only through the drifting beliefs $\gamma_t$. + +Unlike Bayesian or robust decision makers, an anticipated-utility government ignores its period-by-period model misspecification — it does not entertain the possibility that its coefficients will drift, even as they do. + +Yet, as the simulations show, this modest departure from rationality is enough to generate a rich account of the rise and fall of U.S. inflation, and to supply underpinnings for the vindication of econometric policy evaluation. + +## Conclusions + +For long stretches, an adaptive government learns to generate *better than Nash* outcomes. + +These results come from the recurrent dynamics of adaptation: the mean dynamics that under least squares pull the system toward a self-confirming equilibrium continue to operate, but under a constant gain the noise lets the system recurrently escape toward the Ramsey outcome. + +Starting from a self-confirming equilibrium, the adaptive algorithm gradually makes the government put enough weight on the induction hypothesis that chance observations eventually promote a stabilization. + +Adaptation makes the government's beliefs a hidden state that imparts serial correlation into inflation and unemployment — so that an outside forecaster would do well to use a random-coefficients model, or to make the constant adjustments that Lucas noted in his Critique {cite}`lucas1976econometric`. + +In this sense the adaptive models contain the underpinnings for vindicating econometric policy evaluation — the second of the two stories of {doc}`phillips_two_stories`. + +## Exercises + +```{exercise-start} +:label: pl_ex1 +``` + +Build the **Keynesian** adaptive model, in which the government fits the Phillips curve in the reverse direction, regressing inflation on unemployment. + +The regressors are $X_{K,t} = \begin{bmatrix} U_t & U_{t-1} & U_{t-2} & y_{t-1} & y_{t-2} & 1 \end{bmatrix}'$, and the government estimates $\beta$ in $y_t = \beta' X_{K,t} + \varepsilon_{K,t}$, then inverts to $\gamma$ before solving the Phelps problem. + +Rather than re-derive everything, explore the *classical* model's sensitivity to the constant gain: simulate with $\lambda \in \{0.99, 0.975, 0.95\}$ and compare how often inflation escapes toward Ramsey. + +How does a larger gain (smaller $\lambda$, faster discounting of the past) affect the frequency of escapes? + +```{exercise-end} +``` + +```{solution-start} pl_ex1 +:class: dropdown +``` + +```{code-cell} ipython3 +fig, ax = plt.subplots(figsize=(9, 4.5)) +for λ in [0.99, 0.975, 0.95]: + sim = simulate(model, λ=λ, T_prior=300, n=1000, seed=1) + frac = (sim['y'] < 2).mean() + ax.plot(sim['y'], lw=0.6, + label=rf'$\lambda = {λ}$ (near-Ramsey {frac:.0%})') +ax.axhline(0, color='k', lw=0.5) +ax.set_xlabel('$t$') +ax.set_ylabel('inflation $y_t$') +ax.legend() +plt.show() +``` + +A larger constant gain (smaller $\lambda$) discounts the past more heavily, arresting convergence to the self-confirming equilibrium more forcefully and producing more frequent — though also noisier — escapes toward the Ramsey outcome. + +```{solution-end} +``` + +```{exercise-start} +:label: pl_ex2 +``` + +The contrast between Figures 8.1 and 8.2 hinges on the gain, but the least squares result also depends on the tightness of the prior. + +Simulate the least squares system ($\lambda = 1$) for prior tightness $T \in \{500, 2000, 5000\}$, and report the mean inflation rate across several seeds. + +Explain why a looser prior (smaller $T$) makes even the least squares system prone to escapes. + +```{exercise-end} +``` + +```{solution-start} pl_ex2 +:class: dropdown +``` + +```{code-cell} ipython3 +for T in [500, 2000, 5000]: + means = [simulate(model, λ=1.0, T_prior=T, n=1000, seed=s)['y'].mean() + for s in range(6)] + print(f"T = {T:>4}: mean inflation across seeds = " + f"{np.round(means, 1)}") +``` + +With a looser prior the effective gain $1/(T + t)$ starts larger, so early updates are big enough to kick the beliefs off the self-confirming equilibrium. + +Because that equilibrium is only marginally stable — the mean dynamics have an eigenvalue at the boundary of the region of fast convergence — the system can then drift toward the induction hypothesis and get stuck near Ramsey, mimicking the constant-gain escapes. + +A tighter prior keeps the gain small throughout, so least squares reliably hugs the self-confirming equilibrium. + +```{solution-end} +``` \ No newline at end of file diff --git a/lectures/phillips_lost_conquest.md b/lectures/phillips_lost_conquest.md new file mode 100644 index 000000000..3cf10176a --- /dev/null +++ b/lectures/phillips_lost_conquest.md @@ -0,0 +1,475 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.16.7 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(phillips_lost_conquest)= +```{raw} jupyter + +``` + +# The Lost Conquest: Fed Policy in the 2020s + +```{contents} Contents +:depth: 2 +``` + +In addition to what's in Anaconda, this lecture will use the following library to download data from FRED: + +```{code-cell} ipython3 +:tags: [hide-output] + +!pip install pandas_datareader +``` + +## Overview + +This lecture is the contemporary sequel to the *Phillips curve tradeoffs* suite. + +It follows {cite}`SargentWilliams2025`, which turns the tools of {doc}`phillips_learning` and {doc}`phillips_priors` — a {doc}`Phelps control problem `, an *anticipated-utility* government, a *drifting-coefficients* model estimated by *constant-gain recursive least squares*, and a *self-confirming equilibrium* — on the inflation of the 2020s. + +The puzzle is a live one. + +After the COVID-19 pandemic, U.S. inflation surged to its highest rate since the early 1980s — the very episode we plotted in the post-1999 data of {doc}`phillips_two_stories`. + +Yet for more than a year the Federal Reserve did not respond; only in 2022, with inflation already near its peak, did it begin to raise rates aggressively. + +Why was the Fed so slow? + +{cite}`SargentWilliams2025` build an *artificial Fed* that, each period, + +* re-estimates a drifting-coefficients Phillips curve by constant-gain recursive least squares, and +* solves a linear-quadratic Phelps problem — under an anticipated-utility assumption, in the sense of {cite}`Kreps1998` — to set its interest-rate instrument. + +This is "model predictive control": the same *estimate-then-optimize* loop that drove the learning government of {doc}`phillips_learning`, but now the instrument is the policy rate and the beliefs concern the *persistence* of inflation and the *slope* of the Phillips curve. + +Three drifting beliefs turn out to rationalize the slow response: + +1. **Declining inflation persistence** — the Fed had learned that inflation shocks fade quickly, so the surge looked *transitory*. +2. **A flatter Phillips curve** — the Fed had learned that inflation responds weakly to slack, so disinflation looked *costly*. +3. **Real-time output-gap mismeasurement** — the Fed perceived more economic slack than there really was. + +We reproduce the first two from public data, show how they generate a Phelps rule that tracks the actual funds rate, and then — following the paper's New Keynesian appendix — ask a self-confirming-equilibrium question: *were the Fed's benign beliefs a consequence of its own past success?* + +Let's import what we need: + +```{code-cell} ipython3 +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +import datetime +from pandas_datareader import data as web +from scipy.linalg import solve_discrete_are +``` + +## The three elements + +The first two elements are among the most documented facts in modern macroeconomics. + +**Declining persistence.** +From the 1970s into the 1980s inflation was highly persistent; a shock raised inflation for years. +{cite}`CogleySargentConquest2005` and {cite}`StockWatson2007` document a marked decline in persistence after the mid-1980s — inflation began reverting to target much faster. + +**A flatter Phillips curve.** +Since the 1990s, estimates of the Phillips curve's slope have trended toward zero — the "missing disinflation" after the Great Recession being the leading example. +Both facts were on policy makers' minds. +Former Fed Chair Janet Yellen observed in 2019 that "the slope of the Phillips curve … has diminished very significantly since the 1960s … and … inflation has become much less persistent." +And, as {cite}`Bernanke2022` writes, "a flat Phillips curve means that inflation is a less reliable indicator of economic overheating [and] the costs, in terms of unemployment, of bringing inflation back down to target could be higher than in the past." + +**Real-time uncertainty.** +The third element, emphasized by {cite}`Orphanides2001`, is that the output gap is badly mismeasured in *real time*, especially at business-cycle turning points. +Through 2020–2023 the real-time gap was persistently *below* the later-revised measure, so the Fed perceived more slack — reinforcing the belief that inflation would fade on its own. +We use current-vintage data below and return to the real-time distinction in the conclusion. + +## The Fed's drifting-coefficients beliefs + +We give the artificial Fed a backward-looking Phillips curve with drifting coefficients, + +```{math} +:label: lc_pc + +\pi_t = \alpha_{0,t} + \rho_t\, \pi_{t-1} + \kappa_t\, x_t + \varepsilon^{\pi}_t , +``` + +where $\pi_t$ is inflation, $x_t$ the output gap, $\rho_t$ the *perceived persistence*, and $\kappa_t$ the *perceived slope*. + +The Fed updates $\theta_t = (\alpha_{0,t}, \rho_t, \kappa_t)$ by constant-gain recursive least squares — exactly the algorithm of {doc}`phillips_learning` and {doc}`phillips_priors`, with gain $\gamma$ discounting the past so the estimates can *track* drift: + +$$ +\theta_{t+1} = \theta_t + \gamma R_t^{-1} X_t\left(\pi_t - X_t'\theta_t\right), +\qquad +R_{t+1} = R_t + \gamma\left(X_t X_t' - R_t\right), +$$ + +with $X_t = (1, \pi_{t-1}, x_t)'$. + +We download quarterly PCE inflation, the CBO output gap, and the federal funds rate from FRED. + +```{code-cell} ipython3 +start, end = datetime.datetime(1959, 1, 1), datetime.datetime(2025, 7, 1) + +pcepi = web.DataReader('PCEPI', 'fred', start, end)['PCEPI'].resample('QS').mean() +gdp = web.DataReader('GDPC1', 'fred', start, end)['GDPC1'] # real GDP +pot = web.DataReader('GDPPOT', 'fred', start, end)['GDPPOT'] # CBO potential +ff = web.DataReader('FEDFUNDS', 'fred', start, end)['FEDFUNDS'].resample('QS').mean() + +inflation = 100 * (pcepi / pcepi.shift(4) - 1) # year-over-year PCE inflation +gap = 100 * (gdp / pot - 1) # output gap, percent + +data = pd.concat([inflation.rename('pi'), gap.rename('x'), + ff.rename('i')], axis=1).dropna() +print(f"sample: {data.index[0].date()} to {data.index[-1].date()}, " + f"{len(data)} quarters") +``` + +Following the paper, we freeze beliefs during 2020–2021 (setting the gain to zero) because the pandemic observations are extreme outliers that would otherwise whipsaw the estimates; belief updating resumes in 2022. + +```{code-cell} ipython3 +def estimate_beliefs(data, gain=0.03, freeze=(2020, 2021)): + "Constant-gain RLS of the drifting Phillips curve; returns α₀, ρ, κ paths." + pi, x = data['pi'].values, data['x'].values + θ = np.array([0.5, 0.9, 0.05]) # [intercept, persistence, slope] + R = np.diag([1.0, 10.0, 5.0]) + rows = [] + for t in range(1, len(data)): + g = 0.0 if data.index[t].year in freeze else gain + X = np.array([1.0, pi[t - 1], x[t]]) + err = pi[t] - X @ θ + R = R + g * (np.outer(X, X) - R) + θ = θ + g * np.linalg.solve(R, X * err) + rows.append((θ[0], θ[1], θ[2])) + return pd.DataFrame(rows, index=data.index[1:], + columns=['alpha0', 'rho', 'kappa']) + +beliefs = estimate_beliefs(data) +``` + +```{code-cell} ipython3 +fig, axes = plt.subplots(2, 1, figsize=(10, 6), sharex=True) +axes[0].plot(beliefs['rho']) +axes[0].axhline(1, color='k', lw=0.5, ls=':') +axes[0].set_ylabel('persistence $\\rho_t$') +axes[0].set_title('Perceived inflation persistence') + +axes[1].plot(beliefs['kappa'], color='C1') +axes[1].axhline(0, color='k', lw=0.5, ls=':') +axes[1].set_ylabel('slope $\\kappa_t$') +axes[1].set_xlabel('year') +axes[1].set_title('Perceived Phillips-curve slope') + +plt.tight_layout() +plt.show() +``` + +The two panels tell the story. + +Perceived **persistence** $\rho_t$ is near one through the high-inflation 1970s and 1980s, then drifts down after the mid-1980s, reaching a post-2008 trough — and then *jumps back up* toward one when belief updating resumes in 2022, just as the Fed abandoned the "transitory" characterization and began to tighten. + +Perceived **slope** $\kappa_t$ trends toward zero over the 2010s: the Phillips curve flattens. + +By 2019 the Fed's model said inflation was *not persistent* and only *weakly linked to slack* — the beliefs that, fed into a Phelps problem, will counsel patience. + +## The Fed's Phelps problem + +Each period the Fed sets its policy rate by solving a linear-quadratic Phelps problem, taking its *current* estimates as if they will hold forever — the anticipated-utility assumption of {cite}`Kreps1998` that we met in {doc}`phillips_learning`. + +Pairing the belief Phillips curve {eq}`lc_pc` with a fixed "IS curve" $x_t = b_0 + b_1 x_{t-1} + g(i_{t-1} - \pi_{t-1}) + \varepsilon^x_t$ gives linear state dynamics for $X_t = (1, \pi_t, x_t, i_{t-1})'$, + +$$ +X_{t+1} = A_t X_t + B_t\, i_t + C \varepsilon_{t+1}, +$$ + +whose matrices depend on the time-$t$ beliefs. + +The Fed minimizes + +```{math} +:label: lc_loss + +\mathbb E_t \sum_{s=t}^{\infty}\beta^{s-t} +\Big[ (\pi_s - \pi^*)^2 + \lambda_x\, x_s^2 + \eta\,(i_s - i_{s-1})^2 \Big], +``` + +where $\pi^*$ is the 2% target and the last term penalizes abrupt rate changes. + +This is a discounted linear-quadratic regulator with a cross-term; its solution is a *smoothed Taylor rule* $i_t = -F_t X_t$ whose coefficients move as beliefs move. + +```{code-cell} ipython3 +# fixed IS curve, estimated once by OLS over the full sample +pi, x, i_ = data['pi'].values, data['x'].values, data['i'].values +n = len(data) +X_is = np.column_stack([np.ones(n - 1), x[:-1], i_[:-1] - pi[:-1]]) +b0, b1, g = np.linalg.lstsq(X_is, x[1:], rcond=None)[0] + +β, π_star, λ_x, η = 0.95, 2.0, 0.2, 0.5 + +def phelps_rate(θ, state): + "Optimal (subjectively) funds rate given beliefs θ=(α₀,ρ,κ) and state." + α0, ρ, κ = θ + A = np.array([[1, 0, 0, 0], + [α0 + κ * b0, ρ - κ * g, κ * b1, 0], + [b0, -g, b1, 0], + [0, 0, 0, 0]], float) + B = np.array([[0], [κ * g], [g], [1]], float) + c_π = np.array([-π_star, 1, 0, 0.]) # π − π* + c_x = np.array([0, 0, 1, 0.]) # x + e_i = np.array([0, 0, 0, 1.]) # i_{-1} + Q = np.outer(c_π, c_π) + λ_x * np.outer(c_x, c_x) + η * np.outer(e_i, e_i) + N = (-η * e_i).reshape(4, 1) + R = np.array([[η]]) + sb = np.sqrt(β) + P = solve_discrete_are(sb * A, sb * B, Q, R, s=sb * N) + F = np.linalg.solve(R + β * B.T @ P @ B, β * B.T @ P @ A + N.T) + return max((-F @ state).item(), 0.0) # impose the zero lower bound +``` + +```{code-cell} ipython3 +θ = np.array([0.5, 0.9, 0.05]) +R = np.diag([1.0, 10.0, 5.0]) +gain = 0.03 +opt, θ_2000 = [], None +for t in range(1, n): + yr = data.index[t].year + g_t = 0.0 if yr in (2020, 2021) else gain + X = np.array([1.0, pi[t - 1], x[t]]) + R = R + g_t * (np.outer(X, X) - R) + θ = θ + g_t * np.linalg.solve(R, X * (pi[t] - X @ θ)) + if data.index[t].year == 2000 and θ_2000 is None: + θ_2000 = θ.copy() # save beliefs for the counterfactual + state = np.array([1.0, pi[t], x[t], i_[t - 1]]) + opt.append(phelps_rate(θ, state)) + +optimal = pd.Series(opt, index=data.index[1:]) +``` + +```{code-cell} ipython3 +fig, ax = plt.subplots(figsize=(10, 4.5)) +window = slice('1991', None) +ax.plot(optimal[window], 'C0', label="Phelps problem's recommended rate") +ax.plot(data['i'][window], 'C3', lw=1, label='actual federal funds rate') +ax.set_xlabel('year') +ax.set_ylabel('percent') +ax.set_title("The belief-driven Phelps rule vs. actual policy") +ax.legend() +plt.show() + +corr = np.corrcoef(optimal['1991':], data['i']['1991':optimal.index[-1]])[0, 1] +print(f"correlation of recommended and actual rate, 1991-2025: {corr:.2f}") +``` + +The subjectively optimal rate tracks the level and turning points of actual policy over three decades (correlation about 0.95): the late-1990s tightening, the 2001 easing, the zero-bound era after 2008, and the 2015 normalization. + +Crucially, around the 2021 surge the recommended rate barely moves — the belief-driven rule *also* counsels a slow response, and only tightens in 2022, mirroring the Fed. + +## Why the Fed was slow: a counterfactual + +To isolate the role of the drifting beliefs, we recompute the Phelps recommendations holding beliefs *fixed* at their January 2000 values — when inflation was still perceived as persistent and the Phillips curve as steeper. + +```{code-cell} ipython3 +counterfactual = pd.Series( + [phelps_rate(θ_2000, np.array([1.0, pi[t], x[t], i_[t - 1]])) + for t in range(1, n)], + index=data.index[1:]) + +fig, ax = plt.subplots(figsize=(10, 4.5)) +w = slice('2015', None) +ax.plot(optimal[w], 'C0', label='baseline (drifting beliefs)') +ax.plot(counterfactual[w], 'C1--', label='counterfactual (beliefs frozen at 2000)') +ax.plot(data['i'][w], 'C3', lw=1, alpha=0.7, label='actual funds rate') +ax.set_xlabel('year') +ax.set_ylabel('percent') +ax.set_title('Counterfactual: a Fed that had not updated its beliefs since 2000') +ax.legend() +plt.show() +``` + +The contrast is stark. + +A Fed with year-2000 beliefs — perceiving persistent inflation and a steeper Phillips curve — would have tightened *immediately and sharply* in 2021, driving the funds rate well above 4% before the actual Fed had moved at all. + +The muted, delayed response was not a change in objectives; it was a change in *beliefs*. + +Perceiving inflation as transitory and the Phillips curve as flat, the Fed's own Phelps problem told it to wait. + +## A self-confirming equilibrium: nature's New Keynesian model + +Were those benign beliefs *correct*? + +Here the paper adds a twist that connects directly to {doc}`phillips_self_confirming` and {doc}`phillips_escaping_nash`: a **self-confirming equilibrium** in which the Fed's flat-Phillips-curve, low-persistence beliefs are a *consequence of its own aggressive past policy*. + +Suppose nature actually runs a small New Keynesian model, + +```{math} +:label: lc_nk + +\begin{aligned} +\pi_t &= \beta\, \mathbb E_t \pi_{t+1} + \gamma_b\, \pi_{t-1} + \kappa\, x_t + u_t, \\ +x_t &= \mathbb E_t x_{t+1} - \sigma\left(i_t - \mathbb E_t \pi_{t+1} - r^n_t\right), \\ +i_t &= \phi_\pi\, \pi_t , +\end{aligned} +``` + +with a structural slope $\kappa$ and a Taylor rule of aggressiveness $\phi_\pi$. + +In its minimum-state-variable rational expectations equilibrium, $\mathbb E_t \pi_{t+1} = \lambda\, \pi_t$, where $\lambda$ — the *measured* persistence an econometrician would recover — is the stable root of a cubic that depends on $\phi_\pi$. + +The paper proves two comparative statics. + +```{prf:proposition} Aggressive policy lowers measured persistence +:label: lc_prop1 + +In the determinacy region, the stable root $\lambda(\phi_\pi)$ is strictly decreasing in the policy aggressiveness $\phi_\pi$: a more aggressive Fed makes inflation *look* less persistent. +``` + +```{prf:proposition} Aggressive policy flattens the measured slope +:label: lc_prop2 + +Under sufficient conditions (a small lagged-inflation term and a sufficiently aggressive rule), the population OLS slope of a backward-looking Phillips curve is also decreasing in $\phi_\pi$: a more aggressive Fed makes the Phillips curve *look* flatter. +``` + +Let's reproduce {prf:ref}`lc_prop1` by solving the cubic for the stable root. + +```{code-cell} ipython3 +def measured_persistence(φ_π, β=0.99, γ_b=0.5, κ=0.1, σ=1.0): + "Stable MSV root λ(φ_π): the persistence an econometrician would measure." + coeffs = [β, -(1 + β + κ * σ), 1 + γ_b + κ * σ * φ_π, -γ_b] + roots = np.roots(coeffs) + real = roots[np.abs(roots.imag) < 1e-9].real + stable = real[np.abs(real) < 1.0] + return stable[np.argmin(np.abs(stable))] + +φ_grid = np.linspace(1.05, 3.0, 40) +λ_path = [measured_persistence(φ) for φ in φ_grid] + +fig, ax = plt.subplots(figsize=(8, 4.5)) +ax.plot(φ_grid, λ_path) +ax.set_xlabel(r'Taylor-rule aggressiveness $\phi_\pi$') +ax.set_ylabel(r'measured persistence $\lambda$') +ax.set_title('Aggressive policy makes inflation look less persistent') +plt.show() +``` + +As policy grows more aggressive, the measured persistence falls from near one toward one-half. + +The intuition behind both propositions is *policy endogeneity*, the point emphasized by {cite}`McLeayTenreyro2019`: when the Fed offsets inflationary pressure promptly, a regression that treats the output gap as an exogenous driver recovers a *smaller* slope and a *faster-mean-reverting* inflation process than the structural parameters imply. + +### The trap + +Now read {eq}`lc_nk` as nature and the drifting-coefficients model of the previous sections as the Fed's *approximating* model. + +Along a path of consistently aggressive policy — the Volcker–Greenspan conquest — the data the Fed generates make inflation *look* transitory and the Phillips curve *look* flat. + +Estimating its backward-looking model, the Fed comes to believe exactly that. + +Those beliefs are a **self-confirming equilibrium** in the sense of {doc}`phillips_self_confirming`: under the Fed's *current* aggressive policy, its reduced-form model is observationally equivalent to nature's New Keynesian model. + +But the beliefs are *wrong off the equilibrium path* — under a *less* aggressive policy, persistence and the slope would spring back up. + +The Fed cannot see this, because it has no reason to experiment: it sits in the **lack-of-experimentation trap** of {doc}`phillips_learning`, its complacency justified by a belief about a policy it never runs. + +When large post-pandemic shocks finally hit, those self-confirming beliefs told the Fed the surge was transitory and tightening was costly — and the conquest was, for a while, *lost*. + +This is a modern replay of the *Conquest*'s recurrent dynamics, one level up: the mechanism now works through the perceived slope and persistence of the Phillips curve and the policy-rate instrument, and the misspecification is not about expectations but about the *policy endogeneity* of the reduced-form Phillips curve that the Fed treats as structural — ignoring the Lucas Critique in just the way the "vindication" story of {doc}`phillips_two_stories` describes. + +```{note} +As {cite}`SargentWilliams2025` note, the drifting-coefficients model is a purely descriptive "Kepler stage" model, not a structural "Newton stage" one. The paper also acknowledges an alternative reading in which the 2020s accommodation was fiscal in origin — see the fiscal-theory accounts it cites — a very different rationalization of the same policy path. +``` + +## Exercises + +```{exercise-start} +:label: lc_ex1 +``` + +The interest-smoothing weight $\eta$ in the loss {eq}`lc_loss` is, in the paper's words, "one of the most important parameters." + +Recompute the baseline Phelps recommendations for $\eta \in \{0.1, 0.5, 2.0\}$ and plot them against the actual funds rate over 2015-2025. + +How does a larger smoothing penalty change the character of the recommended policy? + +```{exercise-end} +``` + +```{solution-start} lc_ex1 +:class: dropdown +``` + +```{code-cell} ipython3 +def recommend(θ_path_fn, η_val): + global η + η_save = η + η = η_val + θ, R = np.array([0.5, 0.9, 0.05]), np.diag([1.0, 10.0, 5.0]) + out = [] + for t in range(1, n): + g_t = 0.0 if data.index[t].year in (2020, 2021) else gain + X = np.array([1.0, pi[t - 1], x[t]]) + R = R + g_t * (np.outer(X, X) - R) + θ = θ + g_t * np.linalg.solve(R, X * (pi[t] - X @ θ)) + out.append(phelps_rate(θ, np.array([1.0, pi[t], x[t], i_[t - 1]]))) + η = η_save + return pd.Series(out, index=data.index[1:]) + +fig, ax = plt.subplots(figsize=(10, 4.5)) +w = slice('2015', None) +for η_val in [0.1, 0.5, 2.0]: + ax.plot(recommend(None, η_val)[w], lw=1, label=rf'$\eta = {η_val}$') +ax.plot(data['i'][w], 'k:', lw=1.5, label='actual') +ax.set_xlabel('year') +ax.set_ylabel('percent') +ax.legend() +plt.show() +``` + +A small $\eta$ produces a volatile rate that jumps sharply with inflation and the gap; a large $\eta$ makes the recommendation hug the lagged rate, fitting the smooth observed path better but at the cost of interpretability. + +The intermediate value strikes a balance — enough smoothing to resemble real Fed behavior, but still a recognizable response to the state of the economy. + +```{solution-end} +``` + +```{exercise-start} +:label: lc_ex2 +``` + +{prf:ref}`lc_prop1` says an aggressive Taylor rule lowers the *measured* persistence of inflation. + +Trace out the implication for the Fed's own policy by combining the two halves of this lecture: for a grid of structural aggressiveness $\phi_\pi$, compute the measured persistence $\lambda(\phi_\pi)$, then feed a belief with that persistence (holding the slope and intercept fixed) into `phelps_rate` at a representative state, and report the implied inflation response. + +Does a Fed that has historically been *more* aggressive end up *less* willing to respond to a fresh inflation shock? + +```{exercise-end} +``` + +```{solution-start} lc_ex2 +:class: dropdown +``` + +```{code-cell} ipython3 +state = np.array([1.0, 4.0, 1.0, 2.0]) # π=4, x=1, i_{-1}=2: an inflation shock + +print(f"{'φ_π (history)':>14} {'measured ρ':>12} {'recommended i':>15}") +for φ in [1.2, 1.6, 2.0, 2.6]: + ρ_meas = measured_persistence(φ) + i_rec = phelps_rate(np.array([0.3, ρ_meas, 0.05]), state) + print(f"{φ:>14} {ρ_meas:>12.2f} {i_rec:>15.2f}") +``` + +A history of more aggressive policy (higher $\phi_\pi$) leaves the Fed believing inflation is less persistent (lower measured $\rho$), and — by the logic of {prf:ref}`lc_prop1` together with the comparative statics of the Phelps problem — a Fed that thinks inflation will fade on its own responds *less* to a fresh shock. + +Success breeds complacency: the very aggressiveness that conquered inflation teaches the Fed a lesson that, taken as structural, disarms it against the next surge. + +```{solution-end} +``` diff --git a/lectures/phillips_misspecified.md b/lectures/phillips_misspecified.md new file mode 100644 index 000000000..58b079e3e --- /dev/null +++ b/lectures/phillips_misspecified.md @@ -0,0 +1,407 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.16.7 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(phillips_misspecified)= +```{raw} jupyter + +``` + +# Optimal Misspecified Beliefs + +```{contents} Contents +:depth: 2 +``` + +## Overview + +This lecture continues the study of Phillips curve tradeoffs. + +It follows chapter 6 of {cite}`Sargent1999`. + +We describe three conceptual issues that recur throughout this suite of lectures: + +1. how to formulate equilibria in which agents share a common *misspecified* least squares forecasting model, +2. how expectations can contribute independent dynamics within equilibria, and +3. how the classic adaptive expectations scheme can use second moments to approximate a first moment. + +To expose these issues we temporarily set aside the Phillips curve and work with {cite}`Bray1982`'s simple model of the price of a single good, a workhorse for studying bounded rationality. + +We alter Bray's model to illustrate an equilibrium concept that merges aspects of rational and adaptive expectations in a new way, and that we apply to the Phillips curve in {doc}`phillips_self_confirming`. + +The focus is on **market equilibrium with optimal but misspecified forecasts**. + +* *Optimal* means the free parameters of the forecasting scheme are chosen by (nonlinear) least squares. +* *Misspecified* means the forecasting model is wrong in functional form. + +A distinctive feature is that the true model *depends on* how the agents' model is misspecified: agents' beliefs affect their behavior, which shapes the data they then fit. + +We work in the frequency domain, so let's import what we need: + +```{code-cell} ipython3 +import matplotlib.pyplot as plt +import numpy as np +from scipy.optimize import minimize_scalar +``` + +## An experiment in Bray's lab + +Following {cite}`Bray1982`, assume that + +```{math} +:label: pm_bray + +p_t = a + b \, p_{t+1}^e + u_t , +``` + +where $u_t$ is i.i.d. with mean zero and variance $\sigma_u^2$, $a > 0$, $b \in (0, 1)$, $p_t$ is the market price, and $p_{t+1}^e$ is the market's expectation of next period's price. + +The rational expectations equilibrium has $p_{t+1}^e = \frac{a}{1-b}$ and $p_t = \frac{a}{1-b} + u_t$. + +Bray posited that $p_{t+1}^e$ is the empirical average of past prices, and showed that when $0 < b < 1$ this decreasing-gain scheme converges almost surely to the rational expectation $\frac{a}{1-b}$. + +During the transition, the state variable $p_t^e$ contributes dynamics and makes the price serially correlated, but these dynamics are transitory: at the rational expectations equilibrium, $p_t$ is a constant plus a serially uncorrelated shock. + +### Constant-gain adaptive expectations + +To let expectations impart *persistent* serial correlation, we depart from Bray and assume that the market has **constant-gain** adaptive expectations, + +```{math} +:label: pm_bray2 + +p_{t+1}^e = C p_t + (1 - C) p_t^e, \qquad |C| < 1 . +``` + +Bray's scheme replaces $C$ by $\frac{1}{t}$, so that $p_{t+1}^e$ becomes a sample average. + +Fixing $C$ instead *discounts* past observations, which arrests convergence to rational expectations and prevents $p_{t+1}^e$ from converging to a constant. + +Written as a distributed lag, + +```{math} +:label: pm_bray3 + +p_{t+1}^e = \frac{C}{1 - (1 - C) L} p_t , +``` + +where $L$ is the lag operator. + +Equation {eq}`pm_bray2` would be the linear least squares forecast if the price followed the integrated moving-average process + +```{math} +:label: pm_brayper + +p_t = p_{t-1} + \epsilon_t - (1 - C)\epsilon_{t-1} , +``` + +so the market perceives the price as composed of purely permanent and transitory components. + +### The actual law of motion + +Substituting the belief {eq}`pm_bray3` into {eq}`pm_bray` shows that when the market forecasts this way, its actions make the *actual* law of motion for the price + +```{math} +:label: pm_bray4 + +p_t = \frac{a}{1 - b} + \frac{1}{1 - bC} + \left[ \frac{1 - (1 - C)L}{1 - \frac{1 - C}{1 - bC} L} \right] u_t + = \nu + f(L) u_t , +``` + +where $\nu = \frac{a}{1-b}$ and $f(L)$ is defined to match. + +The price has mean $\nu$ and spectral density + +$$ +F(\omega) = f(e^{i\omega}) f(e^{-i\omega}) \, \sigma_u^2, \qquad \omega \in [-\pi, \pi] . +$$ + +Notice that $F$ depends on $C$ through $f$. + +Let's encode the true process. + +```{code-cell} ipython3 +class BrayModel: + """ + Bray's price model with constant-gain adaptive expectations. + + The perceived law of motion is an IMA(1,1) with unit root; to keep its + spectral density well defined we approximate the unit root by a root ρ + slightly below one, following Sargent (1999). + """ + + def __init__(self, a=1.0, b=0.5, σ_u=1.0, ρ=0.995, N=1024): + self.a, self.b, self.σ_u, self.ρ, self.N = a, b, σ_u, ρ, N + self.ν = a / (1 - b) + ω = 2 * np.pi * np.arange(N) / N + self.ω = ω + self.z = np.exp(1j * ω) + + def true_spectrum(self, C): + "Spectral density F(ω) of the actual price process, given belief C." + b, z = self.b, self.z + φ = (1 - C) / (1 - b * C) + scale = 1 / (1 - b * C) + f = scale * (1 - (1 - C) * z) / (1 - φ * z) + return np.abs(f)**2 * self.σ_u**2 + + def approx_spectrum(self, c, σ_ε2=1.0): + "Spectral density G(ω) of the agent's approximating IMA model." + g = (1 - (1 - c) * self.z) / (1 - self.ρ * self.z) + return np.abs(g)**2 * σ_ε2 +``` + +## Optimal misspecification + +Two facts about the actual law {eq}`pm_bray4` motivate an equilibrium restriction on $C$: + +1. Given that the price obeys {eq}`pm_bray4`, the true linear least squares one-step forecasting rule is *not* a geometric distributed lag like {eq}`pm_bray2`. +2. Even restricting the forecast to the form {eq}`pm_bray2`, the *best* such rule would make $C$ solve a forecast-error-minimization problem, so $C$ is an outcome, not a free parameter. + +A rational expectations equilibrium would repair both features. + +Following {cite}`Bray1982` we soften the equilibrium concept: we leave feature 1 untouched (agents keep the wrong functional form) while fixing feature 2 (they choose the best parameter within that form). + +Think of putting a single individual into a market where everyone else (the "representative agent") uses $C$, so the price obeys {eq}`pm_bray4`. + +The individual chooses $c$ to fit the best model of the form + +```{math} +:label: pm_bray6 + +p_t = \frac{1 - (1 - c) L}{1 - L}\epsilon_t = g(L)\epsilon_t , +``` + +by minimizing the one-step-ahead forecast error variance. + +Because $g(L)$ has a unit root, its DC gain is infinite; this is exactly how the perceived model uses a unit root to *fit the constant mean* $\nu$. + +Numerically we replace the unit root by a root $\rho$ slightly below one. + +```{prf:definition} Best-estimate map +:label: pm_bmap + +Given $C$ and the consequent price process {eq}`pm_bray4`, the individual's best forecast parameter $c = B(C)$ is the nonlinear least squares estimator of $c$ in {eq}`pm_bray6`, where the data are generated by {eq}`pm_bray4`. +``` + +Following the frequency-domain method of Hansen and Sargent {cite}`HansenSargent1993`, the best approximating $(c, \sigma_\epsilon^2)$ minimizes + +```{math} +:label: pm_criterion + +A(c, \sigma_\epsilon^2) = \frac{1}{N}\sum_{j=0}^{N-1} +\left\{ \log G(\omega_j, c) + \frac{F(\omega_j)}{G(\omega_j, c)} \right\} ++ \frac{\nu^2}{G(0)} , +``` + +where $\omega_j = \frac{2\pi j}{N}$, and the term $\frac{\nu^2}{G(0)}$ makes the approximating model use its near-unit-root to fit the mean. + +Concentrating out $\sigma_\epsilon^2$ leaves a one-dimensional minimization over $c$. + +```{prf:definition} Equilibrium under forecast misspecification +:label: pm_equilibrium + +An equilibrium under forecast misspecification is a fixed point $C = B(C)$. +``` + +At such a fixed point the representative agent is representative: the single individual's best parameter equals the one everyone uses. + +```{code-cell} ipython3 +def best_estimate(model, C): + "The best-estimate map c = B(C)." + F = model.true_spectrum(C) + z, ν, N = model.z, model.ν, model.N + + def neg_profile(c): + H = np.abs((1 - (1 - c) * z) / (1 - model.ρ * z))**2 # |g|^2 + σ_ε2 = np.mean(F / H) + ν**2 / H[0] # concentrated + return np.log(σ_ε2) + np.mean(np.log(H)) # profiled criterion + + res = minimize_scalar(neg_profile, bounds=(1e-4, 0.99), method='bounded') + return res.x + +def solve_equilibrium(model, C0=0.3, tol=1e-10, maxit=500): + "Iterate the best-estimate map to a fixed point." + C = C0 + for _ in range(maxit): + C_new = best_estimate(model, C) + if abs(C_new - C) < tol: + break + C = C_new + return C_new +``` + +```{code-cell} ipython3 +bray = BrayModel(a=1.0, b=0.5, σ_u=1.0) +C_star = solve_equilibrium(bray) +print(f"equilibrium belief C = {C_star:.4f}") +``` + +For these parameters the equilibrium belief is $C \approx 0.08$, reproducing the value reported in chapter 6 of {cite}`Sargent1999`. + +Let's also report the *actual* one-step-ahead forecast error standard deviation that agents incur by using their misspecified model. + +```{code-cell} ipython3 +def fitted_sigma2(model, C, c): + "Concentrated innovation variance σ_ε^2 of the approximating model." + F = model.true_spectrum(C) + H = np.abs((1 - (1 - c) * model.z) / (1 - model.ρ * model.z))**2 + return np.mean(F / H) + model.ν**2 / H[0] + +c_star = best_estimate(bray, C_star) +σ_bar = np.sqrt(fitted_sigma2(bray, C_star, c_star)) +print(f"actual one-step forecast error std σ̄_ε = {σ_bar:.4f}") +``` + +## Comparing the true and forecasting models + +For the equilibrium $C$, we plot the equilibrium spectral densities of the true and approximating models. + +```{code-cell} ipython3 +F = bray.true_spectrum(C_star) +σ_ε2 = fitted_sigma2(bray, C_star, c_star) +G = bray.approx_spectrum(c_star, σ_ε2) + +half = bray.N // 2 +fig, ax = plt.subplots(figsize=(8, 5)) +ax.plot(bray.ω[:half], np.log(F[:half]), 'C0', label='true model') +ax.plot(bray.ω[:half], np.log(G[:half]), 'C1--', label='forecasting model') +ax.set_xlabel(r'angular frequency $\omega$') +ax.set_ylabel('log spectral density') +ax.legend() +plt.show() +``` + +In minimizing {eq}`pm_criterion`, the approximating model uses its near-unit-root to fit the mean. + +The large gap between the spectral densities at low frequencies reflects how the approximating model fits a *first* moment (the mean $\nu$) with features of *second* moments (a spike in the spectral density at frequency zero). + +The true spectral density decreases sharply with frequency — Granger's "typical spectral shape" {cite}`Granger1966` — revealing substantial positive serial correlation in the price, because agents' belief that the price is subject to permanent shocks makes shocks persist. + +### Impulse responses + +We compare the impulse response functions of the two models by feeding a unit shock through each moving-average representation. + +```{code-cell} ipython3 +def impulse_response(num_roots, den_roots, T=25): + "IRF of (1 - num L)/(1 - den L): coefficients of the ratio of lag polys." + h = np.empty(T) + h[0] = 1.0 + for k in range(1, T): + h[k] = den_roots * h[k - 1] + h[1:] -= num_roots * h[:-1] # apply the numerator (1 - num L) + return h + +φ = (1 - C_star) / (1 - bray.b * C_star) +scale = 1 / (1 - bray.b * C_star) +irf_true = scale * impulse_response(1 - C_star, φ) # f(L) +irf_approx = impulse_response(1 - c_star, bray.ρ) # g(L) + +fig, ax = plt.subplots(figsize=(8, 5)) +ax.plot(irf_true, 'C0o-', ms=4, label='true model') +ax.plot(irf_approx, 'C1s--', ms=4, label='approximating model') +ax.set_xlabel('lag') +ax.set_ylabel('response') +ax.legend() +plt.show() +``` + +The impulse response of the true model affirms the serial correlation in the price. + +The approximating model tends to under-predict the short-term consequences of a shock while over-predicting the long-term ones: its near-unit-root produces a response that does not die out. + +## Lessons + +The agents in this model are **boundedly rational**: *rational* describes their use of least squares, and *bounded* describes their model misspecification. + +Under rational expectations there is only one model in play. + +Under bounded rationality there must be at least two: the one used by the boundedly rational agents, and the true one. + +These mutually influence each other — the boundedly rational agents use their model to approximate the true one, and the true one reflects the decisions of the agents — and both differ from the rational expectations model. + +The peculiar way that the adaptive expectations model uses a unit root to mimic a constant foreshadows a version of the Phillips curve model, developed in {doc}`phillips_self_confirming`, that will help vindicate econometric policy evaluation. + +This same trick — using a unit root to approximate a constant — turns out to be the engine of the *escape dynamics* of {doc}`phillips_learning` and {doc}`phillips_escaping_nash`, where a learning government's estimated Phillips curve drifts toward the induction hypothesis and, believing it, cuts inflation toward Ramsey. + +## Exercises + +```{exercise-start} +:label: pmis_ex1 +``` + +The equilibrium belief $C$ depends on the feedback parameter $b$ in {eq}`pm_bray`. + +Compute and plot the equilibrium $C$ as a function of $b$ over a grid $b \in \{0.1, 0.2, \ldots, 0.8\}$, holding $a = 1$ and $\sigma_u = 1$ fixed. + +How does stronger expectational feedback (larger $b$) affect the equilibrium amount of discounting of past data? + +```{exercise-end} +``` + +```{solution-start} pmis_ex1 +:class: dropdown +``` + +```{code-cell} ipython3 +b_grid = np.arange(0.1, 0.85, 0.1) +C_of_b = [solve_equilibrium(BrayModel(a=1.0, b=b, σ_u=1.0)) for b in b_grid] + +fig, ax = plt.subplots(figsize=(8, 4.5)) +ax.plot(b_grid, C_of_b, 'o-') +ax.set_xlabel('feedback parameter $b$') +ax.set_ylabel('equilibrium belief $C$') +plt.show() +``` + +Stronger feedback raises the equilibrium gain $C$: agents put more weight on recent observations, so the price process they generate is less persistent than it would otherwise be. + +```{solution-end} +``` + +```{exercise-start} +:label: pmis_ex2 +``` + +Verify that the equilibrium is a genuine fixed point by plotting the best-estimate map $c = B(C)$ against the 45-degree line, and mark the fixed point. + +```{exercise-end} +``` + +```{solution-start} pmis_ex2 +:class: dropdown +``` + +```{code-cell} ipython3 +C_grid = np.linspace(0.02, 0.4, 25) +B_vals = [best_estimate(bray, C) for C in C_grid] + +fig, ax = plt.subplots(figsize=(6, 6)) +ax.plot(C_grid, B_vals, 'C0', label='$B(C)$') +ax.plot(C_grid, C_grid, 'k--', lw=1, label='45 degrees') +ax.plot(C_star, C_star, 'ko') +ax.annotate('equilibrium', (C_star, C_star), + (C_star + 0.05, C_star - 0.03)) +ax.set_xlabel('$C$') +ax.set_ylabel('$B(C)$') +ax.legend() +plt.show() +``` + +The best-estimate map crosses the 45-degree line at the equilibrium belief, confirming $C = B(C)$. + +```{solution-end} +``` diff --git a/lectures/phillips_priors.md b/lectures/phillips_priors.md new file mode 100644 index 000000000..13d47cb7f --- /dev/null +++ b/lectures/phillips_priors.md @@ -0,0 +1,603 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.16.7 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(phillips_priors)= +```{raw} jupyter + +``` + +# Priors, Escapes, and Learning Cycles + +```{contents} Contents +:depth: 2 +``` + +## Overview + +> The Bourbons remember everything and learn nothing. +> +> -- Charles Maurice de Talleyrand + +This lecture is a sequel to {doc}`phillips_learning`. + +It follows {cite}`SargentWilliams2005`, which generalizes the adaptive model of chapter 8 of {cite}`Sargent1999` and of {cite}`ChoWilliamsSargent2002` (CWS), whose escape dynamics we derived in {doc}`phillips_escaping_nash`. + +In {doc}`phillips_learning` the government estimated its Phillips curve by recursive least squares. + +That is a very particular way to learn. + +A least squares learner behaves as if it believes that the coefficients of its model follow a specific random walk — one with a specific innovation covariance matrix. + +Here we free the government to hold *any* prior about how its coefficients drift, encoded in a covariance matrix $V$, and we ask how the shape of that prior affects the two forces that drive the model: + +* the **mean dynamics**, which pull the government's beliefs *toward* a self-confirming (Nash) equilibrium; and +* the **escape dynamics**, which occasionally push beliefs *away* from it, toward the low-inflation Ramsey outcome. + +We will find three things. + +1. Some priors make the self-confirming equilibrium *unstable*, so that the mean dynamics themselves produce recurrent disinflations — a **learning cycle** born of a Hopf bifurcation, rather than a rare stochastic escape. +2. The prior shapes the *direction* and *speed* of escapes; but in every case the escape heads toward the Ramsey outcome. +3. The prior explains a long-standing puzzle: why the simulations of Sims and Chung escape Nash inflation and stay near Ramsey *forever*, while those of {cite}`Sargent1999` and CWS escape only to be pulled back. + +Throughout we work with the analytically tractable **static** model of {doc}`phillips_escaping_nash`, in which the government runs a simple regression of unemployment on inflation and a constant. + +Let's start with our imports: + +```{code-cell} ipython3 +import matplotlib.pyplot as plt +import numpy as np +from scipy.linalg import sqrtm +from scipy.integrate import solve_ivp +``` + +## The static model + +The true economy is a version of the natural-rate model of {cite}`KydlandPrescott1977`: + +```{math} +:label: pp_truth + +\begin{aligned} +U_n &= u - (\pi_n - \hat x_n) + \sigma_1 W_{1n}, \qquad u > 0, \\ +\pi_n &= x_n + \sigma_2 W_{2n}, \\ +\hat x_n &= x_n, +\end{aligned} +``` + +where $U_n$ is unemployment, $\pi_n$ is inflation, $x_n$ is the systematic part of inflation set by the government, $\hat x_n$ is the public's (rational) forecast, and $W_n = (W_{1n}, W_{2n})'$ is i.i.d. standard Gaussian noise. + +Since $\pi_n - \hat x_n = \sigma_2 W_{2n}$, the true unemployment rate is $U_n = u - \sigma_2 W_{2n} + \sigma_1 W_{1n}$ — it fluctuates around the natural rate $u$ regardless of systematic policy. + +The government does not know this. + +In the **static model** it fits a non-expectational Phillips curve by regressing unemployment on current inflation and a constant, + +```{math} +:label: pp_belief + +U_n = a + b\, \pi_n + \eta_n , +``` + +with belief vector $\gamma = (a, b)$ (intercept and slope), and it treats $\eta_n$ as an exogenous shock. + +Believing {eq}`pp_belief`, the government solves the Phelps problem — minimize $\hat E \sum_n \delta^n (U_n^2 + \pi_n^2)$ — whose static best response sets inflation to the constant + +```{math} +:label: pp_bestresp + +x(\gamma) = -\frac{a\, b}{1 + b^2} . +``` + +```{code-cell} ipython3 +class StaticPhillips: + "The static Sargent-Williams model: government regresses U on (1, π)." + + def __init__(self, u=5.0, σ1=0.3, σ2=0.3): + self.u, self.σ1, self.σ2 = u, σ1, σ2 + self.σ = σ1 # govt regression error std = σ1 + + def x(self, γ): + "Government best response (systematic inflation) given beliefs γ." + a, b = γ + return -a * b / (1 + b**2) + + def M(self, γ): + "Second moment matrix E[ΦΦ'] of regressors Φ = (1, π), given γ." + x = self.x(γ) + return np.array([[1.0, x], [x, x**2 + self.σ2**2]]) + + def g_bar(self, γ): + "Least squares moment E[Φ(U - Φ'γ)] under the data γ generates." + u, σ2 = self.u, self.σ2 + x = self.x(γ) + E_ΦU = np.array([u, x * u - σ2**2]) + return E_ΦU - self.M(γ) @ γ +``` + +Three belief vectors are worth naming. + +* **Belief 1 (Nash):** $b = -1$ with an intercept that makes the government set $x = u$. This is the time-consistent outcome of {cite}`KydlandPrescott1977`. +* **Belief 2 (Ramsey):** $b = 0$, so the government perceives *no* tradeoff and sets $x = 0$. +* **Belief 3 (induction):** in a dynamic version, coefficients on current and lagged inflation summing to zero, which for a patient government also sends inflation toward $0$. + +## The self-confirming equilibrium + +A self-confirming equilibrium is a belief $\bar\gamma$ that reproduces itself: the data generated when the government acts on $\bar\gamma$ make the population regression coefficients equal $\bar\gamma$, i.e. $\bar g(\bar\gamma) = 0$. + +For the static model this is easy to solve by hand. + +The slope is $b = \operatorname{cov}(U, \pi)/\operatorname{var}(\pi) = -\sigma_2^2/\sigma_2^2 = -1$, and matching means gives the intercept $a = u + x(\bar\gamma)$. + +Substituting the best response {eq}`pp_bestresp` with $b = -1$ gives $x = a/2$, so $a = u + a/2$, i.e. $a = 2u$. + +```{code-cell} ipython3 +model = StaticPhillips(u=5.0, σ1=0.3, σ2=0.3) +γ_sce = np.array([2 * model.u, -1.0]) + +print(f"self-confirming beliefs γ = {γ_sce} (intercept 2u, slope -1)") +print(f"self-confirming inflation x = {model.x(γ_sce):.2f} (= Nash = u)") +print(f"check g_bar(γ_sce) = {model.g_bar(γ_sce)}") +``` + +The self-confirming equilibrium inflation rate equals the Nash (time-consistent) outcome $u = 5$, even though the government's model is misspecified. + +The government's non-expectational Phillips curve is observationally equivalent to the truth *along* the equilibrium path, but wrong *off* it — which is exactly what its adaptive behavior repeatedly probes. + +## Drifting beliefs and the Kalman filter + +We now make the government adaptive. + +Following {cite}`SargentWilliams2005`, the government believes that the coefficients of its Phillips curve *drift* as a random walk, + +```{math} +:label: pp_drift + +\alpha_n = \alpha_{n-1} + \Lambda_n, +\qquad +\operatorname{cov}(\Lambda_n) = V , +``` + +and it forms its estimate $\gamma_n = \hat\alpha_{n \mid n-1}$ by the Kalman filter. + +The covariance matrix $V$ is the government's **prior about parameter drift** — the object we set free. + +With regressors $\Phi_n = (1, \pi_n)'$, a large-sample approximation to the Kalman filter (see {cite}`BenvenisteMetivierPriouret1990`) is + +```{math} +:label: pp_kalman + +\begin{aligned} +\gamma_{n+1} &= \gamma_n + P_n \Phi_n\left(U_n - \Phi_n' \gamma_n\right), \\ +P_{n+1} &= P_n - P_n M(\gamma_n) P_n + \sigma^{-2} V , +\end{aligned} +``` + +where $\sigma^2$ is the variance the government attributes to its regression error $\eta_n$. + +For a fixed $\gamma$, the matrix $P_n$ settles at the solution of the algebraic Riccati equation + +```{math} +:label: pp_riccati + +- P M(\gamma) P + \sigma^{-2} V = 0 . +``` + +The connection to {doc}`phillips_learning` is exact. + +Constant-gain recursive least squares is the special case in which the government's prior is $V = V^* \equiv \epsilon^2 \sigma^2 M(\bar\gamma)^{-1}$ and $\sigma = \sigma_1$; then {eq}`pp_riccati` gives $P = \epsilon M(\bar\gamma)^{-1}$, and {eq}`pp_kalman` reduces to the recursive least squares algorithm of the previous lecture, with gain $\epsilon$. + +```{code-cell} ipython3 +def solve_riccati(V, M, σ): + "Symmetric positive-definite P solving P M P = σ^{-2} V." + W = V / σ**2 + Mh = sqrtm(M).real + Mh_inv = np.linalg.inv(Mh) + return Mh_inv @ sqrtm(Mh @ W @ Mh).real @ Mh_inv + +M_sce = model.M(γ_sce) +V_star = model.σ**2 * np.linalg.inv(M_sce) # the RLS prior (ε = 1) +P_star = solve_riccati(V_star, M_sce, model.σ) + +print("RLS prior gives P = M^{-1}?", np.allclose(P_star, np.linalg.inv(M_sce))) +``` + +## Mean dynamics and E-stability + +As in {doc}`phillips_learning`, the beliefs are organized by mean dynamics — now a *joint* ordinary differential equation in $(\gamma, P)$, + +```{math} +:label: pp_ode + +\dot\gamma = P\, \bar g(\gamma), +\qquad +\dot P = \sigma^{-2} V - P M(\gamma) P . +``` + +A rest point of {eq}`pp_ode` has $\bar g(\gamma) = 0$ and $P = $ the Riccati solution — a self-confirming equilibrium. + +Local stability turns on the Jacobian of $\bar g$ at the self-confirming equilibrium. + +For the static model this can be computed in closed form, + +```{math} +:label: pp_jacobian + +\frac{\partial \bar g}{\partial \gamma}(\bar\gamma) += - \begin{bmatrix} \tfrac12 & u \\[1mm] \tfrac12 u & u^2 + \sigma_2^2 \end{bmatrix} . +``` + +```{code-cell} ipython3 +def jacobian(model, γ, h=1e-6): + "Numerical Jacobian of g_bar at γ." + J = np.zeros((2, 2)) + for j in range(2): + gp, gm = γ.copy(), γ.copy() + gp[j] += h; gm[j] -= h + J[:, j] = (model.g_bar(gp) - model.g_bar(gm)) / (2 * h) + return J + +J = jacobian(model, γ_sce) +print("∂g/∂γ at SCE =\n", J.round(3)) +``` + +Under recursive least squares the $P$ block of {eq}`pp_ode` decouples, and stability is governed by the eigenvalues of $M^{-1}\,\partial\bar g/\partial\gamma$ — the classic **E-stability** condition of {cite}`EvansHonkapohja2001`. + +```{code-cell} ipython3 +eig_rls = np.linalg.eigvals(np.linalg.inv(M_sce) @ J) +print(f"E-stability eigenvalues (RLS): {eig_rls.round(3)}") +print("both negative ⇒ the SCE is E-stable, and least squares converges to Nash") +``` + +Both eigenvalues are negative — one of them exactly $-\tfrac12$, the marginal value that appeared in {doc}`phillips_learning`. + +Under least squares, then, beliefs converge to the Nash self-confirming equilibrium. + +But this reduction relied on the special RLS prior. + +Under a *general* prior $V$, the $P$ block does **not** decouple, and stability is instead governed by the eigenvalues of $\bar P\, \partial\bar g/\partial\gamma$ — where $\bar P$ is the Riccati solution for that prior. + +Prior beliefs about parameter drift now matter for whether the self-confirming equilibrium is even stable. + +## Learning cycles + +Here is the paper's most striking result: some priors make the self-confirming equilibrium *unstable*, and a **stable limit cycle** is born through a Hopf bifurcation. + +We illustrate it by *tightening the government's prior on the slope coefficient*, starting from the RLS prior $V^*$ and shrinking the slope-related entries by a factor $\lambda \in [0, 1]$: + +```{math} +:label: pp_Vlambda + +V(\lambda) = \begin{bmatrix} V^*_{11} & \sqrt\lambda\, V^*_{12} \\ \sqrt\lambda\, V^*_{12} & \lambda\, V^*_{22} \end{bmatrix} . +``` + +For each $\lambda$ we solve the Riccati equation and look at the largest real part among the eigenvalues of $\bar P(\lambda)\, \partial\bar g/\partial\gamma$: where it is positive, the self-confirming equilibrium is unstable. + +```{code-cell} ipython3 +def V_tighten_slope(λ, V_star): + V = V_star.copy() + V[0, 1] = V[1, 0] = np.sqrt(λ) * V_star[0, 1] + V[1, 1] = λ * V_star[1, 1] + return V + +ε = 0.05 # gain (sets the timescale) +λ_grid = np.linspace(0.01, 0.999, 200) +max_re = [] +for λ in λ_grid: + V = ε**2 * V_tighten_slope(λ, V_star) + P = solve_riccati(V, M_sce, model.σ) + max_re.append(np.linalg.eigvals(P @ J).real.max()) + +fig, ax = plt.subplots(figsize=(8, 4.5)) +ax.plot(λ_grid, max_re) +ax.axhline(0, color='k', lw=0.8) +ax.set_xlabel(r'prior-tightening parameter $\lambda$') +ax.set_ylabel('max real part of eigenvalue') +ax.set_title(r'Figure 4: stability of the SCE as the slope prior tightens') +plt.show() +``` + +Over an intermediate range of $\lambda$ the maximum real part becomes *positive*: the self-confirming equilibrium loses stability. + +By the Hopf bifurcation theorem (see {cite}`Perko1996`), a unique stable limit cycle bifurcates as the real parts cross zero — what {cite}`Bullard1994` calls a *learning equilibrium*. + +Let's integrate the mean dynamics for the regression coefficients at $\lambda = 0.7$ (well inside the unstable range), holding $P$ at its Riccati value, and trace the cycle. + +```{code-cell} ipython3 +λ = 0.7 +V = ε**2 * V_tighten_slope(λ, V_star) +P_bar = solve_riccati(V, M_sce, model.σ) + +def coeff_ode(t, γ): + return P_bar @ model.g_bar(γ) + +sol = solve_ivp(coeff_ode, [0, 2500], γ_sce + np.array([0.3, 0.05]), + max_step=1.0, rtol=1e-9, atol=1e-11, dense_output=True) +a_path, b_path = sol.y +x_path = -a_path * b_path / (1 + b_path**2) + +# isolate one mature cycle for the phase plot +mask = sol.t > sol.t[-1] - 800 +``` + +```{code-cell} ipython3 +fig, axes = plt.subplots(1, 2, figsize=(12, 5)) + +axes[0].plot(sol.t[mask], a_path[mask], label='intercept') +axes[0].plot(sol.t[mask], b_path[mask], label='slope') +axes[0].set_xlabel('time') +axes[0].set_ylabel('coefficient') +axes[0].set_title('Figure 5a: coefficients cycle') +axes[0].legend() + +axes[1].plot(a_path[mask], b_path[mask]) +axes[1].plot(*γ_sce, 'kx', ms=10, label='SCE') +axes[1].set_xlabel('intercept') +axes[1].set_ylabel('slope') +axes[1].set_title('Figure 5b: the limit cycle') +axes[1].legend() + +plt.tight_layout() +plt.show() +``` + +The beliefs settle into a closed orbit around the self-confirming equilibrium. + +Because inflation is a function of the coefficients through the best response {eq}`pp_bestresp`, the cycle in beliefs shows up as a cycle in inflation that oscillates between the Nash and Ramsey outcomes. + +```{code-cell} ipython3 +fig, ax = plt.subplots(figsize=(9, 4.5)) +ax.plot(sol.t[mask], x_path[mask]) +ax.axhline(model.u, color='k', ls='--', lw=1, label='Nash') +ax.axhline(0, color='C2', ls=':', lw=1, label='Ramsey') +ax.set_xlabel('time') +ax.set_ylabel('inflation $x$') +ax.set_title('Figure 6: inflation oscillates between Nash and Ramsey along the cycle') +ax.legend() +plt.show() +``` + +This is qualitatively different from the escapes of {doc}`phillips_learning`. + +There, disinflations were *rare* events, driven by unlikely sequences of shocks; between them the system sat at Nash. + +Here the disinflations are a *typical* feature of the time series, produced by the mean dynamics themselves — a deterministic cycle that persists even as the gain shrinks to zero. + +## Escape dynamics and the direction of escape + +For priors that keep the self-confirming equilibrium stable, disinflations return to being rare escapes, exactly as in {doc}`phillips_learning`. + +The theory of large deviations (developed in the primer of {doc}`phillips_learning`, and applied here via {cite}`Williams2019`) characterizes the most likely escape as the solution of a control problem: apply the least-cost perturbation to beliefs that pushes them a fixed distance from $\bar\gamma$. + +The *instantaneous* escape direction has a beautifully simple characterization: it is the eigenvector associated with the largest eigenvalue of the belief-innovation covariance $Q(\bar\gamma, \bar P) = \hat V$ — the prior itself. + +```{code-cell} ipython3 +w, vecs = np.linalg.eigh(V_star) # baseline prior V* +v_escape = vecs[:, np.argmax(w)] +v_escape = v_escape / np.linalg.norm(v_escape) + +# scale the escape so the slope moves from -1 up to 0 +t_star = 1.0 / v_escape[1] +terminal = γ_sce + t_star * v_escape + +print(f"escape direction ≈ {v_escape.round(3)} (∝ [-u, 1])") +print(f"terminal beliefs ≈ {terminal.round(2)} (Belief 2 = [u, 0] = Ramsey)") +``` + +For the baseline prior the escape direction is proportional to $(-u, 1)$, and it carries beliefs from Nash $(2u, -1)$ toward $(u, 0)$ — Belief 2, which supports the Ramsey outcome. + +So an escape, when it happens, is a movement toward zero inflation. + +Different priors bend the *path* and change the *rate* of escape — tightening the slope prior can even destabilize the equilibrium entirely (the cycles above), while tightening the intercept prior speeds escapes up — but the destination is always Ramsey. + +## Sims's nonconvergence + +We can now resolve a puzzle noted in {doc}`phillips_two_stories`. + +The simulations of {cite}`Sims1988` and {cite}`Chung1990` start at the Nash self-confirming outcome, escape to low inflation, and then *stay there*, apparently indefinitely. + +Those of {cite}`Sargent1999` and CWS instead escape and are then pulled back, again and again. + +{cite}`SargentWilliams2005` trace the difference to a single modeling choice: whether the government attributes the *right* amount of variance to its regression error. + +When $\sigma = \sigma_1$ — as in a self-confirming equilibrium, where the regression {eq}`pp_belief` and the truth {eq}`pp_truth` coincide — the government correctly decomposes the variation it sees. + +Sims instead used $\sigma \neq \sigma_1$ (and did not shrink the gain), which *misallocates* the observed variation and produces prolonged, perhaps permanent, departures from the self-confirming equilibrium. + +Let's simulate the static model under both specifications. + +```{code-cell} ipython3 +def simulate(model, σ_govt, ε, λ=1.0, T=3000, seed=0): + "Static Kalman-filter learning; σ_govt is the government's assumed error std." + rng = np.random.default_rng(seed) + u, σ1, σ2 = model.u, model.σ1, model.σ2 + + V = ε**2 * V_tighten_slope(λ, V_star) + γ = γ_sce.copy() + P = ε * np.linalg.inv(M_sce) + infl = np.empty(T) + + for n in range(T): + x = model.x(γ) + w1, w2 = rng.standard_normal(2) + π = x + σ2 * w2 + U = u - σ2 * w2 + σ1 * w1 # truth uses σ1 + Φ = np.array([1.0, π]) + denom = σ_govt**2 + Φ @ P @ Φ + γ = γ + (P @ Φ) / denom * (U - Φ @ γ) + P = P - np.outer(P @ Φ, Φ @ P) / denom + V / σ_govt**2 + infl[n] = π + return infl + +x_base = simulate(model, σ_govt=model.σ1, ε=0.05, seed=1) # σ = σ1 +x_sims = simulate(model, σ_govt=0.1, ε=0.20, seed=1) # σ ≠ σ1 (Sims-like) + +print(f"σ = σ1 : mean inflation {x_base.mean():.2f}, " + f"fraction near Ramsey {(x_base < 2).mean():.0%}") +print(f"σ ≠ σ1 : mean inflation {x_sims.mean():.2f}, " + f"fraction near Ramsey {(x_sims < 2).mean():.0%}") +``` + +```{code-cell} ipython3 +fig, axes = plt.subplots(2, 1, figsize=(9, 7), sharex=True) +axes[0].plot(x_base, lw=0.6) +axes[0].axhline(model.u, color='k', ls='--', lw=1) +axes[0].set_ylabel('inflation') +axes[0].set_title(r'$\sigma = \sigma_1$: recurrent escapes, pulled back to Nash') + +axes[1].plot(x_sims, lw=0.6, color='C1') +axes[1].axhline(model.u, color='k', ls='--', lw=1) +axes[1].set_xlabel('$n$') +axes[1].set_ylabel('inflation') +axes[1].set_title(r'$\sigma \neq \sigma_1$ (Sims): prolonged spells near Ramsey') + +plt.tight_layout() +plt.show() +``` + +With the correct error variance the mean dynamics reassert themselves and inflation is repeatedly pulled back toward Nash. + +With Sims's misallocation the pull is weakened, and the economy lingers near the Ramsey outcome — the government behaves as if it has *permanently* learned a good-enough version of the natural-rate hypothesis. + +As {cite}`SargentWilliams2005` put it, one can read the difference in two equivalent ways: either Sims allowed too much parameter drift to permit convergence, or he did not let the government attribute enough variation to its regression error. + +## Conclusion + +Free parameters are dangerous, as Arthur Goldberger and Robert Lucas warned. + +This lecture's government carries free parameters in the covariance matrix $V$ of the drift in its beliefs. + +But those parameters buy something: the empirical sequel {cite}`SargentWilliamsZha2006` shows that estimating $V$ from post-war U.S. data lets the model reverse-engineer a sequence of policy makers' evolving subjective models of the Phillips curve that rationalize the actual rise and fall of American inflation — the vindication story of {doc}`phillips_two_stories`, made quantitative. + +Related evidence that the covariance of coefficient drift has itself moved over time appears in {cite}`CogleySargent2005`. + +### Are the estimated beliefs realistic? + +That empirical success came with a challenge that is really a challenge about the *prior*. + +To fit the data, {cite}`SargentWilliamsZha2006` estimated a large drift covariance $V$ — a government so open to new data that its beliefs about the monetary transmission mechanism lurch from month to month. + +{cite}`Primiceri2006`, Christopher Sims, and, candidly, {cite}`Sargent2008` in his presidential address, objected that such volatile beliefs are *unrealistic*: the imputed government forecasts unemployment poorly and holds views no real central bank would. + +{cite}`CarboniEllison2009` answer the objection by disciplining the prior with data the Federal Reserve actually produced. + +They re-estimate the model subject to the requirement that its beliefs reproduce the unemployment forecasts published in the Fed's *Greenbook* — imposing on the learning model a cross-equation restriction of the kind familiar from rational-expectations econometrics. + +Imposing it shrinks the estimated $V$ by orders of magnitude and removes the wild belief swings, yet leaves the low-frequency conquest story intact: a *stable* evolution of Federal Reserve beliefs still explains the rise and fall of inflation. + +The volatile beliefs, it turns out, were doing nothing but overfitting the high-frequency wiggles that {cite}`Sargent1999` never set out to explain. + +The moral for this lecture is direct: the prior $V$ is not a nuisance parameter to be maximized over freely — it is an economic object, and pinning it down with independent evidence on what policy makers actually believed is what makes the vindication story credible. + +The broader message is that *how* an adaptive government learns — the prior it brings to the drift in its own beliefs — is not a technical detail. + +It determines whether the economy converges to Nash, cycles between Nash and Ramsey, or escapes to Ramsey and stays there. + +The final lecture, {doc}`phillips_lost_conquest`, carries these same tools — constant-gain learning, an anticipated-utility Phelps problem, and a self-confirming equilibrium — into the present, to rationalize the Federal Reserve's response to the inflation of the 2020s. + +## Exercises + +```{exercise-start} +:label: ppr_ex1 +``` + +The learning cycle above tightened the prior on the *slope* coefficient. + +Tightening the prior on the *intercept* coefficient instead — using + +$$ +V(\lambda) = \begin{bmatrix} \lambda\, V^*_{11} & \sqrt\lambda\, V^*_{12} \\ \sqrt\lambda\, V^*_{12} & V^*_{22} \end{bmatrix} +$$ + +— does *not* destabilize the self-confirming equilibrium. + +Verify this by plotting the maximum real part of the eigenvalues of $\bar P(\lambda)\,\partial\bar g/\partial\gamma$ against $\lambda$ for this intercept-tightening family, and confirm it stays negative. + +```{exercise-end} +``` + +```{solution-start} ppr_ex1 +:class: dropdown +``` + +```{code-cell} ipython3 +def V_tighten_intercept(λ, V_star): + V = V_star.copy() + V[0, 0] = λ * V_star[0, 0] + V[0, 1] = V[1, 0] = np.sqrt(λ) * V_star[0, 1] + return V + +max_re_int = [] +for λ in λ_grid: + V = ε**2 * V_tighten_intercept(λ, V_star) + P = solve_riccati(V, M_sce, model.σ) + max_re_int.append(np.linalg.eigvals(P @ J).real.max()) + +fig, ax = plt.subplots(figsize=(8, 4.5)) +ax.plot(λ_grid, max_re_int, label='tighten intercept') +ax.plot(λ_grid, max_re, ls='--', label='tighten slope (for comparison)') +ax.axhline(0, color='k', lw=0.8) +ax.set_xlabel(r'$\lambda$') +ax.set_ylabel('max real part of eigenvalue') +ax.legend() +plt.show() +``` + +Tightening the intercept prior keeps the maximum real part negative for all $\lambda$: the self-confirming equilibrium stays stable, so there is no learning cycle. + +As {cite}`SargentWilliams2005` show, this prior does still change the *escape* dynamics — it speeds escapes up — but it does not overturn the mean dynamics. + +```{solution-end} +``` + +```{exercise-start} +:label: ppr_ex2 +``` + +The instantaneous escape direction is the dominant eigenvector of the prior covariance $\hat V$. + +Confirm that this direction is robust to the overall *scale* of the prior but sensitive to its *shape*. + +Compute the escape direction (and the implied terminal beliefs) for the baseline prior $V^*$ and for the slope-tightened prior $V(\lambda = 0.5)$, and compare where each sends the government's beliefs. + +```{exercise-end} +``` + +```{solution-start} ppr_ex2 +:class: dropdown +``` + +```{code-cell} ipython3 +def escape_terminal(V): + w, vecs = np.linalg.eigh(V) + v = vecs[:, np.argmax(w)] + v = v / np.linalg.norm(v) + if v[1] < 0: # orient toward increasing slope + v = -v + return γ_sce + (1.0 / v[1]) * v, v + +for name, V in [("baseline V*", V_star), + ("slope-tightened V(0.5)", V_tighten_slope(0.5, V_star))]: + term, v = escape_terminal(V) + print(f"{name:24s}: direction {v.round(3)}, terminal {term.round(2)}") +``` + +Both priors send beliefs toward a terminal point with slope $0$ — the Ramsey belief — but along different directions and to slightly different intercepts. + +The destination (zero inflation) is a robust feature; the *route* depends on the shape of the prior. + +```{solution-end} +``` diff --git a/lectures/phillips_self_confirming.md b/lectures/phillips_self_confirming.md new file mode 100644 index 000000000..2b5eb1b21 --- /dev/null +++ b/lectures/phillips_self_confirming.md @@ -0,0 +1,556 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.16.7 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(phillips_self_confirming)= +```{raw} jupyter + +``` + +# Self-Confirming Equilibria + +```{contents} Contents +:depth: 2 +``` + +In addition to what's in Anaconda, this lecture will use the following library: + +```{code-cell} ipython3 +:tags: [hide-output] + +!pip install quantecon +``` + +## Overview + +This lecture completes the study of Phillips curve tradeoffs begun in {doc}`phillips_credibility`. + +It follows chapter 7 of {cite}`Sargent1999`. + +We seek models that depart minimally from the basic {cite}`KydlandPrescott1977` model of {doc}`phillips_credibility` but that also let a government's *beliefs* be shaped by the data its own policies generate. + +The key equilibrium concept is a **self-confirming equilibrium**: the government has a possibly *wrong* model of the Phillips curve, but it fits that model to the data by least squares, and the data it observes confirm its beliefs. + +We combine ideas from two literatures: + +* {cite}`KingWatson1994` document how inferences about the Phillips curve depend on the *direction of fit* — whether one regresses unemployment on inflation (a *classical* identification) or inflation on unemployment (a *Keynesian* identification). +* A literature going back to Muth, Lucas, and Prescott {cite}`muth1961,Lucas_Prescott_1971` formulates rational expectations equilibria as fixed points of mappings from beliefs to the population moments of statistical models. + +We build two self-confirming equilibria that are identical except for the direction of fit, and we find that the direction of minimization affects outcomes. + +We then study an **equilibrium with misspecified beliefs**, combining the Phelps problem of {doc}`phillips_adaptive` with the optimal-misspecification machinery of {doc}`phillips_misspecified`, and find grounds for optimism: outcomes better than Nash, approaching Ramsey as the government becomes patient. + +Let's import what we need: + +```{code-cell} ipython3 +import matplotlib.pyplot as plt +import numpy as np +import quantecon as qe +from scipy.optimize import minimize_scalar +``` + +## Objects in the Phelps problem + +Recall from {doc}`phillips_adaptive` the ingredients of the general Phelps problem. + +The government believes in a reduced-form Phillips curve that it can fit in either direction: + +$$ +\text{Classical:} \quad U_t = \gamma' X_{C,t} + \varepsilon_{C,t}, +\qquad X_{C,t} = \begin{bmatrix} y_t & X_{t-1}' \end{bmatrix}', +$$ + +$$ +\text{Keynesian:} \quad y_t = \beta' X_{K,t} + \varepsilon_{K,t}, +\qquad X_{K,t} = \begin{bmatrix} U_t & X_{t-1}' \end{bmatrix}' . +$$ + +Solving the Phelps problem takes the government's beliefs $\gamma$ as given and delivers a decision rule $h(\gamma)$ for inflation. + +The two parameterizations are related by the inversion formulas + +```{math} +:label: sc_invert + +\gamma_1 = \beta_1^{-1}, \qquad \gamma_{-1} = - \beta_{-1} / \beta_1 . +``` + +## The actual Phillips curve + +The *actual* Phillips curve extends the one used in earlier lectures to allow for serially correlated shocks: + +```{math} +:label: sc_actual + +U_t = U^* - \frac{\theta}{1 - \rho_2 L}(y_t - x_t) + \frac{v_{1t}}{1 - \rho_1 L}, +``` + +with $|\rho_1| < 1$, $|\rho_2| < 1$, and $v_t = (v_{1t}, v_{2t})'$ a vector white noise, where $v_{2t} \equiv y_t - x_t$ is the surprise in inflation. + +For much of this lecture we set $\rho_1 = \rho_2 = 0$ to make theoretical points, which reduces {eq}`sc_actual` to + +$$ +U_t = U^* - \theta(y_t - x_t) + v_{1t} . +$$ + +## Self-confirming equilibria + +A self-confirming equilibrium reconciles the government's beliefs with the environment those beliefs generate. + +```{prf:definition} Self-confirming equilibrium +:label: sc_def + +A self-confirming equilibrium is a fixed belief vector $\gamma$, a government decision rule $h = h(\gamma)$, and a stationary process for $(y_t, U_t, x_t)$ such that + +(a) inflation solves the Phelps problem, $y_t = h X_{t-1} + v_{2t}$; + +(b) the public optimally forecasts inflation, $x_t = h X_{t-1}$; + +(c) unemployment is generated by the actual Phillips curve {eq}`sc_actual`; and + +(d) the government's beliefs satisfy the least squares orthogonality conditions +$E\left[U_t - \gamma' X_{C,t}\right] X_{C,t}' = 0$ (the **classical** direction of fit). +``` + +Condition (d) makes the government's beliefs depend on moment matrices that, through (a)–(c), themselves depend on the government's beliefs. + +The government's beliefs imply behavior that produces data whose moments *confirm* those beliefs. + +A distinct self-confirming equilibrium results from replacing (d) with the **Keynesian** direction of fit: + +> (d′) the government fits the Keynesian Phillips curve, $E\left[y_t - \beta' X_{K,t}\right] X_{K,t}' = 0$, then recovers $\gamma$ from the inversion formulas {eq}`sc_invert`. + +Because the government's beliefs affect the whole probability distribution of the data, the direction of minimization affects outcomes. + +```{note} +Computing a self-confirming equilibrium in general means finding a fixed point of a map $\gamma = T(h(\gamma))$ (classical) or $\beta = S(h(\gamma(\beta)))$ (Keynesian). The moments in the orthogonality conditions are obtained from the state-space representation of the system by solving a discrete Lyapunov equation. In practice one iterates a relaxation algorithm $\beta_{j+1} = \kappa\beta_j + (1-\kappa) S(\beta_j)$, which resembles the least squares learning recursion of {doc}`phillips_credibility`. +``` + +## The special case solved by hand + +When $\rho_1 = \rho_2 = 0$, the government's problem collapses to a sequence of static problems, and each self-confirming equilibrium can be computed by hand. + +With $X_{t-1} = 1$, the actual Phillips curve implies the second moments + +```{math} +:label: sc_moments + +\operatorname{var}(U_t) = \theta^2 \sigma_2^2 + \sigma_1^2, +\qquad +\operatorname{var}(y_t) = \sigma_2^2, +\qquad +\operatorname{cov}(U_t, y_t) = -\theta \sigma_2^2 . +``` + +**Classical direction of fit** ($U$ on $y$): the slope is + +$$ +\gamma_1 = \frac{\operatorname{cov}(U_t, y_t)}{\operatorname{var}(y_t)} = -\theta, +$$ + +and the requirement that the means lie on the regression line gives the intercept $\gamma_{-1} = (\gamma_1^2 + 1) U^*$. + +**Keynesian direction of fit** ($y$ on $U$): the slope is + +$$ +\beta_1 = \frac{\operatorname{cov}(U_t, y_t)}{\operatorname{var}(U_t)} + = \frac{-\theta \sigma_2^2}{\sigma_1^2 + \theta^2 \sigma_2^2}, +$$ + +with intercept $\beta_{-1} = -\frac{\beta_1^2 + 1}{\beta_1} U^*$, from which the implied classical coefficients follow by inversion, $\gamma_1 = \beta_1^{-1}$ and $\gamma_{-1} = \frac{\beta_1^2 + 1}{\beta_1^2} U^*$. + +```{code-cell} ipython3 +class SelfConfirmingStatic: + """ + The two static self-confirming equilibria (ρ1 = ρ2 = 0), one for + each direction of fit. + """ + + def __init__(self, θ=1.0, U_star=5.0, σ1=0.3, σ2=0.3): + self.θ, self.U_star, self.σ1, self.σ2 = θ, U_star, σ1, σ2 + + def classical(self): + "Perceived Phillips curve U = γ_{-1} + γ_1 y under classical fit." + θ, U_star = self.θ, self.U_star + γ1 = -θ + γ_1 = (γ1**2 + 1) * U_star + y_bar = -γ_1 * γ1 / (γ1**2 + 1) # mean (= Nash) inflation + return γ1, γ_1, y_bar + + def keynesian(self): + "Perceived Phillips curve under Keynesian fit, inverted to γ." + θ, U_star, σ1, σ2 = self.θ, self.U_star, self.σ1, self.σ2 + β1 = -θ * σ2**2 / (σ1**2 + θ**2 * σ2**2) + β_1 = -(β1**2 + 1) / β1 * U_star + γ1 = 1 / β1 + γ_1 = (β1**2 + 1) / β1**2 * U_star + y_bar = β_1 / (β1**2 + 1) # mean inflation + return γ1, γ_1, y_bar +``` + +```{code-cell} ipython3 +sce = SelfConfirmingStatic(θ=1.0, U_star=5.0, σ1=0.3, σ2=0.3) + +γ1_C, γ0_C, y_C = sce.classical() +γ1_K, γ0_K, y_K = sce.keynesian() + +print("Classical direction of fit") +print(f" γ_1 = {γ1_C:.1f}, γ_(-1) = {γ0_C:.1f}, mean inflation = {y_C:.1f}") +print("Keynesian direction of fit") +print(f" γ_1 = {γ1_K:.1f}, γ_(-1) = {γ0_K:.1f}, mean inflation = {y_K:.1f}") +``` + +These reproduce the numerical example of chapter 7 of {cite}`Sargent1999`. + +Under the classical direction of fit, mean inflation is the Nash value $\theta U^* = 5$. + +Under the Keynesian direction of fit, the government estimates a *flatter* Phillips curve and believes the tradeoff is more favorable than it is, so it sets inflation twice as high, at $10$. + +Let's draw the two self-confirming Phillips curves, reproducing Figure 7.1. + +```{code-cell} ipython3 +fig, ax = plt.subplots(figsize=(7, 6)) + +U_grid = np.linspace(0, 12, 100) + +# perceived Phillips curves U = γ_{-1} + γ_1 y => y = (U - γ_{-1}) / γ_1 +ax.plot(U_grid, (U_grid - γ0_C) / γ1_C, 'C0', label='P: classical fit') +ax.plot(U_grid, (U_grid - γ0_K) / γ1_K, 'C1', label='Q: Keynesian fit') + +ax.plot(sce.U_star, y_C, 'C0o') +ax.annotate('Nash', (sce.U_star, y_C), (sce.U_star + 0.4, y_C - 0.6)) +ax.plot(sce.U_star, y_K, 'C1o') +ax.annotate('Keynesian mean', (sce.U_star, y_K), + (sce.U_star + 0.4, y_K + 0.2)) + +ax.set_xlim(0, 12) +ax.set_ylim(0, 12) +ax.set_xlabel('unemployment $U$') +ax.set_ylabel('inflation $y$') +ax.legend() +plt.show() +``` + +Curve P is the self-confirming Phillips curve estimated with the classical direction of fit; curve Q with the Keynesian direction. + +Because the Keynesian fit makes the government believe the tradeoff is more exploitable, average inflation is higher. + +### Why not Ramsey? + +Both self-confirming equilibria give a mean outcome *worse* than the Ramsey outcome. + +This signifies a breakdown of the hypotheses of the proposition in {doc}`phillips_adaptive` that solving the Phelps problem eventually sustains nearly the Ramsey outcome. + +What fails is the **induction hypothesis**. + +Because $\rho_1 = \rho_2 = 0$, the self-confirming equilibria are serially uncorrelated, so lagged inflation drops out of the empirical Phillips curves. + +Deactivating the induction hypothesis makes the government in effect solve a one-period problem and shuts down the intertemporal channel that promotes better outcomes. + +## Equilibrium with misspecified beliefs + +To explore how imputing a *different* wrong model might *improve* on the Nash outcome, we now let the *public*, not the government, make a subtle specification error. + +This links adaptive expectations, the induction hypothesis, and the Phelps problem. + +The government knows the correct model, + +```{math} +:label: sc_bray10 + +U_t = U^* - \theta(y_t - x_t) + v_{1t}, +\qquad +x_t = C y_{t-1} + (1 - C) x_{t-1}, \quad C \in (0, 1), +``` + +where the public has constant-gain adaptive expectations with a parameter $C$ that it *tunes to fit the data*. + +Taking $x_t$ as a state variable, the government solves the Phelps problem: it maximizes $-E_0 \sum_{t=0}^\infty \delta^t\left[(U^* - \theta(y_t - x_t))^2 + y_t^2\right]$ by choice of a feedback rule $y_t = f_1 + f_2 x_t + v_{2t}$. + +This is exactly the LQ Phelps problem of {doc}`phillips_adaptive` with adaptation parameter $\lambda = 1 - C$. + +```{code-cell} ipython3 +def phelps_policy(C, δ, θ=1.0, U_star=5.0): + "Government feedback rule y_t = f1 + f2 x_t, with public gain C." + λ = 1 - C + a = np.array([[U_star], [θ]]) + R = 0.5 * (a @ a.T) + Q = np.array([[0.5 * (θ**2 + 1)]]) + N = -0.5 * θ * a.T + A = np.array([[1.0, 0.0], [0.0, λ]]) + B = np.array([[0.0], [1 - λ]]) + lq = qe.LQ(Q, R, A, B, N=N, beta=min(δ, 1 - 1e-7)) + P, F, d = lq.stationary_values() + return -F[0, 0], -F[0, 1] +``` + +The government's behavior makes the *actual* inflation rate + +```{math} +:label: sc_bray11 + +y_t = \frac{f_1}{1 - f_2} + + \frac{1 - (1 - C)L}{1 - (1 - C(1 - f_2))L} v_{2t} + = \nu + f(L) v_{2t}, +``` + +with mean $\nu = f_1 / (1 - f_2)$ and spectrum $F(\omega; C) = |f(e^{i\omega})|^2 \sigma_2^2$. + +Given $C$, the public seeks the best-fitting misspecified model of the same integrated moving-average form as in {doc}`phillips_misspecified`, + +$$ +y_t = \frac{1 - (1 - c)L}{1 - L}\epsilon_t = g(L)\epsilon_t , +$$ + +and the induced best-estimate map $c = B(C)$ defines an **equilibrium under forecast misspecification** as a fixed point $C = B(C)$. + +This makes both the true and the approximating models equilibrium outcomes, and turns the adaptation parameter $C$ into an outcome rather than a free parameter. + +```{code-cell} ipython3 +class MisspecifiedPhillips: + """ + Equilibrium with misspecified public beliefs: a fixed point of the + best-estimate map, with the government solving the Phelps problem. + """ + + def __init__(self, θ=1.0, U_star=5.0, σ2=0.3, δ=0.97, ρ=0.995, N=1024): + self.θ, self.U_star, self.σ2 = θ, U_star, σ2 + self.δ, self.ρ, self.N = δ, ρ, N + ω = 2 * np.pi * np.arange(N) / N + self.z = np.exp(1j * ω) + self.ω = ω + + def true_process(self, C): + "Return the mean ν, spectrum F, and policy (f1, f2) given belief C." + f1, f2 = phelps_policy(C, self.δ, self.θ, self.U_star) + ν = f1 / (1 - f2) + ψ = 1 - C * (1 - f2) + f = (1 - (1 - C) * self.z) / (1 - ψ * self.z) + F = np.abs(f)**2 * self.σ2**2 + return ν, F, (f1, f2) + + def best_estimate(self, C): + "The best-estimate map c = B(C)." + ν, F, _ = self.true_process(C) + z, ρ = self.z, self.ρ + + def profile(c): + H = np.abs((1 - (1 - c) * z) / (1 - ρ * z))**2 + σ_ε2 = np.mean(F / H) + ν**2 / H[0] + return np.log(σ_ε2) + np.mean(np.log(H)) + + return minimize_scalar(profile, bounds=(1e-4, 0.99), + method='bounded').x + + def solve(self, C0=0.1, tol=1e-10, maxit=500): + "Iterate the best-estimate map to a fixed point." + C = C0 + for _ in range(maxit): + C_new = self.best_estimate(C) + if abs(C_new - C) < tol: + break + C = C_new + ν, _, (f1, f2) = self.true_process(C_new) + return C_new, ν, (f1, f2) +``` + +```{code-cell} ipython3 +mp = MisspecifiedPhillips(δ=0.97) +C_star, ν_star, (f1, f2) = mp.solve() + +print(f"equilibrium gain C = {C_star:.4f}") +print(f"policy rule y = {f1:.4f} + {f2:.4f} x") +print(f"mean inflation ν = {ν_star:.3f}") +print(f"Nash inflation θ U* = {mp.θ * mp.U_star:.3f}") +``` + +The most important outcome is that the implied mean inflation rate is substantially *below* the Nash value of $5$. + +The induction hypothesis embedded in the adaptive expectations scheme, together with a high discount factor, delivers this improvement — and because $C$ is now an equilibrium outcome, the mechanism is sharper than in {doc}`phillips_adaptive`, where $C$ could be manipulated independently of $\delta$. + +### Approaching Ramsey + +As the government becomes more patient, the equilibrium mean inflation rate falls toward the Ramsey value of zero. + +```{code-cell} ipython3 +δ_grid = np.array([0.95, 0.96, 0.97, 0.98, 0.99, 0.995]) +C_vals, ν_vals = [], [] +for δ in δ_grid: + C, ν, _ = MisspecifiedPhillips(δ=δ).solve() + C_vals.append(C) + ν_vals.append(ν) + +fig, axes = plt.subplots(1, 2, figsize=(11, 4.5)) +axes[0].plot(δ_grid, ν_vals, 'o-') +axes[0].axhline(mp.θ * mp.U_star, color='k', ls='--', lw=1, label='Nash') +axes[0].set_xlabel(r'discount factor $\delta$') +axes[0].set_ylabel('mean inflation') +axes[0].legend() + +axes[1].plot(δ_grid, C_vals, 'o-', color='C1') +axes[1].set_xlabel(r'discount factor $\delta$') +axes[1].set_ylabel('equilibrium gain $C$') + +plt.tight_layout() +plt.show() +``` + +Mean inflation lies far below the Nash value at every discount factor and declines toward the Ramsey value of zero as $\delta \to 1$. + +```{note} +The precise equilibrium values depend on the near–unit-root approximation $\rho$ used to keep the perceived model's spectral density well defined, as discussed in {doc}`phillips_misspecified`. The qualitative conclusion — better-than-Nash outcomes that approach Ramsey as $\delta \to 1$ — is robust. +``` + +### Spectra and impulse responses + +Let's compare the true and approximating inflation processes at the equilibrium, as in Figures 7.2 and 7.3 of {cite}`Sargent1999`. + +```{code-cell} ipython3 +ν_star, F, _ = mp.true_process(C_star) +c_star = mp.best_estimate(C_star) +H = np.abs((1 - (1 - c_star) * mp.z) / (1 - mp.ρ * mp.z))**2 +σ_ε2 = np.mean(F / H) + ν_star**2 / H[0] +G = H * σ_ε2 + +half = mp.N // 2 +fig, ax = plt.subplots(figsize=(8, 5)) +ax.plot(mp.ω[:half], np.log(F[:half]), 'C0', label='true model') +ax.plot(mp.ω[:half], np.log(G[:half]), 'C1--', label='approximating model') +ax.set_xlabel(r'angular frequency $\omega$') +ax.set_ylabel('log spectral density') +ax.legend() +plt.show() +``` + +The true and approximating spectral densities match well at all but the lowest frequencies. + +The true inflation rate is only moderately serially correlated, and — as in the Bray model of {doc}`phillips_misspecified` — the approximating model uses a unit root to simulate a mean, capturing first moments with second moments. + +```{code-cell} ipython3 +def ima_impulse(num, den, T=25): + "IRF of (1 - num L)/(1 - den L)." + h = np.empty(T) + h[0] = 1.0 + for k in range(1, T): + h[k] = den * h[k - 1] + h[1:] -= num * h[:-1] + return h + +ψ = 1 - C_star * (1 - f2) +irf_true = ima_impulse(1 - C_star, ψ) +irf_approx = ima_impulse(1 - c_star, mp.ρ) + +fig, ax = plt.subplots(figsize=(8, 5)) +ax.plot(irf_true, 'C0o-', ms=4, label='true model') +ax.plot(irf_approx, 'C1s--', ms=4, label='approximating model') +ax.set_xlabel('lag') +ax.set_ylabel('response') +ax.legend() +plt.show() +``` + +The unit root in the approximating model manifests itself as a nonzero asymptote in its impulse response. + +### Grounds for optimism + +After the disappointments of the self-confirming equilibria, the equilibrium with forecast misspecification is heartening: it supports better-than-Nash outcomes. + +The equilibrium concept is not self-confirming, but it has that spirit — it embodies a type of self-confirmation with a *wrong* model. + +That the approximation error is small shows there is a nearly self-confirming model with much better than Nash outcomes. + +The next lectures in this suite — {doc}`phillips_learning`, {doc}`phillips_escaping_nash`, and {doc}`phillips_priors` — build adaptive, real-time versions of these models in which the government estimates its Phillips curve recursively from the most recent data. + +There the self-confirming equilibrium computed here becomes the *attractor* of the learning dynamics, and the better-than-Nash outcomes reappear as recurrent *escapes* away from it. + +## Exercises + +```{exercise-start} +:label: sc_ex1 +``` + +The gap between the two static self-confirming equilibria is driven by the variances $\sigma_1$ (the Phillips curve shock) and $\sigma_2$ (the inflation surprise). + +The classical equilibrium always delivers mean inflation equal to the Nash value $\theta U^*$, but the Keynesian mean inflation depends on the ratio $\sigma_1 / \sigma_2$. + +Fixing $\sigma_2 = 0.3$, $\theta = 1$, $U^* = 5$, plot Keynesian mean inflation as a function of $\sigma_1 \in [0.05, 1.0]$. + +What happens as $\sigma_1 \to 0$, and why? + +```{exercise-end} +``` + +```{solution-start} sc_ex1 +:class: dropdown +``` + +```{code-cell} ipython3 +σ1_grid = np.linspace(0.05, 1.0, 50) +y_keynes = [SelfConfirmingStatic(σ1=σ1, σ2=0.3).keynesian()[2] + for σ1 in σ1_grid] + +fig, ax = plt.subplots(figsize=(8, 4.5)) +ax.plot(σ1_grid, y_keynes, label='Keynesian mean inflation') +ax.axhline(5.0, color='k', ls='--', lw=1, label='Nash') +ax.set_xlabel(r'$\sigma_1$') +ax.set_ylabel('mean inflation') +ax.legend() +plt.show() +``` + +As $\sigma_1 \to 0$ the Phillips curve becomes nearly deterministic, the Keynesian slope $\beta_1 \to -1/\theta$, and the two directions of fit agree, so Keynesian mean inflation approaches the Nash value. + +As $\sigma_1$ grows, unemployment becomes noisier, the Keynesian regression of $y$ on $U$ flattens, and the government perceives a more exploitable tradeoff, raising mean inflation. + +```{solution-end} +``` + +```{exercise-start} +:label: sc_ex2 +``` + +Verify that the misspecified-beliefs equilibrium is a genuine fixed point. + +For $\delta = 0.97$, plot the best-estimate map $C \mapsto B(C)$ against the 45-degree line and mark the fixed point. + +```{exercise-end} +``` + +```{solution-start} sc_ex2 +:class: dropdown +``` + +```{code-cell} ipython3 +mp = MisspecifiedPhillips(δ=0.97) +C_grid = np.linspace(0.02, 0.3, 20) +B_vals = [mp.best_estimate(C) for C in C_grid] +C_star, _, _ = mp.solve() + +fig, ax = plt.subplots(figsize=(6, 6)) +ax.plot(C_grid, B_vals, 'C0', label='$B(C)$') +ax.plot(C_grid, C_grid, 'k--', lw=1, label='45 degrees') +ax.plot(C_star, C_star, 'ko') +ax.annotate('equilibrium', (C_star, C_star), (C_star + 0.03, C_star - 0.03)) +ax.set_xlabel('$C$') +ax.set_ylabel('$B(C)$') +ax.legend() +plt.show() +``` + +The best-estimate map crosses the 45-degree line at the equilibrium gain, confirming $C = B(C)$. + +```{solution-end} +``` diff --git a/lectures/phillips_two_stories.md b/lectures/phillips_two_stories.md new file mode 100644 index 000000000..0e380be5a --- /dev/null +++ b/lectures/phillips_two_stories.md @@ -0,0 +1,553 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.16.7 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(phillips_two_stories)= +```{raw} jupyter + +``` + +# The Rise and Fall of U.S. Inflation + +```{contents} Contents +:depth: 2 +``` + +In addition to what's in Anaconda, this lecture will use the following libraries to download and filter macroeconomic data: + +```{code-cell} ipython3 +:tags: [hide-output] + +!pip install pandas_datareader +``` + +## Overview + +This is the first lecture in a suite based on Thomas Sargent's *The Conquest of American Inflation* {cite}`Sargent1999`. + +It combines chapters 1 and 2 of that book. + +The suite asks a question about post-war U.S. macroeconomic history: + +> If we take for granted that inflation is under the control of the Federal Reserve, how can we explain the rise of U.S. inflation into the 1970s and its abrupt fall under Paul Volcker in the early 1980s? + +The essay evaluates two interpretations, both based on policy makers' *beliefs* about the Phillips curve. + +In both stories, the Federal Reserve learns the natural-rate-of-unemployment theory from a combination of experience and *a priori* reasoning. + +The stories differ in how that theory is cast: + +* **The triumph of natural-rate theory.** Academic economists discovered the natural-rate hypothesis, taught that any inflation-unemployment tradeoff is temporary, and eventually persuaded policy makers to pursue low inflation. +* **The vindication of econometric policy evaluation.** Policy makers never abandoned the methods that Robert Lucas criticized in his famous Critique. Recurrently re-estimating a Phillips curve and using it to choose a target, they were led by the *data itself* — an adversely shifting empirical Phillips curve — toward lower inflation. + +This lecture presents the facts that motivate both stories, sketches the two interpretations, and reviews the Lucas Critique that chapter 2 both invokes and modifies. + +The remaining lectures in the suite build the models: + +* {doc}`phillips_credibility` — the one-period Kydland-Prescott credibility problem (chapter 3). +* {doc}`phillips_adaptive` — adaptive expectations and the Phelps problem (chapter 5). +* {doc}`phillips_misspecified` — equilibrium under optimal misspecified beliefs (chapter 6). +* {doc}`phillips_self_confirming` — self-confirming equilibria (chapter 7). +* {doc}`phillips_learning` — adaptive learning, escape dynamics, and simulated Volcker stabilizations (chapter 8). +* {doc}`phillips_escaping_nash` — the escape dynamics characterized analytically ({cite}`ChoWilliamsSargent2002`). +* {doc}`phillips_priors` — how the government's prior about drifting coefficients shapes convergence, cycles, and escapes ({cite}`SargentWilliams2005`). +* {doc}`phillips_lost_conquest` — the same tools turned on the 2020s inflation and the Fed's slow response ({cite}`SargentWilliams2025`). +* {doc}`phillips_drifts_volatilities` — an empirical postscript that fits a drifting-coefficient, stochastic-volatility VAR to the data and asks whether the Great Inflation was bad policy or bad luck ({cite}`CogleySargent2005`). + +Let's start with some imports: + +```{code-cell} ipython3 +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +import datetime +from pandas_datareader import data as web +from statsmodels.tsa.filters.bk_filter import bkfilter +``` + +```{note} +The figures in the next two sections reproduce the ones in chapters 1 and 2 of {cite}`Sargent1999`, which were drawn from data available in the late 1990s. +We download the underlying series from [FRED](https://fred.stlouisfed.org/) and restrict attention to the same historical window. +The section {ref}`phillips_after_1999` then carries the most enlightening of these figures through to the present and asks what the additional quarter-century of data means for the two stories. +``` + +## Facts + +We begin with the single fact that the whole essay seeks to explain: the hump-shaped path of U.S. inflation since World War II. + +We measure inflation by the annualized monthly change in the consumer price index (all items), smoothed with a 13-month centered moving average to remove seasonal and high-frequency noise. + +```{code-cell} ipython3 +start, end = datetime.datetime(1948, 1, 1), datetime.datetime(1999, 1, 1) + +cpi = web.DataReader('CPIAUCNS', 'fred', start, end)['CPIAUCNS'] + +# annualized monthly inflation, then a 13-month centered moving average +inflation = 1200 * np.log(cpi).diff() +inflation_ma = inflation.rolling(13, center=True).mean() +``` + +```{code-cell} ipython3 +fig, ax = plt.subplots(figsize=(9, 5)) +ax.plot(inflation_ma, lw=1.2) +ax.axhline(0, color='k', lw=0.5) +ax.set_xlabel('year') +ax.set_ylabel('inflation (percent, annualized)') +ax.set_title('Figure 1.1: Monthly inflation, CPI all items, ' + '13-month centered moving average') +plt.show() +``` + +Inflation was low during the late 1950s and early 1960s, swept upward into the 1970s, and then fell abruptly with Volcker's stabilization in the early 1980s. + +Any explanation that treats inflation as under the Federal Reserve's control must account for this rise and fall. + +## The Phillips curve in the data + +Despite its disrepute in some academic and policy-making circles, the Phillips curve persists in U.S. data, and simple procedures detect it. + +To coax the Phillips curve from the data, we follow the book in two ways. + +First, we use the unemployment rate for a single demographic group — white men 20 years and over — rather than the aggregate rate, which is contaminated by slow-moving shifts in the demographic mix. + +Second, we look at *business-cycle* frequencies, filtering out slowly moving components so that the eye can spot the inverse relationship between inflation and unemployment. + +```{code-cell} ipython3 +u = web.DataReader('LNS14000028', 'fred', start, end)['LNS14000028'] # white men 20+ + +data = pd.concat([inflation.rename('inflation'), + u.rename('unemployment')], axis=1).dropna() +data.head() +``` + +Figure 1.2 plots the two raw series together. + +```{code-cell} ipython3 +fig, ax = plt.subplots(figsize=(9, 5)) +ax.plot(data.index, data['inflation'], 'C0', lw=1, label='inflation (CPI)') +ax.plot(data.index, data['unemployment'], 'C1:', lw=1.2, + label='unemployment (white men 20+)') +ax.axhline(0, color='k', lw=0.5) +ax.set_xlabel('year') +ax.set_ylabel('percent') +ax.set_title('Figure 1.2: Monthly unemployment and inflation rates') +ax.legend() +plt.show() +``` + +To isolate the business-cycle relationship, we apply the finite-lag bandpass filter of Baxter and King {cite}`BaxterKing1999`. + +Following the book, we keep fluctuations with periods between 24 and 84 months and use a lead-lag truncation of 84 months. + +```{code-cell} ipython3 +# Baxter-King bandpass: periods between 24 and 84 months, truncation 84 +bk = bkfilter(data, low=24, high=84, K=84) +bk.columns = ['inflation_cycle', 'unemployment_cycle'] +``` + +```{code-cell} ipython3 +fig, ax = plt.subplots(figsize=(9, 5)) +ax.plot(bk.index, bk['inflation_cycle'], 'C0', lw=1, label='inflation') +ax.plot(bk.index, bk['unemployment_cycle'], 'C1:', lw=1.2, + label='unemployment') +ax.axhline(0, color='k', lw=0.5) +ax.set_xlabel('year') +ax.set_ylabel('deviation from trend (percent)') +ax.set_title('Figure 1.3: Business-cycle components ' + '(Baxter-King bandpass filter)') +ax.legend() +plt.show() +``` + +The filtered components tend to move in opposite directions: a business-cycle Phillips curve. + +We can see the tradeoff more directly in a scatter plot for the subperiod that interests us most, 1960-1982. + +Figure 1.4 plots the raw series against each other, and Figure 1.5 the business-cycle components. + +```{code-cell} ipython3 +sub = slice('1960', '1982') + +fig, axes = plt.subplots(1, 2, figsize=(12, 5)) + +axes[0].scatter(data.loc[sub, 'inflation'], + data.loc[sub, 'unemployment'], s=8, alpha=0.6) +axes[0].set_xlabel('inflation') +axes[0].set_ylabel('unemployment (white men 20+)') +axes[0].set_title('Figure 1.4: Raw series, 1960-1982') + +axes[1].scatter(bk.loc[sub, 'inflation_cycle'], + bk.loc[sub, 'unemployment_cycle'], s=8, alpha=0.6) +axes[1].set_xlabel('inflation (business-cycle component)') +axes[1].set_ylabel('unemployment (business-cycle component)') +axes[1].set_title('Figure 1.5: Business-cycle components, 1960-1982') + +plt.tight_layout() +plt.show() +``` + +Focusing on the business-cycle components sharpens the apparent Phillips curve. + +Figure 1.5 reveals **Phillips loops**: inflation and unemployment trace out counter-clockwise loops rather than a single stable curve, a signature of the shifting expectations that the natural-rate theory places at the center of the story. + +```{note} +The book adjusts for demographic change by choosing a single unemployment series. A broader definition of unemployment would inject additional low-frequency demographic components, which one might model with a unit-root process. The essay instead puts a unit root into the inflation-unemployment process from a different source: the *drifting beliefs* of a monetary authority cut loose from the discipline of Bretton Woods. +``` + +## Two interpretations + +Both stories start from the same initial conditions — the history of inflation and unemployment, and the state of expectations, inherited by policy makers around 1960 — and both assume that the data conform to the natural-rate hypothesis, whether or not policy makers realized it. + +### The triumph of natural-rate theory + +Adherence to the gold standard and then to Bretton Woods gave the U.S. low inflation and low expected inflation. + +In 1960, Paul Samuelson and Robert Solow {cite}`SamuelsonSolow1960` found a Phillips curve in U.S. data and taught that it was *exploitable* — that policy could raise inflation to reduce unemployment. + +Within a decade this recommendation was widely endorsed and implemented. + +To everyone's dismay, the Phillips curve then shifted adversely: inflation rose, but unemployment on average did not fall. + +Meanwhile Edmund Phelps {cite}`Phelps1967`, Milton Friedman {cite}`friedman1968role`, and Robert Lucas {cite}`Lucas1972` created and refined the concept of the natural rate of unemployment, which assigns a central role to expectations of inflation in locating the Phillips curve. + +The natural-rate theory allowed only a *temporary* tradeoff and explained the adverse shifts; its rational expectations version implied that policy makers should ignore the temporary tradeoff and strive only for low inflation. + +In this story, these ideas diffused from academics to policy makers and ultimately produced the lower inflation of the 1980s and 1990s. + +Events were shaped by policy makers' beliefs — some false, others true — and the actions those beliefs inspired. + +### The vindication of econometric policy evaluation + +The alternative story ascribes Volcker's conquest partly to the *success* of the very econometric and policy-making procedures that Lucas challenged. + +Policy makers accepted the Samuelson-Solow Phillips curve as an exploitable tradeoff and adopted their methods for learning from data and deducing policy. + +Recurrently, they re-estimated a distributed-lag Phillips curve and used it to reset a target inflation-unemployment pair. + +Interpreted mechanically — without identifying expectations as the hidden state variable — the adversely shifting empirical Phillips curve eventually led policy makers to pursue lower inflation. + +This story is told with an *adaptive* theory of policy that departs minimally from rational expectations. + +It connects a sequence of ideas that the rest of the suite develops: drifting coefficients, self-confirming equilibria, least squares and other recursive learning algorithms, convergence of least squares learners to self-confirming equilibria, and recurrent dynamics along *escape routes* from those equilibria. + +The key idea, taken from Christopher Sims {cite}`Sims1988`, is that an adaptive model lets a government *learn* from its past attempts to exploit the Phillips curve, and eventually discover a version of the natural-rate hypothesis that instructs it to reduce inflation. + +## Ignoring the Lucas Critique + +Chapter 2 of the book confronts the obvious objection to the vindication story: doesn't the Lucas Critique forbid the mechanical, exploitable Phillips curve on which it rests? + +The essay resurrects the econometric and policy-evaluation procedures that Lucas decisively criticized {cite}`lucas1976econometric`, and emphasizes a neglected aspect of his Critique: **drifting coefficients**. + +### The Critique + +An econometric model is a collection of stochastic difference equations, some of which describe private agents' decision rules. + +Econometric policy evaluation in the Tinbergen-Theil tradition holds those private decision rules *fixed* while the government optimizes its own rule against an objective function. + +Lucas noted that if private agents solve intertemporal optimization problems, their decision rules *depend on* the government's rule. + +By missing this dependence, the Tinbergen-Theil method mistranslates the government's preferences over outcomes into an ordering over decision rules, and so gives unreliable policy advice. + +### The appeal to drifting coefficients + +Lucas conceded the impressive forecasting record of Keynesian models but argued that good forecasting is no evidence for the *invariance under intervention* that Tinbergen-Theil assumes. + +He stressed that forecasters routinely adjusted the constant terms of key equations, and interpreted these adjustments as an adaptive-coefficients model in the spirit of Cooley and Prescott {cite}`CooleyPrescott1973`. + +The intertemporal instability of estimated relationships — coefficient drift — undermined treating them as invariant to systematic changes in policy rules. + +Yet Lucas left the drift *unexplained*, and neither the macroeconomic theory nor the rational expectations econometrics built after the Critique accounts for it: both focus on environments with time-invariant transition functions. + +### Parameter drift as a point of departure + +The essay starts from parameter drift, treating it as a *smoking gun* — the key evidence that the government's beliefs about the economy, and hence its policy toward inflation, have evolved over time. + +It builds a model from two components: + +1. a Tinbergen-Theil theory of government decision making — the Phelps problem of {doc}`phillips_adaptive`; and +2. a drifting-coefficients econometric procedure for the government, featuring the constant adjustments that Lucas wrote about. + +Within a **self-confirming equilibrium** (developed in {doc}`phillips_self_confirming`), some of the force of the Lucas Critique vanishes. + +Although the government's invariance assumption is wrong, it is not disappointed in outcomes, because those outcomes are statistically consistent with its beliefs. + +A self-confirming equilibrium is a rational expectations equilibrium with *fewer* free parameters than the models Lucas used — and precisely those lost parameters would be needed to represent regime changes. + +To admit regime changes and drifting coefficients, convergence to a self-confirming equilibrium must be *resisted*. + +The essay arrests convergence by replacing the government's least squares estimator with a constant-gain, adaptive-coefficients algorithm that overweights recent data — endowing the government with a suspicion that the environment is unstable. + +This weakens the pull toward a self-confirming equilibrium and sustains dynamics along an escape route, along which regime changes occur. + +Ironically, as we shall see in {doc}`phillips_learning`, the procedures that *violate* the Lucas Critique can yield better outcomes than ones that respect it. + +## A premature summary: triumph or vindication? + +The rest of this suite builds models. + +Before we start, it is worth previewing where they lead and the tension they leave unresolved — a premature summary of the journey, adapted from the concluding chapter of {cite}`Sargent1999`. + +Everything can be organized around two *benchmark* models. + +In the first, due to {cite}`Phelps1967`, the public forms expectations *adaptively* while the government chooses policy *optimally*, taking the public's rule as given. + +In the second, the rational-expectations natural-rate model, the public is *rational* while the government's policy is treated as *exogenous and arbitrary*. + +Lucas recommended replacing the first benchmark with the second. + +Coming to grips with the two stories drives us to propose models that make various *compromises* between these poles — and the remaining lectures are those compromises. + +### The road ahead + +We begin, in {doc}`phillips_credibility`, by imposing rationality on *both* sides. + +The one-period {cite}`KydlandPrescott1977` model delivers a pessimistic prediction — the high-inflation time-consistent (Nash) outcome — but a repeated-economy version of the theory of credible policy replaces that pessimism with *agnosticism*: so many outcomes become sustainable that the theory yields only weak predictions. + +That weakness is the first reason to hesitate before declaring the triumph of natural-rate theory. + +We then turn back from the Lucas Critique and start again from the Phelps benchmark, but with one change: the government's model of the private sector is no longer arbitrary — it is *fit to historical data*. + +Varying the details of that fitting problem generates the rest of the suite: + +* self-confirming equilibria ({doc}`phillips_self_confirming`), +* equilibria with optimally *misspecified* forecasting functions ({doc}`phillips_misspecified`), and +* adaptive, "anticipated-utility" learning models ({doc}`phillips_learning`, {doc}`phillips_escaping_nash`, and {doc}`phillips_priors`). + +These adaptive models are a *disciplined* retreat from rational expectations, not an abandonment of it. + +They carry no free parameters governing expectations; period by period they impose the same cross-equation restrictions as a rational expectations model; and — because a self-confirming equilibrium is the attractor of their *mean dynamics* — they converge back to rational expectations under tranquil conditions, satisfying a desideratum of {cite}`Kreps1998`. + +But, following {cite}`Sims1988`, our real interest is in the *recurrent* dynamics that adaptation adds. + +Suspecting that the Phillips curve is prone to wander, the government uses a constant-gain algorithm, which is the sensible choice when coefficients drift. + +The payoff is a striking one: the adaptive models produce abrupt *stabilizations* of inflation that defy the inferior self-confirming outcome toward which the mean dynamics point. + +These regime shifts arise not from any change in the government's procedures, nor from large shocks, but from *changes in beliefs created by the government's own econometrics* — the mathematics of escape routes in the space of approximating models. + +### The induction hypothesis: villain and hero + +At the center of the story sits the **induction hypothesis** — the restriction that the weights on lagged inflation in an expectations equation sum to one, so that a permanently higher inflation rate is eventually fully expected. + +It was built almost without comment into the adaptive expectations hypothesis of {cite}`Friedman1957` and {cite}`Cagan`, and it was the basis of Solow's and Tobin's early tests of the natural-rate hypothesis {cite}`Solow1968,Tobin1968`. + +Cast as a *villain* in Lucas's Critique — a naive restriction that rational expectations does not imply — the induction hypothesis re-emerges as the *hero* of the adaptive models: activating it is exactly what makes the government's Phelps problem call for near-Ramsey, low-inflation policy. + +The escape routes our simulations follow are precisely the paths along which the government's estimated model, by using a unit root to approximate a constant, stumbles into believing the induction hypothesis. + +Wrestling with such approximation problems, with several models simultaneously in play, is what led {cite}`Sims1980` to call bounded rationality a *wilderness*, set apart from the tidy one-model world of rational expectations. + +### The reservation + +Which story, then — triumph or vindication? + +The contest is not rational expectations versus an alternative, because *both* stories selectively apply and withdraw from rational expectations. + +And the vindication story, however well it fits, is an exercise in *positive* economics, not *normative* economics. + +It is tempting to read its long stretches of near-Ramsey inflation as an endorsement of the adaptive policy-making procedures that produce them — but that temptation should be resisted. + +For the same mean dynamics that permit a stabilization also guarantee that it is temporary: once beliefs drift close enough to the induction hypothesis, the mean dynamics begin to point *away* from it, back toward the region where the Phelps problem recommends resuscitating inflation. + +The simulations contain long episodes that look like Paul Volcker — and others that look like Arthur Burns. + +Theoretical work after {cite}`KydlandPrescott1977`, and {cite}`Rogoff1985`, insists that durable low inflation must rest on *commitment mechanisms* that keep a monetary authority from choosing sequentially — not on the hope that an adaptive government, armed with an approximate model, will by chance eventually learn to do approximately the right thing. + +So the book ends on a hope rather than a verdict: we *hope* that the triumph story is the right one — that policy makers have learned a correct rational expectations version of the natural-rate hypothesis and have found devices to commit themselves to low inflation. + +Because if instead the vindication story is closer to the truth, then the same mean dynamics that carried inflation down are always waiting, eventually, to carry it back up. + +The quarter-century of data since 1999 — a long, quiet *Great Moderation* interrupted by a sudden surge in 2021-2022 — is a running test of exactly this hope, and we turn to it next. + +(phillips_after_1999)= +## Data patterns after 1999 + +{cite}`Sargent1999` was written at the end of the long disinflation that began with Volcker. + +We now have another quarter-century of data. + +This section carries the book's most enlightening figures through to the present and asks what the new observations mean for the two stories. + +Let's extend the sample to the latest available month. + +```{code-cell} ipython3 +end_recent = datetime.datetime(2026, 7, 1) + +cpi_full = web.DataReader('CPIAUCNS', 'fred', start, end_recent)['CPIAUCNS'] +u_full = web.DataReader('LNS14000028', 'fred', start, end_recent)['LNS14000028'] + +# the book's measure (annualized monthly change, 13-month centered MA) +inflation_full = 1200 * np.log(cpi_full).diff() +inflation_ma_full = inflation_full.rolling(13, center=True).mean() + +# year-over-year inflation: smoother, and the measure usually quoted today +inflation_yoy = 100 * (cpi_full / cpi_full.shift(12) - 1) +``` + +### The rise and fall, extended + +Figure 1.1 showed inflation rising into the 1970s and falling under Volcker. + +Extending it to the present adds three chapters the book could not see: the *Great Moderation* of low, stable inflation from the mid-1980s; a long spell near — and briefly below — zero after the 2008 financial crisis; and a sudden surge in 2021-2022 to the highest rate since 1981, followed by a rapid decline. + +```{code-cell} ipython3 +fig, ax = plt.subplots(figsize=(11, 5)) +ax.plot(inflation_ma_full, lw=1) +ax.axhline(0, color='k', lw=0.5) +ax.axvspan(pd.Timestamp('1948-01-01'), pd.Timestamp('1999-01-01'), + color='C0', alpha=0.06, label="the book's window") +for date, y, txt in [('1980-03-01', 13.7, '1970s\nacceleration'), + ('1983-06-01', 3.0, 'Volcker'), + ('2009-07-01', -1.5, '2009\ndeflation scare'), + ('2022-06-01', 8.9, '2021-22\nsurge')]: + ax.annotate(txt, (pd.Timestamp(date), y), ha='center', fontsize=9, + color='C3') +ax.set_xlabel('year') +ax.set_ylabel('inflation (percent, annualized)') +ax.set_title('Inflation, CPI all items, 13-month centered moving average, ' + '1948-2026') +ax.legend(loc='upper right') +plt.show() +``` + +The hump that the book set out to explain is now one of *two*. + +The second, in 2021-2022, is a genuinely new episode — a fast acceleration and an almost-as-fast disinflation, all compressed into about three years. + +### Unemployment and inflation to the present + +Figure 1.2 plotted the two series together for the post-war period. + +Extending it shows the two most dramatic macroeconomic events of the new data: the COVID unemployment spike of 2020 — briefly the highest since the Great Depression — and the inflation surge that followed. + +```{code-cell} ipython3 +recent = slice('1990', None) + +fig, ax = plt.subplots(figsize=(11, 5)) +ax.plot(inflation_yoy[recent], 'C0', lw=1, label='inflation (CPI, year-over-year)') +ax.plot(u_full[recent], 'C1:', lw=1.2, label='unemployment (white men 20+)') +ax.axhline(0, color='k', lw=0.5) +ax.set_xlabel('year') +ax.set_ylabel('percent') +ax.set_title('Unemployment and inflation, 1990-2026') +ax.legend() +plt.show() +``` + +Two features stand out. + +From the mid-1990s to 2020, inflation stayed remarkably quiet even as unemployment swung widely — falling to historic lows in the late 1990s and 2019, and doubling in the 2008 recession. + +Then, after the COVID spike, unemployment fell back quickly and inflation surged — a pattern with the fingerprints of a *supply* disturbance rather than the demand-driven tradeoff of the classic Phillips curve. + +### The Phillips curve across three eras + +The book coaxed a Phillips curve from 1960-1982 data. + +The most striking post-1999 pattern is how *unstable* the inflation-unemployment scatter has been across eras. + +We split the sample into the book's acceleration era, the Great Moderation, and the post-2008 period, and plot inflation against unemployment in each. + +```{code-cell} ipython3 +scatter_data = pd.concat([inflation_yoy.rename('inflation'), + u_full.rename('unemployment')], axis=1).dropna() + +eras = [('1960', '1983', '1960-1983 (acceleration)'), + ('1984', '2007', '1984-2007 (Great Moderation)'), + ('2008', None, '2008-2026 (crisis, COVID, surge)')] + +fig, axes = plt.subplots(1, 3, figsize=(14, 4.5), sharex=True, sharey=True) +for ax, (lo, hi, title) in zip(axes, eras): + era_data = scatter_data.loc[lo:hi] + ax.scatter(era_data['unemployment'], era_data['inflation'], s=8, alpha=0.5) + ax.axhline(0, color='k', lw=0.5) + ax.set_xlabel('unemployment') + ax.set_title(title, fontsize=10) +axes[0].set_ylabel('inflation (year-over-year)') +plt.tight_layout() +plt.show() +``` + +The three clouds could hardly look more different. + +In 1960-1983 the points sprawl across a wide range of inflation rates — the era of shifting expectations and Phillips *loops*. + +In 1984-2007 they collapse into a tight, low, nearly flat cloud — the Great Moderation, in which inflation barely responded to unemployment at all. + +Since 2008 the relationship dissolves into a scatter dominated by two outliers: the COVID recession, with double-digit unemployment and still-low inflation, and the 2021-2022 surge, with high inflation and low unemployment. + +Whatever the Phillips curve is, it is not a stable structural relationship — exactly the instability that motivates the book's account of *drifting beliefs*. + +### What the new data mean for the two stories + +The additional quarter-century neither refutes nor confirms either story cleanly, but it sharpens both. + +**For the triumph of natural-rate theory.** +The Great Moderation reads as the triumph completed: with the natural-rate consensus entrenched and central-bank independence established, inflation stayed low and expectations stayed *anchored* for a generation. + +In the book's language, the economy settled into a low-inflation self-confirming equilibrium and stayed there. + +Even the 2021-2022 surge, on this reading, supports the triumph: once the Federal Reserve moved decisively, inflation came down quickly and long-run expectations never came unmoored — the low-inflation equilibrium survived a large shock. + +**For the vindication of econometric policy evaluation.** +The surge is also a reminder that the monetary authority's *model* can still mislead it: the widely-held 2021 view that inflation would be "transitory" was a model that the data falsified, and policy adjusted only after beliefs did. + +The decade of near-zero inflation before 2020 — the apparently *flat* Phillips curve, with neither the "missing disinflation" of 2009-2013 nor the "missing inflation" of 2015-2019 fitting a stable curve — is precisely the kind of drifting empirical relationship whose changing slope and intercept the book's adaptive government tracks in real time. + +```{note} +A caveat the book itself would insist on: its mechanisms assume that the *fundamentals* — the true data-generating process — are stable, so that all the action comes from the government's evolving beliefs. The 2021-2022 episode involved genuine supply shocks (pandemic disruptions, energy prices), which lie outside that assumption. Disentangling shifting beliefs from shifting fundamentals is exactly the identification problem that makes this history so hard, and so interesting. +``` + +The tools built in the rest of this suite — self-confirming equilibria, drifting coefficients, and escape dynamics — remain a natural language for asking the question the new data pose: will a credible low-inflation equilibrium keep re-anchoring after each shock, or can a sequence of surprises still set beliefs drifting, as they did after 1965? + +The final lecture, {doc}`phillips_lost_conquest`, turns exactly these tools on the 2021-2022 surge, and asks why the Federal Reserve was so slow to respond. + +## Exercises + +```{exercise-start} +:label: ts_ex1 +``` + +The claim that "focusing on the business-cycle components sharpens the apparent Phillips curve" can be made quantitative. + +Compute the correlation between inflation and unemployment over 1960-1982 for + +* the raw series (as in Figure 1.4), and +* the Baxter-King business-cycle components (as in Figure 1.5). + +By how much does bandpass filtering sharpen the negative Phillips correlation? + +```{exercise-end} +``` + +```{solution-start} ts_ex1 +:class: dropdown +``` + +```{code-cell} ipython3 +corr_raw = data.loc[sub].corr().iloc[0, 1] +corr_cycle = bk.loc[sub].corr().iloc[0, 1] + +print(f"raw correlation, 1960-1982 : {corr_raw:+.2f}") +print(f"business-cycle correlation, 1960-82: {corr_cycle:+.2f}") +``` + +In the raw series the correlation is close to zero: the adverse *shifts* of the Phillips curve — the slowly moving expectational component that the natural-rate theory emphasizes — swamp the business-cycle tradeoff. + +Once those low-frequency shifts are filtered out, a strong negative relationship emerges, confirming that the Phillips tradeoff operates at business-cycle frequencies. + +```{solution-end} +```