File size: 9,206 Bytes
b0c7d17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0fb1235
b0c7d17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0fb1235
b0c7d17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0fb1235
b0c7d17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0fb1235
 
 
b0c7d17
0fb1235
 
b0c7d17
 
 
0fb1235
b0c7d17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0fb1235
b0c7d17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97aadea
b0c7d17
 
0fb1235
 
b0c7d17
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199

import streamlit as st
import numpy as np
import matplotlib.pyplot as plt

def calculate_metrics(a, b, c, d, price_control=None):
    """
    Calculates equilibrium, consumer surplus, producer surplus, and deadweight loss.

    Args:
        a (float): Y-intercept of the supply curve (P = a + bQ).
        b (float): Slope of the supply curve.
        c (float): Y-intercept of the demand curve (P = c - dQ).
        d (float): Slope of the demand curve.
        price_control (float, optional): An enforced price (floor or ceiling). Defaults to None.

    Returns:
        tuple: (Q_eq, P_eq, CS, PS, TS, DWL, Q_traded, price_control)
               Returns (0, 0, 0, 0, 0, 0, 0, price_control) if parameters are invalid.
    """
    # Ensure positive slopes for realistic curves
    b = max(0.01, b)  # Supply slope
    d = max(0.01, d)  # Demand slope

    # Check for valid parameters that lead to an intersection with positive quantity
    if (c - a) <= 0: # Demand intercept must be above supply intercept for Q_eq > 0
        return 0, 0, 0, 0, 0, 0, 0, price_control

    # 1. Equilibrium Calculation
    Q_eq = (c - a) / (d + b)
    P_eq = a + b * Q_eq

    # Ensure valid equilibrium (positive quantity and price)
    if Q_eq < 0.01 or P_eq < 0.01: # Check for near zero or negative values
        return 0, 0, 0, 0, 0, 0, 0, price_control

    # 2. Consumer Surplus (CS)
    # Area of the triangle above equilibrium price and below demand curve
    CS = 0.5 * Q_eq * (c - P_eq)
    CS = max(0, CS) # Ensure non-negative

    # 3. Producer Surplus (PS)
    # Area of the triangle below equilibrium price and above supply curve
    PS = 0.5 * Q_eq * (P_eq - a)
    PS = max(0, PS) # Ensure non-negative

    # 4. Total Surplus
    TS = CS + PS

    # 5. Deadweight Loss (DWL) and Quantity Traded under Price Control
    DWL = 0
    Q_traded = Q_eq  # Initialize quantity traded to equilibrium quantity

    if price_control is not None and price_control > 0: # If a price control is set
        # Calculate quantity demanded and supplied at the controlled price
        Q_demanded_at_pc = (c - price_control) / d
        Q_supplied_at_pc = (price_control - a) / b

        # The actual quantity traded is the minimum of Qd and Qs at the controlled price,
        # ensuring it's non-negative (cannot trade negative quantity).
        Q_traded = min(max(0, Q_demanded_at_pc), max(0, Q_supplied_at_pc))

        # Calculate DWL if trade is restricted from equilibrium
        if Q_traded < Q_eq:
            # Price on the demand curve at the restricted quantity traded
            P_demand_at_Q_traded = c - d * Q_traded
            # Price on the supply curve at the restricted quantity traded
            P_supply_at_Q_traded = a + b * Q_traded

            # DWL is the area of the triangle between demand and supply curves
            # from Q_traded to Q_eq. Height is the vertical distance between D & S at Q_traded.
            DWL = 0.5 * (Q_eq - Q_traded) * (P_demand_at_Q_traded - P_supply_at_Q_traded)
            DWL = max(0, DWL) # Ensure DWL is non-negative

    return Q_eq, P_eq, CS, PS, TS, DWL, Q_traded, price_control


st.set_page_config(layout="wide", page_title="Supply & Demand Model")
st.title("๐Ÿ“Š Econ 101: Supply and Demand Model")

st.sidebar.header("โš™๏ธ Adjust Model Parameters")

# Sliders for demand curve: P = c - dQ
st.sidebar.subheader("Demand Curve: P = `c` - `d`Q")
c = st.sidebar.slider("c (Demand Intercept)", 10.0, 200.0, 100.0, 1.0, help="Maximum price consumers are willing to pay for 0 quantity.")
d = st.sidebar.slider("d (Demand Slope)", 0.1, 10.0, 2.0, 0.1, help="How much price decreases for each unit demanded.")

# Sliders for supply curve: P = a + bQ
st.sidebar.subheader("Supply Curve: P = `a` + `b`Q")
a = st.sidebar.slider("a (Supply Intercept)", 0.0, 100.0, 10.0, 1.0, help="Minimum price producers are willing to accept for 0 quantity.")
b = st.sidebar.slider("b (Supply Slope)", 0.1, 10.0, 1.0, 0.1, help="How much price increases for each unit supplied.")

# Price control slider
st.sidebar.subheader("Enforced Price (e.g., Price Floor/Ceiling)")
price_control = st.sidebar.slider("Set Enforced Price (0 for none)", 0.0, 200.0, 0.0, 0.5,
                                  help="Set a price floor or ceiling. If 0, no price control is active.")

# Perform calculations
Q_eq, P_eq, CS, PS, TS, DWL, Q_traded, pc_used = calculate_metrics(a, b, c, d, price_control)

# Display results and plot
col1, col2 = st.columns([1, 2])

