humalab 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,81 @@
1
+ from humalab.dists.distribution import Distribution
2
+
3
+ import numpy as np
4
+
5
+ class Categorical(Distribution):
6
+ """Categorical distribution for discrete choices.
7
+
8
+ Samples from a list of choices with optional weights. If weights are not
9
+ provided, samples uniformly from all choices. Weights are automatically
10
+ normalized to sum to 1.
11
+ """
12
+ def __init__(self,
13
+ generator: np.random.Generator,
14
+ choices: list,
15
+ weights: list[float] | None = None,
16
+ size: int | tuple[int, ...] | None = None) -> None:
17
+ """
18
+ Initialize the categorical distribution.
19
+
20
+ Args:
21
+ generator (np.random.Generator): The random number generator.
22
+ choices (list): The list of choices.
23
+ weights (list[float] | None): The weights for each choice.
24
+ size (int | tuple[int, ...] | None): The size of the output.
25
+ """
26
+ super().__init__(generator=generator)
27
+ self._choices = choices
28
+ self._size = size
29
+ if weights is not None and not np.isclose(sum(weights), 1.0):
30
+ weight_sum = sum(weights)
31
+ weights = [w / weight_sum for w in weights]
32
+ self._weights = weights
33
+
34
+ @staticmethod
35
+ def validate(dimensions: int, *args) -> bool:
36
+ """Validate distribution parameters for the given dimensions.
37
+
38
+ Args:
39
+ dimensions (int): The number of dimensions (0 for scalar, -1 for any).
40
+ *args: The distribution parameters (choices, weights).
41
+
42
+ Returns:
43
+ bool: Always returns True as categorical accepts any parameters.
44
+ """
45
+ return True
46
+
47
+ def _sample(self) -> int | float | np.ndarray:
48
+ """Generate a sample from the categorical distribution.
49
+
50
+ Returns:
51
+ int | float | np.ndarray: Sampled choice(s) from the list.
52
+ """
53
+ return self._generator.choice(self._choices, size=self._size, p=self._weights)
54
+
55
+ def __repr__(self) -> str:
56
+ """String representation of the categorical distribution.
57
+
58
+ Returns:
59
+ str: String representation showing choices, size, and weights.
60
+ """
61
+ return f"Categorical(choices={self._choices}, size={self._size}, weights={self._weights})"
62
+
63
+ @staticmethod
64
+ def create(generator: np.random.Generator,
65
+ choices: list,
66
+ weights: list[float] | None = None,
67
+ size: int | tuple[int, ...] | None = None
68
+ ) -> 'Categorical':
69
+ """
70
+ Create a categorical distribution.
71
+
72
+ Args:
73
+ generator (np.random.Generator): The random number generator.
74
+ choices (list): The list of choices.
75
+ size (int | tuple[int, ...] | None): The size of the output.
76
+ weights (list[float] | None): The weights for each choice.
77
+
78
+ Returns:
79
+ Categorical: The created categorical distribution.
80
+ """
81
+ return Categorical(generator=generator, choices=choices, size=size, weights=weights)
@@ -0,0 +1,103 @@
1
+ from humalab.dists.distribution import Distribution
2
+ from typing import Any
3
+
4
+ import numpy as np
5
+
6
+ class Discrete(Distribution):
7
+ """Discrete uniform distribution over integers.
8
+
9
+ Samples integer values uniformly from a range [low, high). The endpoint
10
+ parameter controls whether the upper bound is inclusive or exclusive.
11
+ Supports scalar outputs as well as multi-dimensional arrays with 1D variants.
12
+ """
13
+ def __init__(self,
14
+ generator: np.random.Generator,
15
+ low: int | Any,
16
+ high: int | Any,
17
+ endpoint: bool | None = None,
18
+ size: int | tuple[int, ...] | None = None,
19
+ ) -> None:
20
+ """
21
+ Initialize the discrete distribution.
22
+
23
+ Args:
24
+ generator (np.random.Generator): The random number generator.
25
+ low (int | Any): The lower bound (inclusive).
26
+ high (int | Any): The upper bound (exclusive).
27
+ endpoint (bool | None): Whether to include the endpoint.
28
+ size (int | tuple[int, ...] | None): The size of the output.
29
+ """
30
+ super().__init__(generator=generator)
31
+ self._low = np.array(low)
32
+ self._high = np.array(high)
33
+ self._size = size
34
+ self._endpoint = endpoint if endpoint is not None else True
35
+
36
+ @staticmethod
37
+ def validate(dimensions: int, *args) -> bool:
38
+ """Validate distribution parameters for the given dimensions.
39
+
40
+ Args:
41
+ dimensions (int): The number of dimensions (0 for scalar, -1 for any).
42
+ *args: The distribution parameters (low, high).
43
+
44
+ Returns:
45
+ bool: True if parameters are valid, False otherwise.
46
+ """
47
+ arg1 = args[0]
48
+ arg2 = args[1]
49
+ if dimensions == 0:
50
+ if not isinstance(arg1, int):
51
+ return False
52
+ if not isinstance(arg2, int):
53
+ return False
54
+ return True
55
+ if dimensions == -1:
56
+ return True
57
+ if not isinstance(arg1, int):
58
+ if isinstance(arg1, (list, np.ndarray)):
59
+ if len(arg1) != dimensions:
60
+ return False
61
+ if not isinstance(arg2, int):
62
+ if isinstance(arg2, (list, np.ndarray)):
63
+ if len(arg2) != dimensions:
64
+ return False
65
+ return True
66
+
67
+ def _sample(self) -> int | float | np.ndarray:
68
+ """Generate a sample from the discrete distribution.
69
+
70
+ Returns:
71
+ int | float | np.ndarray: Sampled integer value(s) from [low, high).
72
+ """
73
+ return self._generator.integers(self._low, self._high, size=self._size, endpoint=self._endpoint)
74
+
75
+ def __repr__(self) -> str:
76
+ """String representation of the discrete distribution.
77
+
78
+ Returns:
79
+ str: String representation showing low, high, size, and endpoint.
80
+ """
81
+ return f"Discrete(low={self._low}, high={self._high}, size={self._size}, endpoint={self._endpoint})"
82
+
83
+ @staticmethod
84
+ def create(generator: np.random.Generator,
85
+ low: int | Any,
86
+ high: int | Any,
87
+ endpoint: bool = True,
88
+ size: int | tuple[int, ...] | None = None,
89
+ ) -> 'Discrete':
90
+ """
91
+ Create a discrete distribution.
92
+
93
+ Args:
94
+ generator (np.random.Generator): The random number generator.
95
+ low (int | Any): The lower bound (inclusive).
96
+ high (int | Any): The upper bound (exclusive).
97
+ endpoint (bool): Whether to include the endpoint.
98
+ size (int | tuple[int, ...] | None): The size of the output.
99
+
100
+ Returns:
101
+ Discrete: The created discrete distribution.
102
+ """
103
+ return Discrete(generator=generator, low=low, high=high, size=size, endpoint=endpoint)
@@ -0,0 +1,49 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ import numpy as np
4
+
5
+ class Distribution(ABC):
6
+ """Abstract base class for probability distributions.
7
+
8
+ All distribution classes inherit from this base class and must implement
9
+ the _sample() method. Distributions maintain a random number generator
10
+ and track the last sampled value.
11
+ """
12
+ def __init__(self,
13
+ generator: np.random.Generator) -> None:
14
+ """
15
+ Initialize the distribution.
16
+
17
+ Args:
18
+ generator (np.random.Generator): The random number generator.
19
+ """
20
+ super().__init__()
21
+ self._generator = generator
22
+ self._last_sample = None
23
+
24
+ def sample(self) -> int | float | np.ndarray:
25
+ """
26
+ Sample from the distribution.
27
+
28
+ Returns:
29
+ int | float | np.ndarray: The sampled value(s).
30
+ """
31
+ self._last_sample = self._sample()
32
+ return self._last_sample
33
+
34
+ @abstractmethod
35
+ def _sample(self) -> int | float | np.ndarray:
36
+ """Generate a sample from the distribution.
37
+
38
+ Returns:
39
+ int | float | np.ndarray: The sampled value(s).
40
+ """
41
+ pass
42
+
43
+ @property
44
+ def last_sample(self) -> int | float | np.ndarray | None:
45
+ """Get the last sampled value.
46
+ Returns:
47
+ int | float | np.ndarray | None: The last sampled value, or None if no sample has been taken yet.
48
+ """
49
+ return self._last_sample
@@ -0,0 +1,96 @@
1
+ from humalab.dists.distribution import Distribution
2
+ from typing import Any
3
+ import numpy as np
4
+
5
+
6
+ class Gaussian(Distribution):
7
+ """Gaussian (normal) distribution.
8
+
9
+ Samples values from a normal distribution with specified mean (loc) and
10
+ standard deviation (scale). Supports scalar outputs as well as multi-dimensional
11
+ arrays with 1D, 2D, or 3D variants.
12
+ """
13
+ def __init__(self,
14
+ generator: np.random.Generator,
15
+ loc: float | Any,
16
+ scale: float | Any,
17
+ size: int | tuple[int, ...] | None = None) -> None:
18
+ """
19
+ Initialize the Gaussian (normal) distribution.
20
+
21
+ Args:
22
+ generator (np.random.Generator): The random number generator.
23
+ loc (float | Any): The mean of the distribution.
24
+ scale (float | Any): The standard deviation of the distribution.
25
+ size (int | tuple[int, ...] | None): The size of the output.
26
+ """
27
+ super().__init__(generator=generator)
28
+ self._loc = loc
29
+ self._scale = scale
30
+ self._size = size
31
+
32
+ @staticmethod
33
+ def validate(dimensions: int, *args) -> bool:
34
+ """Validate distribution parameters for the given dimensions.
35
+
36
+ Args:
37
+ dimensions (int): The number of dimensions (0 for scalar, -1 for any).
38
+ *args: The distribution parameters (loc, scale).
39
+
40
+ Returns:
41
+ bool: True if parameters are valid, False otherwise.
42
+ """
43
+ arg1 = args[0]
44
+ arg2 = args[1]
45
+ if dimensions == 0:
46
+ if not isinstance(arg1, (int, float)):
47
+ return False
48
+ if not isinstance(arg2, (int, float)):
49
+ return False
50
+ return True
51
+ if dimensions == -1:
52
+ return True
53
+ if not isinstance(arg1, (int, float)):
54
+ if isinstance(arg1, (list, np.ndarray)):
55
+ if len(arg1) != dimensions:
56
+ return False
57
+ if not isinstance(arg2, (int, float)):
58
+ if isinstance(arg2, (list, np.ndarray)):
59
+ if len(arg2) != dimensions:
60
+ return False
61
+ return True
62
+
63
+ def _sample(self) -> int | float | np.ndarray:
64
+ """Generate a sample from the Gaussian distribution.
65
+
66
+ Returns:
67
+ int | float | np.ndarray: Sampled value(s) from N(loc, scale).
68
+ """
69
+ return self._generator.normal(loc=self._loc, scale=self._scale, size=self._size)
70
+
71
+ def __repr__(self) -> str:
72
+ """String representation of the Gaussian distribution.
73
+
74
+ Returns:
75
+ str: String representation showing loc, scale, and size.
76
+ """
77
+ return f"Gaussian(loc={self._loc}, scale={self._scale}, size={self._size})"
78
+
79
+ @staticmethod
80
+ def create(generator: np.random.Generator,
81
+ loc: float | Any,
82
+ scale: float | Any,
83
+ size: int | tuple[int, ...] | None = None) -> 'Gaussian':
84
+ """
85
+ Create a Gaussian (normal) distribution.
86
+
87
+ Args:
88
+ generator (np.random.Generator): The random number generator.
89
+ loc (float | Any): The mean of the distribution.
90
+ scale (float | Any): The standard deviation of the distribution.
91
+ size (int | tuple[int, ...] | None): The size of the output.
92
+
93
+ Returns:
94
+ Gaussian: The created Gaussian distribution.
95
+ """
96
+ return Gaussian(generator=generator, loc=loc, scale=scale, size=size)
@@ -0,0 +1,97 @@
1
+ from humalab.dists.distribution import Distribution
2
+ from typing import Any
3
+
4
+ import numpy as np
5
+
6
+ class LogUniform(Distribution):
7
+ """Log-uniform distribution.
8
+
9
+ Samples values uniformly in log-space, useful for hyperparameters that
10
+ span multiple orders of magnitude (e.g., learning rates). The result is
11
+ exp(uniform(log(low), log(high))). Supports scalar outputs as well as
12
+ multi-dimensional arrays with 1D variants.
13
+ """
14
+ def __init__(self,
15
+ generator: np.random.Generator,
16
+ low: float | Any,
17
+ high: float | Any,
18
+ size: int | tuple[int, ...]| None = None) -> None:
19
+ """
20
+ Initialize the log-uniform distribution.
21
+
22
+ Args:
23
+ generator (np.random.Generator): The random number generator.
24
+ low (float | Any): The lower bound (inclusive).
25
+ high (float | Any): The upper bound (exclusive).
26
+ size (int | tuple[int, ...]| None): The size of the output.
27
+ """
28
+ super().__init__(generator=generator)
29
+ self._log_low = np.log(np.array(low))
30
+ self._log_high = np.log(np.array(high))
31
+ self._size = size
32
+
33
+ @staticmethod
34
+ def validate(dimensions: int, *args) -> bool:
35
+ """Validate distribution parameters for the given dimensions.
36
+
37
+ Args:
38
+ dimensions (int): The number of dimensions (0 for scalar, -1 for any).
39
+ *args: The distribution parameters (low, high).
40
+
41
+ Returns:
42
+ bool: True if parameters are valid, False otherwise.
43
+ """
44
+ arg1 = args[0]
45
+ arg2 = args[1]
46
+ if dimensions == 0:
47
+ if not isinstance(arg1, (int, float)):
48
+ return False
49
+ if not isinstance(arg2, (int, float)):
50
+ return False
51
+ return True
52
+ if dimensions == -1:
53
+ return True
54
+ if not isinstance(arg1, (int, float)):
55
+ if isinstance(arg1, (list, np.ndarray)):
56
+ if len(arg1) != dimensions:
57
+ return False
58
+ if not isinstance(arg2, (int, float)):
59
+ if isinstance(arg2, (list, np.ndarray)):
60
+ if len(arg2) != dimensions:
61
+ return False
62
+ return True
63
+
64
+ def _sample(self) -> int | float | np.ndarray:
65
+ """Generate a sample from the log-uniform distribution.
66
+
67
+ Returns:
68
+ int | float | np.ndarray: Sampled value(s) in log-space.
69
+ """
70
+ return np.exp(self._generator.uniform(self._log_low, self._log_high, size=self._size))
71
+
72
+ def __repr__(self) -> str:
73
+ """String representation of the log-uniform distribution.
74
+
75
+ Returns:
76
+ str: String representation showing low, high, and size.
77
+ """
78
+ return f"LogUniform(low={np.exp(self._log_low)}, high={np.exp(self._log_high)}, size={self._size})"
79
+
80
+ @staticmethod
81
+ def create(generator: np.random.Generator,
82
+ low: float | Any,
83
+ high: float | Any,
84
+ size: int | tuple[int, ...]| None = None) -> 'LogUniform':
85
+ """
86
+ Create a log-uniform distribution.
87
+
88
+ Args:
89
+ generator (np.random.Generator): The random number generator.
90
+ low (float | Any): The lower bound (inclusive).
91
+ high (float | Any): The upper bound (exclusive).
92
+ size (int | tuple[int, ...]| None): The size of the output.
93
+
94
+ Returns:
95
+ LogUniform: The created log-uniform distribution.
96
+ """
97
+ return LogUniform(generator=generator, low=low, high=high, size=size)
@@ -0,0 +1,129 @@
1
+ from humalab.dists.distribution import Distribution
2
+ from typing import Any
3
+ import numpy as np
4
+
5
+
6
+ class TruncatedGaussian(Distribution):
7
+ """Truncated Gaussian (normal) distribution.
8
+
9
+ Samples values from a normal distribution with specified mean (loc) and
10
+ standard deviation (scale), but constrained to lie within [low, high].
11
+ Values outside the bounds are resampled until they fall within range.
12
+ Supports scalar outputs as well as multi-dimensional arrays with 1D, 2D, or 3D variants.
13
+ """
14
+ def __init__(self,
15
+ generator: np.random.Generator,
16
+ loc: float | Any,
17
+ scale: float | Any,
18
+ low: float | Any,
19
+ high: float | Any,
20
+ size: int | tuple[int, ...] | None = None) -> None:
21
+ """
22
+ Initialize the truncated Gaussian (normal) distribution.
23
+
24
+ Args:
25
+ generator (np.random.Generator): The random number generator.
26
+ loc (float | Any): The mean of the distribution.
27
+ scale (float | Any): The standard deviation of the distribution.
28
+ low (float | Any): The lower truncation bound.
29
+ high (float | Any): The upper truncation bound.
30
+ size (int | tuple[int, ...] | None): The size of the output.
31
+ """
32
+ super().__init__(generator=generator)
33
+ self._loc = loc
34
+ self._scale = scale
35
+ self._low = low
36
+ self._high = high
37
+ self._size = size
38
+
39
+ @staticmethod
40
+ def validate(dimensions: int, *args) -> bool:
41
+ """Validate distribution parameters for the given dimensions.
42
+
43
+ Args:
44
+ dimensions (int): The number of dimensions (0 for scalar, -1 for any).
45
+ *args: The distribution parameters (loc, scale, low, high).
46
+
47
+ Returns:
48
+ bool: True if parameters are valid, False otherwise.
49
+ """
50
+ arg1 = args[0]
51
+ arg2 = args[1]
52
+ arg3 = args[2]
53
+ arg4 = args[3]
54
+ if dimensions == 0:
55
+ if not isinstance(arg1, (int, float)):
56
+ return False
57
+ if not isinstance(arg2, (int, float)):
58
+ return False
59
+ if not isinstance(arg3, (int, float)):
60
+ return False
61
+ if not isinstance(arg4, (int, float)):
62
+ return False
63
+ return True
64
+ if dimensions == -1:
65
+ return True
66
+ if not isinstance(arg1, (int, float)):
67
+ if isinstance(arg1, (list, np.ndarray)):
68
+ if len(arg1) != dimensions:
69
+ return False
70
+ if not isinstance(arg2, (int, float)):
71
+ if isinstance(arg2, (list, np.ndarray)):
72
+ if len(arg2) != dimensions:
73
+ return False
74
+ if not isinstance(arg3, (int, float)):
75
+ if isinstance(arg3, (list, np.ndarray)):
76
+ if len(arg3) != dimensions:
77
+ return False
78
+ if not isinstance(arg4, (int, float)):
79
+ if isinstance(arg4, (list, np.ndarray)):
80
+ if len(arg4) != dimensions:
81
+ return False
82
+ return True
83
+
84
+ def _sample(self) -> int | float | np.ndarray:
85
+ """Generate a sample from the truncated Gaussian distribution.
86
+
87
+ Samples are generated from N(loc, scale) and resampled if they fall
88
+ outside [low, high].
89
+
90
+ Returns:
91
+ int | float | np.ndarray: Sampled value(s) within [low, high].
92
+ """
93
+ samples = self._generator.normal(loc=self._loc, scale=self._scale, size=self._size)
94
+ mask = (samples < self._low) | (samples > self._high)
95
+ while np.any(mask):
96
+ samples[mask] = self._generator.normal(loc=self._loc, scale=self._scale, size=np.sum(mask))
97
+ mask = (samples < self._low) | (samples > self._high)
98
+ return samples
99
+
100
+ def __repr__(self) -> str:
101
+ """String representation of the truncated Gaussian distribution.
102
+
103
+ Returns:
104
+ str: String representation showing loc, scale, low, high, and size.
105
+ """
106
+ return f"TruncatedGaussian(loc={self._loc}, scale={self._scale}, low={self._low}, high={self._high}, size={self._size})"
107
+
108
+ @staticmethod
109
+ def create(generator: np.random.Generator,
110
+ loc: float | Any,
111
+ scale: float | Any,
112
+ low: float | Any,
113
+ high: float | Any,
114
+ size: int | tuple[int, ...] | None = None) -> 'TruncatedGaussian':
115
+ """
116
+ Create a truncated Gaussian (normal) distribution.
117
+
118
+ Args:
119
+ generator (np.random.Generator): The random number generator.
120
+ loc (float | Any): The mean of the distribution.
121
+ scale (float | Any): The standard deviation of the distribution.
122
+ low (float | Any): The lower truncation bound.
123
+ high (float | Any): The upper truncation bound.
124
+ size (int | tuple[int, ...] | None): The size of the output.
125
+
126
+ Returns:
127
+ TruncatedGaussian: The created truncated Gaussian distribution.
128
+ """
129
+ return TruncatedGaussian(generator=generator, loc=loc, scale=scale, low=low, high=high, size=size)
@@ -0,0 +1,95 @@
1
+ from humalab.dists.distribution import Distribution
2
+
3
+ from typing import Any
4
+ import numpy as np
5
+
6
+ class Uniform(Distribution):
7
+ """Uniform distribution over a continuous or discrete range.
8
+
9
+ Samples values uniformly from the half-open interval [low, high). Supports
10
+ scalar outputs as well as multi-dimensional arrays with 1D, 2D, or 3D variants.
11
+ """
12
+ def __init__(self,
13
+ generator: np.random.Generator,
14
+ low: float | Any,
15
+ high: float | Any,
16
+ size: int | tuple[int, ...] | None = None, ) -> None:
17
+ """
18
+ Initialize the uniform distribution.
19
+
20
+ Args:
21
+ generator (np.random.Generator): The random number generator.
22
+ low (float | Any): The lower bound (inclusive).
23
+ high (float | Any): The upper bound (exclusive).
24
+ size (int | tuple[int, ...] | None): The size of the output.
25
+ """
26
+ super().__init__(generator=generator)
27
+ self._low = np.array(low)
28
+ self._high = np.array(high)
29
+ self._size = size
30
+
31
+ @staticmethod
32
+ def validate(dimensions: int, *args) -> bool:
33
+ """Validate distribution parameters for the given dimensions.
34
+
35
+ Args:
36
+ dimensions (int): The number of dimensions (0 for scalar, -1 for any).
37
+ *args: The distribution parameters (low, high).
38
+
39
+ Returns:
40
+ bool: True if parameters are valid, False otherwise.
41
+ """
42
+ arg1 = args[0]
43
+ arg2 = args[1]
44
+ if dimensions == 0:
45
+ if not isinstance(arg1, (int, float)):
46
+ return False
47
+ if not isinstance(arg2, (int, float)):
48
+ return False
49
+ return True
50
+ if dimensions == -1:
51
+ return True
52
+ if not isinstance(arg1, (int, float)):
53
+ if isinstance(arg1, (list, np.ndarray)):
54
+ if len(arg1) > dimensions:
55
+ return False
56
+ if not isinstance(arg2, (int, float)):
57
+ if isinstance(arg2, (list, np.ndarray)):
58
+ if len(arg2) > dimensions:
59
+ return False
60
+ return True
61
+
62
+ def _sample(self) -> int | float | np.ndarray:
63
+ """Generate a sample from the uniform distribution.
64
+
65
+ Returns:
66
+ int | float | np.ndarray: Sampled value(s) from [low, high).
67
+ """
68
+ return self._generator.uniform(self._low, self._high, size=self._size)
69
+
70
+ def __repr__(self) -> str:
71
+ """String representation of the uniform distribution.
72
+
73
+ Returns:
74
+ str: String representation showing low, high, and size.
75
+ """
76
+ return f"Uniform(low={self._low}, high={self._high}, size={self._size})"
77
+
78
+ @staticmethod
79
+ def create(generator: np.random.Generator,
80
+ low: float | Any,
81
+ high: float | Any,
82
+ size: int | tuple[int, ...] | None = None) -> 'Uniform':
83
+ """
84
+ Create a uniform distribution.
85
+
86
+ Args:
87
+ generator (np.random.Generator): The random number generator.
88
+ low (float | Any): The lower bound (inclusive).
89
+ high (float | Any): The upper bound (exclusive).
90
+ size (int | tuple[int, ...] | None): The size of the output.
91
+
92
+ Returns:
93
+ Uniform: The created uniform distribution.
94
+ """
95
+ return Uniform(generator=generator, low=low, high=high, size=size)