Skip to content

Commit cd6cd14

Browse files
Merge pull request #143 from alexanderthclark/feature/price-controls-tutorial
price controls tutorial
2 parents bcdaed8 + 5ce26e5 commit cd6cd14

4 files changed

Lines changed: 367 additions & 1 deletion

File tree

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ received by producers, and the shaded tax revenue rectangle.
122122

123123
tutorials/double_auction
124124
tutorials/market_equilibrium
125+
tutorials/price_controls
125126
tutorials/prisoners_dilemma
126127

127128

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"id": "intro",
6+
"metadata": {},
7+
"source": [
8+
"# Price Controls\n",
9+
"\n",
10+
"This tutorial demonstrates how to model and analyze price controls (ceilings and floors) using FreeRide.\n",
11+
"\n",
12+
"## Setup\n",
13+
"\n",
14+
"First, install FreeRide (if running in Colab):"
15+
]
16+
},
17+
{
18+
"cell_type": "code",
19+
"execution_count": null,
20+
"id": "install",
21+
"metadata": {},
22+
"outputs": [],
23+
"source": [
24+
"!pip install freeride"
25+
]
26+
},
27+
{
28+
"cell_type": "markdown",
29+
"id": "concept",
30+
"metadata": {},
31+
"source": [
32+
"## The Concept\n",
33+
"\n",
34+
"Price controls are government-imposed constraints on market prices:\n",
35+
"\n",
36+
"- **Price Ceiling**: A legal maximum price (e.g., rent control)\n",
37+
"- **Price Floor**: A legal minimum price (e.g., minimum wage)\n",
38+
"\n",
39+
"When binding, price controls create market distortions:\n",
40+
"- Binding ceilings (below equilibrium) create **shortages**\n",
41+
"- Binding floors (above equilibrium) create **surpluses**\n",
42+
"- Both generate **deadweight loss** by preventing mutually beneficial trades"
43+
]
44+
},
45+
{
46+
"cell_type": "markdown",
47+
"id": "modeling",
48+
"metadata": {},
49+
"source": [
50+
"## Modeling with FreeRide\n",
51+
"\n",
52+
"Let's explore how price controls affect market outcomes:"
53+
]
54+
},
55+
{
56+
"cell_type": "code",
57+
"execution_count": null,
58+
"id": "free-market",
59+
"metadata": {},
60+
"outputs": [],
61+
"source": [
62+
"from freeride.curves import Demand, Supply\n",
63+
"from freeride.equilibrium import Market\n",
64+
"\n",
65+
"# Create a market\n",
66+
"demand = Demand.from_formula(\"P = 20 - Q\")\n",
67+
"supply = Supply.from_formula(\"P = 5 + 0.5*Q\")\n",
68+
"\n",
69+
"# Find free market equilibrium\n",
70+
"free_market = Market(demand, supply)\n",
71+
"print(f\"Free Market Equilibrium: P = ${free_market.p:.2f}, Q = {free_market.q:.0f}\")\n",
72+
"print(f\"Total Surplus: ${free_market.total_surplus:.2f}\")"
73+
]
74+
},
75+
{
76+
"cell_type": "markdown",
77+
"id": "ceilings",
78+
"metadata": {},
79+
"source": [
80+
"## Price Ceilings\n",
81+
"\n",
82+
"A binding price ceiling creates a shortage because quantity demanded exceeds quantity supplied:"
83+
]
84+
},
85+
{
86+
"cell_type": "code",
87+
"execution_count": null,
88+
"id": "ceiling-analysis",
89+
"metadata": {},
90+
"outputs": [],
91+
"source": [
92+
"# Apply a binding price ceiling at $8\n",
93+
"ceiling_market = Market(demand, supply, ceiling=8)\n",
94+
"\n",
95+
"# Calculate shortage\n",
96+
"q_demanded = demand.q(8)\n",
97+
"q_supplied = supply.q(8)\n",
98+
"shortage = q_demanded - q_supplied\n",
99+
"\n",
100+
"print(f\"With Price Ceiling at $8:\")\n",
101+
"print(f\" Quantity Supplied: {q_supplied:.0f}\")\n",
102+
"print(f\" Quantity Demanded: {q_demanded:.0f}\")\n",
103+
"print(f\" Shortage: {shortage:.0f} units\")\n",
104+
"print(f\" Deadweight Loss: ${ceiling_market.dwl:.2f}\")"
105+
]
106+
},
107+
{
108+
"cell_type": "code",
109+
"execution_count": null,
110+
"id": "ceiling-plot",
111+
"metadata": {},
112+
"outputs": [],
113+
"source": [
114+
"# Visualize the market with ceiling\n",
115+
"ceiling_market.plot(surplus=True)"
116+
]
117+
},
118+
{
119+
"cell_type": "markdown",
120+
"id": "floors",
121+
"metadata": {},
122+
"source": [
123+
"## Price Floors\n",
124+
"\n",
125+
"A binding price floor creates a surplus because quantity supplied exceeds quantity demanded:"
126+
]
127+
},
128+
{
129+
"cell_type": "code",
130+
"execution_count": null,
131+
"id": "floor-analysis",
132+
"metadata": {},
133+
"outputs": [],
134+
"source": [
135+
"# Apply a binding price floor at $12\n",
136+
"floor_market = Market(demand, supply, floor=12)\n",
137+
"\n",
138+
"# Calculate surplus\n",
139+
"q_demanded = demand.q(12)\n",
140+
"q_supplied = supply.q(12)\n",
141+
"surplus = q_supplied - q_demanded\n",
142+
"\n",
143+
"print(f\"With Price Floor at $12:\")\n",
144+
"print(f\" Quantity Demanded: {q_demanded:.0f}\")\n",
145+
"print(f\" Quantity Supplied: {q_supplied:.0f}\")\n",
146+
"print(f\" Surplus: {surplus:.0f} units\")\n",
147+
"print(f\" Deadweight Loss: ${floor_market.dwl:.2f}\")"
148+
]
149+
},
150+
{
151+
"cell_type": "code",
152+
"execution_count": null,
153+
"id": "floor-plot",
154+
"metadata": {},
155+
"outputs": [],
156+
"source": [
157+
"# Visualize the market with floor\n",
158+
"floor_market.plot(surplus=True)"
159+
]
160+
},
161+
{
162+
"cell_type": "markdown",
163+
"id": "non-binding",
164+
"metadata": {},
165+
"source": [
166+
"## Non-Binding Controls\n",
167+
"\n",
168+
"Price controls only affect the market when they're binding:"
169+
]
170+
},
171+
{
172+
"cell_type": "code",
173+
"execution_count": null,
174+
"id": "non-binding-analysis",
175+
"metadata": {},
176+
"outputs": [],
177+
"source": [
178+
"# Non-binding ceiling (above equilibrium)\n",
179+
"high_ceiling = Market(demand, supply, ceiling=15)\n",
180+
"print(f\"Non-binding ceiling at $15: P = ${high_ceiling.p:.2f}, Q = {high_ceiling.q:.0f}\")\n",
181+
"\n",
182+
"# Non-binding floor (below equilibrium)\n",
183+
"low_floor = Market(demand, supply, floor=7)\n",
184+
"print(f\"Non-binding floor at $7: P = ${low_floor.p:.2f}, Q = {low_floor.q:.0f}\")\n",
185+
"\n",
186+
"# Both should equal free market equilibrium\n",
187+
"print(f\"Free market: P = ${free_market.p:.2f}, Q = {free_market.q:.0f}\")"
188+
]
189+
}
190+
],
191+
"metadata": {
192+
"kernelspec": {
193+
"display_name": "Python 3",
194+
"language": "python",
195+
"name": "python3"
196+
},
197+
"language_info": {
198+
"codemirror_mode": {
199+
"name": "ipython",
200+
"version": 3
201+
},
202+
"file_extension": ".py",
203+
"mimetype": "text/x-python",
204+
"name": "python",
205+
"nbconvert_exporter": "python",
206+
"pygments_lexer": "ipython3",
207+
"version": "3.8.0"
208+
}
209+
},
210+
"nbformat": 4,
211+
"nbformat_minor": 5
212+
}