with col1:
    st.header("๐Ÿ“ˆ Economic Metrics")
    if Q_eq == 0 and P_eq == 0 and CS == 0: # Indicates invalid parameters from calculate_metrics
        st.error("Invalid parameters: Please adjust 'c' to be greater than 'a' to ensure a valid equilibrium.")
    else:
        st.metric("Equilibrium Quantity (Qe)", f"{Q_eq:.2f}")
        st.metric("Equilibrium Price (Pe)", f"${P_eq:.2f}")
        st.metric("Consumer Surplus (CS)", f"${CS:.2f}")
        st.metric("Producer Surplus (PS)", f"${PS:.2f}")
        st.metric("Total Surplus (TS)", f"${TS:.2f}")

        if price_control > 0: # Only show these metrics if a price control is active
            st.subheader("Metrics with Price Control")
            st.metric("Enforced Price", f"${pc_used:.2f}")
            st.metric("Quantity Traded (Q_traded)", f"{Q_traded:.2f}")
            st.metric("Deadweight Loss (DWL)", f"${DWL:.2f}")
            if DWL > 0:
                st.warning("Deadweight Loss indicates market inefficiency due to the enforced price.")
            else:
                st.info("No Deadweight Loss with current enforced price (or price is ineffective).")
        else:
            st.info("Set 'Enforced Price' to calculate Deadweight Loss.")


with col2:
    st.header("๐Ÿ“‰ Supply and Demand Graph")
    fig, ax = plt.subplots(figsize=(12, 8))

    # Determine appropriate x and y axis limits for plotting
    max_Q_plot = max(Q_eq * 1.5, (c / d) * 1.1, 20) # Extends beyond equilibrium and demand intercept
    max_P_plot = max(P_eq * 1.5, c * 1.1, 150) # Extends beyond equilibrium and demand intercept

    Q_values = np.linspace(0, max_Q_plot, 400)

    # Demand Curve: P = c - dQ
    P_demand = c - d * Q_values
    # Supply Curve: P = a + bQ
    P_supply = a + b * Q_values

    # Filter out negative prices/quantities for plotting realism
    P_demand[P_demand < 0] = np.nan # Don't plot negative prices
    P_supply[P_supply < 0] = np.nan # Don't plot negative prices
    Q_values[Q_values < 0] = np.nan # Don't plot negative quantities

    ax.plot(Q_values, P_demand, label=f'Demand: P = {c:.1f} - {d:.1f}Q', color='blue', linewidth=2)
    ax.plot(Q_values, P_supply, label=f'Supply: P = {a:.1f} + {b:.1f}Q', color='red', linewidth=2)

    # Plot Equilibrium Point and lines
    if Q_eq > 0 and P_eq > 0:
        ax.plot(Q_eq, P_eq, 'go', markersize=8, label=f'Equilibrium (Q={Q_eq:.2f}, P=${P_eq:.2f})')
        ax.vlines(Q_eq, 0, P_eq, linestyle=':', color='gray', linewidth=1)
        ax.hlines(P_eq, 0, Q_eq, linestyle=':', color='gray', linewidth=1)

        # Consumer Surplus Area
        Q_cs_plot = np.linspace(0, Q_eq, 100)
        P_cs_plot = c - d * Q_cs_plot
        ax.fill_between(Q_cs_plot, P_eq, P_cs_plot, where=P_cs_plot > P_eq, color='skyblue', alpha=0.3, label='Consumer Surplus')

        # Producer Surplus Area
        Q_ps_plot = np.linspace(0, Q_eq, 100)
        P_ps_plot = a + b * Q_ps_plot
        ax.fill_between(Q_ps_plot, P_ps_plot, P_eq, where=P_eq > P_ps_plot, color='lightcoral', alpha=0.3, label='Producer Surplus')

    # Plot Price Control and Deadweight Loss
    if price_control > 0 and Q_traded < Q_eq:
        ax.hlines(pc_used, 0, Q_traded, linestyle='--', color='purple', label=f'Enforced Price (${pc_used:.2f})', linewidth=1.5)
        ax.vlines(Q_traded, 0, pc_used, linestyle='--', color='purple', linewidth=1.5)

        # Identify the points for the DWL triangle
        # Price on demand curve at Q_traded
        P_demand_at_Q_traded_plot = c - d * Q_traded
        # Price on supply curve at Q_traded
        P_supply_at_Q_traded_plot = a + b * Q_traded

        # Shade DWL triangle
        # The points for the DWL triangle are (Q_traded, P_supply_at_Q_traded), (Q_traded, P_demand_at_Q_traded), and (Q_eq, P_eq)
        # Plotting the triangle as a fill_between area between Q_traded and Q_eq
        Q_dwl_fill = np.linspace(Q_traded, Q_eq, 100)
        P_demand_dwl_fill = c - d * Q_dwl_fill
        P_supply_dwl_fill = a + b * Q_dwl_fill

        ax.fill_between(Q_dwl_fill, P_supply_dwl_fill, P_demand_dwl_fill, color='red', alpha=0.5, label='Deadweight Loss')

        # Mark points relevant to price control
        # ax.plot(Q_traded, c - d * Q_traded, 'kx', markersize=8, label=f'Demand at PC ({Q_traded:.2f})')
        # ax.plot(Q_traded, a + b * Q_traded, 'bx', markersize=8, label=f'Supply at PC ({Q_traded:.2f})')

    ax.set_xlabel("Quantity (Q)", fontsize=12)
    ax.set_ylabel("Price (P)", fontsize=12)
    ax.set_title("Supply and Demand Equilibrium", fontsize=14)
    ax.set_ylim(bottom=0, top=max_P_plot)
    ax.set_xlim(left=0, right=max_Q_plot)
    ax.legend(loc='upper right')
    ax.grid(True, linestyle='--', alpha=0.7)
    st.pyplot(fig)