Skip to content

Commit 8833ba9

Browse files
Merge pull request #144 from alexanderthclark/codex/identify-modules-for-code-linting
deduplicate curve helpers
2 parents cd6cd14 + 726f4ff commit 8833ba9

1 file changed

Lines changed: 7 additions & 135 deletions

File tree

freeride/curves.py

Lines changed: 7 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -3,148 +3,20 @@
33
import warnings
44
from freeride.plotting import textbook_axes, AREA_FILLS, update_axes_limits
55
from freeride.formula import _formula
6-
from freeride.affine import AffineElement, Affine
6+
from freeride.affine import (
7+
AffineElement,
8+
Affine,
9+
intersection,
10+
blind_sum,
11+
horizontal_sum,
12+
)
713
from freeride.quadratic import QuadraticElement, BaseQuadratic
814
from freeride.revenue import Revenue, MarginalRevenue
915
from IPython.display import Latex, display
1016
from bokeh.plotting import figure, show
1117
from bokeh.models import HoverTool, ColumnDataSource
1218
from freeride.exceptions import PPFError
1319

14-
15-
16-
def intersection(element1, element2):
17-
"""Return the intersection of two affine elements.
18-
19-
The result is a 1D array ``[p, q]`` giving the price and quantity at the
20-
intersection. When either line is perfectly vertical (``slope == np.inf``)
21-
or perfectly horizontal (``slope == 0``) the intersection is computed
22-
directly. If both lines are vertical or both horizontal, a
23-
``LinAlgError`` is raised.
24-
25-
Parameters
26-
----------
27-
element1, element2 : :class:`AffineElement`
28-
Lines for which to compute the intersection.
29-
30-
Returns
31-
-------
32-
numpy.ndarray
33-
``[p, q]`` of the intersection point.
34-
35-
Raises
36-
------
37-
numpy.linalg.LinAlgError
38-
If the lines are parallel.
39-
40-
Examples
41-
--------
42-
>>> line1 = AffineElement(intercept=12, slope=-1)
43-
>>> line2 = AffineElement(intercept=0, slope=2)
44-
>>> intersection(line1, line2)
45-
array([8., 4.])
46-
"""
47-
48-
# Parallel vertical or horizontal lines
49-
if (element1.slope == np.inf and element2.slope == np.inf) or (
50-
element1.slope == 0 and element2.slope == 0
51-
):
52-
raise np.linalg.LinAlgError("Lines are parallel")
53-
54-
# Handle a vertical line (perfectly inelastic)
55-
if element1.slope == np.inf:
56-
q = element1.q_intercept
57-
p = element2(q)
58-
return np.array([p, q])
59-
if element2.slope == np.inf:
60-
q = element2.q_intercept
61-
p = element1(q)
62-
return np.array([p, q])
63-
64-
# Handle a horizontal line (perfectly elastic)
65-
if element1.slope == 0:
66-
p = element1.intercept
67-
q = element2.q(p)
68-
return np.array([p, q])
69-
if element2.slope == 0:
70-
p = element2.intercept
71-
q = element1.q(p)
72-
return np.array([p, q])
73-
74-
# Generic case
75-
A = np.array([[1, -element1.slope], [1, -element2.slope]])
76-
b = np.array([[element1.intercept], [element2.intercept]])
77-
yx = np.matmul(np.linalg.inv(A), b)
78-
return np.squeeze(yx)
79-
80-
81-
def blind_sum(*curves):
82-
'''
83-
Computes the horizontal summation of AffineElement objects.
84-
85-
Parameters
86-
----------
87-
*curves : AffineElement
88-
The objects to be summed.
89-
90-
Returns
91-
-------
92-
AffineElement
93-
The horizontal summation of the input curves represented as an AffineElement object.
94-
Returns None if no curves are provided.
95-
'''
96-
if len(curves) == 0:
97-
return None
98-
elastic_curves = [c for c in curves if c.slope == 0]
99-
inelastic_curves = [c for c in curves if c.slope == np.inf]
100-
regular_curves = [c for c in curves if c not in elastic_curves + inelastic_curves]
101-
102-
if not elastic_curves and not inelastic_curves:
103-
qintercept = np.sum([-c.intercept/c.slope for c in curves])
104-
qslope = np.sum([1/c.slope for c in curves])
105-
return AffineElement(qintercept, qslope, inverse = False)
106-
else:
107-
raise Exception("Perfectly Elastic and Inelastic curves not supported")
108-
109-
110-
def horizontal_sum(*curves):
111-
"""
112-
Compute active curves at different price midpoints based on the p-intercepts of input curves.
113-
114-
Parameters
115-
----------
116-
*curves : sequence of AffineElements
117-
Variable-length argument list of Affine curve objects for which
118-
the active curves are to be found.
119-
120-
Returns
121-
-------
122-
tuple
123-
A tuple containing three elements:
124-
- active_curves (list): List of AffineElement objects representing
125-
the active curves at each price midpoint.
126-
- cutoffs (list): List of unique p-intercepts sorted in ascending order.
127-
- midpoints (list): List of midpoints computed based on the cutoffs.
128-
"""
129-
elastic_curves = [c for c in curves if c.slope == 0]
130-
inelastic_curves = [c for c in curves if c.slope == np.inf]
131-
regular_curves = [c for c in curves if c not in elastic_curves + inelastic_curves]
132-
133-
intercepts = [0] + [c.intercept for c in regular_curves + elastic_curves]
134-
cutoffs = sorted(list(set(intercepts)))
135-
136-
# remove negative intercepts
137-
cutoffs = [c for c in cutoffs if c>=0]
138-
139-
# get a point in each region
140-
midpoints = [(a + b) / 2 for a, b in zip(cutoffs[:-1], cutoffs[1:])] + [cutoffs[-1]+1]
141-
142-
# get curves with positive quantity for each region
143-
active_curves = [blind_sum(*[c for c in curves if c.q(price)>0]) for price in midpoints]
144-
145-
return active_curves, cutoffs, midpoints
146-
147-
14820
def ppf_sum(*curves, comparative_advantage=True):
14921
"""Combine production possibilities frontiers.
15022

0 commit comments

Comments
 (0)