Core Models
The core models cover smooth preferences, kinked and linear preferences, and preferences with income or reference-point effects. Choose a group, then switch between its tabs to compare models.
Smooth preferences
These models produce smooth indifference curves and normally have an interior optimum when prices and income are positive.
Cobb-Douglas is the standard model for smooth, strictly convex preferences.
The exponents control the relative weight placed on each good. Its indifference curves approach both axes without touching them.
Parameters
| Parameter | Default | Meaning |
|---|---|---|
alpha |
0.5 |
Weight on good \(x\) |
beta |
0.5 |
Weight on good \(y\) |
Example
from econ_viz import Canvas, levels, solve
from econ_viz.models import CobbDouglas
model = CobbDouglas(alpha=0.5, beta=0.5)
eq = solve(model, px=2.0, py=3.0, income=30.0)
(
Canvas(x_max=20, y_max=15, title="Cobb-Douglas")
.add_utility(model, levels=levels.around(eq.utility, n=5))
.add_budget(2.0, 3.0, 30.0, fill=True)
.add_equilibrium(eq, show_ray=True)
.save("cobb_douglas.png")
)

CES lets the ease of substitution vary while keeping preferences smooth.
The substitution elasticity is \(\sigma=1/(1-\rho)\). As \(\rho\) changes, CES approaches Cobb-Douglas, Leontief, or perfect substitutes.
Parameters
| Parameter | Default | Meaning |
|---|---|---|
alpha |
0.5 |
Weight on good \(x\) |
beta |
0.5 |
Weight on good \(y\) |
rho |
0.5 |
Substitution parameter, with \(\rho\ne1\) |
Example
from econ_viz import Canvas, levels, solve
from econ_viz.models import CES
model = CES(alpha=0.5, beta=0.5, rho=-0.5)
eq = solve(model, px=2.0, py=3.0, income=30.0)
(
Canvas(x_max=20, y_max=15, title="CES")
.add_utility(model, levels=levels.around(eq.utility, n=5))
.add_budget(2.0, 3.0, 30.0)
.add_equilibrium(eq)
.save("ces.png")
)

Translog is a flexible log-quadratic model for smooth preferences.
The quadratic and interaction terms allow curvature to vary across the consumption space. Setting all \(\beta\) coefficients to zero gives a Cobb-Douglas-style log-linear form.
Parameters
| Parameter | Default | Meaning |
|---|---|---|
alpha_0 |
0.0 |
Log-utility intercept |
alpha_x |
0.5 |
First-order weight on \(\ln x\) |
alpha_y |
0.5 |
First-order weight on \(\ln y\) |
beta_xx |
0.0 |
Curvature in \(x\) |
beta_yy |
0.0 |
Curvature in \(y\) |
beta_xy |
0.0 |
Interaction between \(x\) and \(y\) |
Example
from econ_viz import Canvas, levels, solve
from econ_viz.models import Translog
model = Translog(alpha_x=0.6, alpha_y=0.4, beta_xy=0.12)
eq = solve(model, px=2.0, py=3.0, income=30.0)
(
Canvas(x_max=18, y_max=12, title="Translog")
.add_utility(model, levels=levels.around(eq.utility, n=4))
.add_budget(2.0, 3.0, 30.0)
.add_equilibrium(eq)
.save("translog.png")
)

Kinks and corners
These models show how non-smooth or linear preferences change the location of the optimum.
Leontief preferences describe goods consumed in fixed proportions.
Indifference curves are L-shaped. The optimum lies at the kink where the two weighted quantities are equal.
Parameters
| Parameter | Default | Meaning |
|---|---|---|
a |
1.0 |
Weight on good \(x\) |
b |
1.0 |
Weight on good \(y\) |
Example
from econ_viz import Canvas, levels, solve
from econ_viz.models import Leontief
model = Leontief(a=1.0, b=1.0)
eq = solve(model, px=2.0, py=3.0, income=30.0)
(
Canvas(x_max=20, y_max=15, title="Leontief")
.add_utility(
model,
levels=levels.around(eq.utility, n=5),
show_rays=True,
show_kinks=True,
)
.add_budget(2.0, 3.0, 30.0)
.add_equilibrium(eq)
.save("leontief.png")
)

