Computational Analysis of Social Complexity
Fall 2026, Spencer Lyon
Prerequisites
L11.01
L11.02
Game theory (week 8)
Outcomes
Formulate a platform’s two-sided pricing problem
Derive and solve first-order conditions numerically in Julia
Explain why optimal platform prices can be zero or negative on one side
Distinguish price structure from price level and apply the distinction to real cases
References
Required primary text: Easley & Kleinberg, Networks, Crowds, and Markets, chapter 17
Optional extra reading: Matchmakers (Evans & Schmalensee 2016), chapter 11
Ladies’ Night¶
A nightclub charges men 0
Unfair—or is the club solving an optimization problem?
A person on either side values the club more when the other side shows up
Charging everyone the same price may leave the dance floor empty
Today we solve the platform’s actual problem and derive this asymmetry
The Pattern Is Everywhere¶
Credit cards: cardholders receive rewards while merchants pay roughly 2–3%
OpenTable: diners search and book for free while restaurants pay per seated diner
Google search: users search for free while advertisers fund the system
These are not three unrelated discounts
They are choices about who pays so that both sides participate
From Ignition to Pricing¶
In L11.01 we separated same-side network effects from cross-side network effects
In L11.02 the chicken-and-egg problem made it hard to reach critical mass
Once participation passes a threshold, feedback can create tipping
Pricing is one lever for getting the fragile side onto the platform
First, however, we need a one-sided benchmark
using Plots
using ForwardDiff
using LaTeXStrings
using LinearAlgebra
using Random
Random.seed!(6318)
default(; linewidth=3, legend=:best, size=(760, 440))Build 1: A One-Sided Warm-Up¶
Suppose a monopolist sells one product to one group of customers
Demand falls linearly as price rises
The firm trades off a larger margin against fewer sales
There is no second group whose participation changes customer value
Solve It by Hand¶
Let demand be and marginal cost be
Profit is
Differentiate only after the economic tradeoff is clear:
The interior monopoly price is
It sits halfway between marginal cost and the choke price
demand(p; a=10.0, b=1.0) = max(a - b * p, 0.0)
monopoly_profit(p; a=10.0, b=1.0, c=2.0) = (p - c) * demand(p; a, b)
monopoly_price(; a=10.0, b=1.0, c=2.0) = (a + b * c) / (2b)
one_sided = (a=10.0, b=1.0, c=2.0)
p_mono = monopoly_price(; one_sided...)
println("Analytic monopoly price: ", round(p_mono; digits=2))
println("Demand at the optimum: ", demand(p_mono; a=one_sided.a, b=one_sided.b))prices = range(0.0, one_sided.a / one_sided.b; length=301)
profits = [monopoly_profit(p; one_sided...) for p in prices]
plot(prices, profits; xlabel="price p", ylabel="profit", label=L"\pi(p)")
scatter!([p_mono], [monopoly_profit(p_mono; one_sided...)]; markersize=7, label=L"p^*=6")Benchmark Result¶
With , , and , the curve peaks at
A standard one-sided monopolist with downward-sloping demand does not price below marginal cost at an interior optimum
A subsidy loses money on each sale and creates no revenue anywhere else
Keep that logic in mind—the platform result will break it
Build 2: Two Sides Must Meet¶
Now the platform serves buyers and sellers
It sets per-interaction prices and
One buyer without a seller creates no interaction
One seller without a buyer creates no interaction
This complementarity is the whole model
A Simplified Rochet–Tirole Model¶
Participation on each side responds to its own price:
Interactions require both sides, so volume is
With cost per interaction, the margin is
Profit is
We restrict attention to prices with and
The Cross-Side Externality¶
Raise and some buyers leave
A one-sided firm counts the lost buyer sales
A platform also counts the value destroyed for every seller
The same logic runs from sellers back to buyers
This is a cross-side network effect inside the profit function
Question: should the two sides still pay the same price?
D_B(p_B, pars) = pars.aB - pars.bB * p_B
D_S(p_S, pars) = pars.aS - pars.bS * p_S
function platform_profit(p_B, p_S, pars)
buyers = D_B(p_B, pars)
sellers = D_S(p_S, pars)
if buyers < 0 || sellers < 0
return -Inf
end
return (p_B + p_S - pars.c) * buyers * sellers
end
symmetric = (aB=10.0, bB=1.0, aS=10.0, bS=1.0, c=2.0)Build 3: Search the Price Surface¶
Before taking derivatives, let the computer show us the landscape
We evaluate profit on a grid of feasible prices
The window includes subsidies and both choke prices
Then we mark the best grid point
pB_grid = range(-2.0, symmetric.aB / symmetric.bB; length=241)
pS_grid = range(-2.0, symmetric.aS / symmetric.bS; length=241)
profit_grid = [platform_profit(pB, pS, symmetric) for pS in pS_grid, pB in pB_grid]
grid_index = argmax(profit_grid)
grid_solution = (pB=pB_grid[grid_index[2]], pS=pS_grid[grid_index[1]])
grid_profit = profit_grid[grid_index]
println("Best grid prices: ", grid_solution)
println("Best grid profit: ", round(grid_profit; digits=2))heatmap(pB_grid, pS_grid, profit_grid; xlabel=L"p_B", ylabel=L"p_S",
color=:viridis, colorbar_title="profit", label=false)
contour!(pB_grid, pS_grid, profit_grid; levels=10, color=:white, linewidth=1, label=false)
scatter!([grid_solution.pB], [grid_solution.pS]; color=:red, markersize=8, label="grid optimum")What the Grid Says¶
In the symmetric case, the best grid point is
Symmetric demand produces a symmetric price structure
A grid is transparent and robust, but only approximates the optimum
Can we recover the same answer from the first-order conditions?
Derive the First-Order Conditions¶
Write total per-interaction margin as
For positive participation, differentiating gives
Substitute linear demand and divide by the slopes:
Solving yields
function platform_focs(x, pars)
p_B, p_S = x
margin = p_B + p_S - pars.c
buyers = D_B(p_B, pars)
sellers = D_S(p_S, pars)
return [sellers * (buyers - pars.bB * margin),
buyers * (sellers - pars.bS * margin)]
end
function newton_system(f, x0; tolerance=1e-10, max_iterations=50)
x = Float64.(x0)
for iteration in 1:max_iterations
residual = f(x)
if norm(residual, Inf) < tolerance
return (root=x, iterations=iteration - 1, residual=norm(residual, Inf))
end
jacobian = ForwardDiff.jacobian(f, x)
x -= jacobian \ residual
end
error("Newton's method did not converge")
endclosed_form_prices(pars) = (
pB=(2 * pars.aB / pars.bB - pars.aS / pars.bS + pars.c) / 3,
pS=(2 * pars.aS / pars.bS - pars.aB / pars.bB + pars.c) / 3,
)
newton_symmetric = newton_system(x -> platform_focs(x, symmetric), [2.0, 5.0])
analytic_symmetric = closed_form_prices(symmetric)
println("Newton prices: ", round.(newton_symmetric.root; digits=6))
println("Closed-form prices: ", analytic_symmetric)
println("Distance from grid solution: ",
norm(newton_symmetric.root - [grid_solution.pB, grid_solution.pS]))Three Routes, One Answer¶
The grid search, Newton solver, and closed-form algebra all give
ForwardDiff supplies the Jacobian, but the Newton updates are ours
Agreement across methods is evidence that the code matches the model
Symmetry is reassuring—but it hides the interesting economics
Break the Symmetry¶
Let buyers be much more price-sensitive than sellers
Set and , with both intercepts at 10 and cost at 2
Before computing: which side should the platform protect?
Losing one buyer also reduces the value delivered to sellers
asymmetric = (aB=10.0, bB=4.0, aS=10.0, bS=0.5, c=2.0)
newton_asymmetric = newton_system(x -> platform_focs(x, asymmetric), [-4.0, 13.0])
analytic_asymmetric = closed_form_prices(asymmetric)
pB_asym, pS_asym = newton_asymmetric.root
println("Buyer price: ", round(pB_asym; digits=2))
println("Seller price: ", round(pS_asym; digits=2))
println("Total price: ", round(pB_asym + pS_asym; digits=2))
println("Per-interaction margin: ", round(pB_asym + pS_asym - asymmetric.c; digits=2))
println("Closed-form check: ", analytic_asymmetric)Reveal 1: Subsidize the Sensitive Side¶
The optimal buyer price is about −4.33
The optimal seller price is about 13.17
The platform pays buyers to participate and charges sellers for access to them
Yet the total price is about 8.83 and the per-interaction margin is about 6.83
Ladies’ Night, cashback rewards, and free restaurant reservations fall out of the same first-order conditions
A negative price is not irrational when it creates profitable activity on the other side
Price Structure vs Price Level¶
The price level is the total charge per interaction:
The price structure is how that total is divided between the two sides
Holding fixed but changing the split changes participation and volume
That would be impossible in a genuinely one-sided market
This sensitivity to price structure is Rochet and Tirole’s key definition of two-sidedness
Reveal 2: Comparative Statics¶
One asymmetric example could be a coincidence
Sweep buyer price sensitivity while holding the seller side fixed
Trace both optimal prices and find the zero-price crossover
Prediction: as buyers become easier to lose, buyers pay less and sellers pay more
buyer_slopes = range(0.4, 6.0; length=240)
sweep_parameters = [merge(asymmetric, (bB=b,)) for b in buyer_slopes]
sweep_prices = closed_form_prices.(sweep_parameters)
buyer_prices = getproperty.(sweep_prices, :pB)
seller_prices = getproperty.(sweep_prices, :pS)
zero_crossover = 2 * asymmetric.aB / (asymmetric.aS / asymmetric.bS - asymmetric.c)
plot(buyer_slopes, buyer_prices; xlabel=L"b_B", ylabel="optimal price", label=L"p_B^*")
plot!(buyer_slopes, seller_prices; label=L"p_S^*")
hline!([0.0]; color=:black, linestyle=:dot, linewidth=1.5, label="zero price")
vline!([zero_crossover]; color=:gray, linestyle=:dash, linewidth=2,
label="crossover ≈ $(round(zero_crossover; digits=2))")Reading the Sweep¶
The buyer price crosses zero at for these parameters
Beyond that point, greater buyer sensitivity produces a larger buyer subsidy
The seller price rises because sellers benefit from the participation that subsidy creates
A one-sided monopolist would NEVER price below cost; a platform does it routinely and rationally
The qualification matters: the platform earns revenue from the cross-side response
Pricing Can Help a Platform Ignite¶
Subsidizing one side can help solve the chicken-and-egg problem
Reaching critical mass strengthens cross-side participation and may trigger tipping
Same-side congestion or rivalry can push in the opposite direction
Our static linear model compresses those dynamics into demand curves
The lesson is not “always subsidize buyers”; it is “price the network effect, not just the transaction”
Platforms in Court¶
Ohio v. American Express treated credit-card transactions as a two-sided platform market
Evaluating merchant fees alone can miss cardholder rewards and changes in transaction volume
Epic Games v. Apple put platform market definition and rules between users and developers at center stage
A developer commission cannot be interpreted without asking how users and developers respond together
Courts increasingly require two-sided analysis when the relevant market links both groups
Two-sided analysis does not mean every platform practice is efficient or lawful
Multihoming and the Competitive Bottleneck¶
Multihoming means participating on more than one platform
Armstrong’s competitive-bottleneck insight in one line:
Platforms court the side that single-homes and tax the side that multihomes
Winning a single-homing participant can give access to that participant across competing platforms
This adds competitive strategy to the cross-side logic in our monopoly model
Exercise 1: Platform or Tariff?¶
Consider Costco membership, Amazon Prime, and a farmers-market stall fee
Is each example two-sided pricing or a plain two-part tariff?
Identify the sides and the cross-side interaction, if any
Use the test: would changing the price structure while holding the price level fixed change volume?
Be ready to defend why a label such as “membership” or “fee” is not enough
Exercise 2: More Sellers¶
Return to the asymmetric parameterization
Double the seller intercept from to
Re-solve for and
Which price rises? Which falls?
Explain the result using the value of attracting the opposite side—not algebra alone
more_sellers = merge(asymmetric, (aS=20.0,))
# TODO: compute the new optimum with closed_form_prices or newton_system
# TODO: compare both prices with analytic_asymmetric
# TODO: explain why the changes have opposite signsExercise 3: Charge for Membership Instead¶
Our model charges each side per interaction
Consider membership fees and , each paid once
With per-interaction cost , profit becomes
Find the best membership fees on the grid
Compare fees, participation, interaction volume, and profit with the usage-fee optimum
Why are dollar values not directly interchangeable across the two models?
function membership_profit(F_B, F_S, pars)
buyers = D_B(F_B, pars)
sellers = D_S(F_S, pars)
if buyers < 0 || sellers < 0
return -Inf
end
return F_B * buyers + F_S * sellers - pars.c * buyers * sellers
end
membership_B_grid = range(-5.0, asymmetric.aB / asymmetric.bB; length=301)
membership_S_grid = range(-5.0, asymmetric.aS / asymmetric.bS; length=301)
# TODO: evaluate membership_profit over both grids
# TODO: locate the maximizing pair of membership fees
# TODO: compare participation, volume, and profit across pricing modelsExercise 4: Design a Compute Marketplace¶
You run a GPU-compute marketplace matching AI startups to datacenter owners
This calls back to our AI agents unit: startups deploy agents but need scarce compute
Which side would you subsidize at launch, and why?
Discuss price sensitivity, cross-side value, the chicken-and-egg problem, and multihoming
What evidence would make you reverse the subsidy?
Design one non-price incentive that could help reach critical mass
Takeaways¶
A platform chooses a vector of prices, not one price
Cross-side network effects make participation on each side affect the value of the other
The price-sensitive side may optimally pay zero or receive a subsidy
Price structure can change volume even when the total price level does not
Grid search, hand-derived first-order conditions, and Newton’s method tell the same story
As with Braess’ paradox, familiar pieces can produce a surprising system-level result
Next Time: Markets Without Prices¶
Prices are one way to clear a market—but some markets refuse prices entirely
Kidneys. Medical residencies. School seats.
These markets match instead of price
We will define a stable matching and ask whether any blocking pair can upset it
Then we will build the deferred-acceptance algorithm that helped win a Nobel