pyglove 0.4.5.dev20240319__py3-none-any.whl → 0.4.5.dev202501132210__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.
- pyglove/core/__init__.py +54 -20
- pyglove/core/coding/__init__.py +42 -0
- pyglove/core/coding/errors.py +111 -0
- pyglove/core/coding/errors_test.py +98 -0
- pyglove/core/coding/execution.py +309 -0
- pyglove/core/coding/execution_test.py +333 -0
- pyglove/core/{object_utils/codegen.py → coding/function_generation.py} +10 -4
- pyglove/core/{object_utils/codegen_test.py → coding/function_generation_test.py} +5 -7
- pyglove/core/coding/parsing.py +153 -0
- pyglove/core/coding/parsing_test.py +150 -0
- pyglove/core/coding/permissions.py +100 -0
- pyglove/core/coding/permissions_test.py +93 -0
- pyglove/core/geno/base.py +54 -41
- pyglove/core/geno/base_test.py +2 -4
- pyglove/core/geno/categorical.py +37 -28
- pyglove/core/geno/custom.py +19 -16
- pyglove/core/geno/numerical.py +20 -17
- pyglove/core/geno/space.py +4 -5
- pyglove/core/hyper/base.py +6 -6
- pyglove/core/hyper/categorical.py +94 -55
- pyglove/core/hyper/custom.py +7 -7
- pyglove/core/hyper/custom_test.py +9 -10
- pyglove/core/hyper/derived.py +30 -22
- pyglove/core/hyper/derived_test.py +2 -4
- pyglove/core/hyper/dynamic_evaluation.py +5 -6
- pyglove/core/hyper/evolvable.py +57 -46
- pyglove/core/hyper/numerical.py +48 -24
- pyglove/core/hyper/numerical_test.py +9 -9
- pyglove/core/hyper/object_template.py +58 -46
- pyglove/core/io/__init__.py +1 -0
- pyglove/core/io/file_system.py +17 -7
- pyglove/core/io/file_system_test.py +2 -0
- pyglove/core/io/sequence.py +299 -0
- pyglove/core/io/sequence_test.py +124 -0
- pyglove/core/logging_test.py +0 -2
- pyglove/core/patching/object_factory.py +4 -4
- pyglove/core/patching/pattern_based.py +4 -4
- pyglove/core/patching/rule_based.py +17 -5
- pyglove/core/patching/rule_based_test.py +27 -4
- pyglove/core/symbolic/__init__.py +2 -7
- pyglove/core/symbolic/base.py +320 -183
- pyglove/core/symbolic/base_test.py +123 -19
- pyglove/core/symbolic/boilerplate.py +7 -13
- pyglove/core/symbolic/boilerplate_test.py +25 -23
- pyglove/core/symbolic/class_wrapper.py +48 -45
- pyglove/core/symbolic/class_wrapper_test.py +2 -2
- pyglove/core/symbolic/compounding.py +9 -15
- pyglove/core/symbolic/compounding_test.py +2 -4
- pyglove/core/symbolic/dict.py +154 -110
- pyglove/core/symbolic/dict_test.py +238 -130
- pyglove/core/symbolic/diff.py +199 -10
- pyglove/core/symbolic/diff_test.py +226 -0
- pyglove/core/symbolic/flags.py +1 -1
- pyglove/core/symbolic/functor.py +29 -26
- pyglove/core/symbolic/functor_test.py +102 -50
- pyglove/core/symbolic/inferred.py +2 -2
- pyglove/core/symbolic/list.py +81 -50
- pyglove/core/symbolic/list_test.py +119 -97
- pyglove/core/symbolic/object.py +225 -113
- pyglove/core/symbolic/object_test.py +320 -108
- pyglove/core/symbolic/origin.py +17 -14
- pyglove/core/symbolic/origin_test.py +4 -2
- pyglove/core/symbolic/pure_symbolic.py +4 -3
- pyglove/core/symbolic/ref.py +108 -21
- pyglove/core/symbolic/ref_test.py +93 -0
- pyglove/core/symbolic/symbolize_test.py +10 -2
- pyglove/core/tuning/local_backend.py +2 -2
- pyglove/core/tuning/protocols.py +3 -3
- pyglove/core/tuning/sample_test.py +3 -3
- pyglove/core/typing/__init__.py +14 -5
- pyglove/core/typing/annotation_conversion.py +43 -27
- pyglove/core/typing/annotation_conversion_test.py +23 -0
- pyglove/core/typing/callable_ext.py +241 -3
- pyglove/core/typing/callable_ext_test.py +255 -0
- pyglove/core/typing/callable_signature.py +510 -66
- pyglove/core/typing/callable_signature_test.py +619 -99
- pyglove/core/typing/class_schema.py +229 -154
- pyglove/core/typing/class_schema_test.py +149 -95
- pyglove/core/typing/custom_typing.py +5 -4
- pyglove/core/typing/inspect.py +63 -0
- pyglove/core/typing/inspect_test.py +39 -0
- pyglove/core/typing/key_specs.py +10 -11
- pyglove/core/typing/key_specs_test.py +7 -4
- pyglove/core/typing/type_conversion.py +4 -5
- pyglove/core/typing/type_conversion_test.py +12 -12
- pyglove/core/typing/typed_missing.py +6 -7
- pyglove/core/typing/typed_missing_test.py +7 -8
- pyglove/core/typing/value_specs.py +604 -362
- pyglove/core/typing/value_specs_test.py +328 -90
- pyglove/core/utils/__init__.py +164 -0
- pyglove/core/{object_utils → utils}/common_traits.py +3 -67
- pyglove/core/utils/common_traits_test.py +36 -0
- pyglove/core/{object_utils → utils}/docstr_utils.py +23 -0
- pyglove/core/{object_utils → utils}/docstr_utils_test.py +36 -4
- pyglove/core/{object_utils → utils}/error_utils.py +78 -9
- pyglove/core/{object_utils → utils}/error_utils_test.py +61 -5
- pyglove/core/utils/formatting.py +464 -0
- pyglove/core/utils/formatting_test.py +453 -0
- pyglove/core/{object_utils → utils}/hierarchical.py +23 -25
- pyglove/core/{object_utils → utils}/hierarchical_test.py +3 -5
- pyglove/core/{object_utils → utils}/json_conversion.py +177 -52
- pyglove/core/{object_utils → utils}/json_conversion_test.py +97 -16
- pyglove/core/{object_utils → utils}/missing.py +3 -3
- pyglove/core/{object_utils → utils}/missing_test.py +2 -4
- pyglove/core/utils/text_color.py +128 -0
- pyglove/core/utils/text_color_test.py +94 -0
- pyglove/core/{object_utils → utils}/thread_local_test.py +1 -3
- pyglove/core/utils/timing.py +236 -0
- pyglove/core/utils/timing_test.py +154 -0
- pyglove/core/{object_utils → utils}/value_location.py +275 -6
- pyglove/core/utils/value_location_test.py +707 -0
- pyglove/core/views/__init__.py +32 -0
- pyglove/core/views/base.py +804 -0
- pyglove/core/views/base_test.py +580 -0
- pyglove/core/views/html/__init__.py +27 -0
- pyglove/core/views/html/base.py +547 -0
- pyglove/core/views/html/base_test.py +830 -0
- pyglove/core/views/html/controls/__init__.py +35 -0
- pyglove/core/views/html/controls/base.py +275 -0
- pyglove/core/views/html/controls/label.py +207 -0
- pyglove/core/views/html/controls/label_test.py +157 -0
- pyglove/core/views/html/controls/progress_bar.py +183 -0
- pyglove/core/views/html/controls/progress_bar_test.py +97 -0
- pyglove/core/views/html/controls/tab.py +320 -0
- pyglove/core/views/html/controls/tab_test.py +87 -0
- pyglove/core/views/html/controls/tooltip.py +99 -0
- pyglove/core/views/html/controls/tooltip_test.py +99 -0
- pyglove/core/views/html/tree_view.py +1517 -0
- pyglove/core/views/html/tree_view_test.py +1461 -0
- {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/METADATA +18 -4
- pyglove-0.4.5.dev202501132210.dist-info/RECORD +214 -0
- {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/WHEEL +1 -1
- pyglove/core/object_utils/__init__.py +0 -154
- pyglove/core/object_utils/common_traits_test.py +0 -82
- pyglove/core/object_utils/formatting.py +0 -234
- pyglove/core/object_utils/formatting_test.py +0 -223
- pyglove/core/object_utils/value_location_test.py +0 -385
- pyglove/core/symbolic/schema_utils.py +0 -327
- pyglove/core/symbolic/schema_utils_test.py +0 -57
- pyglove/core/typing/class_schema_utils.py +0 -202
- pyglove/core/typing/class_schema_utils_test.py +0 -194
- pyglove-0.4.5.dev20240319.dist-info/RECORD +0 -185
- /pyglove/core/{object_utils → utils}/thread_local.py +0 -0
- {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/LICENSE +0 -0
- {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,100 @@
|
|
1
|
+
# Copyright 2025 The PyGlove Authors
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
"""Python code permissions."""
|
15
|
+
|
16
|
+
import contextlib
|
17
|
+
import enum
|
18
|
+
from typing import Optional
|
19
|
+
|
20
|
+
from pyglove.core import utils
|
21
|
+
|
22
|
+
|
23
|
+
class CodePermission(enum.Flag):
|
24
|
+
"""Permissions for code execution."""
|
25
|
+
|
26
|
+
# Allows assignment.
|
27
|
+
ASSIGN = enum.auto()
|
28
|
+
|
29
|
+
# Allows conditions.
|
30
|
+
CONDITION = enum.auto()
|
31
|
+
|
32
|
+
# Allows loops.
|
33
|
+
LOOP = enum.auto()
|
34
|
+
|
35
|
+
# Call functions or methods.
|
36
|
+
CALL = enum.auto()
|
37
|
+
|
38
|
+
# Allows exception.
|
39
|
+
EXCEPTION = enum.auto()
|
40
|
+
|
41
|
+
# Allows class definitions.
|
42
|
+
CLASS_DEFINITION = enum.auto()
|
43
|
+
|
44
|
+
# Allows function definitions.
|
45
|
+
FUNCTION_DEFINITION = enum.auto()
|
46
|
+
|
47
|
+
# Allows import.
|
48
|
+
IMPORT = enum.auto()
|
49
|
+
|
50
|
+
@classmethod
|
51
|
+
@property
|
52
|
+
def BASIC(cls) -> 'CodePermission': # pylint: disable=invalid-name
|
53
|
+
"""Returns basic permissions."""
|
54
|
+
return CodePermission.ASSIGN | CodePermission.CALL
|
55
|
+
|
56
|
+
@classmethod
|
57
|
+
@property
|
58
|
+
def ALL(cls) -> 'CodePermission': # pylint: disable=invalid-name
|
59
|
+
"""Returns all permissions."""
|
60
|
+
return (
|
61
|
+
CodePermission.BASIC | CodePermission.CONDITION | CodePermission.LOOP |
|
62
|
+
CodePermission.EXCEPTION | CodePermission.CLASS_DEFINITION |
|
63
|
+
CodePermission.FUNCTION_DEFINITION | CodePermission.IMPORT)
|
64
|
+
|
65
|
+
|
66
|
+
_TLS_CODE_RUN_PERMISSION = '__code_run_permission__'
|
67
|
+
|
68
|
+
|
69
|
+
@contextlib.contextmanager
|
70
|
+
def permission(perm: CodePermission):
|
71
|
+
"""Context manager for controling the permission for code execution.
|
72
|
+
|
73
|
+
When the `permission` context manager is nested, the outtermost permission
|
74
|
+
will be used. This design allows users to control permission at the top level.
|
75
|
+
|
76
|
+
Args:
|
77
|
+
perm: Code execution permission.
|
78
|
+
|
79
|
+
Yields:
|
80
|
+
Actual permission applied.
|
81
|
+
"""
|
82
|
+
|
83
|
+
outter_perm = utils.thread_local_get(_TLS_CODE_RUN_PERMISSION, None)
|
84
|
+
|
85
|
+
# Use the top-level permission as the actual permission
|
86
|
+
if outter_perm is not None:
|
87
|
+
perm = outter_perm
|
88
|
+
|
89
|
+
utils.thread_local_set(_TLS_CODE_RUN_PERMISSION, perm)
|
90
|
+
|
91
|
+
try:
|
92
|
+
yield perm
|
93
|
+
finally:
|
94
|
+
if outter_perm is None:
|
95
|
+
utils.thread_local_del(_TLS_CODE_RUN_PERMISSION)
|
96
|
+
|
97
|
+
|
98
|
+
def get_permission() -> Optional[CodePermission]:
|
99
|
+
"""Gets the current permission for code execution."""
|
100
|
+
return utils.thread_local_get(_TLS_CODE_RUN_PERMISSION, None)
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# Copyright 2025 The PyGlove Authors
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
import unittest
|
15
|
+
from pyglove.core.coding import permissions
|
16
|
+
|
17
|
+
|
18
|
+
class CodePermissionTest(unittest.TestCase):
|
19
|
+
|
20
|
+
def assert_set(
|
21
|
+
self,
|
22
|
+
permission: permissions.CodePermission,
|
23
|
+
flag: permissions.CodePermission,
|
24
|
+
):
|
25
|
+
self.assertEqual(permission & flag, flag)
|
26
|
+
|
27
|
+
def assert_not_set(
|
28
|
+
self,
|
29
|
+
permission: permissions.CodePermission,
|
30
|
+
flag: permissions.CodePermission,
|
31
|
+
):
|
32
|
+
self.assertFalse(permission & flag)
|
33
|
+
|
34
|
+
def test_basic(self):
|
35
|
+
self.assert_set(
|
36
|
+
permissions.CodePermission.BASIC, permissions.CodePermission.ASSIGN
|
37
|
+
)
|
38
|
+
self.assert_set(
|
39
|
+
permissions.CodePermission.BASIC, permissions.CodePermission.CALL
|
40
|
+
)
|
41
|
+
self.assert_set(
|
42
|
+
permissions.CodePermission.BASIC, permissions.CodePermission.CALL
|
43
|
+
)
|
44
|
+
|
45
|
+
def test_all(self):
|
46
|
+
self.assert_set(
|
47
|
+
permissions.CodePermission.ALL, permissions.CodePermission.BASIC
|
48
|
+
)
|
49
|
+
self.assert_set(
|
50
|
+
permissions.CodePermission.ALL, permissions.CodePermission.CONDITION
|
51
|
+
)
|
52
|
+
self.assert_set(
|
53
|
+
permissions.CodePermission.ALL, permissions.CodePermission.LOOP
|
54
|
+
)
|
55
|
+
self.assert_set(
|
56
|
+
permissions.CodePermission.ALL, permissions.CodePermission.EXCEPTION
|
57
|
+
)
|
58
|
+
self.assert_set(
|
59
|
+
permissions.CodePermission.ALL,
|
60
|
+
permissions.CodePermission.CLASS_DEFINITION,
|
61
|
+
)
|
62
|
+
self.assert_set(
|
63
|
+
permissions.CodePermission.ALL,
|
64
|
+
permissions.CodePermission.FUNCTION_DEFINITION,
|
65
|
+
)
|
66
|
+
self.assert_set(
|
67
|
+
permissions.CodePermission.ALL, permissions.CodePermission.IMPORT
|
68
|
+
)
|
69
|
+
|
70
|
+
def test_xor(self):
|
71
|
+
self.assert_not_set(
|
72
|
+
permissions.CodePermission.ALL ^ permissions.CodePermission.BASIC,
|
73
|
+
permissions.CodePermission.BASIC,
|
74
|
+
)
|
75
|
+
self.assert_set(
|
76
|
+
permissions.CodePermission.ALL ^ permissions.CodePermission.BASIC,
|
77
|
+
permissions.CodePermission.CONDITION,
|
78
|
+
)
|
79
|
+
|
80
|
+
def test_permission_control(self):
|
81
|
+
self.assertIsNone(permissions.get_permission())
|
82
|
+
with permissions.permission(permissions.CodePermission.BASIC):
|
83
|
+
self.assertEqual(
|
84
|
+
permissions.get_permission(), permissions.CodePermission.BASIC
|
85
|
+
)
|
86
|
+
with permissions.permission(permissions.CodePermission.ALL):
|
87
|
+
self.assertEqual(
|
88
|
+
permissions.get_permission(), permissions.CodePermission.BASIC
|
89
|
+
)
|
90
|
+
|
91
|
+
|
92
|
+
if __name__ == '__main__':
|
93
|
+
unittest.main()
|
pyglove/core/geno/base.py
CHANGED
@@ -19,9 +19,9 @@ import random
|
|
19
19
|
import types
|
20
20
|
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
21
21
|
|
22
|
-
from pyglove.core import object_utils
|
23
22
|
from pyglove.core import symbolic
|
24
23
|
from pyglove.core import typing as pg_typing
|
24
|
+
from pyglove.core import utils
|
25
25
|
|
26
26
|
|
27
27
|
class AttributeDict(dict):
|
@@ -37,12 +37,20 @@ class AttributeDict(dict):
|
|
37
37
|
|
38
38
|
|
39
39
|
@symbolic.members([
|
40
|
-
(
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
40
|
+
(
|
41
|
+
'location',
|
42
|
+
pg_typing.Object(utils.KeyPath, default=utils.KeyPath()),
|
43
|
+
(
|
44
|
+
'KeyPath of associated genetic encoder relative to parent object'
|
45
|
+
' template. This allows DNA generator to apply rule based on'
|
46
|
+
' locations.'
|
47
|
+
),
|
48
|
+
),
|
49
|
+
(
|
50
|
+
'hints',
|
51
|
+
pg_typing.Any(default=None),
|
52
|
+
'Hints for DNA generator to consume.',
|
53
|
+
),
|
46
54
|
])
|
47
55
|
class DNASpec(symbolic.Object):
|
48
56
|
"""Base class for DNA specifications (genotypes).
|
@@ -175,7 +183,7 @@ class DNASpec(symbolic.Object):
|
|
175
183
|
"""Returns all decision points in their declaration order."""
|
176
184
|
|
177
185
|
@property
|
178
|
-
def decision_ids(self) -> List[
|
186
|
+
def decision_ids(self) -> List[utils.KeyPath]:
|
179
187
|
"""Returns decision IDs."""
|
180
188
|
return list(self._decision_point_by_id.keys())
|
181
189
|
|
@@ -286,7 +294,7 @@ class DNASpec(symbolic.Object):
|
|
286
294
|
return self.parent_spec if self.is_space else self.parent_spec.parent_choice
|
287
295
|
|
288
296
|
@property
|
289
|
-
def id(self) ->
|
297
|
+
def id(self) -> utils.KeyPath:
|
290
298
|
"""Returns a path of locations from the root as the ID for current node."""
|
291
299
|
if self._id is None:
|
292
300
|
parent = self.parent_spec
|
@@ -295,18 +303,20 @@ class DNASpec(symbolic.Object):
|
|
295
303
|
elif self.is_space:
|
296
304
|
assert parent.is_categorical, parent
|
297
305
|
assert self.index is not None
|
298
|
-
self._id =
|
299
|
-
|
300
|
-
|
306
|
+
self._id = (
|
307
|
+
utils.KeyPath(
|
308
|
+
ConditionalKey(self.index, len(parent.candidates)), parent.id
|
309
|
+
)
|
310
|
+
+ self.location
|
311
|
+
)
|
301
312
|
else:
|
302
313
|
# Float() or a multi-choice spec of a parent Choice.
|
303
314
|
self._id = parent.id + self.location
|
304
315
|
return self._id
|
305
316
|
|
306
|
-
def get(
|
307
|
-
|
308
|
-
|
309
|
-
) -> Union['DecisionPoint', List['DecisionPoint']]:
|
317
|
+
def get(
|
318
|
+
self, name_or_id: Union[utils.KeyPath, str], default: Any = None
|
319
|
+
) -> Union['DecisionPoint', List['DecisionPoint']]:
|
310
320
|
"""Get decision point(s) by name or ID."""
|
311
321
|
try:
|
312
322
|
return self[name_or_id]
|
@@ -314,9 +324,8 @@ class DNASpec(symbolic.Object):
|
|
314
324
|
return default
|
315
325
|
|
316
326
|
def __getitem__(
|
317
|
-
self,
|
318
|
-
|
319
|
-
) -> Union['DecisionPoint', List['DecisionPoint']]:
|
327
|
+
self, name_or_id: Union[utils.KeyPath, str]
|
328
|
+
) -> Union['DecisionPoint', List['DecisionPoint']]:
|
320
329
|
"""Get decision point(s) by name or ID ."""
|
321
330
|
v = self._named_decision_points.get(name_or_id, None)
|
322
331
|
if v is None:
|
@@ -475,7 +484,7 @@ class DNA(symbolic.Object):
|
|
475
484
|
# Allow assignment on symbolic attributes.
|
476
485
|
allow_symbolic_assignment = True
|
477
486
|
|
478
|
-
@
|
487
|
+
@utils.explicit_method_override
|
479
488
|
def __init__(
|
480
489
|
self,
|
481
490
|
value: Union[None, int, float, str, List[Any], Tuple[Any]] = None,
|
@@ -727,7 +736,7 @@ class DNA(symbolic.Object):
|
|
727
736
|
return self._decision_by_id_cache
|
728
737
|
|
729
738
|
@property
|
730
|
-
def decision_ids(self) -> List[
|
739
|
+
def decision_ids(self) -> List[utils.KeyPath]:
|
731
740
|
"""Returns decision IDs."""
|
732
741
|
self._ensure_dna_spec()
|
733
742
|
return self._spec.decision_ids
|
@@ -1249,9 +1258,11 @@ class DNA(symbolic.Object):
|
|
1249
1258
|
return dna
|
1250
1259
|
|
1251
1260
|
def to_numbers(
|
1252
|
-
self,
|
1253
|
-
|
1254
|
-
|
1261
|
+
self,
|
1262
|
+
flatten: bool = True,
|
1263
|
+
) -> Union[
|
1264
|
+
List[Union[int, float, str]], utils.Nestable[Union[int, float, str]]
|
1265
|
+
]:
|
1255
1266
|
"""Returns a (maybe) nested structure of numbers as decisions.
|
1256
1267
|
|
1257
1268
|
Args:
|
@@ -1338,7 +1349,7 @@ class DNA(symbolic.Object):
|
|
1338
1349
|
f'Location: {dna_spec.location.path}.')
|
1339
1350
|
children = []
|
1340
1351
|
for i, choice in enumerate(decision):
|
1341
|
-
choice_location =
|
1352
|
+
choice_location = utils.KeyPath(i, dna_spec.location)
|
1342
1353
|
if not isinstance(choice, int):
|
1343
1354
|
raise ValueError(
|
1344
1355
|
f'Choice value should be int. Encountered: {choice}, '
|
@@ -1410,7 +1421,7 @@ class DNA(symbolic.Object):
|
|
1410
1421
|
|
1411
1422
|
if type_info:
|
1412
1423
|
json_value = {
|
1413
|
-
|
1424
|
+
utils.JSONConvertible.TYPE_NAME_KEY: (
|
1414
1425
|
self.__class__.__serialization_key__
|
1415
1426
|
),
|
1416
1427
|
'format': 'compact',
|
@@ -1435,7 +1446,8 @@ class DNA(symbolic.Object):
|
|
1435
1446
|
json_value: Dict[str, Any],
|
1436
1447
|
*,
|
1437
1448
|
allow_partial: bool = False,
|
1438
|
-
root_path: Optional[
|
1449
|
+
root_path: Optional[utils.KeyPath] = None,
|
1450
|
+
) -> 'DNA':
|
1439
1451
|
"""Class method that load a DNA from a JSON value.
|
1440
1452
|
|
1441
1453
|
Args:
|
@@ -1472,8 +1484,8 @@ class DNA(symbolic.Object):
|
|
1472
1484
|
return not self.children
|
1473
1485
|
|
1474
1486
|
def __getitem__(
|
1475
|
-
self, key: Union[int, slice, str,
|
1476
|
-
|
1487
|
+
self, key: Union[int, slice, str, utils.KeyPath, 'DecisionPoint']
|
1488
|
+
) -> Union[None, 'DNA', List[Optional['DNA']]]:
|
1477
1489
|
"""Get an immediate child DNA or DNA in the sub-tree.
|
1478
1490
|
|
1479
1491
|
Args:
|
@@ -1504,10 +1516,11 @@ class DNA(symbolic.Object):
|
|
1504
1516
|
v = self._decision_by_id[key]
|
1505
1517
|
return v
|
1506
1518
|
|
1507
|
-
def get(
|
1508
|
-
|
1509
|
-
|
1510
|
-
|
1519
|
+
def get(
|
1520
|
+
self,
|
1521
|
+
key: Union[int, slice, str, utils.KeyPath, 'DecisionPoint'],
|
1522
|
+
default: Any = None,
|
1523
|
+
) -> Union[Any, None, 'DNA', List[Optional['DNA']]]:
|
1511
1524
|
"""Get an immediate child DNA or DNA in the sub-tree."""
|
1512
1525
|
try:
|
1513
1526
|
return self[key]
|
@@ -1529,8 +1542,9 @@ class DNA(symbolic.Object):
|
|
1529
1542
|
return True
|
1530
1543
|
else:
|
1531
1544
|
raise ValueError(
|
1532
|
-
|
1533
|
-
f'{
|
1545
|
+
'DNA.__contains__ does not accept '
|
1546
|
+
f'{utils.quote_if_str(dna_or_value)!r}.'
|
1547
|
+
)
|
1534
1548
|
return False
|
1535
1549
|
|
1536
1550
|
def __hash__(self):
|
@@ -1599,21 +1613,20 @@ class DNA(symbolic.Object):
|
|
1599
1613
|
verbose: bool = True,
|
1600
1614
|
root_indent: int = 0,
|
1601
1615
|
*,
|
1602
|
-
markdown: bool = False,
|
1603
1616
|
list_wrap_threshold: int = 80,
|
1604
1617
|
as_dict: bool = False,
|
1605
1618
|
**kwargs,
|
1606
1619
|
):
|
1607
1620
|
"""Customize format method for DNA for more compact representation."""
|
1608
1621
|
if as_dict and self.spec:
|
1609
|
-
details =
|
1622
|
+
details = utils.format(
|
1610
1623
|
self.to_dict(value_type='choice_and_literal'),
|
1611
1624
|
False,
|
1612
1625
|
verbose,
|
1613
1626
|
root_indent,
|
1614
|
-
**kwargs
|
1627
|
+
**kwargs,
|
1628
|
+
)
|
1615
1629
|
s = f'DNA({details})'
|
1616
|
-
compact = False
|
1617
1630
|
else:
|
1618
1631
|
if 'list_wrap_threshold' not in kwargs:
|
1619
1632
|
kwargs['list_wrap_threshold'] = list_wrap_threshold
|
@@ -1623,7 +1636,7 @@ class DNA(symbolic.Object):
|
|
1623
1636
|
elif self.is_leaf:
|
1624
1637
|
s = f'DNA({self.value!r})'
|
1625
1638
|
else:
|
1626
|
-
rep =
|
1639
|
+
rep = utils.format(
|
1627
1640
|
self.to_json(compact=True, type_info=False),
|
1628
1641
|
compact,
|
1629
1642
|
verbose,
|
@@ -1636,7 +1649,7 @@ class DNA(symbolic.Object):
|
|
1636
1649
|
s = f'DNA{rep}'
|
1637
1650
|
else:
|
1638
1651
|
s = f'DNA({rep})'
|
1639
|
-
return
|
1652
|
+
return s
|
1640
1653
|
|
1641
1654
|
def parameters(
|
1642
1655
|
self, use_literal_values: bool = False) -> Dict[str, str]:
|
pyglove/core/geno/base_test.py
CHANGED
@@ -11,12 +11,10 @@
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
|
-
"""Tests for pyglove.geno.DNA."""
|
15
|
-
|
16
14
|
import unittest
|
17
15
|
|
18
|
-
from pyglove.core import object_utils
|
19
16
|
from pyglove.core import symbolic
|
17
|
+
from pyglove.core import utils
|
20
18
|
from pyglove.core.geno.base import ConditionalKey
|
21
19
|
from pyglove.core.geno.base import DNA
|
22
20
|
from pyglove.core.geno.categorical import manyof
|
@@ -1144,7 +1142,7 @@ class ConditionalKeyTest(unittest.TestCase):
|
|
1144
1142
|
self.assertEqual(key.num_choices, 5)
|
1145
1143
|
|
1146
1144
|
def test_to_str(self):
|
1147
|
-
key =
|
1145
|
+
key = utils.KeyPath(['a', ConditionalKey(1, 5), 'b'])
|
1148
1146
|
self.assertEqual(str(key), 'a[=1/5].b')
|
1149
1147
|
|
1150
1148
|
|
pyglove/core/geno/categorical.py
CHANGED
@@ -18,10 +18,9 @@ import re
|
|
18
18
|
import types
|
19
19
|
from typing import Any, List, Optional, Union
|
20
20
|
|
21
|
-
from pyglove.core import object_utils
|
22
21
|
from pyglove.core import symbolic
|
23
22
|
from pyglove.core import typing as pg_typing
|
24
|
-
|
23
|
+
from pyglove.core import utils
|
25
24
|
from pyglove.core.geno.base import DecisionPoint
|
26
25
|
from pyglove.core.geno.base import DNA
|
27
26
|
from pyglove.core.geno.base import DNASpec
|
@@ -139,14 +138,15 @@ class Choices(DecisionPoint):
|
|
139
138
|
for i in range(self.num_choices):
|
140
139
|
subchoice_spec = Choices(
|
141
140
|
subchoice_index=i,
|
142
|
-
location=
|
141
|
+
location=utils.KeyPath(i),
|
143
142
|
num_choices=1,
|
144
143
|
candidates=self.candidates,
|
145
144
|
literal_values=self.literal_values,
|
146
145
|
distinct=self.distinct,
|
147
146
|
sorted=self.sorted,
|
148
147
|
name=self.name,
|
149
|
-
hints=self.hints
|
148
|
+
hints=self.hints,
|
149
|
+
)
|
150
150
|
self._decision_points.extend(subchoice_spec.decision_points)
|
151
151
|
subchoice_specs.append(subchoice_spec)
|
152
152
|
self._subchoice_specs = symbolic.List(subchoice_specs)
|
@@ -158,7 +158,8 @@ class Choices(DecisionPoint):
|
|
158
158
|
self._decision_points.extend(c.decision_points)
|
159
159
|
|
160
160
|
def _update_children_paths(
|
161
|
-
self, old_path:
|
161
|
+
self, old_path: utils.KeyPath, new_path: utils.KeyPath
|
162
|
+
):
|
162
163
|
"""Trigger path change for subchoices so their IDs can be invalidated."""
|
163
164
|
super()._update_children_paths(old_path, new_path)
|
164
165
|
if self._subchoice_specs:
|
@@ -337,7 +338,7 @@ class Choices(DecisionPoint):
|
|
337
338
|
f'DNA child values should be sorted. '
|
338
339
|
f'Encountered: {sub_dna_values}, Location: {self.location.path}.')
|
339
340
|
for i, sub_dna in enumerate(dna):
|
340
|
-
sub_location =
|
341
|
+
sub_location = utils.KeyPath(i, self.location)
|
341
342
|
if not isinstance(sub_dna.value, int):
|
342
343
|
raise ValueError(
|
343
344
|
f'Choice value should be int. Encountered: {sub_dna.value}, '
|
@@ -598,16 +599,21 @@ class Choices(DecisionPoint):
|
|
598
599
|
s.append(_indent(f'({i}): {value_str}\n', root_indent + 1))
|
599
600
|
s.append(_indent(']', root_indent))
|
600
601
|
if show_id:
|
601
|
-
kvlist = [('id',
|
602
|
+
kvlist = [('id', str(self.id), '\'\'')]
|
602
603
|
else:
|
603
604
|
kvlist = []
|
604
|
-
additionl_properties =
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
605
|
+
additionl_properties = utils.kvlist_str(
|
606
|
+
kvlist
|
607
|
+
+ [
|
608
|
+
('name', self.name, None),
|
609
|
+
('distinct', self.distinct, True),
|
610
|
+
('sorted', self.sorted, False),
|
611
|
+
('hints', self.hints, None),
|
612
|
+
('subchoice_index', self.subchoice_index, None),
|
613
|
+
],
|
614
|
+
compact=False,
|
615
|
+
root_indent=root_indent,
|
616
|
+
)
|
611
617
|
if additionl_properties:
|
612
618
|
s.append(', ')
|
613
619
|
s.append(additionl_properties)
|
@@ -615,14 +621,16 @@ class Choices(DecisionPoint):
|
|
615
621
|
return ''.join(s)
|
616
622
|
|
617
623
|
|
618
|
-
def manyof(
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
624
|
+
def manyof(
|
625
|
+
num_choices: int,
|
626
|
+
candidates: List[DNASpec],
|
627
|
+
distinct: bool = True,
|
628
|
+
sorted: bool = False, # pylint: disable=redefined-builtin
|
629
|
+
literal_values: Optional[List[Union[str, int, float]]] = None,
|
630
|
+
hints: Any = None,
|
631
|
+
location: Union[str, utils.KeyPath] = utils.KeyPath(),
|
632
|
+
name: Optional[str] = None,
|
633
|
+
) -> Choices:
|
626
634
|
"""Returns a multi-choice specification.
|
627
635
|
|
628
636
|
It creates the genotype for :func:`pyglove.manyof`.
|
@@ -674,11 +682,13 @@ def manyof(num_choices: int,
|
|
674
682
|
hints=hints, location=location, name=name)
|
675
683
|
|
676
684
|
|
677
|
-
def oneof(
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
685
|
+
def oneof(
|
686
|
+
candidates: List[DNASpec],
|
687
|
+
literal_values: Optional[List[Union[str, int, float]]] = None,
|
688
|
+
hints: Any = None,
|
689
|
+
location: Union[str, utils.KeyPath] = utils.KeyPath(),
|
690
|
+
name: Optional[str] = None,
|
691
|
+
) -> Choices:
|
682
692
|
"""Returns a single choice specification.
|
683
693
|
|
684
694
|
It creates the genotype for :func:`pyglove.oneof`.
|
@@ -716,4 +726,3 @@ def oneof(candidates: List[DNASpec],
|
|
716
726
|
"""
|
717
727
|
return manyof(1, candidates, literal_values=literal_values,
|
718
728
|
hints=hints, location=location, name=name)
|
719
|
-
|
pyglove/core/geno/custom.py
CHANGED
@@ -17,10 +17,9 @@ import random
|
|
17
17
|
import types
|
18
18
|
from typing import Any, Callable, List, Optional, Union
|
19
19
|
|
20
|
-
from pyglove.core import object_utils
|
21
20
|
from pyglove.core import symbolic
|
22
21
|
from pyglove.core import typing as pg_typing
|
23
|
-
|
22
|
+
from pyglove.core import utils
|
24
23
|
from pyglove.core.geno.base import DecisionPoint
|
25
24
|
from pyglove.core.geno.base import DNA
|
26
25
|
|
@@ -125,7 +124,7 @@ class CustomDecisionPoint(DecisionPoint):
|
|
125
124
|
f'CustomDecisionPoint expects string type DNA. '
|
126
125
|
f'Encountered: {dna!r}, Location: {self.location.path}.')
|
127
126
|
|
128
|
-
def sym_jsonify(self, **kwargs: Any) ->
|
127
|
+
def sym_jsonify(self, **kwargs: Any) -> utils.JSONValueType:
|
129
128
|
"""Overrides sym_jsonify to exclude non-serializable fields."""
|
130
129
|
exclude_keys = kwargs.pop('exclude_keys', [])
|
131
130
|
exclude_keys.extend(['random_dna_fn', 'next_dna_fn'])
|
@@ -150,24 +149,28 @@ class CustomDecisionPoint(DecisionPoint):
|
|
150
149
|
if not compact:
|
151
150
|
return super().format(compact, verbose, root_indent, **kwargs)
|
152
151
|
if show_id:
|
153
|
-
kvlist = [('id',
|
152
|
+
kvlist = [('id', str(self.id), '\'\'')]
|
154
153
|
else:
|
155
154
|
kvlist = []
|
156
|
-
details =
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
155
|
+
details = utils.kvlist_str(
|
156
|
+
kvlist
|
157
|
+
+ [
|
158
|
+
('hyper_type', self.hyper_type, None),
|
159
|
+
('name', self.name, None),
|
160
|
+
('hints', self.hints, None),
|
161
|
+
]
|
162
|
+
)
|
161
163
|
return f'{self.__class__.__name__}({details})'
|
162
164
|
|
163
165
|
|
164
|
-
def custom(
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
166
|
+
def custom(
|
167
|
+
hyper_type: Optional[str] = None,
|
168
|
+
next_dna_fn: Optional[Callable[[Optional[DNA]], Optional[DNA]]] = None,
|
169
|
+
random_dna_fn: Optional[Callable[[Any], DNA]] = None,
|
170
|
+
hints: Any = None,
|
171
|
+
location: utils.KeyPath = utils.KeyPath(),
|
172
|
+
name: Optional[str] = None,
|
173
|
+
) -> CustomDecisionPoint:
|
171
174
|
"""Returns a custom decision point.
|
172
175
|
|
173
176
|
It creates the genotype for subclasses of :func:`pyglove.hyper.CustomHyper`.
|