rangekeeper 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,40 @@
1
+ from . import api as api
2
+ from . import distribution as distribution
3
+ from . import extrapolation as extrapolation
4
+ from . import flux as flux
5
+ from . import graph as graph
6
+ from . import measure as measure
7
+ from . import periodicity as periodicity
8
+ from . import policy as policy
9
+ from . import projection as projection
10
+ from . import span as span
11
+ from . import segmentation as segmentation
12
+ from . import space as space
13
+
14
+ from . import dynamics as dynamics
15
+
16
+ from . import models as models
17
+
18
+ # Helper Methods:
19
+ import functools
20
+
21
+
22
+ def update_class(
23
+ main_class=None, exclude=("__module__", "__name__", "__dict__", "__weakref__")
24
+ ):
25
+ """Class decorator. Adds all methods and members from the wrapped class to main_class
26
+
27
+ Args:
28
+ - main_class: class to which to append members. Defaults to the class with the same name as the wrapped class
29
+ - exclude: black-list of members which should not be copied
30
+ """
31
+
32
+ def decorates(main_class, exclude, appended_class):
33
+ if main_class is None:
34
+ main_class = locals()[appended_class.__name__]
35
+ for k, v in appended_class.__dict__.items():
36
+ if k not in exclude:
37
+ setattr(main_class, k, v)
38
+ return main_class
39
+
40
+ return functools.partial(decorates, main_class, exclude)
rangekeeper/api.py ADDED
@@ -0,0 +1,88 @@
1
+ import specklepy
2
+ import specklepy.api.credentials
3
+ import specklepy.api.client
4
+ from specklepy.transports.server import ServerTransport
5
+ from specklepy.api import operations
6
+ from specklepy import objects
7
+ from specklepy.api.wrapper import StreamWrapper
8
+
9
+
10
+ class Speckle:
11
+ def __init__(
12
+ self,
13
+ host="speckle.xyz",
14
+ token=None):
15
+
16
+ print("Connecting to {} with {}".format(host, token))
17
+ self.client = specklepy.api.client.SpeckleClient(host=host)
18
+ account = specklepy.api.credentials.get_account_from_token(token, host)
19
+ self.client.authenticate_with_account(account)
20
+ print(self.client)
21
+
22
+ def get_item(
23
+ self,
24
+ stream_id: str,
25
+ commit_id: str):
26
+ stream = self.client.stream.get(id=stream_id)
27
+ transport = ServerTransport(client=self.client, stream_id=stream_id)
28
+ # latest_commit = stream.branches.items[0].commits.items[0]
29
+
30
+ commit = self.client.commit.get(stream_id, commit_id)
31
+ received_base = operations.receive(obj_id=commit.referencedObject, remote_transport=transport)
32
+ data = received_base
33
+ return data
34
+
35
+
36
+ # @staticmethod
37
+ # def query(
38
+ # stream_id: str,
39
+ # commit_id: str,
40
+ # host: str = 'https://speckle.xyz/',
41
+ # account: specklepy.api.credentials.Account = None):
42
+ # foo = 1
43
+ # account = get_default_account() if account is None else account
44
+ # client = SpeckleClient(host=host)
45
+ # client.authenticate_with_account(account)
46
+ #
47
+ # commit = client.commit.get(stream_id, commit_id)
48
+ # transport = ServerTransport(client=client, stream_id=stream_id)
49
+ #
50
+ # obj = operations.receive(commit.referencedObject, transport)
51
+ #
52
+ # data = obj['data']
53
+ #
54
+ # return data
55
+ #
56
+ # @staticmethod
57
+ # def query2(resource: str):
58
+ # wrapper = StreamWrapper(resource)
59
+ # client = wrapper.get_client()
60
+ # transport = wrapper.get_transport()
61
+ #
62
+ # commit_id = wrapper.commit_id
63
+ #
64
+ # commit = client.commit.get(wrapper.stream_id, commit_id)
65
+ #
66
+ # obj = client.object.get(wrapper.stream_id, wrapper.object_id)
67
+ #
68
+ # return client.object.get(wrapper.stream_id, obj.get_id())#[obj.get_dynamic_member_names()[0]].id
69
+ # # obj = operations.receive(commit.referencedObject, transport)
70
+ #
71
+ #
72
+ #
73
+ #
74
+ #
75
+ #
76
+ #
77
+ #
78
+ #
79
+ # # or whatever your host is
80
+
81
+ #
82
+ # class Conversion:
83
+ # @staticmethod
84
+ # def from_speckle(
85
+ # element: specklepy.objects.Base
86
+ # ):
87
+ # foo = specklepy.objects.base.Base()
88
+ # foo.totalChildrenCount
@@ -0,0 +1,269 @@
1
+ from typing import Optional
2
+
3
+ import enum
4
+ import numpy as np
5
+ import scipy.stats as ss
6
+ from abc import abstractmethod
7
+
8
+
9
+ class Type(enum.Enum):
10
+ UNIFORM = 'Uniform' # Continuous uniform distribution or rectangular distribution
11
+ TRIANGULAR = 'Triangular' # Continuous linear distribution with lower limit a, upper limit b and mode c, where a < b and a ≤ c ≤ b.
12
+ NORMAL = 'Normal' # Continuous probability distribution defined as the limiting case of a discrete binomial distribution.
13
+ PERT = 'PERT' # Transformation of the four-parameter Beta distribution defined by the minimum, most likely, and maximum values.
14
+
15
+
16
+ class Form:
17
+ type: Type
18
+
19
+ def __init__(
20
+ self,
21
+ generator: Optional[np.random.Generator] = None):
22
+ self.generator = generator
23
+ self.dist = ss.rv_continuous()
24
+
25
+ def sample(
26
+ self,
27
+ size: int = 1):
28
+
29
+ if hasattr(self.dist, 'b'):
30
+ if self.dist.b == 0.:
31
+ return self.dist.a # If the distribution is a point mass, return the value of the point mass
32
+
33
+ if self.generator is None:
34
+ generator = np.random.default_rng()
35
+ else:
36
+ generator = self.generator
37
+ return self.dist.rvs(size=size, random_state=generator)
38
+
39
+ # if size == 1:
40
+ # return variates[0]
41
+ # else:
42
+ # return variates
43
+
44
+ @abstractmethod
45
+ def interval_density(
46
+ self,
47
+ parameters: [float]):
48
+ """
49
+ Returns the cumulative density (integral of the interpolated curve)
50
+ between n parameter pairs as intervals (i.e. returns n-1 results)
51
+
52
+ :param parameters: Any set of floats between 0 and 1
53
+ :return: List of floats representing the cumulative density of that interval.
54
+ If the input parameters span 0 to 1, the sum of the interval densities will reach 1.
55
+ """
56
+
57
+ if (all(parameters) >= 0) & (all(parameters) <= 1):
58
+ return [(self.dist.cdf(parameters[i + 1]) - self.dist.cdf(parameters[i]))
59
+ for i in (range(0, len(parameters) - 1))]
60
+ else:
61
+ raise ValueError("Error: Parameter must be between 0 and 1 inclusive")
62
+
63
+ @abstractmethod
64
+ def cumulative_density(
65
+ self,
66
+ parameters: [float]):
67
+ """
68
+ Returns the cumulative distribution at parameters between 0 and 1.
69
+ """
70
+ if (all(parameters) >= 0) & (all(parameters) <= 1):
71
+ return [self.dist.cdf(parameter) for parameter in parameters]
72
+ else:
73
+ raise ValueError("Error: Parameter must be between 0 and 1 inclusive")
74
+
75
+
76
+ class Symmetric(Form):
77
+ def __init__(
78
+ self,
79
+ type: Type,
80
+ residual: float,
81
+ mean: float = 0,
82
+ generator: Optional[np.random.Generator] = None):
83
+ """
84
+ Parameters that define the mean and residual (maximum deviation)
85
+ of a symmetric distribution
86
+
87
+ :param type: Type of symmetric distribution
88
+ :param residual: Maximum deviation from the mean
89
+ :param mean: Mean of the distribution. Defaults to 0.
90
+ """
91
+ super().__init__(generator=generator)
92
+ if type is Type.UNIFORM or type is Type.PERT or type is Type.TRIANGULAR:
93
+ self.type = type
94
+ else:
95
+ raise ValueError("Distribution type must be able to be symmetrical about its mean")
96
+ self.mean = mean
97
+ self.residual = residual
98
+ self.generator = generator
99
+ self.dist = self._distribution().dist
100
+
101
+ def _distribution(self):
102
+ if self.type == Type.UNIFORM:
103
+ return Uniform(
104
+ lower=self.mean - self.residual,
105
+ range=self.residual * 2,
106
+ generator=self.generator)
107
+ elif self.type == Type.PERT:
108
+ return PERT.symmetric(
109
+ peak=self.mean,
110
+ residual=self.residual)
111
+ elif self.type == Type.TRIANGULAR:
112
+ return Triangular.symmetric(
113
+ mode=self.mean,
114
+ residual=self.residual)
115
+
116
+ def interval_density(
117
+ self,
118
+ parameters: [float]) -> [float]:
119
+ return self._distribution().interval_density(parameters)
120
+
121
+ def cumulative_density(
122
+ self,
123
+ parameters: [float]) -> [float]:
124
+ return self._distribution().cumulative_density(parameters)
125
+
126
+
127
+ class Uniform(Form):
128
+ """
129
+ For default arguments, this is a continuous, uniform distribution between 0 and 1,
130
+ such the cumulative distribution reaches 1. (For uniform distribution, this means the density is continuously 1).
131
+
132
+ For specified arguments, this distribution is constant between lower, and lower + range.
133
+ """
134
+
135
+ def __init__(
136
+ self,
137
+ lower: float = 0.,
138
+ range: float = 1.,
139
+ generator: Optional[np.random.Generator] = None):
140
+ super().__init__(generator=generator)
141
+ self.dist = ss.uniform(loc=lower, scale=range)
142
+ self.type = Type.UNIFORM
143
+
144
+ @classmethod
145
+ def symmetric(
146
+ cls,
147
+ mean: float,
148
+ residual: float,
149
+ generator: Optional[np.random.Generator] = None):
150
+ return cls(
151
+ lower=mean - residual,
152
+ range=residual * 2,
153
+ generator=generator)
154
+
155
+ def interval_density(
156
+ self,
157
+ parameters: [float]) -> [float]:
158
+ return super().interval_density(parameters)
159
+
160
+ def cumulative_density(
161
+ self,
162
+ parameters: [float]) -> [float]:
163
+ return super().cumulative_density(parameters)
164
+
165
+
166
+ class Triangular(Form):
167
+ """
168
+ A continuous probability distribution with lower limit a, upper limit b and mode c, where a < b and a ≤ c ≤ b.
169
+ """
170
+
171
+ def __init__(
172
+ self,
173
+ lower: float = 0.,
174
+ upper: float = 1.,
175
+ mode: float = 0.5,
176
+ generator: Optional[np.random.Generator] = None):
177
+ super().__init__(generator=generator)
178
+ loc = lower
179
+ scale = upper - lower
180
+ if scale == 0:
181
+ c = lower
182
+ else:
183
+ c = (mode - lower) / (upper - lower)
184
+ self.dist = ss.triang(loc=loc, scale=scale, c=c)
185
+ self.type = Type.TRIANGULAR
186
+
187
+ @classmethod
188
+ def symmetric(
189
+ cls,
190
+ mode: float,
191
+ residual: float):
192
+ return cls(
193
+ lower=mode - residual,
194
+ upper=mode + residual,
195
+ mode=mode)
196
+
197
+ def interval_density(
198
+ self,
199
+ parameters: [float]) -> [float]:
200
+ return super().interval_density(parameters)
201
+
202
+ def cumulative_density(
203
+ self,
204
+ parameters: [float]) -> [float]:
205
+ return super().cumulative_density(parameters)
206
+
207
+
208
+ class PERT(Form):
209
+ """
210
+ A continuous distribution that is controlled by the placement and weighting of the mode (peak) value.
211
+ It produces similar distributions to the Normal (Gaussian) distribution within a specified domain.
212
+ Its cumulative distribution function (CDF) curve is a classic 's-curve'.
213
+
214
+ Peak must be between 0 and 1. Weighting must be >= 0. Standard weighting is 4.
215
+
216
+ Specifically, it is a Beta Distribution extended to specified domain,
217
+ with the mean (μ) constrained so that μ = (a + Ɣ*b + c) / (Ɣ + 2),
218
+ where a and c are the limits (0 and 1 in this formulation), b is the mode (peak value),
219
+ and Ɣ is a weighting factor for the mode.
220
+
221
+ This function is built from the formulation in Tensorflow:
222
+ https://github.com/tensorflow/probability/blob/master/tensorflow_probability/python/distributions/pert.py
223
+ """
224
+
225
+ def __init__(
226
+ self,
227
+ peak: float = 0.5,
228
+ weighting: float = 4.0,
229
+ minimum: float = 0.0,
230
+ maximum: float = 1.0,
231
+ generator: Optional[np.random.Generator] = None):
232
+ super().__init__(generator=generator)
233
+ self.type = Type.PERT
234
+ self.peak = peak
235
+ self.weighting = weighting
236
+ self.minimum = minimum
237
+ self.maximum = maximum
238
+ self.scale = maximum - minimum
239
+ if (self.weighting >= 0) & (self.peak >= minimum) & (self.peak <= maximum):
240
+ a = (1. + self.weighting * (self.peak - minimum) / (maximum - minimum))
241
+ b = (1. + self.weighting * (maximum - self.peak) / (maximum - minimum))
242
+ self.dist = ss.beta(
243
+ loc=minimum,
244
+ scale=self.scale,
245
+ a=a,
246
+ b=b)
247
+ else:
248
+ raise ValueError("Error: Weighting must be greater than 0 and Peak must be between 0 and 1 inclusive")
249
+
250
+ @classmethod
251
+ def symmetric(
252
+ cls,
253
+ peak: float,
254
+ residual: float):
255
+ return cls(
256
+ peak=peak,
257
+ weighting=4.,
258
+ minimum=peak - residual,
259
+ maximum=peak + residual)
260
+
261
+ def interval_density(
262
+ self,
263
+ parameters: [float]) -> [float]:
264
+ return super().interval_density(parameters)
265
+
266
+ def cumulative_density(
267
+ self,
268
+ parameters: [float]) -> [float]:
269
+ return super().cumulative_density(parameters)
@@ -0,0 +1,6 @@
1
+ from . import cyclicality as cyclicality
2
+ from . import market as market
3
+ from . import trend as trend
4
+ from . import volatility as volatility
5
+ from . import noise as noise
6
+ from . import black_swan as black_swan
@@ -0,0 +1,77 @@
1
+ from __future__ import annotations
2
+
3
+ import numpy as np
4
+ import pandas as pd
5
+ from numba import jit
6
+
7
+ import rangekeeper as rk
8
+
9
+
10
+ class BlackSwan:
11
+ def __init__(
12
+ self,
13
+ sequence: pd.PeriodIndex,
14
+ likelihood: float,
15
+ dissipation_rate: float,
16
+ probability: rk.distribution.Form,
17
+ impact: float):
18
+ """
19
+ This random variable will determine whether a "black swan" event
20
+ occurs in any given year. We ensure that no more than one black
21
+ swan will occur in the 24-yr history, as black swans are by definition
22
+ rare events.
23
+
24
+ This column causes the effect of the Black Swan event to dissipate
25
+ over time, geometrically, at the same mean reversion rate as is
26
+ applied in general to the rents (entered in co.F).
27
+
28
+ This is another source or type of uncertainty in real estate pricing.
29
+ This will apply the given "black swan" result, but then reduces
30
+ the subsequent impact of the event gradually as mean-reversion
31
+ takes effect.
32
+ """
33
+ self.sequence = sequence
34
+ self.likelihood = likelihood
35
+ self.dissipation_rate = dissipation_rate
36
+ self.probability = probability
37
+ self.impact = impact
38
+
39
+ def generate(self) -> rk.flux.Flow:
40
+ return rk.flux.Flow(
41
+ name='Black Swan Effect',
42
+ movements=pd.Series(
43
+ data=self.calculate_black_swan_effects(
44
+ likelihood=self.likelihood,
45
+ dissipation_rate=self.dissipation_rate,
46
+ events=self.probability.sample(self.sequence.size)),
47
+ index=rk.periodicity.to_datestamps(period_index=self.sequence)))
48
+
49
+ @staticmethod
50
+ @jit(nopython=True)
51
+ def calculate_black_swan_effects(
52
+ likelihood: float,
53
+ dissipation_rate: float,
54
+ events: [float]):
55
+ """
56
+
57
+ :param likelihood:
58
+ :type likelihood:
59
+ :param dissipation_rate:
60
+ :type dissipation_rate:
61
+ :param events:
62
+ :type events:
63
+ :return:
64
+ :rtype:
65
+ """
66
+ idx = -1
67
+ impacts = []
68
+ for i in range(len(events)):
69
+ if idx == -1:
70
+ if events[i] < likelihood:
71
+ idx = i
72
+ impacts.append(1)
73
+ else:
74
+ impacts.append(0)
75
+ else:
76
+ impacts.append(np.power((1 - dissipation_rate), (i - idx)))
77
+ return impacts