docs/tutorials/market_equilibrium.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,4 @@ Click the **"Open in Colab"** button above to run this example interactively! Yo
107107
3. Explore how more/less elastic demands respond differently to supply shocks
108108
4. Build intuition about price sensitivity
109109

110-
**Next:** Try the Prisoner's Dilemma tutorial at :doc:`prisoners_dilemma`!
110+
**Next:** Learn about price controls at :doc:`price_controls` or try the Prisoner's Dilemma tutorial at :doc:`prisoners_dilemma`!

docs/tutorials/price_controls.rst

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
Price Controls
2+
==============
3+
4+
.. raw:: html
5+
6+
<div style="margin-bottom: 1rem;">
7+
<a href="https://colab.research.google.com/github/alexanderthclark/FreeRide/blob/main/docs/notebooks/price_controls.ipynb" target="_blank">
8+
<img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab" style="margin-right: 10px;"/>
9+
</a>
10+
<a href="../notebooks/price_controls.ipynb" download>
11+
<img src="https://img.shields.io/badge/Download-Notebook-blue?style=flat&logo=jupyter" alt="Download Notebook"/>
12+
</a>
13+
</div>
14+
15+
16+
The Concept
17+
-----------
18+
19+
Price controls are government-imposed constraints on market prices:
20+
21+
- **Price Ceiling**: A legal maximum price (e.g., rent control)
22+
- **Price Floor**: A legal minimum price (e.g., minimum wage)
23+
24+
When binding, price controls create market distortions:
25+
- Binding ceilings (below equilibrium) create **shortages**
26+
- Binding floors (above equilibrium) create **surpluses**
27+
- Both generate **deadweight loss** by preventing mutually beneficial trades
28+
29+
Modeling with FreeRide
30+
----------------------
31+
32+
Let's explore how price controls affect market outcomes:
33+
34+
.. code-block:: python
35+
36+
from freeride.curves import Demand, Supply
37+
from freeride.equilibrium import Market
38+
39+
# Create a market
40+
demand = Demand.from_formula("P = 20 - Q")
41+
supply = Supply.from_formula("P = 5 + 0.5*Q")
42+
43+
# Find free market equilibrium
44+
free_market = Market(demand, supply)
45+
print(f"Free Market Equilibrium: P = ${free_market.p:.2f}, Q = {free_market.q:.0f}")
46+
print(f"Total Surplus: ${free_market.total_surplus:.2f}")
47+
48+
**Expected Output:**
49+
50+
.. code-block:: text
51+
52+
Free Market Equilibrium: P = $10.00, Q = 10
53+
Total Surplus: $125.00
54+
55+
Price Ceilings
56+
--------------
57+
58+
A binding price ceiling creates a shortage because quantity demanded exceeds quantity supplied:
59+
60+
.. code-block:: python
61+
62+
# Apply a binding price ceiling at $8
63+
ceiling_market = Market(demand, supply, ceiling=8)
64+
65+
# Calculate shortage
66+
q_demanded = demand.q(8)
67+
q_supplied = supply.q(8)
68+
shortage = q_demanded - q_supplied
69+
70+
print(f"With Price Ceiling at $8:")
71+
print(f" Quantity Supplied: {q_supplied:.0f}")
72+
print(f" Quantity Demanded: {q_demanded:.0f}")
73+
print(f" Shortage: {shortage:.0f} units")
74+
print(f" Deadweight Loss: ${ceiling_market.dwl:.2f}")
75+
76+
# Visualize the market with ceiling
77+
ceiling_market.plot(surplus=True)
78+
79+
**Expected Output:**
80+
81+
.. code-block:: text
82+
83+
With Price Ceiling at $8:
84+
Quantity Supplied: 6
85+
Quantity Demanded: 12
86+
Shortage: 6 units
87+
Deadweight Loss: $8.00
88+
89+
The plot shows the binding ceiling creating a wedge between quantity supplied and demanded,
90+
with the red area representing deadweight loss.
91+
92+
Price Floors
93+
------------
94+
95+
A binding price floor creates a surplus because quantity supplied exceeds quantity demanded:
96+
97+
.. code-block:: python
98+
99+
# Apply a binding price floor at $12
100+
floor_market = Market(demand, supply, floor=12)
101+
102+
# Calculate surplus
103+
q_demanded = demand.q(12)
104+
q_supplied = supply.q(12)
105+
surplus = q_supplied - q_demanded
106+
107+
print(f"With Price Floor at $12:")
108+
print(f" Quantity Demanded: {q_demanded:.0f}")
109+
print(f" Quantity Supplied: {q_supplied:.0f}")
110+
print(f" Surplus: {surplus:.0f} units")
111+
print(f" Deadweight Loss: ${floor_market.dwl:.2f}")
112+
113+
# Visualize the market with floor
114+
floor_market.plot(surplus=True)
115+
116+
**Expected Output:**
117+
118+
.. code-block:: text
119+
120+
With Price Floor at $12:
121+
Quantity Demanded: 8
122+
Quantity Supplied: 14
123+
Surplus: 6 units
124+
Deadweight Loss: $8.00
125+
126+
The plot shows the binding floor creating excess supply, with producers unable to sell all
127+
they wish at the floor price.
128+
129+
Non-Binding Controls
130+
--------------------
131+
132+
Price controls only affect the market when they're binding:
133+
134+
.. code-block:: python
135+
136+
# Non-binding ceiling (above equilibrium)
137+
high_ceiling = Market(demand, supply, ceiling=15)
138+
print(f"Non-binding ceiling at $15: P = ${high_ceiling.p:.2f}, Q = {high_ceiling.q:.0f}")
139+
140+
# Non-binding floor (below equilibrium)
141+
low_floor = Market(demand, supply, floor=7)
142+
print(f"Non-binding floor at $7: P = ${low_floor.p:.2f}, Q = {low_floor.q:.0f}")
143+
144+
# Both should equal free market equilibrium
145+
print(f"Free market: P = ${free_market.p:.2f}, Q = {free_market.q:.0f}")
146+
147+
**Expected Output:**
148+
149+
.. code-block:: text
150+
151+
Non-binding ceiling at $15: P = $10.00, Q = 10
152+
Non-binding floor at $7: P = $10.00, Q = 10
153+
Free market: P = $10.00, Q = 10

0 commit comments

Comments
 (0)