my-markdown-library 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/F24LS_md/ Lecture 4 - Public.md +347 -0
- data/F24LS_md/Lecture 1 - Introduction and Overview.md +327 -0
- data/F24LS_md/Lecture 10 - Development_.md +631 -0
- data/F24LS_md/Lecture 11 - Econometrics.md +345 -0
- data/F24LS_md/Lecture 12 - Finance.md +692 -0
- data/F24LS_md/Lecture 13 - Environmental Economics.md +299 -0
- data/F24LS_md/Lecture 15 - Conclusion.md +272 -0
- data/F24LS_md/Lecture 2 - Demand.md +349 -0
- data/F24LS_md/Lecture 3 - Supply.md +329 -0
- data/F24LS_md/Lecture 5 - Production C-D.md +291 -0
- data/F24LS_md/Lecture 6 - Utility and Latex.md +440 -0
- data/F24LS_md/Lecture 7 - Inequality.md +607 -0
- data/F24LS_md/Lecture 8 - Macroeconomics.md +704 -0
- data/F24LS_md/Lecture 8 - Macro.md +700 -0
- data/F24LS_md/Lecture 9 - Game Theory_.md +436 -0
- data/F24LS_md/summary.yaml +105 -0
- data/F24Lec_MD/LecNB_summary.yaml +206 -0
- data/F24Lec_MD/lec01/lec01.md +267 -0
- data/F24Lec_MD/lec02/Avocados_demand.md +425 -0
- data/F24Lec_MD/lec02/Demand_Steps_24.md +126 -0
- data/F24Lec_MD/lec02/PriceElasticity.md +83 -0
- data/F24Lec_MD/lec02/ScannerData_Beer.md +171 -0
- data/F24Lec_MD/lec02/demand-curve-Fa24.md +213 -0
- data/F24Lec_MD/lec03/3.0-CubicCostCurve.md +239 -0
- data/F24Lec_MD/lec03/3.1-Supply.md +274 -0
- data/F24Lec_MD/lec03/3.2-sympy.md +332 -0
- data/F24Lec_MD/lec03/3.3a-california-energy.md +120 -0
- data/F24Lec_MD/lec03/3.3b-a-really-hot-tuesday.md +121 -0
- data/F24Lec_MD/lec04/lec04-CSfromSurvey-closed.md +335 -0
- data/F24Lec_MD/lec04/lec04-CSfromSurvey.md +331 -0
- data/F24Lec_MD/lec04/lec04-Supply-Demand-closed.md +519 -0
- data/F24Lec_MD/lec04/lec04-Supply-Demand.md +514 -0
- data/F24Lec_MD/lec04/lec04-four-plot-24.md +34 -0
- data/F24Lec_MD/lec04/lec04-four-plot.md +34 -0
- data/F24Lec_MD/lec05/Lec5-Cobb-Douglas.md +131 -0
- data/F24Lec_MD/lec05/Lec5-CobbD-AER1928.md +283 -0
- data/F24Lec_MD/lec06/6.1-Sympy-Differentiation.md +253 -0
- data/F24Lec_MD/lec06/6.2-3D-utility.md +287 -0
- data/F24Lec_MD/lec06/6.3-QuantEcon-Optimization.md +399 -0
- data/F24Lec_MD/lec06/6.4-latex.md +138 -0
- data/F24Lec_MD/lec06/6.5-Edgeworth.md +269 -0
- data/F24Lec_MD/lec07/7.1-inequality.md +283 -0
- data/F24Lec_MD/lec07/7.2-historical-inequality.md +237 -0
- data/F24Lec_MD/lec08/macro-fred-api.md +313 -0
- data/F24Lec_MD/lec09/lecNB-prisoners-dilemma.md +88 -0
- data/F24Lec_MD/lec10/Lec10.2-waterguard.md +401 -0
- data/F24Lec_MD/lec10/lec10.1-mapping.md +199 -0
- data/F24Lec_MD/lec11/11.1-slr.md +305 -0
- data/F24Lec_MD/lec11/11.2-mlr.md +171 -0
- data/F24Lec_MD/lec12/Lec12-4-PersonalFinance.md +590 -0
- data/F24Lec_MD/lec12/lec12-1_Interest_Payments.md +267 -0
- data/F24Lec_MD/lec12/lec12-2-stocks-options.md +235 -0
- data/F24Lec_MD/lec13/Co2_ClimateChange.md +139 -0
- data/F24Lec_MD/lec13/ConstructingMAC.md +213 -0
- data/F24Lec_MD/lec13/EmissionsTracker.md +170 -0
- data/F24Lec_MD/lec13/KuznetsHypothesis.md +219 -0
- data/F24Lec_MD/lec13/RoslingPlots.md +217 -0
- data/F24Lec_MD/lec15/vibecession.md +485 -0
- data/F24Textbook_MD/00-intro/index.md +292 -0
- data/F24Textbook_MD/01-demand/01-demand.md +152 -0
- data/F24Textbook_MD/01-demand/02-example.md +131 -0
- data/F24Textbook_MD/01-demand/03-log-log.md +284 -0
- data/F24Textbook_MD/01-demand/04-elasticity.md +248 -0
- data/F24Textbook_MD/01-demand/index.md +15 -0
- data/F24Textbook_MD/02-supply/01-supply.md +203 -0
- data/F24Textbook_MD/02-supply/02-eep147-example.md +86 -0
- data/F24Textbook_MD/02-supply/03-sympy.md +138 -0
- data/F24Textbook_MD/02-supply/04-market-equilibria.md +204 -0
- data/F24Textbook_MD/02-supply/index.md +16 -0
- data/F24Textbook_MD/03-public/govt-intervention.md +73 -0
- data/F24Textbook_MD/03-public/index.md +10 -0
- data/F24Textbook_MD/03-public/surplus.md +351 -0
- data/F24Textbook_MD/03-public/taxes-subsidies.md +282 -0
- data/F24Textbook_MD/04-production/index.md +15 -0
- data/F24Textbook_MD/04-production/production.md +178 -0
- data/F24Textbook_MD/04-production/shifts.md +296 -0
- data/F24Textbook_MD/05-utility/budget-constraints.md +166 -0
- data/F24Textbook_MD/05-utility/index.md +15 -0
- data/F24Textbook_MD/05-utility/utility.md +136 -0
- data/F24Textbook_MD/06-inequality/historical-inequality.md +253 -0
- data/F24Textbook_MD/06-inequality/index.md +15 -0
- data/F24Textbook_MD/06-inequality/inequality.md +226 -0
- data/F24Textbook_MD/07-game-theory/bertrand.md +257 -0
- data/F24Textbook_MD/07-game-theory/cournot.md +333 -0
- data/F24Textbook_MD/07-game-theory/equilibria-oligopolies.md +96 -0
- data/F24Textbook_MD/07-game-theory/expected-utility.md +61 -0
- data/F24Textbook_MD/07-game-theory/index.md +19 -0
- data/F24Textbook_MD/07-game-theory/python-classes.md +340 -0
- data/F24Textbook_MD/08-development/index.md +35 -0
- data/F24Textbook_MD/09-macro/CentralBanks.md +101 -0
- data/F24Textbook_MD/09-macro/Indicators.md +77 -0
- data/F24Textbook_MD/09-macro/fiscal_policy.md +36 -0
- data/F24Textbook_MD/09-macro/index.md +14 -0
- data/F24Textbook_MD/09-macro/is_curve.md +76 -0
- data/F24Textbook_MD/09-macro/phillips_curve.md +70 -0
- data/F24Textbook_MD/10-finance/index.md +10 -0
- data/F24Textbook_MD/10-finance/options.md +178 -0
- data/F24Textbook_MD/10-finance/value-interest.md +60 -0
- data/F24Textbook_MD/11-econometrics/index.md +16 -0
- data/F24Textbook_MD/11-econometrics/multivariable.md +218 -0
- data/F24Textbook_MD/11-econometrics/reading-econ-papers.md +25 -0
- data/F24Textbook_MD/11-econometrics/single-variable.md +483 -0
- data/F24Textbook_MD/11-econometrics/statsmodels.md +58 -0
- data/F24Textbook_MD/12-environmental/KuznetsHypothesis-Copy1.md +187 -0
- data/F24Textbook_MD/12-environmental/KuznetsHypothesis.md +187 -0
- data/F24Textbook_MD/12-environmental/MAC.md +254 -0
- data/F24Textbook_MD/12-environmental/index.md +36 -0
- data/F24Textbook_MD/LICENSE.md +11 -0
- data/F24Textbook_MD/intro.md +26 -0
- data/F24Textbook_MD/references.md +25 -0
- data/F24Textbook_MD/summary.yaml +414 -0
- metadata +155 -0
@@ -0,0 +1,287 @@
|
|
1
|
+
---
|
2
|
+
title: "6.2-3D-utility"
|
3
|
+
type: lecture-notebook
|
4
|
+
week: 6
|
5
|
+
source_path: "/Users/ericvandusen/Documents/Data88E-ForTraining/F24Lec_NBs/lec06/6.2-3D-utility.ipynb"
|
6
|
+
---
|
7
|
+
|
8
|
+
<table style="width: 100%;" id="nb-header">
|
9
|
+
<tr style="background-color: transparent;"><td>
|
10
|
+
<img src="https://data-88e.github.io/assets/images/blue_text.png" width="250px" style="margin-left: 0;" />
|
11
|
+
</td><td>
|
12
|
+
<p style="text-align: right; font-size: 10pt;"><strong>Economic Models</strong>, <br>
|
13
|
+
Dr. Eric Van Dusen <br>
|
14
|
+
Akhil Venkatesh <br>
|
15
|
+
</table>
|
16
|
+
|
17
|
+
```python
|
18
|
+
import pandas as pd
|
19
|
+
import numpy as np
|
20
|
+
#import chart_studio.plotly as py
|
21
|
+
import plotly.graph_objs as go
|
22
|
+
from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot
|
23
|
+
from ipywidgets import interact, interactive, fixed, interact_manual
|
24
|
+
import ipywidgets as widgets
|
25
|
+
from IPython.display import display, HTML
|
26
|
+
import warnings
|
27
|
+
warnings.filterwarnings('ignore')
|
28
|
+
import plotly.io as pio
|
29
|
+
pio.renderers.default = 'notebook'
|
30
|
+
```
|
31
|
+
|
32
|
+
# Utility Functions and Indifference Curves
|
33
|
+
|
34
|
+
## What is Utility?
|
35
|
+
|
36
|
+
When we consume a good, we assume that the good will have some impact on our total utility. Utility is a fundamental measure that helps economists model how consumers make decisions. An assumed rule in economics is that consumers will always act rationally, which translates to the assumption that consumers will always attempt to maximize their own utility.
|
37
|
+
|
38
|
+
It is important to note that utility doesn't have specified units and even the face value of utility doesn't have any meaning. *What does an apple providing 5 utility units even mean?* What is valuable, however, is that utility can be compared; if an apple provides 5 utility units and an orange provides 3 utility units, then we prefer apples to oranges.
|
39
|
+
|
40
|
+
As a very simple example, say Anne has 6 dollars and she can choose to buy any combination of goods A and B. If good A costs 2 dollars and provides 5 utility units per unit of A consumed, while good B costs 3 dollars and provides 6 utility units per unit of B consumed, then Anne will buy 3 units of good A, since that maximizes her utility.
|
41
|
+
|
42
|
+
In economics, however, our models are a little more complex than that. Typically, utility is the product of the consumption of many goods; typically having a lot of one good but not another does not provide much utility. In addition, consumption of one good faces diminishing marginal returns, i.e. holding all things equal, the consumption of one additional unit of a good will provide less utility than the utility received from the previous unit. Intuitively, imagine Bob is very hungry and decides to eat slices of pizza. The first slice of pizza will bring Bob the most utility, but the 8th slice will be much less satisfying to eat.
|
43
|
+
|
44
|
+
## Utility Functions
|
45
|
+
A consumer's utility is determined by the amount of consumption from all the goods they consume. Typically, utility functions are multivariate: they take in multiple inputs (which represent the different amounts of consumption for each good, which we call a consumption bundle), and output one value, the utility. Today, we'll only look at the case where consumers can only choose between 2 goods $x_1$ and $x_2$. Hence, a utility function can be represented by: $u(x_1,x_2)$.
|
46
|
+
|
47
|
+
With that in mind, let's start graphing some utility functions!
|
48
|
+
|
49
|
+
### Cobb-Douglas Utility Function
|
50
|
+
|
51
|
+
Consider the following utility function across $x_1$ and $x_2$:
|
52
|
+
|
53
|
+
$$u(x_1, x_2)=x_1^{\alpha}x_2^{1-\alpha}\quad\text{where } 0<\alpha<1$$
|
54
|
+
|
55
|
+
This is known as the **Cobb-Douglas utility function**. To visualize this function, we'll need a 3D plot.
|
56
|
+
|
57
|
+
```python
|
58
|
+
alpha = 0.5
|
59
|
+
def cobb_douglas(x1, x2):
|
60
|
+
return (x1 ** alpha) * (x2 ** (1-alpha))
|
61
|
+
x1 = np.linspace(0,10,10)
|
62
|
+
x2 = np.linspace(0,10,10)
|
63
|
+
X1,X2 = np.meshgrid(x1,x2)
|
64
|
+
z = cobb_douglas(X1,X2)
|
65
|
+
data = [go.Surface(z=z, contours=go.surface.Contours(z=go.surface.contours.Z(show=True,usecolormap=True,highlightcolor="#42f462",project=dict(z=True))))]
|
66
|
+
layout = go.Layout(title='Cobb-Douglas Utility Function (alpha = 0.5)',autosize=False,width=500,height=500,margin=dict(l=65,r=50,b=65,t=90),
|
67
|
+
scene = dict(xaxis = dict(title='X1'),yaxis = dict(title=r'X2'),zaxis = dict(title='Utility'),))
|
68
|
+
fig = go.Figure(data=data, layout=layout)
|
69
|
+
|
70
|
+
plot(fig, filename="fig1.html", auto_open=False)
|
71
|
+
display(HTML("fig1.html"))
|
72
|
+
```
|
73
|
+
|
74
|
+
## Examining the Utility Function
|
75
|
+
|
76
|
+
There are 2 rules that utility functions generally follow:
|
77
|
+
|
78
|
+
- Non-negative marginal utility: the consumption of a good will not decrease the utility. Economists generally assume that 'more is better.' If the consumption of a good decreased utility, then we would consume less of a good.
|
79
|
+
- Diminishing marginal returns: all else equal, as consumption increases the marginal utility derived from each additional unit declines.
|
80
|
+
|
81
|
+
### Non-negative Marginal Utility
|
82
|
+
Say we are currently consuming 2 units of $x_1$ and $x_2$ each with $\alpha = \frac{1}{2}$, providing $u(2,2)=2^{0.5}2^{0.5}=2$ utility units. One additional unit of $x_1$ will provide me a higher point of utility: we can verify this result both graphically and numerically: $u(3,2)=3^{0.5}2^{0.5}\approx2.45$. Indeed, consuming one more unit of a good should increase our utility!
|
83
|
+
|
84
|
+
### Marginal Utility and the Law of Diminishing Returns
|
85
|
+
Now let's check for the second result: diminishing marginal returns. From above, we know that holding the consumption of $x_2$ constant at 2, going from 2 to 3 units of $x_1$ increases our utility by $2.45-2=0.45$. Going from 3 to 4 units of $x_1$ brings our utility to $u(4,2)=4^{0.5}2^{0.5}\approx 2.83$, an increase of $2.83-2.45=0.38$ utility units.
|
86
|
+
|
87
|
+
Using calculus, we can more formally define the marginal utility of a good. Since marginal utility is the change in utility that one additional unit of consumption provides (holding all others constant), the marginal utility with respect to $x_1$ is its partial derivative: $\frac{\partial u}{\partial x_1}$. In our case:
|
88
|
+
|
89
|
+
$$
|
90
|
+
\begin{aligned}
|
91
|
+
\textrm{Marginal Utility of } x_1: &\quad\frac{\partial u}{\partial x_1} = \frac{1}{2}x_1^{-0.5}x_2^{0.5} \\
|
92
|
+
\textrm{Marginal Utility of } x_2: &\quad\frac{\partial u}{\partial x_2} = \frac{1}{2}x_1^{0.5}x_2^{-0.5}
|
93
|
+
\end{aligned}
|
94
|
+
$$
|
95
|
+
|
96
|
+
Or, more generally,
|
97
|
+
|
98
|
+
$$\begin{aligned}
|
99
|
+
\textrm{Marginal Utility of } x_1: &\quad\frac{\partial u}{\partial x_1} = \alpha x_1^{\alpha-1}x_2^{1-\alpha} \\
|
100
|
+
\textrm{Marginal Utility of } x_2: &\quad\frac{\partial u}{\partial x_2} = (1-\alpha) x_1^{\alpha}x_2^{-\alpha}
|
101
|
+
\end{aligned}$$
|
102
|
+
|
103
|
+
|
104
|
+
With marginal utility defined, note that both conditions can be explained using the marginal utility function $\frac{\partial u}{\partial x}$:
|
105
|
+
|
106
|
+
- Non-negative marginal utility: $\frac{\partial u}{\partial x} \geq 0$
|
107
|
+
- Diminishing marginal returns: $\frac{\partial^2 u}{\partial x^2} < 0$
|
108
|
+
|
109
|
+
## Indifference Curves
|
110
|
+
|
111
|
+
Although the utility function above in 3D is cool, you'll typically find utility graphs to be in 2D with $x_1$ and $x_2$ as the axis (eliminating the utility axis).
|
112
|
+
|
113
|
+
To represent utility levels, we plot a set of indifference curves on the 2D graph. An indifference curve satisfies the property in which **any point on the curve has the exact same amount of utility**, so that consumers are _indifferent_ to any point on the curve. In our 3D plot, any point on the indifference curve has the exact same height, which represents the value of utility. If you're familar with contour plots, you can also think of indifference curves as following the same idea.
|
114
|
+
|
115
|
+
```python
|
116
|
+
alpha = 0.5
|
117
|
+
utilities = np.arange(1, 9)
|
118
|
+
x1_indiff_val = np.linspace(0,50,1000)
|
119
|
+
x2_indiff_vals = []
|
120
|
+
for u in utilities:
|
121
|
+
x2_indiff_vals.append(((u/(x1_indiff_val ** alpha)) ** (1/(1-alpha))))
|
122
|
+
traces = []
|
123
|
+
colors = ['blue', 'red','green','purple'] + ['blue', 'red','green','purple']
|
124
|
+
for u, c, x2 in zip(utilities, colors, x2_indiff_vals):
|
125
|
+
traces.append(go.Scatter(
|
126
|
+
x = x1_indiff_val,
|
127
|
+
y = x2,
|
128
|
+
name = 'utility = ' + str(u),
|
129
|
+
line = dict(color = c,width = 1)))
|
130
|
+
|
131
|
+
data = traces
|
132
|
+
|
133
|
+
# Edit the layout
|
134
|
+
layout = dict(title = 'Indifference Curves for the Cobb-Douglas Utility Function (alpha = ' + str(alpha) + ')',
|
135
|
+
xaxis = dict(title = 'X1', range = [0,10]),
|
136
|
+
yaxis = dict(title = 'X2', range = [0,10]),)
|
137
|
+
|
138
|
+
fig = dict(data=data, layout=layout)
|
139
|
+
|
140
|
+
plot(fig, filename="fig2.html", auto_open=False)
|
141
|
+
display(HTML("fig2.html"))
|
142
|
+
```
|
143
|
+
|
144
|
+
|
145
|
+
|
146
|
+
# Budget Constraints and Utility Maximization
|
147
|
+
|
148
|
+
In this section, we will assume that $\alpha = 0.5$ (i.e. the utility function is: $u(x_1, x_2) = x_1^{0.5}x_2^{0.5}$).
|
149
|
+
|
150
|
+
Now we introduce the concept of money into our model. Consumers face a budget constraint when choosing to maximize their utility. Given an income $M$ and prices $p_1$ for good $x_1$ and $p_2$ for good $x_2$, the consumer can at most spend up to $M$ for both goods:
|
151
|
+
|
152
|
+
$$M \geq p_1x_1 + p_2x_2$$
|
153
|
+
|
154
|
+
Since goods will always bring non-negative marginal utility, consumers will try to consume as many goods as they can. Hence, we can rewrite the budget constraint as an equality instead (since if they have more income leftover, they will use it to buy more goods).
|
155
|
+
|
156
|
+
$$M = p_1x_1 + p_2x_2$$
|
157
|
+
|
158
|
+
This means that any bundle of goods $(x_1,x_2)$ that consumers choose to consume will adhere to the equality above. What does this mean on our graph? Let's examine the indifference curve plots, assuming that $M = 32$, and $p_1 =2$ and $p_2 = 4$.
|
159
|
+
|
160
|
+
```python
|
161
|
+
M = 32
|
162
|
+
p_1 = 2
|
163
|
+
p_2 = 4
|
164
|
+
|
165
|
+
# Plot default indifference curves
|
166
|
+
utilities = np.arange(1, 9)
|
167
|
+
x1_indiff_val = np.linspace(0,50,1000)
|
168
|
+
x2_indiff_vals = []
|
169
|
+
for u in utilities:
|
170
|
+
x2_indiff_vals.append(((u/(x1_indiff_val ** (1/2))) ** (2)))
|
171
|
+
traces = []
|
172
|
+
colors = ['blue', 'red','green','purple'] + ['blue', 'red','green','purple']
|
173
|
+
for u,c,x2 in zip(utilities,colors,x2_indiff_vals):
|
174
|
+
traces.append(go.Scatter(
|
175
|
+
x = x1_indiff_val,
|
176
|
+
y = x2,
|
177
|
+
name = 'utility = ' + str(u),
|
178
|
+
line = dict(color = c,width = 1)))
|
179
|
+
|
180
|
+
# for i in range(len(traces) - 4):
|
181
|
+
# del traces[-1] # This is a hacky method to not continually append to TRACES upon an update from the slider.
|
182
|
+
x2_bc_val = (M - (p_1*x1_indiff_val))/p_2
|
183
|
+
traces.append(go.Scatter(
|
184
|
+
x = x1_indiff_val,
|
185
|
+
y = x2_bc_val,
|
186
|
+
name = 'Budget Constraint',
|
187
|
+
line = dict(color = 'black',width = 1,dash="dot")))
|
188
|
+
data = traces
|
189
|
+
layout = dict(title = 'Budget Constraint and Indifference Curves for the Cobb-Douglas Utility Function (alpha = 0.5)',
|
190
|
+
xaxis = dict(title = 'X1', range = [0,18]),
|
191
|
+
yaxis = dict(title = 'X2', range = [0,10]),)
|
192
|
+
fig = dict(data=data, layout=layout)
|
193
|
+
|
194
|
+
plot(fig, filename="fig3.html", auto_open=False)
|
195
|
+
display(HTML("fig3.html"))
|
196
|
+
```
|
197
|
+
|
198
|
+
The budget constraint is like a possibilities curve: moving up or down the constraint means gaining more of one good while sacrificing the other.
|
199
|
+
|
200
|
+
Let's take a look at what this budget constraint means. Because of the budget constraint, any bundle of goods $(x_1,x_2)$ that consumers ultimately decide to consume will lie on the budget constraint line. Adhering to this constraint where $M=32, p_1 = 2, p_2 = 4$, we can see that consumers will be able to achieve 2 units of utility, and can also achieve 4 units of utility. But what is the maximum amount of utility that consumers can achieve?
|
201
|
+
|
202
|
+
Notice an interesting property about indifference curves: **the utility level of the indifference curves gets larger as we move up and to the right.** Hence, the maximizing amount of utility in this budget constraint is the rightmost indifference curve that still touches the budget constraint line. In fact, it'll only 'touch' (and not intersect) the budget constraint and be tangential to it.
|
203
|
+
|
204
|
+
```python
|
205
|
+
M = 32
|
206
|
+
p_1 = 2
|
207
|
+
p_2 = 4
|
208
|
+
|
209
|
+
# Plot default indifference curves
|
210
|
+
utilities = np.arange(1, 9)
|
211
|
+
x1_indiff_val = np.linspace(0,50,1000)
|
212
|
+
x2_indiff_vals = []
|
213
|
+
for u in utilities:
|
214
|
+
x2_indiff_vals.append(((u/(x1_indiff_val ** (1/2))) ** (2)))
|
215
|
+
traces = []
|
216
|
+
colors = ['blue', 'red','green','purple'] + ['blue', 'red','green','purple']
|
217
|
+
for u,c,x2 in zip(utilities,colors,x2_indiff_vals):
|
218
|
+
traces.append(go.Scatter(
|
219
|
+
x = x1_indiff_val,
|
220
|
+
y = x2,
|
221
|
+
name = 'utility = ' + str(u),
|
222
|
+
line = dict(color = c,width = 1)))
|
223
|
+
|
224
|
+
# PLOT BC
|
225
|
+
x2_bc_val = (M - (p_1*x1_indiff_val))/p_2
|
226
|
+
traces.append(go.Scatter(
|
227
|
+
x = x1_indiff_val,
|
228
|
+
y = x2_bc_val,
|
229
|
+
name = 'Budget Constraint',
|
230
|
+
line = dict(color = 'black',width = 1,dash="dot")))
|
231
|
+
|
232
|
+
|
233
|
+
# PLOT MAX UTIL INDIFF CURVE
|
234
|
+
max_utility = ((1/2*M/p_1) ** (1/2)) * ((1/2*M/p_2) ** (1/2))
|
235
|
+
x2_max_util = (max_utility/(x1_indiff_val ** (1/2))) ** 2
|
236
|
+
x2_max_util = (max_utility/(x1_indiff_val ** (1/2))) ** 2
|
237
|
+
traces.append(go.Scatter(
|
238
|
+
x = x1_indiff_val,
|
239
|
+
y = x2_max_util,
|
240
|
+
name = 'Maximized Utility = ' + str(round(max_utility, 2)),
|
241
|
+
line = dict(color = 'black',width = 2)))
|
242
|
+
data = traces
|
243
|
+
|
244
|
+
layout = dict(title = 'Budget Constraint and Indifference Curves for the Cobb-Douglas Utility Function (alpha = 0.5)',
|
245
|
+
xaxis = dict(title = 'X1', range = [0,20]),
|
246
|
+
yaxis = dict(title = 'X2', range = [0,15]),)
|
247
|
+
fig = dict(data=data, layout=layout)
|
248
|
+
|
249
|
+
plot(fig, filename="fig4.html", auto_open=False)
|
250
|
+
display(HTML("fig4.html"))
|
251
|
+
```
|
252
|
+
|
253
|
+
Notice that as the price of one good increases, the indifference curve that represents the maximum attainable utility shifts towards the left (i.e. the max utility decreases). Intuitively, this makes sense. As the price of one good increases, consumers have to make adjustments to their consumption bundles and buy less of one, or both, goods. Hence, their maximum utility will decrease.
|
254
|
+
|
255
|
+
Let's visualize the budget constraint in 3D where $M=30, p_1=3, p_2=3$. Here, any point along the curve in which the 2 planes intersect represents an amount of utility in which the budget constraint holds true (i.e. where we've spent all our income). The utility maximizing quantity is a point on this intersecting curve at which the utility level is the highest.
|
256
|
+
|
257
|
+
```python
|
258
|
+
def cobb_douglas(x1, x2):
|
259
|
+
return (x1 ** (1/2)) * (x2 ** (1/2))
|
260
|
+
x1 = np.linspace(0,10,10)
|
261
|
+
x2 = np.linspace(0,10,10)
|
262
|
+
X1,X2 = np.meshgrid(x1,x2)
|
263
|
+
z = cobb_douglas(X1,X2)
|
264
|
+
|
265
|
+
def budget_constraint(x1, x2):
|
266
|
+
return 10000*(3*x1 + 3*x2 - 30) # We multiply this by 10000 to get a very steep plane, which should be similar to the actual BC, a vertical plane.
|
267
|
+
|
268
|
+
z2 = budget_constraint(X1, X2)
|
269
|
+
|
270
|
+
data = [go.Surface(
|
271
|
+
z=z, contours=go.surface.Contours(z=go.surface.contours.Z(show=True,usecolormap=True,highlightcolor="#42f462",
|
272
|
+
project=dict(z=True))), name="Cobb-Douglas Utility Function"),
|
273
|
+
go.Surface(
|
274
|
+
z=z2, contours=go.surface.Contours(z=go.surface.contours.Z(show=True,usecolormap=False,
|
275
|
+
highlightcolor="#42f462",project=dict(z=True))),showscale=False, colorscale="balance", name="Budget Constraint")]
|
276
|
+
layout = go.Layout(
|
277
|
+
title='Cobb-Douglas Utility Function with Budget Constraint', autosize=False,width=500, height=500, margin=dict(l=65,r=50,b=65,t=90),
|
278
|
+
scene = dict(xaxis = dict(title='X1', range = [0,10]), yaxis = dict(title='X2'),
|
279
|
+
zaxis = dict(title = 'Utility', nticks=4, range = [0,10],)))
|
280
|
+
fig = go.Figure(data=data, layout=layout)
|
281
|
+
|
282
|
+
plot(fig, filename="fig5.html", auto_open=False)
|
283
|
+
display(HTML("fig5.html"))
|
284
|
+
```
|
285
|
+
|
286
|
+
|
287
|
+
|
@@ -0,0 +1,399 @@
|
|
1
|
+
---
|
2
|
+
title: "6.3-QuantEcon-Optimization"
|
3
|
+
type: lecture-notebook
|
4
|
+
week: 6
|
5
|
+
source_path: "/Users/ericvandusen/Documents/Data88E-ForTraining/F24Lec_NBs/lec06/6.3-QuantEcon-Optimization.ipynb"
|
6
|
+
---
|
7
|
+
|
8
|
+
# Optimization from QuantEcon
|
9
|
+
|
10
|
+
This is a Lecture written by the QuantEcon project
|
11
|
+
https://datascience.quantecon.org/scientific/optimization.html
|
12
|
+
|
13
|
+
|
14
|
+
|
15
|
+
**Prerequisites**
|
16
|
+
|
17
|
+
- [Introduction to Numpy](https://datascience.quantecon.org/numpy_arrays.html)
|
18
|
+
- [Applied Linear Algebra](https://datascience.quantecon.org/applied_linalg.html)
|
19
|
+
|
20
|
+
|
21
|
+
**Outcomes**
|
22
|
+
|
23
|
+
- Perform optimization by hand using derivatives
|
24
|
+
- Understand ideas from gradient descent
|
25
|
+
|
26
|
+
```python
|
27
|
+
import numpy as np
|
28
|
+
import matplotlib.pyplot as plt
|
29
|
+
%matplotlib inline
|
30
|
+
```
|
31
|
+
|
32
|
+
## What is Optimization?
|
33
|
+
|
34
|
+
Optimization is the branch of mathematics focused on finding extreme values (max or min) of
|
35
|
+
functions.
|
36
|
+
|
37
|
+
Optimization tools will appear in many places throughout this course, including:
|
38
|
+
|
39
|
+
- Building economic models in which individuals make decisions that maximize their utility.
|
40
|
+
- Building statistical models and maximizing the fit of these models by optimizing certain fit
|
41
|
+
functions.
|
42
|
+
|
43
|
+
|
44
|
+
In this lecture, we will focus mostly on the first to limit the moving pieces, but in other lectures, we’ll discuss the second in detail.
|
45
|
+
|
46
|
+
### Derivatives and Optima
|
47
|
+
|
48
|
+
Here, we revisit some of the theory that you have already learned in your calculus class.
|
49
|
+
|
50
|
+
Consider function $ f(x) $ which maps a number into another number. We can say that any point
|
51
|
+
where $ f'(x) = 0 $ is a local extremum of $ f $.
|
52
|
+
|
53
|
+
Let’s work through an example. Consider the function
|
54
|
+
|
55
|
+
$$
|
56
|
+
f(x) = x^4 - 3 x^2
|
57
|
+
$$
|
58
|
+
|
59
|
+
Its derivative is given by
|
60
|
+
|
61
|
+
$$
|
62
|
+
\frac{\partial f}{\partial x} = 4 x^3 - 6 x
|
63
|
+
$$
|
64
|
+
|
65
|
+
Let’s plot the function and its derivative to pick out the local extremum by hand.
|
66
|
+
|
67
|
+
```python
|
68
|
+
def f(x):
|
69
|
+
return x**4 - 3*x**2
|
70
|
+
|
71
|
+
|
72
|
+
def fp(x):
|
73
|
+
return 4*x**3 - 6*x
|
74
|
+
|
75
|
+
# Create 100 evenly spaced points between -2 and 2
|
76
|
+
x = np.linspace(-2., 2., 100)
|
77
|
+
|
78
|
+
# Evaluate the functions at x values
|
79
|
+
fx = f(x)
|
80
|
+
fpx = fp(x)
|
81
|
+
|
82
|
+
# Create plot
|
83
|
+
fig, ax = plt.subplots(1, 2)
|
84
|
+
|
85
|
+
ax[0].plot(x, fx)
|
86
|
+
ax[0].set_title("Function")
|
87
|
+
|
88
|
+
ax[1].plot(x, fpx)
|
89
|
+
ax[1].hlines(0.0, -2.5, 2.5, color="k", linestyle="--")
|
90
|
+
ax[1].set_title("Derivative")
|
91
|
+
|
92
|
+
for _ax in ax:
|
93
|
+
_ax.spines["right"].set_visible(False)
|
94
|
+
_ax.spines["top"].set_visible(False)
|
95
|
+
```
|
96
|
+
|
97
|
+
If you stare at this picture, you can probably determine the the local maximum is at
|
98
|
+
$ x = 0 $ and the local minima at $ x \approx -1 $ and $ x \approx 1 $.
|
99
|
+
|
100
|
+
To properly determine the minima and maxima, we find the solutions to $ f'(x) = 0 $ below:
|
101
|
+
|
102
|
+
$$
|
103
|
+
f'(x) = 4 x^3 - 6 x = 0
|
104
|
+
$$
|
105
|
+
|
106
|
+
$$
|
107
|
+
\rightarrow x = \left\{0, \frac{\sqrt{6}}{2}, \frac{-\sqrt{6}}{2} \right\}
|
108
|
+
$$
|
109
|
+
|
110
|
+
Let’s check whether we can get the same answers with Python! To do this, we import a new
|
111
|
+
package that we haven’t seen yet.
|
112
|
+
|
113
|
+
```python
|
114
|
+
import scipy.optimize as opt
|
115
|
+
```
|
116
|
+
|
117
|
+
Then using the function definitions from earlier, we search for the minimum and maximum values.
|
118
|
+
|
119
|
+
```python
|
120
|
+
# For a scalar problem, we give it the function and the bounds between
|
121
|
+
# which we want to search
|
122
|
+
neg_min = opt.minimize_scalar(f, [-2, -0.5])
|
123
|
+
pos_min = opt.minimize_scalar(f, [0.5, 2.0])
|
124
|
+
print("The negative minimum is: \n", neg_min)
|
125
|
+
print("The positive minimum is: \n", pos_min)
|
126
|
+
```
|
127
|
+
|
128
|
+
The scipy optimize package only has functions that find minimums… You might be wondering, then, how we
|
129
|
+
will verify our maximum value.
|
130
|
+
|
131
|
+
It turns out that finding the maximum is equivalent to simply finding the minimum of the negative function.
|
132
|
+
|
133
|
+
```python
|
134
|
+
# Create a function that evaluates to negative f
|
135
|
+
def neg_f(x):
|
136
|
+
return -f(x)
|
137
|
+
|
138
|
+
max_out = opt.minimize_scalar(neg_f, [-0.35, 0.35])
|
139
|
+
print("The maximum is: \n", max_out)
|
140
|
+
```
|
141
|
+
|
142
|
+
We won’t dive into the details of optimization algorithms in this lecture, but we’ll impart some brief
|
143
|
+
intuition to help you understand the types of problems these algorithms are good at solving and
|
144
|
+
the types of problems they will struggle with:
|
145
|
+
|
146
|
+
The general intuition is that when you’re finding a maximum, an algorithm takes a step
|
147
|
+
in the direction of the derivative… (Conversely, to find a minimum, the algorithm takes a step opposite the direction of the derivative.)
|
148
|
+
This requires the function to be relatively smooth and continuous. The algorithm also has an easier time if there is only one (or very few) extremum to be found…
|
149
|
+
|
150
|
+
For minimization, you can imagine the algorithm as a marble in a bowl.
|
151
|
+
|
152
|
+
The marble will keep rolling down the slope of the bowl until it finds the bottom.
|
153
|
+
|
154
|
+
It may overshoot, but once it hits the slope on the other side, it will continue to roll back
|
155
|
+
and forth until it comes to rest.
|
156
|
+
|
157
|
+
Thus, when deciding whether numerical optimization is an effective method for a
|
158
|
+
particular problem, you could try visualizing the function to determine whether a marble
|
159
|
+
would be able to come to rest at the extreme values you are looking for.
|
160
|
+
|
161
|
+
### Application: Consumer Theory
|
162
|
+
|
163
|
+
A common use of maximization in economics is to model
|
164
|
+
optimal consumption decisions [https://en.wikipedia.org/wiki/Consumer_choice](https://en.wikipedia.org/wiki/Consumer_choice).
|
165
|
+
|
166
|
+
#### Preferences and Utility Functions
|
167
|
+
|
168
|
+
To summarize introductory economics, take a set of
|
169
|
+
[preferences](https://en.wikipedia.org/wiki/Preference_%28economics%29) of consumers over “bundles”
|
170
|
+
of goods (e.g. 2 apples and 3 oranges is preferred to 3 apples and 2 oranges, or a 100% chance to
|
171
|
+
win $ 1 $ dollar is preferred to a 50% chance to win $ 2.10 $ dollars).
|
172
|
+
|
173
|
+
Under certain assumptions, you rationalize the preferences as a utility function over the different
|
174
|
+
goods (always remembering that the utility is simply a tool to order preferences and the numbers are
|
175
|
+
usually not meaningful themselves).
|
176
|
+
|
177
|
+
For example, consider a utility function over bundles of bananas (B) and apples (A)
|
178
|
+
|
179
|
+
$$
|
180
|
+
U(B, A) = B^{\alpha}A^{1-\alpha}
|
181
|
+
$$
|
182
|
+
|
183
|
+
Where $ \alpha \in [0,1] $.
|
184
|
+
|
185
|
+
First, let’s take a look at this particular utility function.
|
186
|
+
|
187
|
+
```python
|
188
|
+
def U(A, B, alpha=1/3):
|
189
|
+
return B**alpha * A**(1-alpha)
|
190
|
+
|
191
|
+
fig, ax = plt.subplots()
|
192
|
+
B = 1.5
|
193
|
+
A = np.linspace(1, 10, 100)
|
194
|
+
ax.plot(A, U(A, B))
|
195
|
+
ax.set_xlabel("A")
|
196
|
+
ax.set_ylabel("U(B=1.5, A)")
|
197
|
+
```
|
198
|
+
|
199
|
+
We note that
|
200
|
+
|
201
|
+
- $ U(B,1) $ is always higher with more B, hence, consuming more bananas has a
|
202
|
+
: positive marginal utility i.e. $ \frac{d U(B,1)}{d B} > 0 $.
|
203
|
+
- The more bananas we consume, the smaller the change in marginal utility, i.e.
|
204
|
+
$ \frac{d^2 U(B,1)}{d B^2} < 0 $.
|
205
|
+
|
206
|
+
|
207
|
+
If we plot both the $ B $ and the $ A $, we can see how the utility changes with different
|
208
|
+
bundles.
|
209
|
+
|
210
|
+
```python
|
211
|
+
fig, ax = plt.subplots()
|
212
|
+
B = np.linspace(1, 20, 100).reshape((100, 1))
|
213
|
+
contours = ax.contourf(A, B.flatten(), U(A, B))
|
214
|
+
fig.colorbar(contours)
|
215
|
+
ax.set_xlabel("A")
|
216
|
+
ax.set_ylabel("B")
|
217
|
+
ax.set_title("U(A,B)")
|
218
|
+
```
|
219
|
+
|
220
|
+
We can find the bundles between which the consumer would be indifferent by fixing a
|
221
|
+
utility $ \bar{U} $ and by determining all combinations of $ A $ and $ B $ where
|
222
|
+
$ \bar{U} = U(B, A) $.
|
223
|
+
|
224
|
+
In this example, we can implement this calculation by letting $ B $ be the variable on the
|
225
|
+
x-axis and solving for $ A(\bar{U}, B) $
|
226
|
+
|
227
|
+
$$
|
228
|
+
A(B, \bar{U}) = U^{\frac{1}{1-\alpha}}B^{\frac{-\alpha}{1-\alpha}}
|
229
|
+
$$
|
230
|
+
|
231
|
+
```python
|
232
|
+
def A_indifference(B, ubar, alpha=1/3):
|
233
|
+
return ubar**(1/(1-alpha)) * B**(-alpha/(1-alpha))
|
234
|
+
|
235
|
+
def plot_indifference_curves(ax, alpha=1/3):
|
236
|
+
ubar = np.arange(1, 11, 2)
|
237
|
+
ax.plot(B, A_indifference(B, ubar, alpha))
|
238
|
+
ax.legend([r"$\bar{U}$" + " = {}".format(i) for i in ubar])
|
239
|
+
ax.set_xlabel("B")
|
240
|
+
ax.set_ylabel(r"$A(B, \bar{U}$)")
|
241
|
+
|
242
|
+
fig, ax = plt.subplots()
|
243
|
+
plot_indifference_curves(ax)
|
244
|
+
```
|
245
|
+
|
246
|
+
Note that in every case, if you increase either the number of apples or bananas (holding the other
|
247
|
+
fixed), you reach a higher indifference curve.
|
248
|
+
|
249
|
+
Consequently, in a world without scarcity or budgets, consumers would consume
|
250
|
+
an arbitrarily high number of both to maximize their utility.
|
251
|
+
|
252
|
+
#### Budget Constraints
|
253
|
+
|
254
|
+
While the above example plots consumer preferences, it says nothing about what the consumers can afford.
|
255
|
+
|
256
|
+
The simplest sort of constraint is a budget constraint where bananas and apples both have a price
|
257
|
+
and the consumer has a limited amount of funds.
|
258
|
+
|
259
|
+
If the prices per banana and per apple are identical, no matter how many you consume, then the
|
260
|
+
affordable bundles are simply all pairs of apples and bananas below the line.
|
261
|
+
$ p_a A + p_b B \leq W $.
|
262
|
+
|
263
|
+
For example, if consumer has a budget of $ W $, the price of apples is $ p_A = 2 $ dollars per
|
264
|
+
apple, and the price of bananas is normalized to be $ p_B = 1 $ dollar per banana, then the consumer
|
265
|
+
can afford anything below the line.
|
266
|
+
|
267
|
+
$$
|
268
|
+
2 A + B \leq W
|
269
|
+
$$
|
270
|
+
|
271
|
+
Or, letting $ W = 20 $ and plotting
|
272
|
+
|
273
|
+
```python
|
274
|
+
def A_bc(B, W=20, pa=2):
|
275
|
+
"Given B, W, and pa return the max amount of A our consumer can afford"
|
276
|
+
return (W - B) / pa
|
277
|
+
|
278
|
+
def plot_budget_constraint(ax, W=20, pa=2):
|
279
|
+
B_bc = np.array([0, W])
|
280
|
+
A = A_bc(B_bc, W, pa)
|
281
|
+
ax.plot(B_bc, A)
|
282
|
+
ax.fill_between(B_bc, 0, A, alpha=0.2)
|
283
|
+
ax.set_xlabel("B")
|
284
|
+
ax.set_ylabel("A")
|
285
|
+
return ax
|
286
|
+
|
287
|
+
fig, ax = plt.subplots()
|
288
|
+
plot_budget_constraint(ax, 20, 2)
|
289
|
+
```
|
290
|
+
|
291
|
+
While the consumer can afford any of the bundles in that area, most will not be optimal.
|
292
|
+
|
293
|
+
#### Optimal Choice
|
294
|
+
|
295
|
+
Putting the budget constraints and the utility functions together lets us visualize the optimal
|
296
|
+
decision of a consumer. Choose the bundle with the highest possible indifference curve within its
|
297
|
+
budget set.
|
298
|
+
|
299
|
+
```python
|
300
|
+
fig, ax = plt.subplots()
|
301
|
+
plot_indifference_curves(ax)
|
302
|
+
plot_budget_constraint(ax)
|
303
|
+
```
|
304
|
+
|
305
|
+
We have several ways to find the particular point $ A, B $ of maximum utility, such as
|
306
|
+
finding the point where the indifference curve and the budget constraint have the same slope, but a
|
307
|
+
simple approach is to just solve the direct maximization problem.
|
308
|
+
|
309
|
+
$$
|
310
|
+
\begin{aligned}
|
311
|
+
\max_{A, B} & B^{\alpha}A^{1-\alpha}\\
|
312
|
+
\text{s.t. } & p_A A + B \leq W
|
313
|
+
\end{aligned}
|
314
|
+
$$
|
315
|
+
|
316
|
+
Solving this problem directly requires solving a multi-dimensional constrained optimization problem,
|
317
|
+
where scipy [https://docs.scipy.org/doc/scipy/reference/tutorial/optimize.html#constrained-minimization-of-multivariate-scalar-functions-minimize](https://docs.scipy.org/doc/scipy/reference/tutorial/optimize.html#constrained-minimization-of-multivariate-scalar-functions-minimize)
|
318
|
+
has several options.
|
319
|
+
|
320
|
+
For this particular problem, we notice two things: (1) The utility function is increasing in both
|
321
|
+
$ A $ and $ B $, and (2) there are only 2 goods.
|
322
|
+
|
323
|
+
This allows us 1) to assume that the budget constraint holds at equality, $ p_a A + B = W $, 2) to
|
324
|
+
form a new function $ A(B) = (W - B) / p_a $ by rearranging the budget constraint at equality, and
|
325
|
+
3) to substitute that function directly to form:
|
326
|
+
|
327
|
+
$$
|
328
|
+
\max_{B} B^{\alpha}A(B)^{1-\alpha}
|
329
|
+
$$
|
330
|
+
|
331
|
+
Compared to before, this problem has been turned into an unconstrained univariate optimization
|
332
|
+
problem.
|
333
|
+
|
334
|
+
To implement this in code, notice that the $ A(B) $ function is what we defined before
|
335
|
+
as `A_bc`.
|
336
|
+
|
337
|
+
We will solve this by using the function `scipy.optimize.minimize_scalar`, which takes a function
|
338
|
+
`f(x)` and returns the value of `x` that minimizes `f`.
|
339
|
+
|
340
|
+
```python
|
341
|
+
from scipy.optimize import minimize_scalar
|
342
|
+
|
343
|
+
def objective(B, W=20, pa=2):
|
344
|
+
"""
|
345
|
+
Return value of -U for a given B, when we consume as much A as possible
|
346
|
+
|
347
|
+
Note that we return -U because scipy wants to minimize functions,
|
348
|
+
and the value of B that minimizes -U will maximize U
|
349
|
+
"""
|
350
|
+
A = A_bc(B, W, pa)
|
351
|
+
return -U(A, B)
|
352
|
+
|
353
|
+
result = minimize_scalar(objective)
|
354
|
+
optimal_B = result.x
|
355
|
+
optimal_A = A_bc(optimal_B, 20, 2)
|
356
|
+
optimal_U = U(optimal_A, optimal_B)
|
357
|
+
|
358
|
+
print("The optimal U is ", optimal_U)
|
359
|
+
print("and was found at (A,B) =", (optimal_A, optimal_B))
|
360
|
+
```
|
361
|
+
|
362
|
+
This allows us to do experiments, such as examining how consumption patterns change as prices or
|
363
|
+
wealth levels change.
|
364
|
+
|
365
|
+
But First, Let's fix the graph!
|
366
|
+
Lets add UBar to the Set of Indifference Curves
|
367
|
+
|
368
|
+
```python
|
369
|
+
def A_indifference(B, ubar, alpha=1/3):
|
370
|
+
return ubar**(1/(1-alpha)) * B**(-alpha/(1-alpha))
|
371
|
+
|
372
|
+
def plot_indifference_curves(ax, alpha=1/3):
|
373
|
+
ubar = np.arange(1, 11, 2)
|
374
|
+
ubar = np.append(ubar, optimal_U)
|
375
|
+
ax.plot(B, A_indifference(B, ubar, alpha))
|
376
|
+
ax.legend([r"$\bar{U}$" + " = {}".format(i) for i in ubar])
|
377
|
+
ax.set_xlabel("B")
|
378
|
+
ax.set_ylabel(r"$A(B, \bar{U}$)")
|
379
|
+
|
380
|
+
fig, ax = plt.subplots()
|
381
|
+
plot_indifference_curves(ax)
|
382
|
+
```
|
383
|
+
|
384
|
+
Then lets set the optimal level of consumpton of A and B
|
385
|
+
and add them to the graph
|
386
|
+
|
387
|
+
```python
|
388
|
+
fig, ax = plt.subplots()
|
389
|
+
plot_indifference_curves(ax)
|
390
|
+
plot_budget_constraint(ax)
|
391
|
+
ax.plot(optimal_B, optimal_A, 'go', label=f"Optimal Point (A, B) = ({optimal_A:.2f}, {optimal_B:.2f})")
|
392
|
+
ax.annotate(f"({optimal_A:.2f}, {optimal_B:.2f})", (optimal_B, optimal_A),
|
393
|
+
textcoords="offset points", xytext=(5,5), ha='center')
|
394
|
+
```
|
395
|
+
|
396
|
+
```python
|
397
|
+
|
398
|
+
```
|
399
|
+
|