InfluenceDiffusion 0.0.15__tar.gz → 0.0.17__tar.gz

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.
Files changed (24) hide show
  1. {influencediffusion-0.0.15 → influencediffusion-0.0.17}/InfluenceDiffusion/Inference.py +14 -2
  2. influencediffusion-0.0.17/InfluenceDiffusion/estimation_models/CDFEstimation.py +177 -0
  3. {influencediffusion-0.0.15 → influencediffusion-0.0.17}/InfluenceDiffusion/estimation_models/OptimEstimation.py +28 -1
  4. {influencediffusion-0.0.15 → influencediffusion-0.0.17}/InfluenceDiffusion/plot_utils.py +36 -22
  5. {influencediffusion-0.0.15 → influencediffusion-0.0.17}/InfluenceDiffusion/utils.py +0 -14
  6. {influencediffusion-0.0.15 → influencediffusion-0.0.17}/InfluenceDiffusion.egg-info/PKG-INFO +1 -1
  7. {influencediffusion-0.0.15 → influencediffusion-0.0.17}/PKG-INFO +1 -1
  8. {influencediffusion-0.0.15 → influencediffusion-0.0.17}/setup.py +1 -1
  9. influencediffusion-0.0.15/InfluenceDiffusion/estimation_models/CDFEstimation.py +0 -73
  10. {influencediffusion-0.0.15 → influencediffusion-0.0.17}/InfluenceDiffusion/Graph.py +0 -0
  11. {influencediffusion-0.0.15 → influencediffusion-0.0.17}/InfluenceDiffusion/Trace.py +0 -0
  12. {influencediffusion-0.0.15 → influencediffusion-0.0.17}/InfluenceDiffusion/__init__.py +0 -0
  13. {influencediffusion-0.0.15 → influencediffusion-0.0.17}/InfluenceDiffusion/estimation_models/BaseWeightEstimator.py +0 -0
  14. {influencediffusion-0.0.15 → influencediffusion-0.0.17}/InfluenceDiffusion/estimation_models/EMEstimation.py +0 -0
  15. {influencediffusion-0.0.15 → influencediffusion-0.0.17}/InfluenceDiffusion/estimation_models/__init__.py +0 -0
  16. {influencediffusion-0.0.15 → influencediffusion-0.0.17}/InfluenceDiffusion/influence_models.py +0 -0
  17. {influencediffusion-0.0.15 → influencediffusion-0.0.17}/InfluenceDiffusion/weight_samplers.py +0 -0
  18. {influencediffusion-0.0.15 → influencediffusion-0.0.17}/InfluenceDiffusion.egg-info/SOURCES.txt +0 -0
  19. {influencediffusion-0.0.15 → influencediffusion-0.0.17}/InfluenceDiffusion.egg-info/dependency_links.txt +0 -0
  20. {influencediffusion-0.0.15 → influencediffusion-0.0.17}/InfluenceDiffusion.egg-info/requires.txt +0 -0
  21. {influencediffusion-0.0.15 → influencediffusion-0.0.17}/InfluenceDiffusion.egg-info/top_level.txt +0 -0
  22. {influencediffusion-0.0.15 → influencediffusion-0.0.17}/LICENSE +0 -0
  23. {influencediffusion-0.0.15 → influencediffusion-0.0.17}/README.md +0 -0
  24. {influencediffusion-0.0.15 → influencediffusion-0.0.17}/setup.cfg +0 -0
@@ -4,9 +4,9 @@ import numpy as np
4
4
  from typing import Dict, Callable
5
5
  from functools import partial
6
6
  from scipy.stats import norm
7
+ from scipy.stats._distn_infrastructure import rv_frozen
7
8
 
8
9
  from .estimation_models.OptimEstimation import GLTWeightEstimator
9
- from .utils import make_jax_cdf
10
10
 
11
11
  __all__ = ["GLTInferenceModule"]
12
12
 
@@ -15,7 +15,7 @@ class GLTInferenceModule:
15
15
  def __init__(self, estimator: GLTWeightEstimator, vertex_2_jax_cdf: Dict[int, Callable] = None):
16
16
  self.estimator = estimator
17
17
  if vertex_2_jax_cdf is None:
