|
3 | 3 | import warnings |
4 | 4 | from freeride.plotting import textbook_axes, AREA_FILLS, update_axes_limits |
5 | 5 | 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 | +) |
7 | 13 | from freeride.quadratic import QuadraticElement, BaseQuadratic |
8 | 14 | from freeride.revenue import Revenue, MarginalRevenue |
9 | 15 | from IPython.display import Latex, display |
10 | 16 | from bokeh.plotting import figure, show |
11 | 17 | from bokeh.models import HoverTool, ColumnDataSource |
12 | 18 | from freeride.exceptions import PPFError |
13 | 19 |
|
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 | | - |
148 | 20 | def ppf_sum(*curves, comparative_advantage=True): |
149 | 21 | """Combine production possibilities frontiers. |
150 | 22 |
|
|
0 commit comments