hypergrid 0.0.1__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.
- hypergrid-0.0.1/LICENSE +19 -0
- hypergrid-0.0.1/PKG-INFO +97 -0
- hypergrid-0.0.1/README.md +76 -0
- hypergrid-0.0.1/pyproject.toml +117 -0
- hypergrid-0.0.1/setup.cfg +4 -0
- hypergrid-0.0.1/src/hypergrid/__init__.py +0 -0
- hypergrid-0.0.1/src/hypergrid/dimension.py +44 -0
- hypergrid-0.0.1/src/hypergrid/dsl.py +6 -0
- hypergrid-0.0.1/src/hypergrid/ext/__init__.py +0 -0
- hypergrid-0.0.1/src/hypergrid/ext/sklearn.py +32 -0
- hypergrid-0.0.1/src/hypergrid/gen/__init__.py +0 -0
- hypergrid-0.0.1/src/hypergrid/gen/distribution.py +27 -0
- hypergrid-0.0.1/src/hypergrid/gen/iterable.py +32 -0
- hypergrid-0.0.1/src/hypergrid/grid.py +310 -0
- hypergrid-0.0.1/src/hypergrid/py.typed +0 -0
- hypergrid-0.0.1/src/hypergrid/util.py +5 -0
- hypergrid-0.0.1/src/hypergrid.egg-info/PKG-INFO +97 -0
- hypergrid-0.0.1/src/hypergrid.egg-info/SOURCES.txt +20 -0
- hypergrid-0.0.1/src/hypergrid.egg-info/dependency_links.txt +1 -0
- hypergrid-0.0.1/src/hypergrid.egg-info/not-zip-safe +1 -0
- hypergrid-0.0.1/src/hypergrid.egg-info/requires.txt +3 -0
- hypergrid-0.0.1/src/hypergrid.egg-info/top_level.txt +1 -0
hypergrid-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2023-present Justin Yan
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
hypergrid-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: hypergrid
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Hypergrid enables concise declaration of parameter grids for hyperparameter optimization and batch jobs.
|
|
5
|
+
Author-email: Justin Yan <justin@iomorphic.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/justin-yan/hypergrid
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Requires-Python: >=3.11
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Provides-Extra: sklearn
|
|
20
|
+
Requires-Dist: scikit-learn<2; extra == "sklearn"
|
|
21
|
+
|
|
22
|
+
# hypergrid
|
|
23
|
+
|
|
24
|
+
Hypergrid enables concise declaration and manipulation of parameter grid spaces, with an aim towards use cases such as hyperparameter tuning or defining large batch jobs.
|
|
25
|
+
|
|
26
|
+
Use the following features to lazily declare a parameter grid:
|
|
27
|
+
|
|
28
|
+
- Dimension and Grid direct instantiation
|
|
29
|
+
- `+` and `|` for "sum" or "union" types (concatenation)
|
|
30
|
+
- `*` for "product" types
|
|
31
|
+
- `&` for coiteration (zip)
|
|
32
|
+
- `select` to project dimensions by name
|
|
33
|
+
|
|
34
|
+
There are also a few transformations that can be lazily applied element-wise, which take a GridElement (a namedtuple of dimension<->value) as input.
|
|
35
|
+
|
|
36
|
+
- `filter` to apply boolean predicate
|
|
37
|
+
- `map` for lambda transformation
|
|
38
|
+
- `map_to` for map + concat
|
|
39
|
+
|
|
40
|
+
Once a parameter grid is declared, there are two ways to "materialize" your grid, which return GridElements.
|
|
41
|
+
|
|
42
|
+
- `__iter__`: a grid is directly iterable
|
|
43
|
+
- `sample`: allows you to sample from the grid according to a sampling strategy
|
|
44
|
+
|
|
45
|
+
## Usage Examples
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from hypergrid.dsl import *
|
|
49
|
+
from dataclasses import dataclass
|
|
50
|
+
|
|
51
|
+
# First, we need to create a Dimension, which is essentially a named, finite, 1-d collection
|
|
52
|
+
d = Dimension(custom_name=[1, 2, 3]) # any python Collection will work - set, dict, range(), etc.
|
|
53
|
+
assert d.name == "custom_name" # the argument name is used as the dimension's name.
|
|
54
|
+
d.with_name("ints") # which you can reset
|
|
55
|
+
Uniform(low=1, high=5).take(5)
|
|
56
|
+
ExponentialStep(start=1, step=1.1).take(6) # You can also take a dimension from a Distribution or HIterable
|
|
57
|
+
|
|
58
|
+
# You can `len(d)` or `[i for i in d]`, but grids are more interesting
|
|
59
|
+
g = d.to_grid()
|
|
60
|
+
i2d = Dimension(ints=[4, 5, 6])
|
|
61
|
+
cd = Dimension(chars=["a", "b", "c", "d"])
|
|
62
|
+
union_ints = g + i2d # result is length 6: Concatenate two grids that have the same underlying dimensions
|
|
63
|
+
product_g = g * cd # result is length 12: tuples (1, "a") - take the cartesian product of the underlying grids
|
|
64
|
+
zip_g = g & cd # result is length 3: tuples (1, "a") - zip two grids together, up to the shorter grid
|
|
65
|
+
|
|
66
|
+
ml = [i for i in zip_g] # You can iterate through a grid
|
|
67
|
+
tl = zip_g.take(5) # or you can just take up to a certain number of grid elements from it
|
|
68
|
+
print(tl[0].ints), print(tl[0].chars) # The iterator elements are python NamedTuples taken from the dimension names.
|
|
69
|
+
|
|
70
|
+
# These gridelements can be referenced and used in the grid higher-order functions
|
|
71
|
+
zip_g.filter(lambda ge: ge.chars in ["a", "b"]) # result is length 2: keep the tuples (1, "a") and (2, "b")
|
|
72
|
+
zip_g.map(doubled=lambda ge: ge.ints * 2) # result is length 3, with single attribute (drops `ints` and `chars`)
|
|
73
|
+
mt = zip_g.map_to(doubled=lambda ge: ge.ints * 2) # result is length 3, appends `doubled` and keeps `ints` and `chars`
|
|
74
|
+
print(mt.select("doubled", "ints").take(1)[0]) # resulting gridelement no longer has `chars`
|
|
75
|
+
|
|
76
|
+
# There are some other utility methods on a grid:
|
|
77
|
+
zip_g.sample() # Randomly samples a single grid element from a grid
|
|
78
|
+
zip_g.to_sklearn() # The Grid.to_* methods convert HyperGrids to other grid formats
|
|
79
|
+
|
|
80
|
+
# The general idea is to allow for fairly extensive grid construction routines
|
|
81
|
+
@dataclass
|
|
82
|
+
class FakeModel:
|
|
83
|
+
idx: int
|
|
84
|
+
param1: float
|
|
85
|
+
|
|
86
|
+
g = HyperGrid( # A grid with 4 x 10 combinations
|
|
87
|
+
ExponentialStep(start=1.0, step=1.5).take(4).with_name("param1"),
|
|
88
|
+
idx=range(10)
|
|
89
|
+
).instantiate(model=FakeModel).select("model") + \
|
|
90
|
+
HyperGrid( # A different grid with 15 combinations
|
|
91
|
+
Uniform(low=-1, high=1).take(5).with_name("param1"),
|
|
92
|
+
idx=[10, 20, 30]
|
|
93
|
+
).instantiate(model=FakeModel).select("model")
|
|
94
|
+
assert len(g) == 55
|
|
95
|
+
g.sample()
|
|
96
|
+
```
|
|
97
|
+
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# hypergrid
|
|
2
|
+
|
|
3
|
+
Hypergrid enables concise declaration and manipulation of parameter grid spaces, with an aim towards use cases such as hyperparameter tuning or defining large batch jobs.
|
|
4
|
+
|
|
5
|
+
Use the following features to lazily declare a parameter grid:
|
|
6
|
+
|
|
7
|
+
- Dimension and Grid direct instantiation
|
|
8
|
+
- `+` and `|` for "sum" or "union" types (concatenation)
|
|
9
|
+
- `*` for "product" types
|
|
10
|
+
- `&` for coiteration (zip)
|
|
11
|
+
- `select` to project dimensions by name
|
|
12
|
+
|
|
13
|
+
There are also a few transformations that can be lazily applied element-wise, which take a GridElement (a namedtuple of dimension<->value) as input.
|
|
14
|
+
|
|
15
|
+
- `filter` to apply boolean predicate
|
|
16
|
+
- `map` for lambda transformation
|
|
17
|
+
- `map_to` for map + concat
|
|
18
|
+
|
|
19
|
+
Once a parameter grid is declared, there are two ways to "materialize" your grid, which return GridElements.
|
|
20
|
+
|
|
21
|
+
- `__iter__`: a grid is directly iterable
|
|
22
|
+
- `sample`: allows you to sample from the grid according to a sampling strategy
|
|
23
|
+
|
|
24
|
+
## Usage Examples
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from hypergrid.dsl import *
|
|
28
|
+
from dataclasses import dataclass
|
|
29
|
+
|
|
30
|
+
# First, we need to create a Dimension, which is essentially a named, finite, 1-d collection
|
|
31
|
+
d = Dimension(custom_name=[1, 2, 3]) # any python Collection will work - set, dict, range(), etc.
|
|
32
|
+
assert d.name == "custom_name" # the argument name is used as the dimension's name.
|
|
33
|
+
d.with_name("ints") # which you can reset
|
|
34
|
+
Uniform(low=1, high=5).take(5)
|
|
35
|
+
ExponentialStep(start=1, step=1.1).take(6) # You can also take a dimension from a Distribution or HIterable
|
|
36
|
+
|
|
37
|
+
# You can `len(d)` or `[i for i in d]`, but grids are more interesting
|
|
38
|
+
g = d.to_grid()
|
|
39
|
+
i2d = Dimension(ints=[4, 5, 6])
|
|
40
|
+
cd = Dimension(chars=["a", "b", "c", "d"])
|
|
41
|
+
union_ints = g + i2d # result is length 6: Concatenate two grids that have the same underlying dimensions
|
|
42
|
+
product_g = g * cd # result is length 12: tuples (1, "a") - take the cartesian product of the underlying grids
|
|
43
|
+
zip_g = g & cd # result is length 3: tuples (1, "a") - zip two grids together, up to the shorter grid
|
|
44
|
+
|
|
45
|
+
ml = [i for i in zip_g] # You can iterate through a grid
|
|
46
|
+
tl = zip_g.take(5) # or you can just take up to a certain number of grid elements from it
|
|
47
|
+
print(tl[0].ints), print(tl[0].chars) # The iterator elements are python NamedTuples taken from the dimension names.
|
|
48
|
+
|
|
49
|
+
# These gridelements can be referenced and used in the grid higher-order functions
|
|
50
|
+
zip_g.filter(lambda ge: ge.chars in ["a", "b"]) # result is length 2: keep the tuples (1, "a") and (2, "b")
|
|
51
|
+
zip_g.map(doubled=lambda ge: ge.ints * 2) # result is length 3, with single attribute (drops `ints` and `chars`)
|
|
52
|
+
mt = zip_g.map_to(doubled=lambda ge: ge.ints * 2) # result is length 3, appends `doubled` and keeps `ints` and `chars`
|
|
53
|
+
print(mt.select("doubled", "ints").take(1)[0]) # resulting gridelement no longer has `chars`
|
|
54
|
+
|
|
55
|
+
# There are some other utility methods on a grid:
|
|
56
|
+
zip_g.sample() # Randomly samples a single grid element from a grid
|
|
57
|
+
zip_g.to_sklearn() # The Grid.to_* methods convert HyperGrids to other grid formats
|
|
58
|
+
|
|
59
|
+
# The general idea is to allow for fairly extensive grid construction routines
|
|
60
|
+
@dataclass
|
|
61
|
+
class FakeModel:
|
|
62
|
+
idx: int
|
|
63
|
+
param1: float
|
|
64
|
+
|
|
65
|
+
g = HyperGrid( # A grid with 4 x 10 combinations
|
|
66
|
+
ExponentialStep(start=1.0, step=1.5).take(4).with_name("param1"),
|
|
67
|
+
idx=range(10)
|
|
68
|
+
).instantiate(model=FakeModel).select("model") + \
|
|
69
|
+
HyperGrid( # A different grid with 15 combinations
|
|
70
|
+
Uniform(low=-1, high=1).take(5).with_name("param1"),
|
|
71
|
+
idx=[10, 20, 30]
|
|
72
|
+
).instantiate(model=FakeModel).select("model")
|
|
73
|
+
assert len(g) == 55
|
|
74
|
+
g.sample()
|
|
75
|
+
```
|
|
76
|
+
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "hypergrid"
|
|
7
|
+
version = "0.0.1"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Justin Yan", email="justin@iomorphic.com" }
|
|
10
|
+
]
|
|
11
|
+
description = "Hypergrid enables concise declaration of parameter grids for hyperparameter optimization and batch jobs."
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.11"
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Programming Language :: Python",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
######
|
|
27
|
+
### Custom Dependencies Section Begin
|
|
28
|
+
######
|
|
29
|
+
|
|
30
|
+
######
|
|
31
|
+
### Custom Dependencies Section End
|
|
32
|
+
######
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
"Homepage" = "https://github.com/justin-yan/hypergrid"
|
|
37
|
+
|
|
38
|
+
[dependency-groups]
|
|
39
|
+
dev = [
|
|
40
|
+
"pytest>5",
|
|
41
|
+
"hypothesis>5",
|
|
42
|
+
"coverage>5",
|
|
43
|
+
"ruff>0.2.1",
|
|
44
|
+
"mypy>1.2"
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[tool.setuptools]
|
|
48
|
+
zip-safe = false
|
|
49
|
+
include-package-data = true
|
|
50
|
+
|
|
51
|
+
[tool.setuptools.package-data]
|
|
52
|
+
"hypergrid" = ["py.typed"]
|
|
53
|
+
|
|
54
|
+
[tool.setuptools.packages.find]
|
|
55
|
+
where = ["src"]
|
|
56
|
+
|
|
57
|
+
#######
|
|
58
|
+
### Miscellaneous Tool Configuration
|
|
59
|
+
#######
|
|
60
|
+
[tool.ruff]
|
|
61
|
+
line-length = 150
|
|
62
|
+
target-version = "py311"
|
|
63
|
+
|
|
64
|
+
[tool.ruff.format]
|
|
65
|
+
quote-style = "double"
|
|
66
|
+
|
|
67
|
+
[tool.ruff.lint]
|
|
68
|
+
select = ["E", "F", "W", "I"]
|
|
69
|
+
|
|
70
|
+
[tool.ruff.lint.isort]
|
|
71
|
+
known-first-party = ["hypergrid"]
|
|
72
|
+
|
|
73
|
+
[tool.pytest.ini_options]
|
|
74
|
+
addopts = "-ra -q --doctest-modules"
|
|
75
|
+
log_cli = true
|
|
76
|
+
log_cli_level = "WARN"
|
|
77
|
+
log_cli_format = "%(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)"
|
|
78
|
+
log_cli_date_format = "%Y-%m-%d %H:%M:%S"
|
|
79
|
+
|
|
80
|
+
[tool.mypy]
|
|
81
|
+
mypy_path = "src"
|
|
82
|
+
disallow_untyped_defs = true
|
|
83
|
+
disallow_any_unimported = true
|
|
84
|
+
allow_redefinition = false
|
|
85
|
+
ignore_errors = false
|
|
86
|
+
implicit_reexport = false
|
|
87
|
+
local_partial_types = true
|
|
88
|
+
no_implicit_optional = true
|
|
89
|
+
strict_equality = true
|
|
90
|
+
strict_optional = true
|
|
91
|
+
warn_no_return = true
|
|
92
|
+
warn_redundant_casts = true
|
|
93
|
+
warn_unreachable = true
|
|
94
|
+
warn_unused_configs = true
|
|
95
|
+
warn_unused_ignores = true
|
|
96
|
+
|
|
97
|
+
######
|
|
98
|
+
### Custom Directives Section Begin
|
|
99
|
+
######
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
[[tool.mypy.overrides]]
|
|
103
|
+
module = [
|
|
104
|
+
"sklearn.*",
|
|
105
|
+
]
|
|
106
|
+
ignore_errors = true
|
|
107
|
+
ignore_missing_imports = true
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
[project.optional-dependencies]
|
|
111
|
+
sklearn = [
|
|
112
|
+
"scikit-learn<2",
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
######
|
|
116
|
+
### Custom Directives Section End
|
|
117
|
+
######
|
|
File without changes
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import random
|
|
2
|
+
from collections.abc import Collection
|
|
3
|
+
from typing import TYPE_CHECKING, Generic, Iterator, Self, TypeAlias, TypeVar
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from hypergrid.grid import HyperGrid
|
|
7
|
+
|
|
8
|
+
T = TypeVar("T")
|
|
9
|
+
RawDimension: TypeAlias = tuple[str, Collection]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Dimension(Generic[T]):
|
|
13
|
+
name: str
|
|
14
|
+
|
|
15
|
+
def __init__(self, **kwargs: Collection[T]):
|
|
16
|
+
assert len(kwargs) == 1, "Dimension is 1-d, use Grids for multiple dimensions"
|
|
17
|
+
for name, values in kwargs.items():
|
|
18
|
+
assert isinstance(values, Collection), "Dimension assumes finite length"
|
|
19
|
+
self.name = name
|
|
20
|
+
self.values = values
|
|
21
|
+
|
|
22
|
+
def __repr__(self) -> str:
|
|
23
|
+
return f"Dimension({repr(self.values)})"
|
|
24
|
+
|
|
25
|
+
def __str__(self) -> str:
|
|
26
|
+
return self.__repr__()
|
|
27
|
+
|
|
28
|
+
def __len__(self) -> int:
|
|
29
|
+
return len(self.values)
|
|
30
|
+
|
|
31
|
+
def __iter__(self) -> Iterator[T]:
|
|
32
|
+
yield from self.values
|
|
33
|
+
|
|
34
|
+
def sample(self) -> T:
|
|
35
|
+
return random.choice(self.values) # type: ignore
|
|
36
|
+
|
|
37
|
+
def with_name(self, name: str) -> Self:
|
|
38
|
+
self.name = name
|
|
39
|
+
return self
|
|
40
|
+
|
|
41
|
+
def to_grid(self) -> "HyperGrid":
|
|
42
|
+
from hypergrid.grid import HyperGrid
|
|
43
|
+
|
|
44
|
+
return HyperGrid(self)
|
|
File without changes
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
try:
|
|
2
|
+
from sklearn.model_selection import ParameterGrid
|
|
3
|
+
except ImportError:
|
|
4
|
+
raise ImportError("If using sklearn conversion functionality, install hypergrid with `sklearn` extras via `pip install hypergrid[sklearn]`")
|
|
5
|
+
|
|
6
|
+
from hypergrid.grid import Grid, HyperGrid, ProductGrid
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _grid_to_sklearn(grid: Grid) -> ParameterGrid: # type: ignore[no-any-unimported]
|
|
10
|
+
"""
|
|
11
|
+
SKLearn's ParameterGrid accepts {str: sequence}
|
|
12
|
+
|
|
13
|
+
Because these ParameterGrids don't directly compose, we use a recursive helper, and then convert the composable dicts
|
|
14
|
+
into a ParameterGrid in this outer wrapper.
|
|
15
|
+
"""
|
|
16
|
+
return ParameterGrid(_grid_to_sklearn_recursive_helper(grid))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _grid_to_sklearn_recursive_helper(grid: Grid) -> dict:
|
|
20
|
+
"""
|
|
21
|
+
SKLearn's param_grid dictionaries only support simple cartesian products, so only the Grid and ProductGrid elements
|
|
22
|
+
are convertible to SKLearn parameter grids.
|
|
23
|
+
"""
|
|
24
|
+
match grid:
|
|
25
|
+
case HyperGrid():
|
|
26
|
+
return {dim.name: [v for v in dim] for dim in grid.dimensions}
|
|
27
|
+
case ProductGrid():
|
|
28
|
+
d1 = _grid_to_sklearn_recursive_helper(grid.grid1)
|
|
29
|
+
d2 = _grid_to_sklearn_recursive_helper(grid.grid2)
|
|
30
|
+
return d1 | d2
|
|
31
|
+
case _:
|
|
32
|
+
raise ValueError("Converting Grid to SKLearn ParameterGrid is not compatible with")
|
|
File without changes
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import random
|
|
2
|
+
from typing import Any, Iterator, Protocol, TypeVar, runtime_checkable
|
|
3
|
+
|
|
4
|
+
from hypergrid.gen.iterable import HIterable
|
|
5
|
+
|
|
6
|
+
T = TypeVar("T", covariant=True)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@runtime_checkable
|
|
10
|
+
class Distribution(HIterable, Protocol[T]):
|
|
11
|
+
def sample(self) -> T: ...
|
|
12
|
+
|
|
13
|
+
def __iter__(self) -> Iterator[T]:
|
|
14
|
+
while True:
|
|
15
|
+
yield self.sample()
|
|
16
|
+
|
|
17
|
+
def __call__(self, *args: Any, **kwargs: Any) -> T:
|
|
18
|
+
return self.sample()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Uniform(Distribution):
|
|
22
|
+
def __init__(self, low: float, high: float) -> None:
|
|
23
|
+
self.low = low
|
|
24
|
+
self.high = high
|
|
25
|
+
|
|
26
|
+
def sample(self) -> float:
|
|
27
|
+
return random.uniform(self.low, self.high)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from itertools import islice
|
|
2
|
+
from typing import Iterator, Protocol, Self, TypeVar, runtime_checkable
|
|
3
|
+
|
|
4
|
+
from hypergrid.dimension import Dimension
|
|
5
|
+
|
|
6
|
+
T = TypeVar("T")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@runtime_checkable
|
|
10
|
+
class HIterable(Protocol[T]):
|
|
11
|
+
name: str = "anonymous"
|
|
12
|
+
|
|
13
|
+
def __iter__(self) -> Iterator[T]: ...
|
|
14
|
+
|
|
15
|
+
def take(self, n: int) -> Dimension[T]:
|
|
16
|
+
return Dimension(**{self.name: [i for i in islice(self, n)]})
|
|
17
|
+
|
|
18
|
+
def with_name(self, name: str) -> Self:
|
|
19
|
+
self.name = name
|
|
20
|
+
return self
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ExponentialStep(HIterable):
|
|
24
|
+
def __init__(self, start: float, step: float) -> None:
|
|
25
|
+
self.start = start
|
|
26
|
+
self.step = step
|
|
27
|
+
|
|
28
|
+
def __iter__(self) -> Iterator[float]:
|
|
29
|
+
cursor = self.start
|
|
30
|
+
while True:
|
|
31
|
+
yield cursor
|
|
32
|
+
cursor *= self.step
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import itertools
|
|
4
|
+
import random
|
|
5
|
+
from collections import namedtuple
|
|
6
|
+
from collections.abc import Collection
|
|
7
|
+
from functools import cached_property
|
|
8
|
+
from math import prod
|
|
9
|
+
from typing import TYPE_CHECKING, Any, Callable, Iterator, Optional, Protocol, Type, runtime_checkable
|
|
10
|
+
|
|
11
|
+
from hypergrid.gen.iterable import HIterable
|
|
12
|
+
from hypergrid.util import instantiate_lambda
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from sklearn.model_selection import ParameterGrid
|
|
16
|
+
|
|
17
|
+
from hypergrid.dimension import Dimension, RawDimension
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@runtime_checkable
|
|
21
|
+
class Grid(Protocol):
|
|
22
|
+
grid_element: Type[tuple]
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def dimension_names(self) -> list[str]:
|
|
26
|
+
return list(self.grid_element._fields) # type: ignore[attr-defined]
|
|
27
|
+
|
|
28
|
+
def __repr__(self) -> str: ...
|
|
29
|
+
|
|
30
|
+
def __str__(self) -> str:
|
|
31
|
+
return self.__repr__()
|
|
32
|
+
|
|
33
|
+
def __len__(self) -> int: ...
|
|
34
|
+
|
|
35
|
+
def __iter__(self) -> Iterator: ...
|
|
36
|
+
|
|
37
|
+
def take(self, n: int) -> list:
|
|
38
|
+
return [i for i in itertools.islice(self, n)]
|
|
39
|
+
|
|
40
|
+
def sample(self) -> tuple: ...
|
|
41
|
+
|
|
42
|
+
def __add__(self, other: Grid | Dimension | RawDimension) -> SumGrid:
|
|
43
|
+
match other:
|
|
44
|
+
case Grid():
|
|
45
|
+
return SumGrid(self, other)
|
|
46
|
+
case Dimension():
|
|
47
|
+
return SumGrid(self, HyperGrid(other))
|
|
48
|
+
case (str(s), coll) if isinstance(coll, Collection): # RawDimension
|
|
49
|
+
return SumGrid(self, HyperGrid(Dimension(**{s: coll})))
|
|
50
|
+
case _:
|
|
51
|
+
raise ValueError("Invalid argument for grid operation")
|
|
52
|
+
|
|
53
|
+
def __or__(self, other: Grid | Dimension | RawDimension) -> SumGrid:
|
|
54
|
+
return self.__add__(other)
|
|
55
|
+
|
|
56
|
+
def __mul__(self, other: Grid | Dimension | RawDimension) -> ProductGrid:
|
|
57
|
+
match other:
|
|
58
|
+
case Grid():
|
|
59
|
+
return ProductGrid(self, other)
|
|
60
|
+
case Dimension():
|
|
61
|
+
return ProductGrid(self, HyperGrid(other))
|
|
62
|
+
case (str(s), coll) if isinstance(coll, Collection): # RawDimension
|
|
63
|
+
return ProductGrid(self, HyperGrid(Dimension(**{s: coll})))
|
|
64
|
+
case _:
|
|
65
|
+
raise ValueError("Invalid argument for grid operation")
|
|
66
|
+
|
|
67
|
+
def __and__(self, other: Grid | Dimension | HIterable | RawDimension) -> ZipGrid:
|
|
68
|
+
match other:
|
|
69
|
+
case Grid():
|
|
70
|
+
return ZipGrid(self, other)
|
|
71
|
+
case Dimension():
|
|
72
|
+
return ZipGrid(self, HyperGrid(other))
|
|
73
|
+
case (str(s), coll) if isinstance(coll, Collection): # RawDimension
|
|
74
|
+
return ZipGrid(self, HyperGrid(Dimension(**{s: coll})))
|
|
75
|
+
case HIterable():
|
|
76
|
+
return ZipGrid(self, HyperGrid(other.take(len(self))))
|
|
77
|
+
case _:
|
|
78
|
+
raise ValueError("Invalid argument for grid operation")
|
|
79
|
+
|
|
80
|
+
def filter(self, predicate: Callable[[Any], bool]) -> FilterGrid:
|
|
81
|
+
return FilterGrid(self, predicate)
|
|
82
|
+
|
|
83
|
+
def select(self, *dim_names: str) -> SelectGrid:
|
|
84
|
+
return SelectGrid(self, *dim_names)
|
|
85
|
+
|
|
86
|
+
def map(self, **kwargs: Callable[[Any], Any]) -> MapGrid:
|
|
87
|
+
return MapGrid(self, **kwargs)
|
|
88
|
+
|
|
89
|
+
def map_to(self, **kwargs: Callable[[Any], Any]) -> MapToGrid:
|
|
90
|
+
return MapToGrid(self, **kwargs)
|
|
91
|
+
|
|
92
|
+
def instantiate(self, **kwargs: Type) -> MapToGrid:
|
|
93
|
+
return self.map_to(**{name: instantiate_lambda(cls) for name, cls in kwargs.items()})
|
|
94
|
+
|
|
95
|
+
def to_sklearn(self) -> ParameterGrid: # type: ignore[no-any-unimported]
|
|
96
|
+
from hypergrid.ext.sklearn import _grid_to_sklearn
|
|
97
|
+
|
|
98
|
+
return _grid_to_sklearn(self)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class HyperGrid(Grid):
|
|
102
|
+
dimensions: list[Dimension]
|
|
103
|
+
|
|
104
|
+
def __init__(self, *args: Dimension, **kwargs: Collection) -> None:
|
|
105
|
+
dims = list(args)
|
|
106
|
+
for dim, values in kwargs.items():
|
|
107
|
+
dims.append(Dimension(**{dim: values}))
|
|
108
|
+
assert len(dims) > 0, "Must provide at least one meaningful dimension"
|
|
109
|
+
assert len(dims) == len(set(dims)), "Dimension names must be unique"
|
|
110
|
+
self.dimensions = dims
|
|
111
|
+
self.grid_element = namedtuple("GridElement", [dim.name for dim in self.dimensions]) # type: ignore[misc]
|
|
112
|
+
|
|
113
|
+
def __repr__(self) -> str:
|
|
114
|
+
dim_str = ", ".join([repr(dim) for dim in self.dimensions])
|
|
115
|
+
return f"HyperGrid({dim_str})"
|
|
116
|
+
|
|
117
|
+
def __len__(self) -> int:
|
|
118
|
+
return prod([len(dim) for dim in self.dimensions])
|
|
119
|
+
|
|
120
|
+
def __iter__(self) -> Iterator:
|
|
121
|
+
for element_tuple in itertools.product(*[dim.__iter__() for dim in self.dimensions]):
|
|
122
|
+
yield self.grid_element(*element_tuple)
|
|
123
|
+
|
|
124
|
+
def sample(self) -> tuple:
|
|
125
|
+
return self.grid_element(*tuple([dim.sample() for dim in self.dimensions]))
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class SumGrid(Grid):
|
|
129
|
+
def __init__(self, grid1: Grid, grid2: Grid) -> None:
|
|
130
|
+
assert set(grid1.dimension_names) == set(grid2.dimension_names)
|
|
131
|
+
self.grid1 = grid1
|
|
132
|
+
self.grid2 = grid2
|
|
133
|
+
self.grid_element = grid1.grid_element
|
|
134
|
+
|
|
135
|
+
def __repr__(self) -> str:
|
|
136
|
+
return f"SumGrid({repr(self.grid1)}, {repr(self.grid2)})"
|
|
137
|
+
|
|
138
|
+
def __len__(self) -> int:
|
|
139
|
+
return len(self.grid1) + len(self.grid2)
|
|
140
|
+
|
|
141
|
+
def __iter__(self) -> Iterator:
|
|
142
|
+
for grid_element in itertools.chain(self.grid1, self.grid2):
|
|
143
|
+
yield grid_element
|
|
144
|
+
|
|
145
|
+
def sample(self) -> tuple:
|
|
146
|
+
return random.choice([ge for ge in self])
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class ProductGrid(Grid):
|
|
150
|
+
def __init__(self, grid1: Grid, grid2: Grid) -> None:
|
|
151
|
+
assert set(grid1.dimension_names).isdisjoint(set(grid2.dimension_names)), "Dimensions must be exactly matching"
|
|
152
|
+
self.grid1 = grid1
|
|
153
|
+
self.grid2 = grid2
|
|
154
|
+
self.grid_element = namedtuple("GridElement", grid1.dimension_names + grid2.dimension_names) # type: ignore[misc]
|
|
155
|
+
|
|
156
|
+
def __repr__(self) -> str:
|
|
157
|
+
return f"ProductGrid({repr(self.grid1)}, {repr(self.grid2)})"
|
|
158
|
+
|
|
159
|
+
def __len__(self) -> int:
|
|
160
|
+
return len(self.grid1) * len(self.grid2)
|
|
161
|
+
|
|
162
|
+
def __iter__(self) -> Iterator:
|
|
163
|
+
for grid_element1, grid_element2 in itertools.product(self.grid1, self.grid2):
|
|
164
|
+
yield self.grid_element(*(grid_element1 + grid_element2))
|
|
165
|
+
|
|
166
|
+
def sample(self) -> tuple:
|
|
167
|
+
ge1 = self.grid1.sample()
|
|
168
|
+
ge2 = self.grid2.sample()
|
|
169
|
+
return self.grid_element(*(ge1 + ge2))
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class ZipGrid(Grid):
|
|
173
|
+
"""
|
|
174
|
+
Mimic python "zip" of two iterables.
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
def __init__(self, grid1: Grid, grid2: Grid) -> None:
|
|
178
|
+
assert set(grid1.dimension_names).isdisjoint(set(grid2.dimension_names)), "Dimensions must be exactly matching"
|
|
179
|
+
self.grid1 = grid1
|
|
180
|
+
self.grid2 = grid2
|
|
181
|
+
self.grid_element = namedtuple("GridElement", grid1.dimension_names + grid2.dimension_names) # type: ignore[misc]
|
|
182
|
+
|
|
183
|
+
def __repr__(self) -> str:
|
|
184
|
+
return f"ZipGrid({repr(self.grid1)}, {repr(self.grid2)})"
|
|
185
|
+
|
|
186
|
+
def __len__(self) -> int:
|
|
187
|
+
return min(len(self.grid1), len(self.grid2))
|
|
188
|
+
|
|
189
|
+
def __iter__(self) -> Iterator:
|
|
190
|
+
for grid_element1, grid_element2 in zip(self.grid1, self.grid2):
|
|
191
|
+
yield self.grid_element(*(grid_element1 + grid_element2))
|
|
192
|
+
|
|
193
|
+
def sample(self) -> tuple:
|
|
194
|
+
return random.choice([ge for ge in self])
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class FilterGrid(Grid):
|
|
198
|
+
_iter_cache: Optional[list] = None
|
|
199
|
+
|
|
200
|
+
def __init__(self, grid: Grid, predicate: Callable[[Any], bool]) -> None:
|
|
201
|
+
self.grid = grid
|
|
202
|
+
self.predicate = predicate
|
|
203
|
+
self.grid_element = grid.grid_element
|
|
204
|
+
|
|
205
|
+
def __repr__(self) -> str:
|
|
206
|
+
return f"FilterGrid({repr(self.grid)}, {self.predicate.__name__})"
|
|
207
|
+
|
|
208
|
+
def __len__(self) -> int:
|
|
209
|
+
return self._len
|
|
210
|
+
|
|
211
|
+
@cached_property
|
|
212
|
+
def _len(self) -> int:
|
|
213
|
+
return len([x for x in self])
|
|
214
|
+
|
|
215
|
+
def __iter__(self) -> Iterator:
|
|
216
|
+
for grid_element in self.grid:
|
|
217
|
+
if self.predicate(grid_element):
|
|
218
|
+
yield grid_element
|
|
219
|
+
|
|
220
|
+
def sample(self) -> tuple:
|
|
221
|
+
if self._iter_cache is not None:
|
|
222
|
+
sublist = self._iter_cache
|
|
223
|
+
else:
|
|
224
|
+
sublist = [ge for ge in self]
|
|
225
|
+
self._iter_cache = sublist
|
|
226
|
+
return random.choice(sublist)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class SelectGrid(Grid):
|
|
230
|
+
def __init__(self, grid: Grid, *select_dims: str) -> None:
|
|
231
|
+
assert len(set(select_dims)) == len(select_dims), "Selected columns must all be unique"
|
|
232
|
+
assert set(select_dims) <= set(grid.dimension_names), "Selected dimensions must be subset of grid dimensions"
|
|
233
|
+
self.grid = grid
|
|
234
|
+
self.select_dims = select_dims
|
|
235
|
+
self.grid_element = namedtuple("GridElement", [name for name in grid.dimension_names if name in self.select_dims]) # type: ignore[misc]
|
|
236
|
+
|
|
237
|
+
def __repr__(self) -> str:
|
|
238
|
+
return f"SelectGrid({repr(self.grid)}, {repr(self.select_dims)})"
|
|
239
|
+
|
|
240
|
+
def __len__(self) -> int:
|
|
241
|
+
return len(self.grid)
|
|
242
|
+
|
|
243
|
+
def __iter__(self) -> Iterator:
|
|
244
|
+
for grid_element in self.grid:
|
|
245
|
+
yield self.grid_element(*self._process_single(grid_element))
|
|
246
|
+
|
|
247
|
+
def sample(self) -> tuple:
|
|
248
|
+
return self.grid_element(*self._process_single(self.grid.sample()))
|
|
249
|
+
|
|
250
|
+
def _process_single(self, ge: tuple) -> list:
|
|
251
|
+
element_list = []
|
|
252
|
+
for dim_name in self.dimension_names:
|
|
253
|
+
try:
|
|
254
|
+
selected_value = getattr(ge, dim_name)
|
|
255
|
+
except AttributeError:
|
|
256
|
+
selected_value = None
|
|
257
|
+
element_list.append(selected_value)
|
|
258
|
+
return element_list
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class MapGrid(Grid):
|
|
262
|
+
def __init__(self, grid: Grid, **kwargs: Callable[[Any], Any]) -> None:
|
|
263
|
+
assert len(set(kwargs.keys())) == len(kwargs.keys()), "New columns must all have unique names"
|
|
264
|
+
self.grid = grid
|
|
265
|
+
self.dimension_mapping = kwargs
|
|
266
|
+
self.grid_element = namedtuple("GridElement", list(kwargs.keys())) # type: ignore[misc]
|
|
267
|
+
|
|
268
|
+
def __repr__(self) -> str:
|
|
269
|
+
mappings_str = ", ".join([f"{dim_name}={func.__name__}" for dim_name, func in self.dimension_mapping.items()])
|
|
270
|
+
return f"MapGrid({repr(self.grid)}, {mappings_str})"
|
|
271
|
+
|
|
272
|
+
def __len__(self) -> int:
|
|
273
|
+
return len(self.grid)
|
|
274
|
+
|
|
275
|
+
def __iter__(self) -> Iterator:
|
|
276
|
+
for grid_element in self.grid:
|
|
277
|
+
yield self.grid_element(**self._process_single(grid_element))
|
|
278
|
+
|
|
279
|
+
def sample(self) -> tuple:
|
|
280
|
+
return self.grid_element(**self._process_single(self.grid.sample()))
|
|
281
|
+
|
|
282
|
+
def _process_single(self, ge: tuple) -> dict:
|
|
283
|
+
return {dim_name: func(ge) for dim_name, func in self.dimension_mapping.items()}
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
class MapToGrid(Grid):
|
|
287
|
+
def __init__(self, grid: Grid, **kwargs: Callable[[Any], Any]) -> None:
|
|
288
|
+
assert len(set(kwargs.keys())) == len(kwargs.keys()), "New columns must all have unique names"
|
|
289
|
+
assert set(grid.dimension_names).isdisjoint(set(kwargs.keys())), "New columns must not have name collisions with old columns"
|
|
290
|
+
self.grid = grid
|
|
291
|
+
self.dimension_mapping = kwargs
|
|
292
|
+
self.grid_element = namedtuple("GridElement", grid.dimension_names + list(kwargs.keys())) # type: ignore[misc]
|
|
293
|
+
|
|
294
|
+
def __repr__(self) -> str:
|
|
295
|
+
mappings_str = ", ".join([f"{dim_name}={func.__name__}" for dim_name, func in self.dimension_mapping.items()])
|
|
296
|
+
return f"MapToGrid({repr(self.grid)}, {mappings_str})"
|
|
297
|
+
|
|
298
|
+
def __len__(self) -> int:
|
|
299
|
+
return len(self.grid)
|
|
300
|
+
|
|
301
|
+
def __iter__(self) -> Iterator:
|
|
302
|
+
for grid_element in self.grid:
|
|
303
|
+
yield self.grid_element(**self._process_single(grid_element))
|
|
304
|
+
|
|
305
|
+
def sample(self) -> tuple:
|
|
306
|
+
return self.grid_element(**self._process_single(self.grid.sample()))
|
|
307
|
+
|
|
308
|
+
def _process_single(self, ge: tuple) -> dict:
|
|
309
|
+
new_values = {dim_name: func(ge) for dim_name, func in self.dimension_mapping.items()}
|
|
310
|
+
return ge._asdict() | new_values # type: ignore
|
|
File without changes
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: hypergrid
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Hypergrid enables concise declaration of parameter grids for hyperparameter optimization and batch jobs.
|
|
5
|
+
Author-email: Justin Yan <justin@iomorphic.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/justin-yan/hypergrid
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Requires-Python: >=3.11
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Provides-Extra: sklearn
|
|
20
|
+
Requires-Dist: scikit-learn<2; extra == "sklearn"
|
|
21
|
+
|
|
22
|
+
# hypergrid
|
|
23
|
+
|
|
24
|
+
Hypergrid enables concise declaration and manipulation of parameter grid spaces, with an aim towards use cases such as hyperparameter tuning or defining large batch jobs.
|
|
25
|
+
|
|
26
|
+
Use the following features to lazily declare a parameter grid:
|
|
27
|
+
|
|
28
|
+
- Dimension and Grid direct instantiation
|
|
29
|
+
- `+` and `|` for "sum" or "union" types (concatenation)
|
|
30
|
+
- `*` for "product" types
|
|
31
|
+
- `&` for coiteration (zip)
|
|
32
|
+
- `select` to project dimensions by name
|
|
33
|
+
|
|
34
|
+
There are also a few transformations that can be lazily applied element-wise, which take a GridElement (a namedtuple of dimension<->value) as input.
|
|
35
|
+
|
|
36
|
+
- `filter` to apply boolean predicate
|
|
37
|
+
- `map` for lambda transformation
|
|
38
|
+
- `map_to` for map + concat
|
|
39
|
+
|
|
40
|
+
Once a parameter grid is declared, there are two ways to "materialize" your grid, which return GridElements.
|
|
41
|
+
|
|
42
|
+
- `__iter__`: a grid is directly iterable
|
|
43
|
+
- `sample`: allows you to sample from the grid according to a sampling strategy
|
|
44
|
+
|
|
45
|
+
## Usage Examples
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from hypergrid.dsl import *
|
|
49
|
+
from dataclasses import dataclass
|
|
50
|
+
|
|
51
|
+
# First, we need to create a Dimension, which is essentially a named, finite, 1-d collection
|
|
52
|
+
d = Dimension(custom_name=[1, 2, 3]) # any python Collection will work - set, dict, range(), etc.
|
|
53
|
+
assert d.name == "custom_name" # the argument name is used as the dimension's name.
|
|
54
|
+
d.with_name("ints") # which you can reset
|
|
55
|
+
Uniform(low=1, high=5).take(5)
|
|
56
|
+
ExponentialStep(start=1, step=1.1).take(6) # You can also take a dimension from a Distribution or HIterable
|
|
57
|
+
|
|
58
|
+
# You can `len(d)` or `[i for i in d]`, but grids are more interesting
|
|
59
|
+
g = d.to_grid()
|
|
60
|
+
i2d = Dimension(ints=[4, 5, 6])
|
|
61
|
+
cd = Dimension(chars=["a", "b", "c", "d"])
|
|
62
|
+
union_ints = g + i2d # result is length 6: Concatenate two grids that have the same underlying dimensions
|
|
63
|
+
product_g = g * cd # result is length 12: tuples (1, "a") - take the cartesian product of the underlying grids
|
|
64
|
+
zip_g = g & cd # result is length 3: tuples (1, "a") - zip two grids together, up to the shorter grid
|
|
65
|
+
|
|
66
|
+
ml = [i for i in zip_g] # You can iterate through a grid
|
|
67
|
+
tl = zip_g.take(5) # or you can just take up to a certain number of grid elements from it
|
|
68
|
+
print(tl[0].ints), print(tl[0].chars) # The iterator elements are python NamedTuples taken from the dimension names.
|
|
69
|
+
|
|
70
|
+
# These gridelements can be referenced and used in the grid higher-order functions
|
|
71
|
+
zip_g.filter(lambda ge: ge.chars in ["a", "b"]) # result is length 2: keep the tuples (1, "a") and (2, "b")
|
|
72
|
+
zip_g.map(doubled=lambda ge: ge.ints * 2) # result is length 3, with single attribute (drops `ints` and `chars`)
|
|
73
|
+
mt = zip_g.map_to(doubled=lambda ge: ge.ints * 2) # result is length 3, appends `doubled` and keeps `ints` and `chars`
|
|
74
|
+
print(mt.select("doubled", "ints").take(1)[0]) # resulting gridelement no longer has `chars`
|
|
75
|
+
|
|
76
|
+
# There are some other utility methods on a grid:
|
|
77
|
+
zip_g.sample() # Randomly samples a single grid element from a grid
|
|
78
|
+
zip_g.to_sklearn() # The Grid.to_* methods convert HyperGrids to other grid formats
|
|
79
|
+
|
|
80
|
+
# The general idea is to allow for fairly extensive grid construction routines
|
|
81
|
+
@dataclass
|
|
82
|
+
class FakeModel:
|
|
83
|
+
idx: int
|
|
84
|
+
param1: float
|
|
85
|
+
|
|
86
|
+
g = HyperGrid( # A grid with 4 x 10 combinations
|
|
87
|
+
ExponentialStep(start=1.0, step=1.5).take(4).with_name("param1"),
|
|
88
|
+
idx=range(10)
|
|
89
|
+
).instantiate(model=FakeModel).select("model") + \
|
|
90
|
+
HyperGrid( # A different grid with 15 combinations
|
|
91
|
+
Uniform(low=-1, high=1).take(5).with_name("param1"),
|
|
92
|
+
idx=[10, 20, 30]
|
|
93
|
+
).instantiate(model=FakeModel).select("model")
|
|
94
|
+
assert len(g) == 55
|
|
95
|
+
g.sample()
|
|
96
|
+
```
|
|
97
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/hypergrid/__init__.py
|
|
5
|
+
src/hypergrid/dimension.py
|
|
6
|
+
src/hypergrid/dsl.py
|
|
7
|
+
src/hypergrid/grid.py
|
|
8
|
+
src/hypergrid/py.typed
|
|
9
|
+
src/hypergrid/util.py
|
|
10
|
+
src/hypergrid.egg-info/PKG-INFO
|
|
11
|
+
src/hypergrid.egg-info/SOURCES.txt
|
|
12
|
+
src/hypergrid.egg-info/dependency_links.txt
|
|
13
|
+
src/hypergrid.egg-info/not-zip-safe
|
|
14
|
+
src/hypergrid.egg-info/requires.txt
|
|
15
|
+
src/hypergrid.egg-info/top_level.txt
|
|
16
|
+
src/hypergrid/ext/__init__.py
|
|
17
|
+
src/hypergrid/ext/sklearn.py
|
|
18
|
+
src/hypergrid/gen/__init__.py
|
|
19
|
+
src/hypergrid/gen/distribution.py
|
|
20
|
+
src/hypergrid/gen/iterable.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
hypergrid
|