18
- self.vertex_2_jax_cdf = {vertex: make_jax_cdf(distrib)
18
+ self.vertex_2_jax_cdf = {vertex: self._make_jax_cdf(distrib)
19
19
  for vertex, distrib in self.estimator.vertex_2_distrib.items()}
20
20
  else:
21
21
  self.vertex_2_jax_cdf = vertex_2_jax_cdf
@@ -41,6 +41,18 @@ class GLTInferenceModule:
41
41
  masks_tm1 = jnp.vstack([tm1_active_masks, tm1_failed_masks], dtype=jnp.float32)
42
42
  return ys, masks_t, masks_tm1
43
43
 
44
+ @staticmethod
45
+ def _make_jax_cdf(distrib: rv_frozen):
46
+ name = distrib.dist.name
47
+ args = distrib.args
48
+ kwargs = distrib.kwds
49
+ local_dic = {}
50
+ exec(f"jax_distrib=jax.scipy.stats.{name}", None, local_dic)
51
+ jax_distrib = local_dic["jax_distrib"]
52
+ if name == "expon":
53
+ return lambda x: 1. - jnp.exp(-x)
54
+ return lambda x: jax_distrib.cdf(x, *args, **kwargs)
55
+
44
56
  @staticmethod
45
57
  def _vertex_ll(parent_weights, cdf, ys, masks_t, masks_tm1, eps=1e-6):
46
58
  F_t = cdf(masks_t @ parent_weights)
@@ -0,0 +1,177 @@
1
+ from typing import List, Tuple, Any
2
+ from scipy.stats._distn_infrastructure import rv_continuous
3
+ from scipy.interpolate import interp1d
4
+ import numpy as np
5
+
6
+ __all__ = ["CensoredCDFEstimator"]
7
+
8
+
9
+ class CensoredCDFEstimator(rv_continuous):
10
+ """
11
+ This class implements the Turnbull estimator of the cumulative distribution function (CDF)
12
+ from a collection of censored intervals within which sample points are observed.
13
+ See: Turnbull, Bruce W. (1976).
14
+ “The Empirical Distribution Function with Arbitrarily Grouped, Censored and Truncated Data.”
15
+ Journal of the Royal Statistical Society. Series B (Methodological) 38(3), 290–295.
16
+
17
+ Parameters
18
+ ----------
19
+ support : tuple of float, optional
20
+ Explicit support (min, max) for the estimator. Default is (-np.inf, np.inf).
21
+ a : float, optional
22
+ Lower bound of the distribution. Default is None.
23
+ b : float, optional
24
+ Upper bound of the distribution. Default is None.
25
+ momtype : int, optional
26
+ Moment type used by `rv_continuous`. Default is 1.
27
+ xtol : float, optional
28
+ Tolerance for calculations in `rv_continuous`. Default is 1e-14.
29
+ badvalue : optional
30
+ Value to return for invalid inputs. Default is None.
31
+ name : str, optional
32
+ Name of the distribution. Default is None.
33
+ longname : str, optional
34
+ Long descriptive name of the distribution. Default is None.
35
+ shapes : str or None, optional
36
+ Shape parameters (if any) for the distribution. Default is None.
37
+ extradoc : str, optional
38
+ Additional documentation string. Default is None.
39
+ seed : int or np.random.Generator, optional
40
+ Random seed for reproducibility. Default is None.
41
+ """
42
+
43
+ def __init__(self, support: Tuple[float, float] = (-np.inf, np.inf),
44
+ momtype=1,
45
+ a=None,
46
+ b=None,
47
+ xtol=1e-14,
48
+ badvalue=None,
49
+ name=None,
50
+ longname=None,
51
+ shapes=None,
52
+ extradoc=None,
53
+ seed=None):
54
+
55
+ super().__init__(momtype=momtype, a=a, b=b, xtol=xtol, badvalue=badvalue,
56
+ name=name, longname=longname, shapes=shapes, seed=seed)
57
+ self.support_ = support
58
+
59
+ def fit(self, intervals: List[Tuple[float, float]],
60
+ max_iter=50, tol=1e-4, verbose=False, verbose_interval=1):
61
+ """
62
+ Fit the CDF estimator to interval-censored data using an EM-like algorithm.
63
+
64
+ Parameters
65
+ ----------
66
+ intervals : list of tuple of float
67
+ List of observed intervals (left, right) for each sample.
68
+ max_iter : int, default 50
69
+ Maximum number of iterations for the EM procedure.
70
+ tol : float, default 1e-4
71
+ Convergence tolerance for the probability updates.
72
+ verbose : bool, default False
73
+ If True, prints iteration progress.
74
+ verbose_interval : int, default 1
75
+ Interval at which verbose output is printed.
76
+
77
+ Returns
78
+ -------
79
+ self : CensoredCDFEstimator
80
+ The fitted estimator with computed CDF probabilities and interpolator.
81
+ """
82
+ self.qp_ints_ = self._extract_disjoint_intervals(intervals)
83
+
84
+ alphas = np.fromfunction(
85
+ np.vectorize(lambda i, j: self._if_sub_interval(intervals[i], self.qp_ints_[j])),
86
+ shape=(len(intervals), len(self.qp_ints_)), dtype=int)
87
+
88
+ self.qp_probs_ = np.ones(len(self.qp_ints_)) / len(self.qp_ints_)
89
+
90
+ for iteration in range(max_iter):
91
+ cur_probs = self.qp_probs_.copy()
92
+ ms = (alphas * cur_probs) / (alphas @ cur_probs).reshape(-1, 1)
93
+ self.qp_probs_ = ms.sum(0) / ms.sum()
94
+ diff = np.linalg.norm(cur_probs - self.qp_probs_)
95
+ if verbose and iteration % verbose_interval == 0:
96
+ print(f"Iteration: {iteration}, Probs diff l2-norm: {round(diff, int(2 - np.log10(tol)))}")
97
+ if diff < tol:
98
+ break
99
+
100
+ if len(self.qp_ints_[:, 0]) >= 2:
101
+ self._cdf_interpolator = interp1d(self.qp_ints_[:, 0], np.cumsum(self.qp_probs_),
102
+ bounds_error=False, fill_value=(0.0, 1.0))
103
+ else:
104
+ self._cdf_interpolator = lambda x: np.clip(x, 0, 1)
105
+
106
+ return self
107
+
108
+ def _cdf(self, x: Any, *args, **kwargs):
109
+ """
110
+ Evaluate the CDF at given points.
111
+
112
+ Parameters
113
+ ----------
114
+ x : array-like or float
115
+ Points at which to evaluate the CDF.
116
+
117
+ Returns
118
+ -------
119
+ array-like or float
120
+ Evaluated CDF values at `x`.
121
+ """
122
+ return self._cdf_interpolator(x)
123
+
124
+ def support(self):
125
+ """
126
+ Get the support of the distribution.
127
+
128
+ Returns
129
+ -------
130
+ tuple of float
131
+ Lower and upper bounds of the support.
132
+ """
133
+ return self.support_
134
+
135
+ @staticmethod
136
+ def _if_sub_interval(interval1: Tuple[float, float], interval2: Tuple[float, float]):
137
+ """
138
+ Check if interval2 is fully contained within interval1.
139
+
140
+ Parameters
141
+ ----------
142
+ interval1 : tuple of float
143
+ Parent interval (left, right).
144
+ interval2 : tuple of float
145
+ Candidate sub-interval (left, right).
146
+
147
+ Returns
148
+ -------
149
+ bool
150
+ True if interval2 is contained within interval1, False otherwise.
151
+ """
152
+ return (interval1[0] <= interval2[0]) and (interval1[1] >= interval2[1])
153
+
154
+ @staticmethod
155
+ def _extract_disjoint_intervals(intervals: List[Tuple[float, float]]):
156
+ """
157
+ Convert overlapping intervals into a sorted array of disjoint intervals.
158
+
159
+ Parameters
160
+ ----------
161
+ intervals : list of tuple of float
162
+ List of input intervals (left, right).
163
+
164
+ Returns
165
+ -------
166
+ np.ndarray, shape (n_disjoint, 2)
167
+ Array of disjoint intervals covering the same range as the input.
168
+ """
169
+ assert len(intervals) > 0, "At least one interval should be provided"
170
+ assert all(interval[0] <= interval[1] for interval in intervals)
171
+ lefts, rights = zip(*intervals)
172
+ sort_endpoints = sorted([(left, "L") for left in lefts] + [(right, "R") for right in rights])
173
+ disjoint_intervals = []
174
+ for (ep, next_ep) in zip(sort_endpoints[:-1], sort_endpoints[1:]):
175
+ if ep[1] == "L" and next_ep[1] == "R":
176
+ disjoint_intervals.append((ep[0], next_ep[0]))
177
+ return np.array(disjoint_intervals)
@@ -1,4 +1,5 @@
1
1
  import numpy as np
2
+ from warnings import warn
2
3
  from typing import List, Union, Dict, Tuple
3
4
  from scipy.optimize import LinearConstraint, minimize
4
5
  from scipy.stats._distn_infrastructure import rv_frozen
@@ -294,7 +295,13 @@ class GLTGridSearchEstimator(GLTWeightEstimator):
294
295
 
295
296
  class GLTWeightDistribEstimator(GLTWeightEstimator):
296
297
  """
297
- Estimator of weights and vertex threshold distributions under the GLT diffusion model
298
+ Estimator of weights and vertex threshold distributions under the GLT diffusion model.
299
+ This estimator alternates estimation of the GLT weights and estimation of the vertex threshold CDF
300
+ using the Turnbull estimator. For the Turnbull estimator, see:
301
+
302
+ Turnbull, Bruce W. (1976).
303
+ “The Empirical Distribution Function with Arbitrarily Grouped, Censored and Truncated Data.”
304
+ Journal of the Royal Statistical Society. Series B (Methodological) 38(3), 290–295.
298
305
  """
299
306
 
300
307
  def __init__(self, graph: Graph,
@@ -303,17 +310,37 @@ class GLTWeightDistribEstimator(GLTWeightEstimator):
303
310
 
304
311
  """Initialize the GLTWeightDistribEstimator.
305
312
 
313
+
306
314
  Parameters
307
315
  ----------
308
316
  graph : Graph
309
317
  The graph structure.
318
+ threshold_support : Tuple[float, float]
319
+ Support for the vertex threshold distributions.
310
320
  n_jobs : int, optional
311
321
  Number of jobs for parallel processing.
312
322
  """
313
323
  super().__init__(graph=graph, vertex_2_distrib=None, n_jobs=n_jobs)
324
+ warn("This estimator is currently unstable and can only estimate CDF accurately for high indeg vertices!")
314
325
  self.threshold_support = threshold_support
315
326
 
316
327
  def _construct_vertex_threshold_observed_intervals(self, vertex: int, parent_weights: np.array):
328
+ """
329
+ Construct the observed intervals for a vertex's threshold based on parent activations.
330
+
331
+ Parameters
332
+ ----------
333
+ vertex : int
334
+ The vertex for which to construct threshold intervals.
335
+ parent_weights : np.ndarray
336
+ Current weights of the parent vertices.
337
+
338
+ Returns
339
+ -------
340
+ List[Tuple[float, float]]
341
+ List of intervals containing the lower and upper bounds for the vertex threshold
342
+ based on observed activations.
343
+ """
317
344
  intervals = []
318
345
  for mask_tm1, mask_t in zip(self._vertex_2_active_parent_mask_tm1[vertex],
319
346
  self._vertex_2_active_parent_mask_t[vertex]):
@@ -1,7 +1,6 @@
1
1
  import numpy as np
2
2
  import matplotlib.pyplot as plt
3
3
 
4
-
5
4
  __all__ = ["plot_with_conf_intervals", "plot_hist_with_normal_fit"]
6
5
 
7
6
 
@@ -13,22 +12,31 @@ def plot_with_conf_intervals(x_true, x_pred, conf_intervals=None,
13
12
  Plot a scatter plot of `x_true` vs `x_pred` with confidence intervals as a filled area
14
13
  and a diagonal line y=x.
15
14
 
16
- :param x_true: Array-like, true values.
17
- :param x_pred: Array-like, predicted values.
18
- :param conf_intervals: 2D array of shape (2, len(x_pred)),
19
- containing the lower and upper bounds of the confidence intervals.
20
- If None, no confidence intervals are plotted.
21
- :param fontsize: int, font size for axis labels. Default is 12.
22
- :param figsize: tuple, size of the figure (width, height) in inches. Default is (10, 8).
23
- :param color: str or tuple, color of the scatter points and confidence interval area. Default is "blue".
24
- :param ax: matplotlib.axes.Axes, optional existing axes to plot on.
25
- If None, a new figure and axes are created.
26
- :param xlab: str, label for the x-axis. Default is "True activation probability".
27
- :param ylab: str, label for the y-axis. Default is "Predicted activation probability".
15
+ Parameters
16
+ ----------
17
+ x_true : array-like
18
+ True values.
19
+ x_pred : array-like
20
+ Predicted values.
21
+ conf_intervals : array-like, shape (2, len(x_pred)), optional
22
+ Lower and upper bounds of the confidence intervals. If None, no intervals are plotted.
23
+ fontsize : int, default 12
24
+ Font size for axis labels.
25
+ figsize : tuple, default (10, 8)
26
+ Figure size in inches (width, height).
27
+ color : str or tuple, default "blue"
28
+ Color of the scatter points and confidence interval area.
29
+ ax : matplotlib.axes.Axes, optional
30
+ Existing axes to plot on. If None, a new figure and axes are created.
31
+ xlab : str, default "True activation probability"
32
+ Label for the x-axis.
33
+ ylab : str, default "Predicted activation probability"
34
+ Label for the y-axis.
28
35
  """
29
36
  assert len(x_pred) == len(x_true), "x_pred and x_true must have the same length"
30
- assert conf_intervals.shape[1] == len(x_pred), "conf_intervals must have the same second dim as x_pred"
31
- assert conf_intervals.shape[0] == 2, "conf_intervals must have two rows for lower and upper bounds"
37
+ if conf_intervals is not None:
38
+ assert conf_intervals.shape[1] == len(x_pred), "conf_intervals must have same second dim as x_pred"
39
+ assert conf_intervals.shape[0] == 2, "conf_intervals must have two rows for lower and upper bounds"
32
40
 
33
41
  # Sort by x_true so fill_between works correctly
34
42
  sort_idx = np.argsort(x_true)
@@ -62,10 +70,16 @@ def plot_hist_with_normal_fit(sample, true_value, true_std=None, n_bins=20):
62
70
  """
63
71
  Plot a histogram of a sample with a fitted normal curve and a vertical line at the true value.
64
72
 
65
- :param sample: Array-like, sample data points
66
- :param true_value: Float, the true value
67
- :param true_std: Float, the true std
68
- :param n_bins: Int, the number of histogram bins
73
+ Parameters
74
+ ----------
75
+ sample : array-like
76
+ Sample data points.
77
+ true_value : float
78
+ The true value.
79
+ true_std : float, optional
80
+ The true standard deviation, used to plot the theoretical normal curve.
81
+ n_bins : int, default 20
82
+ Number of bins for the histogram.
69
83
  """
70
84
  from scipy.stats import norm
71
85
 
@@ -80,15 +94,15 @@ def plot_hist_with_normal_fit(sample, true_value, true_std=None, n_bins=20):
80
94
  p_fit = norm.pdf(x, loc=mean, scale=std)
81
95
  plt.plot(x, p_fit, 'b', linewidth=2, label="Fitted Gaussian")
82
96
 
83
- # Create the normal distribution's PDF
97
+ # Plot the theoretical normal curve
84
98
  if true_std is not None:
85
99
  p = norm.pdf(x, loc=true_value, scale=true_std)
86
100
  plt.plot(x, p, 'r', linewidth=2, label="Theoretical Gaussian")
87
101
 
88
- # Plot the vertical line at the true value
102
+ # Plot vertical line at the true value
89
103
  plt.axvline(true_value, color='black', linestyle='--', linewidth=1.5, label="True value")
90
104
 
91
- # Add labels and title
105
+ # Labels and legend
92
106
  plt.xlabel('Value')
93
107
  plt.ylabel('Density')
94
108
  plt.legend()
@@ -1,7 +1,5 @@
1
1
  import numpy as np
2
2
  from typing import Iterable, Set
3
- from scipy.stats._distn_infrastructure import rv_frozen
4
- import jax.numpy as jnp
5
3
 
6
4
 
7
5
  def multiple_union(set_list: Iterable[Set]):
@@ -29,15 +27,3 @@ def random_vector_inside_simplex(dim: int, ub: float = 1.):
29
27
  def random_vector_on_simplex(dim: int, ub: float = 1.):
30
28
  X = random_vector_inside_simplex(dim=dim, ub=1)
31
29
  return X / np.sum(X) * ub
32
-
33
-
34
- def make_jax_cdf(distrib: rv_frozen):
35
- name = distrib.dist.name
36
- args = distrib.args
37
- kwargs = distrib.kwds
38
- local_dic = {}
39
- exec(f"jax_distrib=jax.scipy.stats.{name}", None, local_dic)
40
- jax_distrib = local_dic["jax_distrib"]
41
- if name == "expon":
42
- return lambda x: 1. - jnp.exp(-x)
43
- return lambda x: jax_distrib.cdf(x, *args, **kwargs)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: InfluenceDiffusion
3
- Version: 0.0.15
3
+ Version: 0.0.17
4
4
  Summary: InfluenceDiffusion package
5
5
  Author: Alexander Kagan
6
6
  Author-email: <amkagan@umich.edu>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: InfluenceDiffusion
3
- Version: 0.0.15
3
+ Version: 0.0.17
4
4
  Summary: InfluenceDiffusion package
5
5
  Author: Alexander Kagan
6
6
  Author-email: <amkagan@umich.edu>
@@ -3,7 +3,7 @@ from pathlib import Path
3
3
  this_directory = Path(__file__).parent
4
4
  LONG_DESCRIPTION = (this_directory / "README.md").read_text()
5
5
 
6
- VERSION = '0.0.15'
6
+ VERSION = '0.0.17'
7
7
  DESCRIPTION = 'InfluenceDiffusion package'
8
8
 
9
9
  setup(
@@ -1,73 +0,0 @@
1
- from typing import List, Tuple, Any
2
- from scipy.stats._distn_infrastructure import rv_continuous
3
- from scipy.interpolate import interp1d
4
- import numpy as np
5
-
6
-
7
- class CensoredCDFEstimator(rv_continuous):
8
- def __init__(self, support: Tuple[float, float] = (-np.inf, np.inf),
9
- momtype=1,
10
- a=None,
11
- b=None,
12
- xtol=1e-14,
13
- badvalue=None,
14
- name=None,
15
- longname=None,
16
- shapes=None,
17
- extradoc=None,
18
- seed=None):
19
-
20
- super().__init__(momtype=momtype, a=a, b=b, xtol=xtol, badvalue=badvalue,
21
- name=name, longname=longname, shapes=shapes, seed=seed)
22
- self.support_ = support
23
-
24
- def fit(self, intervals: List[Tuple[float, float]],
25
- max_iter=50, tol=1e-4, verbose=False, verbose_interval=1):
26
-
27
- self.qp_ints_ = self._extract_disjoint_intervals(intervals)
28
-
29
- alphas = np.fromfunction(
30
- np.vectorize(lambda i, j: self._if_sub_interval(intervals[i], self.qp_ints_[j])),
31
- shape=(len(intervals), len(self.qp_ints_)), dtype=int)
32
-
33
- self.qp_probs_ = np.ones(len(self.qp_ints_)) / len(self.qp_ints_)
34
-
35
- for iteration in range(max_iter):
36
- cur_probs = self.qp_probs_.copy()
37
- ms = (alphas * cur_probs) / (alphas @ cur_probs).reshape(-1, 1)
38
- self.qp_probs_ = ms.sum(0) / ms.sum()
39
- diff = np.linalg.norm(cur_probs - self.qp_probs_)
40
- if verbose and iteration % verbose_interval == 0:
41
- print(f"Iteration: {iteration}, Probs diff l2-norm: {round(diff, int(2 - np.log10(tol)))}")
42
- if diff < tol:
43
- break
44
-
45
- if len(self.qp_ints_[:, 0]) >= 2:
46
- self._cdf_interpolator = interp1d(self.qp_ints_[:, 0], np.cumsum(self.qp_probs_),
47
- bounds_error=False, fill_value=(0.0, 1.0))
48
- else:
49
- self._cdf_interpolator = lambda x: np.clip(x, 0, 1)
50
-
51
- return self
52
-
53
- def _cdf(self, x: Any, *args, **kwargs):
54
- return self._cdf_interpolator(x)
55
-
56
- def support(self):
57
- return self.support_
58
-
59
- @staticmethod
60
- def _if_sub_interval(interval1: Tuple[float, float], interval2: Tuple[float, float]):
61
- return (interval1[0] <= interval2[0]) and (interval1[1] >= interval2[1])
62
-
63
- @staticmethod
64
- def _extract_disjoint_intervals(intervals: List[Tuple[float, float]]):
65
- assert len(intervals) > 0, "At least one interval should be provided"
66
- assert all(interval[0] <= interval[1] for interval in intervals)
67
- lefts, rights = zip(*intervals)
68
- sort_endpoints = sorted([(left, "L") for left in lefts] + [(right, "R") for right in rights])
69
- disjoint_intervals = []
70
- for (ep, next_ep) in zip(sort_endpoints[:-1], sort_endpoints[1:]):
71
- if ep[1] == "L" and next_ep[1] == "R":
72
- disjoint_intervals.append((ep[0], next_ep[0]))
73
- return np.array(disjoint_intervals)