Perfect substitutes provide constant utility trade-offs between goods.
Indifference curves are straight lines. The consumer normally chooses the good with the greater marginal utility per dollar, producing a corner solution.
Parameters
| Parameter | Default | Meaning |
|---|---|---|
a |
1.0 |
Marginal utility of good \(x\) |
b |
1.0 |
Marginal utility of good \(y\) |
Example
from econ_viz import Canvas, levels, solve
from econ_viz.models import PerfectSubstitutes
model = PerfectSubstitutes(a=1.0, b=2.0)
eq = solve(model, px=2.0, py=3.0, income=30.0)
(
Canvas(x_max=20, y_max=15, title="Perfect Substitutes")
.add_utility(model, levels=levels.around(eq.utility, n=5))
.add_budget(2.0, 3.0, 30.0)
.add_equilibrium(eq)
.save("perfect_substitutes.png")
)

Maximum utility keeps only the larger weighted quantity in each bundle.
Each indifference curve has two arms that extend toward the axes. The resulting preferences are non-convex, so the optimum on a linear budget normally lies at the intercept with the greater weighted utility.
Parameters
| Parameter | Default | Meaning |
|---|---|---|
a |
1.0 |
Weight on good \(x\) |
b |
1.0 |
Weight on good \(y\) |
Example
import numpy as np
from econ_viz import Canvas, levels, solve
from econ_viz.models import CustomUtility
a, b = 1.0, 1.0
model = CustomUtility(
func=lambda x, y: np.maximum(a * x, b * y),
name="maximum",
)
eq = solve(model, px=2.0, py=3.0, income=30.0)
(
Canvas(x_max=25, y_max=20, title="Maximum utility")
.add_utility(model, levels=levels.around(eq.utility, n=5))
.add_budget(2.0, 3.0, 30.0)
.add_equilibrium(eq)
.save("maximum.png")
)

Income and reference points
These models add special income effects, subsistence requirements, or a preferred consumption point.
Quasi-linear preferences place one good linearly in utility.
The non-linear good has no income effect once the solution is interior.
linear_in can reverse the roles of \(x\) and \(y\).
Parameters
| Parameter | Default | Meaning |
|---|---|---|
v_func |
numpy.log |
Increasing, concave function \(f\) |
linear_in |
"y" |
Good that enters linearly |
Example
import numpy as np
from econ_viz import Canvas, levels, solve
from econ_viz.models import QuasiLinear
model = QuasiLinear(v_func=np.log, linear_in="y")
eq = solve(model, px=2.0, py=1.0, income=20.0)
(
Canvas(x_max=15, y_max=15, title="Quasi-Linear")
.add_utility(model, levels=levels.around(eq.utility, n=5))
.add_budget(2.0, 1.0, 20.0)
.add_equilibrium(eq)
.save("quasi_linear.png")
)

Stone-Geary extends Cobb-Douglas with minimum consumption requirements.
The consumer first covers subsistence quantities \(\bar{x}\) and \(\bar{y}\), then allocates the remaining income like a Cobb-Douglas consumer.
Parameters
| Parameter | Default | Meaning |
|---|---|---|
alpha |
0.5 |
Weight on supernumerary \(x\) |
beta |
0.5 |
Weight on supernumerary \(y\) |
bar_x |
1.0 |
Subsistence quantity \(\bar{x}\) |
bar_y |
1.0 |
Subsistence quantity \(\bar{y}\) |
Example
from econ_viz import Canvas, levels, solve
from econ_viz.models import StoneGeary
model = StoneGeary(alpha=0.5, beta=0.5, bar_x=2.0, bar_y=2.0)
eq = solve(model, px=2.0, py=3.0, income=30.0)
(
Canvas(x_max=20, y_max=15, title="Stone-Geary")
.add_utility(model, levels=levels.around(eq.utility, n=5))
.add_budget(2.0, 3.0, 30.0, fill=True)
.add_equilibrium(eq)
.save("stone_geary.png")
)

Satiation preferences have a bliss point that maximises utility.
Utility falls in every direction away from \((x^*,y^*)\), so indifference curves form closed ellipses and monotonicity does not hold.
Parameters
| Parameter | Default | Meaning |
|---|---|---|
bliss_x |
5.0 |
Bliss-point coordinate \(x^*\) |
bliss_y |
5.0 |
Bliss-point coordinate \(y^*\) |
a |
1.0 |
Curvature along the \(x\)-axis |
b |
1.0 |
Curvature along the \(y\)-axis |
Example
import numpy as np
from econ_viz import Canvas, levels
from econ_viz.models import Satiation
model = Satiation(bliss_x=6.0, bliss_y=4.0)
x = np.linspace(0.1, 12.0, 300)
y = np.linspace(0.1, 10.0, 300)
X, Y = np.meshgrid(x, y)
(
Canvas(x_max=12, y_max=10, title="Satiation")
.add_utility(model, levels=levels.percentile(model(X, Y), n=5))
.save("satiation.png")
)

For custom functions and multi-good Cobb-Douglas models, see Advanced Models.