FlowCyPy 0.5.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.
Files changed (44) hide show
  1. FlowCyPy/__init__.py +15 -0
  2. FlowCyPy/_version.py +16 -0
  3. FlowCyPy/classifier.py +196 -0
  4. FlowCyPy/coupling_mechanism/__init__.py +4 -0
  5. FlowCyPy/coupling_mechanism/empirical.py +47 -0
  6. FlowCyPy/coupling_mechanism/mie.py +205 -0
  7. FlowCyPy/coupling_mechanism/rayleigh.py +115 -0
  8. FlowCyPy/coupling_mechanism/uniform.py +39 -0
  9. FlowCyPy/cytometer.py +198 -0
  10. FlowCyPy/detector.py +616 -0
  11. FlowCyPy/directories.py +36 -0
  12. FlowCyPy/distribution/__init__.py +16 -0
  13. FlowCyPy/distribution/base_class.py +59 -0
  14. FlowCyPy/distribution/delta.py +86 -0
  15. FlowCyPy/distribution/lognormal.py +94 -0
  16. FlowCyPy/distribution/normal.py +95 -0
  17. FlowCyPy/distribution/particle_size_distribution.py +110 -0
  18. FlowCyPy/distribution/uniform.py +96 -0
  19. FlowCyPy/distribution/weibull.py +80 -0
  20. FlowCyPy/event_correlator.py +244 -0
  21. FlowCyPy/flow_cell.py +122 -0
  22. FlowCyPy/helper.py +85 -0
  23. FlowCyPy/logger.py +322 -0
  24. FlowCyPy/noises.py +29 -0
  25. FlowCyPy/particle_count.py +102 -0
  26. FlowCyPy/peak_locator/__init__.py +4 -0
  27. FlowCyPy/peak_locator/base_class.py +163 -0
  28. FlowCyPy/peak_locator/basic.py +108 -0
  29. FlowCyPy/peak_locator/derivative.py +143 -0
  30. FlowCyPy/peak_locator/moving_average.py +114 -0
  31. FlowCyPy/physical_constant.py +19 -0
  32. FlowCyPy/plottings.py +270 -0
  33. FlowCyPy/population.py +239 -0
  34. FlowCyPy/populations_instances.py +49 -0
  35. FlowCyPy/report.py +236 -0
  36. FlowCyPy/scatterer.py +373 -0
  37. FlowCyPy/source.py +249 -0
  38. FlowCyPy/units.py +26 -0
  39. FlowCyPy/utils.py +191 -0
  40. FlowCyPy-0.5.0.dist-info/LICENSE +21 -0
  41. FlowCyPy-0.5.0.dist-info/METADATA +252 -0
  42. FlowCyPy-0.5.0.dist-info/RECORD +44 -0
  43. FlowCyPy-0.5.0.dist-info/WHEEL +5 -0
  44. FlowCyPy-0.5.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,59 @@
1
+ import numpy as np
2
+ from typing import Optional, Tuple
3
+ import matplotlib.pyplot as plt
4
+ from MPSPlots.styles import mps
5
+ from FlowCyPy.units import particle, Quantity
6
+
7
+
8
+ class Base:
9
+ """
10
+ Base class for distributions used to define particle sizes in the flow cytometer.
11
+
12
+ This class provides a structure for generating random scatterer sizes based on different statistical distributions.
13
+ Each subclass must implement the `generate` method to generate a distribution of sizes and `get_pdf` to compute the
14
+ probability density function (PDF) values.
15
+
16
+ Parameters
17
+ ----------
18
+ scale_factor : float
19
+ A scaling factor applied to the PDF of the distribution. By default, it is set to 1 (equal weight).
20
+ """
21
+
22
+ scale_factor: Optional[float] = 1.0
23
+
24
+ def generate(self, n_samples: int) -> np.ndarray:
25
+ """Generate a distribution of scatterer sizes."""
26
+ raise NotImplementedError("Must be implemented by subclasses")
27
+
28
+ def get_pdf(self, x: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
29
+ """Compute the probability density function (PDF) values."""
30
+ raise NotImplementedError("Must be implemented by subclasses")
31
+
32
+ def plot(self, n_samples: int = 4000, bins: int = 50) -> None:
33
+ """
34
+ Plots a histogram of the generated particle sizes based on the log-normal distribution.
35
+
36
+ Parameters
37
+ ----------
38
+ n_samples : int, optional
39
+ The number of particle sizes to generate for the histogram (default is 1000).
40
+ bins : int, optional
41
+ The number of bins in the histogram (default is 50).
42
+ """
43
+ samples = self.generate(Quantity(n_samples, particle))
44
+
45
+ # Plotting the PDF
46
+ with plt.style.context(mps): # Assuming mps is a custom style
47
+ figure, ax = plt.subplots(1, 1)
48
+ ax.hist(samples, bins=bins, color='blue', edgecolor='black', alpha=0.7)
49
+
50
+ ax.set(
51
+ title='Distribution',
52
+ xlabel=f'Distributed parameter [{self._main_units}]',
53
+ ylabel='Probability Density Function (PDF)'
54
+ )
55
+
56
+ plt.show()
57
+
58
+ def __str__(self) -> str:
59
+ return self.__repr__()
@@ -0,0 +1,86 @@
1
+
2
+ from FlowCyPy.distribution.base_class import Base
3
+ import numpy as np
4
+ from typing import Tuple
5
+ from PyMieSim.units import Quantity
6
+ from pydantic.dataclasses import dataclass
7
+
8
+ config_dict = dict(
9
+ arbitrary_types_allowed=True,
10
+ kw_only=True,
11
+ slots=True,
12
+ extra='forbid'
13
+ )
14
+
15
+
16
+ @dataclass(config=config_dict)
17
+ class Delta(Base):
18
+ r"""
19
+ Represents a delta-like distribution for particle sizes.
20
+
21
+ In a delta distribution, all particle sizes are the same, representing a delta function:
22
+
23
+ .. math::
24
+ f(x) = \delta(x - x_0)
25
+
26
+ where:
27
+ - :math:`x_0` is the singular particle size.
28
+
29
+ Parameters
30
+ ----------
31
+ position : Quantity
32
+ The particle size for the delta distribution in meters.
33
+ scale_factor : float, optional
34
+ A scaling factor applied to the PDF (not the sizes).
35
+ """
36
+
37
+ position: Quantity
38
+ _name = 'Delta'
39
+
40
+ def __post_init__(self):
41
+ self._main_units = self.position.units
42
+
43
+ def generate(self, n_samples: int) -> np.ndarray:
44
+ r"""
45
+ Generates a singular distribution of scatterer sizes.
46
+
47
+ All sizes generated will be exactly the same as `position`.
48
+
49
+ Parameters
50
+ ----------
51
+ n_samples : int
52
+ The number of particle sizes to generate.
53
+
54
+ Returns
55
+ -------
56
+ np.ndarray
57
+ An array of identical scatterer sizes in meters.
58
+ """
59
+ return np.ones(n_samples.magnitude) * self.position.magnitude * self._main_units
60
+
61
+ def get_pdf(self, x: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
62
+ r"""
63
+ Returns the x-values and the scaled PDF values for the singular distribution.
64
+
65
+ The PDF is represented as a delta-like function centered at `position`.
66
+
67
+ Parameters
68
+ ----------
69
+ x : np.ndarray
70
+ The input x-values (particle sizes) over which to compute the PDF.
71
+
72
+ Returns
73
+ -------
74
+ Tuple[np.ndarray, np.ndarray]
75
+ The input x-values and the corresponding scaled PDF values.
76
+ """
77
+ common_units = x.units
78
+
79
+ pdf = np.zeros_like(x)
80
+
81
+ idx = (np.abs(x.magnitude - self.position.to(common_units).magnitude)).argmin() # Delta-like function for singular value
82
+ pdf[idx] = 1.0
83
+ return x, pdf
84
+
85
+ def __repr__(self) -> str:
86
+ return f"Delta({self.position:.3f~P})"
@@ -0,0 +1,94 @@
1
+ from FlowCyPy.distribution.base_class import Base
2
+ import numpy as np
3
+ from typing import Tuple
4
+ from scipy.stats import lognorm
5
+ from PyMieSim.units import Quantity
6
+ from pydantic.dataclasses import dataclass
7
+
8
+ config_dict = dict(
9
+ arbitrary_types_allowed=True,
10
+ kw_only=True,
11
+ slots=True,
12
+ extra='forbid'
13
+ )
14
+
15
+
16
+ @dataclass(config=config_dict)
17
+ class LogNormal(Base):
18
+ r"""
19
+ Represents a log-normal distribution for particle sizes.
20
+
21
+ The log-normal distribution is described by its mean and standard deviation of the logarithm of the values:
22
+
23
+ .. math::
24
+ f(x) = \frac{1}{x \sigma \sqrt{2 \pi}} \exp \left( - \frac{(\ln(x) - \mu)^2}{2 \sigma^2} \right)
25
+
26
+ where:
27
+ - :math:`\mu` is the mean of the natural logarithm of the particle sizes.
28
+ - :math:`\sigma` is the standard deviation of the logarithm of particle sizes.
29
+
30
+ Parameters
31
+ ----------
32
+ mean : Quantity
33
+ The mean particle size in meters.
34
+ std_dev : Quantity
35
+ The standard deviation of the logarithm of particle sizes.
36
+ scale_factor : float, optional
37
+ A scaling factor applied to the PDF (not the sizes).
38
+ """
39
+
40
+ mean: Quantity
41
+ std_dev: Quantity
42
+ _name = 'Log-normal'
43
+
44
+ def __post_init__(self):
45
+ self._main_units = self.mean.units
46
+
47
+ def generate(self, n_samples: int) -> Quantity:
48
+ """
49
+ Generates a log-normal distribution of scatterer sizes.
50
+
51
+ The generated sizes follow a log-normal distribution, where the logarithm of the sizes is normally distributed.
52
+
53
+ Parameters
54
+ ----------
55
+ n_samples : Quantity
56
+ The number of particle sizes to generate.
57
+
58
+ Returns
59
+ -------
60
+ Quantity
61
+ An array of scatterer sizes in meters.
62
+ """
63
+ return np.random.lognormal(
64
+ mean=self.mean.to(self._main_units).magnitude,
65
+ sigma=self.std_dev.to(self._main_units).magnitude,
66
+ size=n_samples.magnitude
67
+ ) * self._main_units
68
+
69
+ def get_pdf(self, x: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
70
+ """
71
+ Returns the x-values and the scaled PDF values for the log-normal distribution.
72
+
73
+ Parameters
74
+ ----------
75
+ x : np.ndarray
76
+ The input x-values (particle sizes) over which to compute the PDF.
77
+
78
+ Returns
79
+ -------
80
+ Tuple[np.ndarray, np.ndarray]
81
+ The input x-values and the corresponding scaled PDF values.
82
+ """
83
+ common_units = x.units
84
+
85
+ pdf = lognorm.pdf(
86
+ x.to(common_units).magnitude,
87
+ s=self.std_dev.to(common_units).magnitude,
88
+ scale=self.mean.to(common_units).magnitude
89
+ )
90
+
91
+ return x, pdf
92
+
93
+ def __repr__(self) -> str:
94
+ return f"Log-Normal({self.mean:.3f~P}, {self.std_dev:.3f~P})"
@@ -0,0 +1,95 @@
1
+ from FlowCyPy.distribution.base_class import Base
2
+ import numpy as np
3
+ from typing import Tuple
4
+ from scipy.stats import norm
5
+ from PyMieSim.units import Quantity
6
+ from pydantic.dataclasses import dataclass
7
+
8
+ config_dict = dict(
9
+ arbitrary_types_allowed=True,
10
+ kw_only=True,
11
+ slots=True,
12
+ extra='forbid'
13
+ )
14
+
15
+
16
+ @dataclass(config=config_dict)
17
+ class Normal(Base):
18
+ r"""
19
+ Represents a normal (Gaussian) distribution for particle sizes.
20
+
21
+ The normal distribution is described by its mean and standard deviation:
22
+
23
+ .. math::
24
+ f(x) = \frac{1}{\sqrt{2 \pi \sigma^2}} \exp \left( - \frac{(x - \mu)^2}{2 \sigma^2} \right)
25
+
26
+ where:
27
+ - :math:`\mu` is the mean of the distribution (average particle size).
28
+ - :math:`\sigma` is the standard deviation (width of the distribution).
29
+ - :math:`x` represents particle sizes.
30
+
31
+ Parameters
32
+ ----------
33
+ mean : Quantity
34
+ The mean (average) particle size in meters.
35
+ std_dev : Quantity
36
+ The standard deviation of particle sizes in meters.
37
+ """
38
+
39
+ mean: Quantity
40
+ std_dev: Quantity
41
+ _name = 'Normal'
42
+
43
+ def __post_init__(self):
44
+ self._main_units = self.mean.units
45
+
46
+ def generate(self, n_samples: int) -> np.ndarray:
47
+ """
48
+ Generates a normal distribution of scatterer sizes.
49
+
50
+ The generated sizes are based on the normal distribution's mean and standard deviation.
51
+
52
+ Parameters
53
+ ----------
54
+ n_samples : int
55
+ The number of particle sizes to generate.
56
+
57
+ Returns
58
+ -------
59
+ np.ndarray
60
+ An array of scatterer sizes in meters.
61
+ """
62
+ return np.random.normal(
63
+ loc=self.mean.to(self._main_units).magnitude,
64
+ scale=self.std_dev.to(self._main_units).magnitude,
65
+ size=int(n_samples.magnitude)
66
+ ) * self._main_units
67
+
68
+ def get_pdf(self, x: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
69
+ """
70
+ Returns the x-values and the scaled PDF values for the normal distribution.
71
+
72
+ The `scale_factor` is applied to the PDF, not the generated sizes.
73
+
74
+ Parameters
75
+ ----------
76
+ x : np.ndarray
77
+ The input x-values (particle sizes) over which to compute the PDF.
78
+
79
+ Returns
80
+ -------
81
+ Tuple[np.ndarray, np.ndarray]
82
+ The input x-values and the corresponding scaled PDF values.
83
+ """
84
+ common_units = x.units
85
+
86
+ pdf = norm.pdf(
87
+ x.magnitude,
88
+ loc=self.mean.to(common_units).magnitude,
89
+ scale=self.std_dev.to(common_units).magnitude
90
+ )
91
+
92
+ return x, pdf
93
+
94
+ def __repr__(self) -> str:
95
+ return f"Normal({self.mean:.3f~P}, {self.std_dev:.3f~P})"
@@ -0,0 +1,110 @@
1
+ from FlowCyPy.distribution.base_class import Base
2
+ import numpy as np
3
+ from typing import Tuple
4
+ from PyMieSim.units import Quantity
5
+ from pydantic.dataclasses import dataclass
6
+
7
+ config_dict = dict(
8
+ arbitrary_types_allowed=True,
9
+ kw_only=True,
10
+ slots=True,
11
+ extra='forbid'
12
+ )
13
+
14
+
15
+ @dataclass(config=config_dict)
16
+ class RosinRammler(Base):
17
+ r"""
18
+ Represents a Particle Size Distribution using the Rosin-Rammler model.
19
+
20
+ The Rosin-Rammler distribution is described by its characteristic size and
21
+ spread parameter, and is used to model particle sizes in systems such as
22
+ powders or granular materials.
23
+
24
+ The distribution function is given by:
25
+
26
+ .. math::
27
+ F(x) = 1 - \exp \left( - \left( \frac{x}{d} \right)^k \right)
28
+
29
+ where:
30
+ - :math:`x` is the particle size.
31
+ - :math:`d` is the characteristic particle size.
32
+ - :math:`k` is the spread parameter.
33
+
34
+ Parameters
35
+ ----------
36
+ characteristic_size : Quantity
37
+ The characteristic particle size in meters.
38
+ spread : float
39
+ The spread parameter (shape factor).
40
+ """
41
+
42
+ characteristic_size: Quantity
43
+ spread: float
44
+ _name = 'Rosin-Rammler'
45
+
46
+ def __post_init__(self):
47
+ self._main_units = self.characteristic_size.units
48
+
49
+ def generate(self, n_samples: Quantity) -> Quantity:
50
+ """
51
+ Generates a particle size distribution based on the Rosin-Rammler model.
52
+
53
+ Parameters
54
+ ----------
55
+ n_samples : Quantity
56
+ The number of particle sizes to generate (dimensionless).
57
+
58
+ Returns
59
+ -------
60
+ Quantity
61
+ An array of particle sizes in meters (or other units).
62
+ """
63
+ # Validate inputs
64
+ if not isinstance(n_samples, Quantity) or not n_samples.check("particle"):
65
+ raise ValueError("n_samples must be a dimensionless Quantity.")
66
+ if self.spread <= 0:
67
+ raise ValueError("Spread parameter must be greater than zero.")
68
+
69
+ # Convert characteristic size to main units
70
+ d = self.characteristic_size.to(self._main_units).magnitude
71
+
72
+ # Generate uniform random samples in [0, 1)
73
+ u = np.random.uniform(size=n_samples.magnitude)
74
+ u = np.clip(u, 1e-10, 1 - 1e-10) # Avoid numerical issues
75
+
76
+ # Apply inverse CDF of Rosin-Rammler distribution
77
+ sizes = d * (-np.log(1 - u))**(1 / self.spread)
78
+
79
+ return sizes * self._main_units
80
+
81
+ def get_pdf(self, x: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
82
+ r"""
83
+ Returns the x-values and the scaled PDF values for the particle size distribution.
84
+
85
+ The PDF for the Rosin-Rammler distribution is derived from the CDF:
86
+
87
+ .. math::
88
+ f(x) = \frac{k}{d} \left( \frac{x}{d} \right)^{k-1} \exp \left( - \left( \frac{x}{d} \right)^k \right)
89
+
90
+ Parameters
91
+ ----------
92
+ x : np.ndarray
93
+ The input x-values (particle sizes) over which to compute the PDF.
94
+
95
+ Returns
96
+ -------
97
+ Tuple[np.ndarray, np.ndarray]
98
+ The input x-values and the corresponding scaled PDF values.
99
+ """
100
+ common_units = x.units
101
+ d = self.characteristic_size.to(common_units).magnitude
102
+ k = self.spread
103
+
104
+ # Rosin-Rammler PDF formula
105
+ pdf = (k / d) * (x.magnitude / d)**(k - 1) * np.exp(-(x.magnitude / d)**k)
106
+
107
+ return x, pdf
108
+
109
+ def __repr__(self) -> str:
110
+ return f"RR({self.characteristic_size:.3f~P}, {self.spread:.3f})"
@@ -0,0 +1,96 @@
1
+ from FlowCyPy.distribution.base_class import Base
2
+ import numpy as np
3
+ from typing import Tuple
4
+ from scipy.stats import uniform
5
+ from PyMieSim.units import Quantity
6
+ from pydantic.dataclasses import dataclass
7
+
8
+ config_dict = dict(
9
+ arbitrary_types_allowed=True,
10
+ kw_only=True,
11
+ slots=True,
12
+ extra='forbid'
13
+ )
14
+
15
+
16
+ @dataclass(config=config_dict)
17
+ class Uniform(Base):
18
+ r"""
19
+ Represents a uniform distribution for particle sizes.
20
+
21
+ The uniform distribution assigns equal probability to all particle sizes within a specified range:
22
+
23
+ .. math::
24
+ f(x) = \frac{1}{b - a} \quad \text{for} \quad a \leq x \leq b
25
+
26
+ where:
27
+ - :math:`a` is the lower bound of the distribution.
28
+ - :math:`b` is the upper bound of the distribution.
29
+
30
+ Parameters
31
+ ----------
32
+ lower_bound : float
33
+ The lower bound for particle sizes in meters.
34
+ upper_bound : float
35
+ The upper bound for particle sizes in meters.
36
+ scale_factor : float, optional
37
+ A scaling factor applied to the PDF (not the sizes).
38
+ """
39
+
40
+ lower_bound: Quantity
41
+ upper_bound: Quantity
42
+ _name = 'Uniform'
43
+
44
+ def __post_init__(self):
45
+ self._main_units = self.lower_bound.units
46
+
47
+ def generate(self, n_samples: Quantity) -> Quantity:
48
+ """
49
+ Generates a uniform distribution of scatterer sizes.
50
+
51
+ The generated sizes are uniformly distributed between the specified `lower_bound` and `upper_bound`.
52
+
53
+ Parameters
54
+ ----------
55
+ n_samples : Quantity
56
+ The number of particle sizes to generate.
57
+
58
+ Returns
59
+ -------
60
+ np.ndarray
61
+ An array of scatterer sizes in meters.
62
+ """
63
+ return np.random.uniform(
64
+ self.lower_bound.to(self._main_units).magnitude,
65
+ self.upper_bound.to(self._main_units).magnitude,
66
+ n_samples.magnitude
67
+ ) * self._main_units
68
+
69
+ def get_pdf(self, x: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
70
+ """
71
+ Returns the x-values and the scaled PDF values for the uniform distribution.
72
+
73
+ The `scale_factor` is applied to the PDF, not the generated sizes.
74
+
75
+ Parameters
76
+ ----------
77
+ x : np.ndarray
78
+ The input x-values (particle sizes) over which to compute the PDF.
79
+
80
+ Returns
81
+ -------
82
+ Tuple[np.ndarray, np.ndarray]
83
+ The input x-values and the corresponding scaled PDF values.
84
+ """
85
+ common_unit = self.lower_bound.units
86
+
87
+ pdf = uniform.pdf(
88
+ x.to(common_unit).magnitude,
89
+ loc=self.lower_bound.magnitude,
90
+ scale=self.upper_bound.to(common_unit).magnitude - self.lower_bound.magnitude
91
+ )
92
+
93
+ return x, pdf
94
+
95
+ def __repr__(self) -> str:
96
+ return f"Uniform({self.lower_bound:.3f~P}, {self.upper_bound:.3f~P})"
@@ -0,0 +1,80 @@
1
+ import numpy as np
2
+ from typing import Tuple
3
+ from PyMieSim.units import Quantity
4
+ from FlowCyPy.distribution.base_class import Base
5
+ from pydantic.dataclasses import dataclass
6
+
7
+
8
+ config_dict = dict(
9
+ arbitrary_types_allowed=True,
10
+ kw_only=True,
11
+ slots=True,
12
+ extra='forbid'
13
+ )
14
+
15
+
16
+ @dataclass(config=config_dict)
17
+ class Weibull(Base):
18
+ r"""
19
+ Represents a Weibull distribution for particle sizes.
20
+
21
+ The Weibull distribution is commonly used for modeling size distributions in biological systems.
22
+
23
+ Parameters
24
+ ----------
25
+ shape : Quantity
26
+ The shape parameter (k), controls the skewness of the distribution.
27
+ scale : Quantity
28
+ The scale parameter (λ), controls the spread of the distribution.
29
+ scale_factor : float, optional
30
+ A scaling factor applied to the PDF (not the sizes).
31
+ """
32
+
33
+ shape: Quantity
34
+ scale: Quantity
35
+ _name = 'Weibull'
36
+
37
+ def __post_init__(self):
38
+ self._main_units = self.shape.units
39
+
40
+ def generate(self, n_samples: int) -> np.ndarray:
41
+ """
42
+ Generates a Weibull distribution of scatterer sizes.
43
+
44
+ Parameters
45
+ ----------
46
+ n_samples : int
47
+ The number of particle sizes to generate.
48
+
49
+ Returns
50
+ -------
51
+ np.ndarray
52
+ An array of particle sizes in meters.
53
+ """
54
+ common_unit = self.shape.units
55
+
56
+ return np.random.weibull(
57
+ self.shape.to(self._main_units).magnitude,
58
+ size=n_samples.magnitude
59
+ ) * common_unit
60
+
61
+ def get_pdf(self, x: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
62
+ """
63
+ Returns the x-values and the PDF values for the Weibull distribution.
64
+
65
+ Parameters
66
+ ----------
67
+ x : np.ndarray
68
+ The input x-values (particle sizes) over which to compute the PDF.
69
+
70
+ Returns
71
+ -------
72
+ Tuple[np.ndarray, np.ndarray]
73
+ The input x-values and the corresponding PDF values.
74
+ """
75
+ a = self.shape / self.scale
76
+ b = (x / self.scale)
77
+ c = np.exp(-(x / self.scale) ** self.shape)
78
+ pdf = a * b ** (self.shape - 1) * c
79
+
80
+ return x, self.scale_factor * pdf