experimaestro 1.11.1__py3-none-any.whl → 2.0.0b4__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.
Potentially problematic release.
This version of experimaestro might be problematic. Click here for more details.
- experimaestro/__init__.py +10 -11
- experimaestro/annotations.py +167 -206
- experimaestro/cli/__init__.py +140 -16
- experimaestro/cli/filter.py +42 -74
- experimaestro/cli/jobs.py +157 -106
- experimaestro/cli/progress.py +269 -0
- experimaestro/cli/refactor.py +249 -0
- experimaestro/click.py +0 -1
- experimaestro/commandline.py +19 -3
- experimaestro/connectors/__init__.py +22 -3
- experimaestro/connectors/local.py +12 -0
- experimaestro/core/arguments.py +192 -37
- experimaestro/core/identifier.py +127 -12
- experimaestro/core/objects/__init__.py +6 -0
- experimaestro/core/objects/config.py +702 -285
- experimaestro/core/objects/config_walk.py +24 -6
- experimaestro/core/serialization.py +91 -34
- experimaestro/core/serializers.py +1 -8
- experimaestro/core/subparameters.py +164 -0
- experimaestro/core/types.py +198 -83
- experimaestro/exceptions.py +26 -0
- experimaestro/experiments/cli.py +107 -25
- experimaestro/generators.py +50 -9
- experimaestro/huggingface.py +3 -1
- experimaestro/launcherfinder/parser.py +29 -0
- experimaestro/launcherfinder/registry.py +3 -3
- experimaestro/launchers/__init__.py +26 -1
- experimaestro/launchers/direct.py +12 -0
- experimaestro/launchers/slurm/base.py +154 -2
- experimaestro/mkdocs/base.py +6 -8
- experimaestro/mkdocs/metaloader.py +0 -1
- experimaestro/mypy.py +452 -7
- experimaestro/notifications.py +75 -16
- experimaestro/progress.py +404 -0
- experimaestro/rpyc.py +0 -1
- experimaestro/run.py +19 -6
- experimaestro/scheduler/__init__.py +18 -1
- experimaestro/scheduler/base.py +504 -959
- experimaestro/scheduler/dependencies.py +43 -28
- experimaestro/scheduler/dynamic_outputs.py +259 -130
- experimaestro/scheduler/experiment.py +582 -0
- experimaestro/scheduler/interfaces.py +474 -0
- experimaestro/scheduler/jobs.py +485 -0
- experimaestro/scheduler/services.py +186 -12
- experimaestro/scheduler/signal_handler.py +32 -0
- experimaestro/scheduler/state.py +1 -1
- experimaestro/scheduler/state_db.py +388 -0
- experimaestro/scheduler/state_provider.py +2345 -0
- experimaestro/scheduler/state_sync.py +834 -0
- experimaestro/scheduler/workspace.py +52 -10
- experimaestro/scriptbuilder.py +7 -0
- experimaestro/server/__init__.py +153 -32
- experimaestro/server/data/index.css +0 -125
- experimaestro/server/data/index.css.map +1 -1
- experimaestro/server/data/index.js +194 -58
- experimaestro/server/data/index.js.map +1 -1
- experimaestro/settings.py +47 -6
- experimaestro/sphinx/__init__.py +3 -3
- experimaestro/taskglobals.py +20 -0
- experimaestro/tests/conftest.py +80 -0
- experimaestro/tests/core/test_generics.py +2 -2
- experimaestro/tests/identifier_stability.json +45 -0
- experimaestro/tests/launchers/bin/sacct +6 -2
- experimaestro/tests/launchers/bin/sbatch +4 -2
- experimaestro/tests/launchers/common.py +2 -2
- experimaestro/tests/launchers/test_slurm.py +80 -0
- experimaestro/tests/restart.py +1 -1
- experimaestro/tests/tasks/all.py +7 -0
- experimaestro/tests/tasks/test_dynamic.py +231 -0
- experimaestro/tests/test_checkers.py +2 -2
- experimaestro/tests/test_cli_jobs.py +615 -0
- experimaestro/tests/test_dependencies.py +11 -17
- experimaestro/tests/test_deprecated.py +630 -0
- experimaestro/tests/test_environment.py +200 -0
- experimaestro/tests/test_experiment.py +3 -3
- experimaestro/tests/test_file_progress.py +425 -0
- experimaestro/tests/test_file_progress_integration.py +477 -0
- experimaestro/tests/test_forward.py +3 -3
- experimaestro/tests/test_generators.py +93 -0
- experimaestro/tests/test_identifier.py +520 -169
- experimaestro/tests/test_identifier_stability.py +458 -0
- experimaestro/tests/test_instance.py +16 -21
- experimaestro/tests/test_multitoken.py +442 -0
- experimaestro/tests/test_mypy.py +433 -0
- experimaestro/tests/test_objects.py +314 -30
- experimaestro/tests/test_outputs.py +8 -8
- experimaestro/tests/test_param.py +22 -26
- experimaestro/tests/test_partial_paths.py +231 -0
- experimaestro/tests/test_progress.py +2 -50
- experimaestro/tests/test_resumable_task.py +480 -0
- experimaestro/tests/test_serializers.py +141 -60
- experimaestro/tests/test_state_db.py +434 -0
- experimaestro/tests/test_subparameters.py +160 -0
- experimaestro/tests/test_tags.py +151 -15
- experimaestro/tests/test_tasks.py +137 -160
- experimaestro/tests/test_token_locking.py +252 -0
- experimaestro/tests/test_tokens.py +25 -19
- experimaestro/tests/test_types.py +133 -11
- experimaestro/tests/test_validation.py +19 -19
- experimaestro/tests/test_workspace_triggers.py +158 -0
- experimaestro/tests/token_reschedule.py +5 -3
- experimaestro/tests/utils.py +2 -2
- experimaestro/tokens.py +154 -57
- experimaestro/tools/diff.py +8 -1
- experimaestro/tui/__init__.py +8 -0
- experimaestro/tui/app.py +2303 -0
- experimaestro/tui/app.tcss +353 -0
- experimaestro/tui/log_viewer.py +228 -0
- experimaestro/typingutils.py +11 -2
- experimaestro/utils/__init__.py +23 -0
- experimaestro/utils/environment.py +148 -0
- experimaestro/utils/git.py +129 -0
- experimaestro/utils/resources.py +1 -1
- experimaestro/version.py +34 -0
- {experimaestro-1.11.1.dist-info → experimaestro-2.0.0b4.dist-info}/METADATA +70 -39
- experimaestro-2.0.0b4.dist-info/RECORD +181 -0
- {experimaestro-1.11.1.dist-info → experimaestro-2.0.0b4.dist-info}/WHEEL +1 -1
- experimaestro-2.0.0b4.dist-info/entry_points.txt +16 -0
- experimaestro/compat.py +0 -6
- experimaestro/core/objects.pyi +0 -225
- experimaestro/server/data/0c35d18bf06992036b69.woff2 +0 -0
- experimaestro/server/data/219aa9140e099e6c72ed.woff2 +0 -0
- experimaestro/server/data/3a4004a46a653d4b2166.woff +0 -0
- experimaestro/server/data/3baa5b8f3469222b822d.woff +0 -0
- experimaestro/server/data/4d73cb90e394b34b7670.woff +0 -0
- experimaestro/server/data/4ef4218c522f1eb6b5b1.woff2 +0 -0
- experimaestro/server/data/5d681e2edae8c60630db.woff +0 -0
- experimaestro/server/data/6f420cf17cc0d7676fad.woff2 +0 -0
- experimaestro/server/data/c380809fd3677d7d6903.woff2 +0 -0
- experimaestro/server/data/f882956fd323fd322f31.woff +0 -0
- experimaestro-1.11.1.dist-info/RECORD +0 -158
- experimaestro-1.11.1.dist-info/entry_points.txt +0 -17
- {experimaestro-1.11.1.dist-info → experimaestro-2.0.0b4.dist-info/licenses}/LICENSE +0 -0
|
@@ -5,8 +5,8 @@ from pathlib import Path
|
|
|
5
5
|
from typing import Dict, List, Optional
|
|
6
6
|
from experimaestro import (
|
|
7
7
|
Param,
|
|
8
|
-
deprecate,
|
|
9
8
|
Config,
|
|
9
|
+
InstanceConfig,
|
|
10
10
|
Constant,
|
|
11
11
|
Meta,
|
|
12
12
|
Option,
|
|
@@ -14,6 +14,8 @@ from experimaestro import (
|
|
|
14
14
|
field,
|
|
15
15
|
Task,
|
|
16
16
|
LightweightTask,
|
|
17
|
+
subparameters,
|
|
18
|
+
param_group,
|
|
17
19
|
)
|
|
18
20
|
from experimaestro.core.objects import (
|
|
19
21
|
ConfigInformation,
|
|
@@ -34,7 +36,7 @@ class B(Config):
|
|
|
34
36
|
|
|
35
37
|
|
|
36
38
|
class C(Config):
|
|
37
|
-
a: Param[int] = 1
|
|
39
|
+
a: Param[int] = field(ignore_default=1)
|
|
38
40
|
b: Param[int]
|
|
39
41
|
|
|
40
42
|
|
|
@@ -68,42 +70,42 @@ def assert_notequal(a, b, message=""):
|
|
|
68
70
|
assert getidentifier(a) != getidentifier(b), message
|
|
69
71
|
|
|
70
72
|
|
|
71
|
-
def
|
|
72
|
-
assert_equal(A(a=1), A(a=1))
|
|
73
|
+
def test_identifier_int():
|
|
74
|
+
assert_equal(A.C(a=1), A.C(a=1))
|
|
73
75
|
|
|
74
76
|
|
|
75
|
-
def
|
|
76
|
-
assert_notequal(A(a=1), B(a=1))
|
|
77
|
+
def test_identifier_different_type():
|
|
78
|
+
assert_notequal(A.C(a=1), B.C(a=1))
|
|
77
79
|
|
|
78
80
|
|
|
79
|
-
def
|
|
80
|
-
assert_equal(Values(value1=1, value2=2), Values(value2=2, value1=1))
|
|
81
|
+
def test_identifier_order():
|
|
82
|
+
assert_equal(Values.C(value1=1, value2=2), Values.C(value2=2, value1=1))
|
|
81
83
|
|
|
82
84
|
|
|
83
|
-
def
|
|
84
|
-
assert_equal(C(a=1, b=2), C(b=2))
|
|
85
|
+
def test_identifier_default():
|
|
86
|
+
assert_equal(C.C(a=1, b=2), C.C(b=2))
|
|
85
87
|
|
|
86
88
|
|
|
87
89
|
def test_identifier_default_field():
|
|
88
|
-
assert_equal(CField(a=1, b=2), CField(b=2))
|
|
90
|
+
assert_equal(CField.C(a=1, b=2), CField.C(b=2))
|
|
89
91
|
|
|
90
92
|
|
|
91
|
-
def
|
|
92
|
-
assert_equal(D(a=A(a=1)), D(a=A(a=1)))
|
|
93
|
+
def test_identifier_inner_eq():
|
|
94
|
+
assert_equal(D.C(a=A.C(a=1)), D.C(a=A.C(a=1)))
|
|
93
95
|
|
|
94
96
|
|
|
95
|
-
def
|
|
96
|
-
assert_equal(Float(value=1), Float(value=1))
|
|
97
|
+
def test_identifier_float():
|
|
98
|
+
assert_equal(Float.C(value=1), Float.C(value=1))
|
|
97
99
|
|
|
98
100
|
|
|
99
|
-
def
|
|
100
|
-
assert_equal(Float(value=1.0), Float(value=1))
|
|
101
|
+
def test_identifier_float2():
|
|
102
|
+
assert_equal(Float.C(value=1.0), Float.C(value=1))
|
|
101
103
|
|
|
102
104
|
|
|
103
105
|
# --- Argument name
|
|
104
106
|
|
|
105
107
|
|
|
106
|
-
def
|
|
108
|
+
def test_identifier_name():
|
|
107
109
|
"""The identifier fully determines the hash code"""
|
|
108
110
|
|
|
109
111
|
class Config0(Config):
|
|
@@ -118,28 +120,28 @@ def test_param_name():
|
|
|
118
120
|
__xpmid__ = "test.identifier.argumentname"
|
|
119
121
|
a: Param[int]
|
|
120
122
|
|
|
121
|
-
assert_notequal(Config0(a=2), Config1(b=2))
|
|
122
|
-
assert_equal(Config0(a=2), Config3(a=2))
|
|
123
|
+
assert_notequal(Config0.C(a=2), Config1.C(b=2))
|
|
124
|
+
assert_equal(Config0.C(a=2), Config3.C(a=2))
|
|
123
125
|
|
|
124
126
|
|
|
125
127
|
# --- Test option
|
|
126
128
|
|
|
127
129
|
|
|
128
|
-
def
|
|
130
|
+
def test_identifier_option():
|
|
129
131
|
class OptionConfig(Config):
|
|
130
132
|
__xpmid__ = "test.identifier.option"
|
|
131
133
|
a: Param[int]
|
|
132
|
-
b: Option[int] = 1
|
|
134
|
+
b: Option[int] = field(ignore_default=1)
|
|
133
135
|
|
|
134
|
-
assert_notequal(OptionConfig(a=2), OptionConfig(a=1))
|
|
135
|
-
assert_equal(OptionConfig(a=1, b=2), OptionConfig(a=1))
|
|
136
|
-
assert_equal(OptionConfig(a=1, b=2), OptionConfig(a=1, b=2))
|
|
136
|
+
assert_notequal(OptionConfig.C(a=2), OptionConfig.C(a=1))
|
|
137
|
+
assert_equal(OptionConfig.C(a=1, b=2), OptionConfig.C(a=1))
|
|
138
|
+
assert_equal(OptionConfig.C(a=1, b=2), OptionConfig.C(a=1, b=2))
|
|
137
139
|
|
|
138
140
|
|
|
139
141
|
# --- Dictionnary
|
|
140
142
|
|
|
141
143
|
|
|
142
|
-
def
|
|
144
|
+
def test_identifier_dict():
|
|
143
145
|
"""Test identifiers of dictionary structures"""
|
|
144
146
|
|
|
145
147
|
class B(Config):
|
|
@@ -148,11 +150,14 @@ def test_param_identifier_dict():
|
|
|
148
150
|
class A(Config):
|
|
149
151
|
bs: Param[Dict[str, B]]
|
|
150
152
|
|
|
151
|
-
assert_equal(A(bs={"b1": B(x=1)}), A(bs={"b1": B(x=1)}))
|
|
152
|
-
assert_equal(
|
|
153
|
+
assert_equal(A.C(bs={"b1": B.C(x=1)}), A.C(bs={"b1": B.C(x=1)}))
|
|
154
|
+
assert_equal(
|
|
155
|
+
A.C(bs={"b1": B.C(x=1), "b2": B.C(x=2)}),
|
|
156
|
+
A.C(bs={"b2": B.C(x=2), "b1": B.C(x=1)}),
|
|
157
|
+
)
|
|
153
158
|
|
|
154
|
-
assert_notequal(A(bs={"b1": B(x=1)}), A(bs={"b1": B(x=2)}))
|
|
155
|
-
assert_notequal(A(bs={"b1": B(x=1)}), A(bs={"b2": B(x=1)}))
|
|
159
|
+
assert_notequal(A.C(bs={"b1": B.C(x=1)}), A.C(bs={"b1": B.C(x=2)}))
|
|
160
|
+
assert_notequal(A.C(bs={"b1": B.C(x=1)}), A.C(bs={"b2": B.C(x=1)}))
|
|
156
161
|
|
|
157
162
|
|
|
158
163
|
# --- Ignore paths
|
|
@@ -163,16 +168,16 @@ class TypeWithPath(Config):
|
|
|
163
168
|
path: Param[Path]
|
|
164
169
|
|
|
165
170
|
|
|
166
|
-
def
|
|
171
|
+
def test_identifier_path():
|
|
167
172
|
"""Path should be ignored"""
|
|
168
|
-
assert_equal(TypeWithPath(a=1, path="/a/b"), TypeWithPath(a=1, path="/c/d"))
|
|
169
|
-
assert_notequal(TypeWithPath(a=2, path="/a/b"), TypeWithPath(a=1, path="/c/d"))
|
|
173
|
+
assert_equal(TypeWithPath.C(a=1, path="/a/b"), TypeWithPath.C(a=1, path="/c/d"))
|
|
174
|
+
assert_notequal(TypeWithPath.C(a=2, path="/a/b"), TypeWithPath.C(a=1, path="/c/d"))
|
|
170
175
|
|
|
171
176
|
|
|
172
177
|
# --- Test with added arguments
|
|
173
178
|
|
|
174
179
|
|
|
175
|
-
def
|
|
180
|
+
def test_identifier_pathoption():
|
|
176
181
|
"""Path arguments should be ignored"""
|
|
177
182
|
|
|
178
183
|
class A_with_path(Config):
|
|
@@ -184,10 +189,10 @@ def test_param_identifier_pathoption():
|
|
|
184
189
|
__xpmid__ = "pathoption_test"
|
|
185
190
|
a: Param[int]
|
|
186
191
|
|
|
187
|
-
assert_equal(A_with_path(a=1), A_without_path(a=1))
|
|
192
|
+
assert_equal(A_with_path.C(a=1), A_without_path.C(a=1))
|
|
188
193
|
|
|
189
194
|
|
|
190
|
-
def
|
|
195
|
+
def test_identifier_enum():
|
|
191
196
|
"""test enum parameters"""
|
|
192
197
|
from enum import Enum
|
|
193
198
|
|
|
@@ -198,11 +203,11 @@ def test_param_identifier_enum():
|
|
|
198
203
|
class EnumConfig(Config):
|
|
199
204
|
a: Param[EnumParam]
|
|
200
205
|
|
|
201
|
-
assert_notequal(EnumConfig(a=EnumParam.FIRST), EnumConfig(a=EnumParam.SECOND))
|
|
202
|
-
assert_equal(EnumConfig(a=EnumParam.FIRST), EnumConfig(a=EnumParam.FIRST))
|
|
206
|
+
assert_notequal(EnumConfig.C(a=EnumParam.FIRST), EnumConfig.C(a=EnumParam.SECOND))
|
|
207
|
+
assert_equal(EnumConfig.C(a=EnumParam.FIRST), EnumConfig.C(a=EnumParam.FIRST))
|
|
203
208
|
|
|
204
209
|
|
|
205
|
-
def
|
|
210
|
+
def test_identifier_addnone():
|
|
206
211
|
"""Test the case of new parameter (with None default)"""
|
|
207
212
|
|
|
208
213
|
class B(Config):
|
|
@@ -215,28 +220,28 @@ def test_param_identifier_addnone():
|
|
|
215
220
|
class A(Config):
|
|
216
221
|
__xpmid__ = "defaultnone"
|
|
217
222
|
|
|
218
|
-
assert_equal(A_with_b(), A())
|
|
219
|
-
assert_notequal(A_with_b(b=B(x=1)), A())
|
|
223
|
+
assert_equal(A_with_b.C(), A.C())
|
|
224
|
+
assert_notequal(A_with_b.C(b=B.C(x=1)), A.C())
|
|
220
225
|
|
|
221
226
|
|
|
222
|
-
def
|
|
227
|
+
def test_identifier_defaultnew():
|
|
223
228
|
"""Path arguments should be ignored"""
|
|
224
229
|
|
|
225
230
|
class A_with_b(Config):
|
|
226
231
|
__xpmid__ = "defaultnew"
|
|
227
232
|
|
|
228
233
|
a: Param[int]
|
|
229
|
-
b: Param[int] = 1
|
|
234
|
+
b: Param[int] = field(ignore_default=1)
|
|
230
235
|
|
|
231
236
|
class A(Config):
|
|
232
237
|
__xpmid__ = "defaultnew"
|
|
233
238
|
a: Param[int]
|
|
234
239
|
|
|
235
|
-
assert_equal(A_with_b(a=1, b=1), A(a=1))
|
|
236
|
-
assert_equal(A_with_b(a=1), A(a=1))
|
|
240
|
+
assert_equal(A_with_b.C(a=1, b=1), A.C(a=1))
|
|
241
|
+
assert_equal(A_with_b.C(a=1), A.C(a=1))
|
|
237
242
|
|
|
238
243
|
|
|
239
|
-
def
|
|
244
|
+
def test_identifier_taskconfigidentifier():
|
|
240
245
|
"""Test whether the embedded task arguments make the configuration different"""
|
|
241
246
|
|
|
242
247
|
class MyConfig(Config):
|
|
@@ -246,19 +251,19 @@ def test_param_taskconfigidentifier():
|
|
|
246
251
|
x: Param[int]
|
|
247
252
|
|
|
248
253
|
def task_outputs(self, dep):
|
|
249
|
-
return dep(MyConfig(a=1))
|
|
254
|
+
return dep(MyConfig.C(a=1))
|
|
250
255
|
|
|
251
256
|
assert_equal(
|
|
252
|
-
MyTask(x=1).submit(run_mode=RunMode.DRY_RUN),
|
|
253
|
-
MyTask(x=1).submit(run_mode=RunMode.DRY_RUN),
|
|
257
|
+
MyTask.C(x=1).submit(run_mode=RunMode.DRY_RUN),
|
|
258
|
+
MyTask.C(x=1).submit(run_mode=RunMode.DRY_RUN),
|
|
254
259
|
)
|
|
255
260
|
assert_notequal(
|
|
256
|
-
MyTask(x=2).submit(run_mode=RunMode.DRY_RUN),
|
|
257
|
-
MyTask(x=1).submit(run_mode=RunMode.DRY_RUN),
|
|
261
|
+
MyTask.C(x=2).submit(run_mode=RunMode.DRY_RUN),
|
|
262
|
+
MyTask.C(x=1).submit(run_mode=RunMode.DRY_RUN),
|
|
258
263
|
)
|
|
259
264
|
|
|
260
265
|
|
|
261
|
-
def
|
|
266
|
+
def test_identifier_constant():
|
|
262
267
|
"""Test if constants are taken into account for signature computation"""
|
|
263
268
|
|
|
264
269
|
class A1(Config):
|
|
@@ -269,53 +274,20 @@ def test_param_constant():
|
|
|
269
274
|
__xpmid__ = "test.constant"
|
|
270
275
|
version: Constant[int] = 1
|
|
271
276
|
|
|
272
|
-
assert_equal(A1(), A1bis())
|
|
277
|
+
assert_equal(A1.C(), A1bis.C())
|
|
273
278
|
|
|
274
279
|
class A2(Config):
|
|
275
280
|
__xpmid__ = "test.constant"
|
|
276
281
|
version: Constant[int] = 2
|
|
277
282
|
|
|
278
|
-
assert_notequal(A1(), A2())
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
def test_param_identifier_deprecated_class():
|
|
282
|
-
"""Test that when submitting the task, the computed identifier is the one of
|
|
283
|
-
the new class"""
|
|
284
|
-
|
|
285
|
-
class NewConfig(Config):
|
|
286
|
-
__xpmid__ = "new"
|
|
287
|
-
|
|
288
|
-
@deprecate
|
|
289
|
-
class OldConfig(NewConfig):
|
|
290
|
-
__xpmid__ = "old"
|
|
291
|
-
|
|
292
|
-
class DerivedConfig(NewConfig):
|
|
293
|
-
__xpmid__ = "derived"
|
|
294
|
-
|
|
295
|
-
assert_notequal(
|
|
296
|
-
NewConfig(), DerivedConfig(), "A derived configuration has another ID"
|
|
297
|
-
)
|
|
298
|
-
assert_equal(
|
|
299
|
-
NewConfig(), OldConfig(), "Deprecated and new configuration have the same ID"
|
|
300
|
-
)
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
def test_param_identifier_deprecated_attribute():
|
|
304
|
-
class Values(Config):
|
|
305
|
-
values: Param[List[int]] = []
|
|
306
|
-
|
|
307
|
-
@deprecate
|
|
308
|
-
def value(self, x):
|
|
309
|
-
self.values = [x]
|
|
310
|
-
|
|
311
|
-
assert_equal(Values(values=[1]), Values(value=1))
|
|
283
|
+
assert_notequal(A1.C(), A2.C())
|
|
312
284
|
|
|
313
285
|
|
|
314
286
|
class MetaA(Config):
|
|
315
287
|
x: Param[int]
|
|
316
288
|
|
|
317
289
|
|
|
318
|
-
def
|
|
290
|
+
def test_identifier_meta():
|
|
319
291
|
"""Test forced meta-parameter"""
|
|
320
292
|
|
|
321
293
|
class B(Config):
|
|
@@ -331,121 +303,136 @@ def test_param_identifier_meta():
|
|
|
331
303
|
params: Param[Dict[str, MetaA]]
|
|
332
304
|
|
|
333
305
|
# As meta
|
|
334
|
-
assert_notequal(B(a=MetaA(x=1)), B(a=MetaA(x=2)))
|
|
335
|
-
assert_equal(B(a=setmeta(MetaA(x=1), True)), B(a=setmeta(MetaA(x=2), True)))
|
|
306
|
+
assert_notequal(B.C(a=MetaA.C(x=1)), B.C(a=MetaA.C(x=2)))
|
|
307
|
+
assert_equal(B.C(a=setmeta(MetaA.C(x=1), True)), B.C(a=setmeta(MetaA.C(x=2), True)))
|
|
336
308
|
|
|
337
309
|
# As parameter
|
|
338
|
-
assert_equal(C(a=MetaA(x=1)), C(a=MetaA(x=2)))
|
|
339
|
-
assert_notequal(
|
|
310
|
+
assert_equal(C.C(a=MetaA.C(x=1)), C.C(a=MetaA.C(x=2)))
|
|
311
|
+
assert_notequal(
|
|
312
|
+
C.C(a=setmeta(MetaA.C(x=1), False)), C.C(a=setmeta(MetaA.C(x=2), False))
|
|
313
|
+
)
|
|
340
314
|
|
|
341
315
|
# Array with mixed
|
|
342
316
|
assert_equal(
|
|
343
|
-
ArrayConfig(array=[MetaA(x=1)]),
|
|
344
|
-
ArrayConfig(array=[MetaA(x=1), setmeta(MetaA(x=2), True)]),
|
|
317
|
+
ArrayConfig.C(array=[MetaA.C(x=1)]),
|
|
318
|
+
ArrayConfig.C(array=[MetaA.C(x=1), setmeta(MetaA.C(x=2), True)]),
|
|
345
319
|
)
|
|
346
320
|
|
|
347
321
|
# Array with empty list
|
|
348
|
-
assert_equal(
|
|
322
|
+
assert_equal(
|
|
323
|
+
ArrayConfig.C(array=[]), ArrayConfig.C(array=[setmeta(MetaA.C(x=2), True)])
|
|
324
|
+
)
|
|
349
325
|
|
|
350
326
|
# Dict with mixed
|
|
351
327
|
assert_equal(
|
|
352
|
-
DictConfig(params={"a": MetaA(x=1)}),
|
|
353
|
-
DictConfig(params={"a": MetaA(x=1), "b": setmeta(MetaA(x=2), True)}),
|
|
328
|
+
DictConfig.C(params={"a": MetaA.C(x=1)}),
|
|
329
|
+
DictConfig.C(params={"a": MetaA.C(x=1), "b": setmeta(MetaA.C(x=2), True)}),
|
|
354
330
|
)
|
|
355
331
|
|
|
356
332
|
|
|
357
|
-
def
|
|
333
|
+
def test_identifier_meta_default_dict():
|
|
358
334
|
class DictConfig(Config):
|
|
359
|
-
params: Param[Dict[str, MetaA]] = {}
|
|
335
|
+
params: Param[Dict[str, MetaA]] = field(ignore_default={})
|
|
360
336
|
|
|
361
337
|
assert_equal(
|
|
362
|
-
DictConfig(params={}),
|
|
363
|
-
DictConfig(params={"b": setmeta(MetaA(x=2), True)}),
|
|
338
|
+
DictConfig.C(params={}),
|
|
339
|
+
DictConfig.C(params={"b": setmeta(MetaA.C(x=2), True)}),
|
|
364
340
|
)
|
|
365
341
|
|
|
366
342
|
# Dict with mixed
|
|
367
343
|
assert_equal(
|
|
368
|
-
DictConfig(params={"a": MetaA(x=1)}),
|
|
369
|
-
DictConfig(params={"a": MetaA(x=1), "b": setmeta(MetaA(x=2), True)}),
|
|
344
|
+
DictConfig.C(params={"a": MetaA.C(x=1)}),
|
|
345
|
+
DictConfig.C(params={"a": MetaA.C(x=1), "b": setmeta(MetaA.C(x=2), True)}),
|
|
370
346
|
)
|
|
371
347
|
|
|
372
348
|
|
|
373
|
-
def
|
|
349
|
+
def test_identifier_meta_default_array():
|
|
374
350
|
class ArrayConfigWithDefault(Config):
|
|
375
|
-
array: Param[List[MetaA]] = []
|
|
351
|
+
array: Param[List[MetaA]] = field(ignore_default=[])
|
|
376
352
|
|
|
377
353
|
# Array (with default) with mixed
|
|
378
354
|
assert_equal(
|
|
379
|
-
ArrayConfigWithDefault(array=[MetaA(x=1)]),
|
|
380
|
-
ArrayConfigWithDefault(array=[MetaA(x=1), setmeta(MetaA(x=2), True)]),
|
|
355
|
+
ArrayConfigWithDefault.C(array=[MetaA.C(x=1)]),
|
|
356
|
+
ArrayConfigWithDefault.C(array=[MetaA.C(x=1), setmeta(MetaA.C(x=2), True)]),
|
|
381
357
|
)
|
|
382
358
|
# Array (with default) with empty list
|
|
383
359
|
assert_equal(
|
|
384
|
-
ArrayConfigWithDefault(array=[]),
|
|
385
|
-
ArrayConfigWithDefault(array=[setmeta(MetaA(x=2), True)]),
|
|
360
|
+
ArrayConfigWithDefault.C(array=[]),
|
|
361
|
+
ArrayConfigWithDefault.C(array=[setmeta(MetaA.C(x=2), True)]),
|
|
386
362
|
)
|
|
387
363
|
|
|
388
364
|
|
|
389
|
-
def
|
|
365
|
+
def test_identifier_init_task():
|
|
390
366
|
class MyConfig(Config):
|
|
391
367
|
pass
|
|
392
368
|
|
|
393
|
-
class
|
|
369
|
+
class IdentifierInitTask(LightweightTask):
|
|
370
|
+
pass
|
|
371
|
+
|
|
372
|
+
class IdentifierInitTask2(Task):
|
|
394
373
|
pass
|
|
395
374
|
|
|
396
|
-
class
|
|
375
|
+
class IdentifierTask(Task):
|
|
397
376
|
x: Param[MyConfig]
|
|
398
377
|
|
|
399
|
-
task =
|
|
400
|
-
task_with_pre = (
|
|
401
|
-
|
|
402
|
-
.
|
|
403
|
-
.submit(run_mode=RunMode.DRY_RUN)
|
|
378
|
+
task = IdentifierTask.C(x=MyConfig.C()).submit(run_mode=RunMode.DRY_RUN)
|
|
379
|
+
task_with_pre = IdentifierTask.C(x=MyConfig.C()).submit(
|
|
380
|
+
run_mode=RunMode.DRY_RUN,
|
|
381
|
+
init_tasks=[IdentifierInitTask.C(), IdentifierInitTask2.C()],
|
|
404
382
|
)
|
|
405
|
-
task_with_pre_2 = (
|
|
406
|
-
|
|
407
|
-
.
|
|
408
|
-
|
|
383
|
+
task_with_pre_2 = IdentifierTask.C(x=MyConfig.C()).submit(
|
|
384
|
+
run_mode=RunMode.DRY_RUN,
|
|
385
|
+
init_tasks=[IdentifierInitTask.C(), IdentifierInitTask2.C()],
|
|
386
|
+
)
|
|
387
|
+
task_with_pre_3 = IdentifierTask.C(x=MyConfig.C()).submit(
|
|
388
|
+
run_mode=RunMode.DRY_RUN,
|
|
389
|
+
init_tasks=[IdentifierInitTask2.C(), IdentifierInitTask.C()],
|
|
409
390
|
)
|
|
410
|
-
task_with_pre_3 = IdentifierPreTask(
|
|
411
|
-
x=MyConfig().add_pretasks(IdentifierPreLightTask())
|
|
412
|
-
).submit(run_mode=RunMode.DRY_RUN)
|
|
413
391
|
|
|
414
|
-
assert_notequal(task, task_with_pre, "
|
|
392
|
+
assert_notequal(task, task_with_pre, "Should be different with init-task")
|
|
415
393
|
assert_equal(task_with_pre, task_with_pre_2, "Same parameters")
|
|
416
|
-
|
|
394
|
+
assert_notequal(task_with_pre, task_with_pre_3, "Other parameters")
|
|
417
395
|
|
|
418
396
|
|
|
419
|
-
def
|
|
420
|
-
class
|
|
421
|
-
|
|
397
|
+
def test_identifier_init_task_dep():
|
|
398
|
+
class Loader(LightweightTask):
|
|
399
|
+
param1: Param[float]
|
|
422
400
|
|
|
423
|
-
|
|
424
|
-
|
|
401
|
+
def execute(self):
|
|
402
|
+
pass
|
|
425
403
|
|
|
426
|
-
class
|
|
427
|
-
|
|
404
|
+
class FirstTask(Task):
|
|
405
|
+
def task_outputs(self, dep):
|
|
406
|
+
return dep(Loader.C(param1=1))
|
|
428
407
|
|
|
429
|
-
|
|
430
|
-
|
|
408
|
+
def execute(self):
|
|
409
|
+
pass
|
|
431
410
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
411
|
+
class SecondTask(Task):
|
|
412
|
+
param3: Param[int]
|
|
413
|
+
|
|
414
|
+
def execute(self):
|
|
415
|
+
pass
|
|
416
|
+
|
|
417
|
+
# Two identical tasks
|
|
418
|
+
task_a_1 = FirstTask.C()
|
|
419
|
+
task_a_2 = FirstTask.C()
|
|
420
|
+
assert_equal(task_a_1, task_a_2)
|
|
421
|
+
|
|
422
|
+
# We process them with two different init tasks
|
|
423
|
+
loader_1 = task_a_1.submit(
|
|
424
|
+
init_tasks=[Loader.C(param1=0.5)], run_mode=RunMode.DRY_RUN
|
|
440
425
|
)
|
|
441
|
-
|
|
442
|
-
run_mode=RunMode.DRY_RUN
|
|
443
|
-
init_tasks=[IdentifierInitTask2(), IdentifierInitTask()],
|
|
426
|
+
loader_2 = task_a_2.submit(
|
|
427
|
+
init_tasks=[Loader.C(param1=5)], run_mode=RunMode.DRY_RUN
|
|
444
428
|
)
|
|
429
|
+
assert_notequal(loader_1, loader_2)
|
|
445
430
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
431
|
+
# Now, we process
|
|
432
|
+
c_1 = SecondTask.C(param3=2).submit(init_tasks=[loader_1], run_mode=RunMode.DRY_RUN)
|
|
433
|
+
|
|
434
|
+
c_2 = SecondTask.C(param3=2).submit(init_tasks=[loader_2], run_mode=RunMode.DRY_RUN)
|
|
435
|
+
assert_notequal(c_1, c_2)
|
|
449
436
|
|
|
450
437
|
|
|
451
438
|
# --- Check configuration reloads
|
|
@@ -463,7 +450,7 @@ def check_reload(config):
|
|
|
463
450
|
new_config = ConfigInformation.fromParameters(
|
|
464
451
|
data, as_instance=False, discard_id=True
|
|
465
452
|
)
|
|
466
|
-
assert new_config.__xpm__.
|
|
453
|
+
assert new_config.__xpm__._identifier is None
|
|
467
454
|
new_identifier = new_config.__xpm__.identifier.all
|
|
468
455
|
|
|
469
456
|
assert new_identifier == old_identifier
|
|
@@ -473,28 +460,28 @@ class IdentifierReloadConfig(Config):
|
|
|
473
460
|
id: Param[str]
|
|
474
461
|
|
|
475
462
|
|
|
476
|
-
def
|
|
463
|
+
def test_identifier_reload_config():
|
|
477
464
|
# Creates the configuration
|
|
478
|
-
check_reload(IdentifierReloadConfig(id="123"))
|
|
465
|
+
check_reload(IdentifierReloadConfig.C(id="123"))
|
|
479
466
|
|
|
480
467
|
|
|
481
468
|
class IdentifierReload(Task):
|
|
482
469
|
id: Param[str]
|
|
483
470
|
|
|
484
|
-
def task_outputs(self, dep):
|
|
485
|
-
return IdentifierReloadConfig(id=self.id)
|
|
471
|
+
def task_outputs(self, dep) -> IdentifierReloadConfig.C:
|
|
472
|
+
return IdentifierReloadConfig.C(id=self.id)
|
|
486
473
|
|
|
487
474
|
|
|
488
475
|
class IdentifierReloadDerived(Config):
|
|
489
476
|
task: Param[IdentifierReloadConfig]
|
|
490
477
|
|
|
491
478
|
|
|
492
|
-
def
|
|
479
|
+
def test_identifier_reload_taskoutput():
|
|
493
480
|
"""When using a task output, the identifier should not be different"""
|
|
494
481
|
|
|
495
482
|
# Creates the configuration
|
|
496
|
-
task = IdentifierReload(id="123").submit(run_mode=RunMode.DRY_RUN)
|
|
497
|
-
config = IdentifierReloadDerived(task=task)
|
|
483
|
+
task = IdentifierReload.C(id="123").submit(run_mode=RunMode.DRY_RUN)
|
|
484
|
+
config = IdentifierReloadDerived.C(task=task)
|
|
498
485
|
check_reload(config)
|
|
499
486
|
|
|
500
487
|
|
|
@@ -511,23 +498,23 @@ class IdentifierReloadTaskDerived(Config):
|
|
|
511
498
|
other: Param[IdentifierReloadTaskConfig]
|
|
512
499
|
|
|
513
500
|
|
|
514
|
-
def
|
|
501
|
+
def test_identifier_reload_task_direct():
|
|
515
502
|
"""When using a direct task output, the identifier should not be different"""
|
|
516
503
|
|
|
517
504
|
# Creates the configuration
|
|
518
|
-
task = IdentifierReloadTask(id="123").submit(run_mode=RunMode.DRY_RUN)
|
|
519
|
-
config = IdentifierReloadTaskDerived(
|
|
520
|
-
task=task, other=IdentifierReloadTaskConfig(x=2)
|
|
505
|
+
task = IdentifierReloadTask.C(id="123").submit(run_mode=RunMode.DRY_RUN)
|
|
506
|
+
config = IdentifierReloadTaskDerived.C(
|
|
507
|
+
task=task, other=IdentifierReloadTaskConfig.C(x=2)
|
|
521
508
|
)
|
|
522
509
|
check_reload(config)
|
|
523
510
|
|
|
524
511
|
|
|
525
|
-
def
|
|
512
|
+
def test_identifier_reload_meta():
|
|
526
513
|
"""Test identifier don't change when using meta"""
|
|
527
514
|
# Creates the configuration
|
|
528
|
-
task = IdentifierReloadTask(id="123").submit(run_mode=RunMode.DRY_RUN)
|
|
529
|
-
config = IdentifierReloadTaskDerived(
|
|
530
|
-
task=task, other=setmeta(IdentifierReloadTaskConfig(x=2), True)
|
|
515
|
+
task = IdentifierReloadTask.C(id="123").submit(run_mode=RunMode.DRY_RUN)
|
|
516
|
+
config = IdentifierReloadTaskDerived.C(
|
|
517
|
+
task=task, other=setmeta(IdentifierReloadTaskConfig.C(x=2), True)
|
|
531
518
|
)
|
|
532
519
|
check_reload(config)
|
|
533
520
|
|
|
@@ -545,10 +532,10 @@ class LoopC(Config):
|
|
|
545
532
|
param_b: Param["LoopB"]
|
|
546
533
|
|
|
547
534
|
|
|
548
|
-
def
|
|
549
|
-
c = LoopC()
|
|
550
|
-
b = LoopB(param_c=c)
|
|
551
|
-
a = LoopA(param_b=b)
|
|
535
|
+
def test_identifier_loop():
|
|
536
|
+
c = LoopC.C()
|
|
537
|
+
b = LoopB.C(param_c=c)
|
|
538
|
+
a = LoopA.C(param_b=b)
|
|
552
539
|
c.param_a = a
|
|
553
540
|
c.param_b = b
|
|
554
541
|
|
|
@@ -570,3 +557,367 @@ def test_param_identifier_loop():
|
|
|
570
557
|
for i in range(len(configs)):
|
|
571
558
|
for j in range(1, len(configs)):
|
|
572
559
|
assert identifiers[i][0] == identifiers[i][j]
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
# --- Test InstanceConfig
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
class SubModel(InstanceConfig):
|
|
566
|
+
"""Test InstanceConfig - instances are distinguished even with same params"""
|
|
567
|
+
|
|
568
|
+
pass
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
class SubModelAsConfig(Config):
|
|
572
|
+
"""Same as SubModel but as regular Config for backwards compat testing"""
|
|
573
|
+
|
|
574
|
+
__xpmid__ = "test.SubModel"
|
|
575
|
+
pass
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
class Model(Config):
|
|
579
|
+
"""Model that can contain SubModel instances"""
|
|
580
|
+
|
|
581
|
+
m1: Param[SubModel]
|
|
582
|
+
m2: Param[SubModel]
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
class ModelWithRegularConfig(Config):
|
|
586
|
+
"""Model using regular Config instead of InstanceConfig"""
|
|
587
|
+
|
|
588
|
+
__xpmid__ = "test.Model"
|
|
589
|
+
m1: Param[SubModelAsConfig]
|
|
590
|
+
m2: Param[SubModelAsConfig]
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
def test_instanceconfig_backwards_compat():
|
|
594
|
+
"""Model using single InstanceConfig should have same ID as with regular Config"""
|
|
595
|
+
# Using InstanceConfig (first occurrence only, no instance marker added)
|
|
596
|
+
sm1 = SubModel.C()
|
|
597
|
+
sm1.__xpmtype__.identifier.name = "test.SubModel" # Match the __xpmid__
|
|
598
|
+
m_instance = Model.C(m1=sm1, m2=sm1)
|
|
599
|
+
m_instance.__xpmtype__.identifier.name = "test.Model"
|
|
600
|
+
|
|
601
|
+
# Using regular Config
|
|
602
|
+
sc1 = SubModelAsConfig.C()
|
|
603
|
+
m_regular = ModelWithRegularConfig.C(m1=sc1, m2=sc1)
|
|
604
|
+
|
|
605
|
+
# Should have same identifier (backwards compatible)
|
|
606
|
+
assert_equal(
|
|
607
|
+
m_instance, m_regular, "Single InstanceConfig should be backwards compatible"
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
def test_instanceconfig_same_params_different_instances():
|
|
612
|
+
"""Model with separate InstanceConfig instances should differ from shared"""
|
|
613
|
+
sm1 = SubModel.C()
|
|
614
|
+
sm2 = SubModel.C()
|
|
615
|
+
|
|
616
|
+
# Using the same instance twice (shared)
|
|
617
|
+
m1 = Model.C(m1=sm1, m2=sm1)
|
|
618
|
+
|
|
619
|
+
# Using different instances (separate)
|
|
620
|
+
m2 = Model.C(m1=sm1, m2=sm2)
|
|
621
|
+
|
|
622
|
+
# These should be different because sm2 is a second instance with same params
|
|
623
|
+
assert_notequal(m1, m2, "Models with shared vs separate instances should differ")
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
def test_instanceconfig_reused_instance():
|
|
627
|
+
"""Reusing the same InstanceConfig instance should give same ID"""
|
|
628
|
+
sm1 = SubModel.C()
|
|
629
|
+
|
|
630
|
+
# Using the same instance object multiple times should be OK
|
|
631
|
+
m1 = Model.C(m1=sm1, m2=sm1)
|
|
632
|
+
m2 = Model.C(m1=sm1, m2=sm1)
|
|
633
|
+
|
|
634
|
+
# These should be the same because we're reusing the exact same objects
|
|
635
|
+
assert_equal(m1, m2, "Models with same instance objects should be equal")
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
def test_instanceconfig_serialization():
|
|
639
|
+
"""InstanceConfig identifiers should be stable after serialization"""
|
|
640
|
+
sm1 = SubModel.C()
|
|
641
|
+
sm2 = SubModel.C()
|
|
642
|
+
|
|
643
|
+
# Create a model with two different instances
|
|
644
|
+
m1 = Model.C(m1=sm1, m2=sm2)
|
|
645
|
+
original_id = getidentifier(m1)
|
|
646
|
+
|
|
647
|
+
# Serialize and reload
|
|
648
|
+
check_reload(m1)
|
|
649
|
+
|
|
650
|
+
# The identifier should remain the same
|
|
651
|
+
assert getidentifier(m1) == original_id
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
# --- Test ignore_default vs default in field() ---
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
def test_identifier_field_ignore_default():
|
|
658
|
+
"""Test that field(ignore_default=X) ignores value in identifier when value == X"""
|
|
659
|
+
|
|
660
|
+
class ConfigWithIgnoreDefault(Config):
|
|
661
|
+
__xpmid__ = "test.identifier.field_ignore_default"
|
|
662
|
+
a: Param[int] = field(ignore_default=1)
|
|
663
|
+
b: Param[int]
|
|
664
|
+
|
|
665
|
+
# When a=1 (matches ignore_default), should be same as not specifying a
|
|
666
|
+
class ConfigWithoutA(Config):
|
|
667
|
+
__xpmid__ = "test.identifier.field_ignore_default"
|
|
668
|
+
b: Param[int]
|
|
669
|
+
|
|
670
|
+
assert_equal(
|
|
671
|
+
ConfigWithIgnoreDefault.C(a=1, b=2),
|
|
672
|
+
ConfigWithIgnoreDefault.C(b=2),
|
|
673
|
+
"field(ignore_default=1) should ignore a=1 in identifier",
|
|
674
|
+
)
|
|
675
|
+
assert_equal(
|
|
676
|
+
ConfigWithIgnoreDefault.C(a=1, b=2),
|
|
677
|
+
ConfigWithoutA.C(b=2),
|
|
678
|
+
"Config with ignore_default should match config without that param",
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
# When a=2 (doesn't match ignore_default), should be included
|
|
682
|
+
assert_notequal(
|
|
683
|
+
ConfigWithIgnoreDefault.C(a=2, b=2),
|
|
684
|
+
ConfigWithIgnoreDefault.C(b=2),
|
|
685
|
+
"field(ignore_default=1) should include a=2 in identifier",
|
|
686
|
+
)
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
def test_identifier_field_default():
|
|
690
|
+
"""Test that field(default=X) includes value in identifier even when value == X"""
|
|
691
|
+
|
|
692
|
+
class ConfigWithDefault(Config):
|
|
693
|
+
__xpmid__ = "test.identifier.field_default"
|
|
694
|
+
a: Param[int] = field(default=1)
|
|
695
|
+
b: Param[int]
|
|
696
|
+
|
|
697
|
+
class ConfigWithoutA(Config):
|
|
698
|
+
__xpmid__ = "test.identifier.field_default"
|
|
699
|
+
b: Param[int]
|
|
700
|
+
|
|
701
|
+
# When a=1 (matches default), should still be included in identifier
|
|
702
|
+
# so Config with a=1 should differ from Config without a
|
|
703
|
+
assert_notequal(
|
|
704
|
+
ConfigWithDefault.C(a=1, b=2),
|
|
705
|
+
ConfigWithoutA.C(b=2),
|
|
706
|
+
"field(default=1) should include a=1 in identifier",
|
|
707
|
+
)
|
|
708
|
+
|
|
709
|
+
# But two configs with same a=1 should be equal
|
|
710
|
+
assert_equal(
|
|
711
|
+
ConfigWithDefault.C(a=1, b=2),
|
|
712
|
+
ConfigWithDefault.C(a=1, b=2),
|
|
713
|
+
"Same values should have same identifier",
|
|
714
|
+
)
|
|
715
|
+
|
|
716
|
+
|
|
717
|
+
def test_identifier_field_default_vs_ignore_default():
|
|
718
|
+
"""Test difference between field(default=X) and field(ignore_default=X)"""
|
|
719
|
+
|
|
720
|
+
class ConfigWithDefault(Config):
|
|
721
|
+
__xpmid__ = "test.identifier.field_default_vs_ignore"
|
|
722
|
+
a: Param[int] = field(default=1)
|
|
723
|
+
b: Param[int]
|
|
724
|
+
|
|
725
|
+
class ConfigWithIgnoreDefault(Config):
|
|
726
|
+
__xpmid__ = "test.identifier.field_default_vs_ignore"
|
|
727
|
+
a: Param[int] = field(ignore_default=1)
|
|
728
|
+
b: Param[int]
|
|
729
|
+
|
|
730
|
+
# Both with a=1, b=2 - should differ because one includes a, other doesn't
|
|
731
|
+
assert_notequal(
|
|
732
|
+
ConfigWithDefault.C(a=1, b=2),
|
|
733
|
+
ConfigWithIgnoreDefault.C(a=1, b=2),
|
|
734
|
+
"field(default=1) vs field(ignore_default=1) should differ when a=1",
|
|
735
|
+
)
|
|
736
|
+
|
|
737
|
+
# Both with a=2 (not matching default), should be the same
|
|
738
|
+
assert_equal(
|
|
739
|
+
ConfigWithDefault.C(a=2, b=2),
|
|
740
|
+
ConfigWithIgnoreDefault.C(a=2, b=2),
|
|
741
|
+
"field(default=1) vs field(ignore_default=1) should be same when a!=1",
|
|
742
|
+
)
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
# --- Test partial identifiers (subparameters) ---
|
|
746
|
+
|
|
747
|
+
|
|
748
|
+
# Define parameter groups at module level
|
|
749
|
+
iter_group = param_group("iter")
|
|
750
|
+
model_group = param_group("model")
|
|
751
|
+
|
|
752
|
+
|
|
753
|
+
def get_partial_identifier(config, sp):
|
|
754
|
+
"""Helper to get partial identifier for a config and subparameters"""
|
|
755
|
+
return config.__xpm__.get_partial_identifier(sp).all
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
def test_partial_identifier_excludes_grouped_params():
|
|
759
|
+
"""Test that partial identifier excludes parameters in excluded groups"""
|
|
760
|
+
|
|
761
|
+
class ConfigWithGroups(Config):
|
|
762
|
+
checkpoints = subparameters(exclude_groups=[iter_group])
|
|
763
|
+
max_iter: Param[int] = field(groups=[iter_group])
|
|
764
|
+
learning_rate: Param[float]
|
|
765
|
+
|
|
766
|
+
c1 = ConfigWithGroups.C(max_iter=100, learning_rate=0.1)
|
|
767
|
+
c2 = ConfigWithGroups.C(max_iter=200, learning_rate=0.1)
|
|
768
|
+
|
|
769
|
+
# Full identifiers should differ (max_iter is different)
|
|
770
|
+
assert_notequal(c1, c2, "Full identifiers should differ when max_iter differs")
|
|
771
|
+
|
|
772
|
+
# Partial identifiers should be the same (max_iter is excluded)
|
|
773
|
+
pid1 = get_partial_identifier(c1, ConfigWithGroups.checkpoints)
|
|
774
|
+
pid2 = get_partial_identifier(c2, ConfigWithGroups.checkpoints)
|
|
775
|
+
assert (
|
|
776
|
+
pid1 == pid2
|
|
777
|
+
), "Partial identifiers should match when only excluded params differ"
|
|
778
|
+
|
|
779
|
+
|
|
780
|
+
def test_partial_identifier_includes_ungrouped_params():
|
|
781
|
+
"""Test that partial identifier includes parameters not in excluded groups"""
|
|
782
|
+
|
|
783
|
+
class ConfigWithGroups(Config):
|
|
784
|
+
checkpoints = subparameters(exclude_groups=[iter_group])
|
|
785
|
+
max_iter: Param[int] = field(groups=[iter_group])
|
|
786
|
+
learning_rate: Param[float]
|
|
787
|
+
|
|
788
|
+
c1 = ConfigWithGroups.C(max_iter=100, learning_rate=0.1)
|
|
789
|
+
c2 = ConfigWithGroups.C(max_iter=100, learning_rate=0.2)
|
|
790
|
+
|
|
791
|
+
# Partial identifiers should differ (learning_rate is not excluded)
|
|
792
|
+
pid1 = get_partial_identifier(c1, ConfigWithGroups.checkpoints)
|
|
793
|
+
pid2 = get_partial_identifier(c2, ConfigWithGroups.checkpoints)
|
|
794
|
+
assert (
|
|
795
|
+
pid1 != pid2
|
|
796
|
+
), "Partial identifiers should differ when non-excluded params differ"
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
def test_partial_identifier_matches_config_without_excluded():
|
|
800
|
+
"""Test that partial identifier matches config without the excluded fields"""
|
|
801
|
+
|
|
802
|
+
class ConfigWithIter(Config):
|
|
803
|
+
__xpmid__ = "test.partial_identifier.config"
|
|
804
|
+
checkpoints = subparameters(exclude_groups=[iter_group])
|
|
805
|
+
max_iter: Param[int] = field(groups=[iter_group])
|
|
806
|
+
learning_rate: Param[float]
|
|
807
|
+
|
|
808
|
+
class ConfigWithoutIter(Config):
|
|
809
|
+
__xpmid__ = "test.partial_identifier.config"
|
|
810
|
+
learning_rate: Param[float]
|
|
811
|
+
|
|
812
|
+
c_with = ConfigWithIter.C(max_iter=100, learning_rate=0.1)
|
|
813
|
+
c_without = ConfigWithoutIter.C(learning_rate=0.1)
|
|
814
|
+
|
|
815
|
+
# The partial identifier of c_with should match full identifier of c_without
|
|
816
|
+
pid = get_partial_identifier(c_with, ConfigWithIter.checkpoints)
|
|
817
|
+
full_id = getidentifier(c_without)
|
|
818
|
+
assert (
|
|
819
|
+
pid == full_id
|
|
820
|
+
), "Partial identifier should match config without excluded fields"
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
def test_partial_identifier_multiple_groups():
|
|
824
|
+
"""Test partial identifier with parameter in multiple groups"""
|
|
825
|
+
|
|
826
|
+
class ConfigMultiGroup(Config):
|
|
827
|
+
checkpoints = subparameters(exclude_groups=[iter_group])
|
|
828
|
+
# This parameter is in both groups - should be excluded if any group is excluded
|
|
829
|
+
x: Param[int] = field(groups=[iter_group, model_group])
|
|
830
|
+
y: Param[float]
|
|
831
|
+
|
|
832
|
+
c1 = ConfigMultiGroup.C(x=1, y=0.1)
|
|
833
|
+
c2 = ConfigMultiGroup.C(x=2, y=0.1)
|
|
834
|
+
|
|
835
|
+
# Partial identifiers should be the same (x is in iter_group which is excluded)
|
|
836
|
+
pid1 = get_partial_identifier(c1, ConfigMultiGroup.checkpoints)
|
|
837
|
+
pid2 = get_partial_identifier(c2, ConfigMultiGroup.checkpoints)
|
|
838
|
+
assert (
|
|
839
|
+
pid1 == pid2
|
|
840
|
+
), "Partial identifiers should match when param is in any excluded group"
|
|
841
|
+
|
|
842
|
+
|
|
843
|
+
def test_partial_identifier_include_overrides_exclude():
|
|
844
|
+
"""Test that include_groups overrides exclude_groups"""
|
|
845
|
+
|
|
846
|
+
class ConfigIncludeOverride(Config):
|
|
847
|
+
# iter_group is excluded but also included, so it should NOT be excluded
|
|
848
|
+
partial = subparameters(
|
|
849
|
+
exclude_groups=[iter_group, model_group], include_groups=[iter_group]
|
|
850
|
+
)
|
|
851
|
+
x: Param[int] = field(groups=[iter_group])
|
|
852
|
+
y: Param[int] = field(groups=[model_group])
|
|
853
|
+
z: Param[float]
|
|
854
|
+
|
|
855
|
+
c1 = ConfigIncludeOverride.C(x=1, y=1, z=0.1)
|
|
856
|
+
c2 = ConfigIncludeOverride.C(x=2, y=1, z=0.1)
|
|
857
|
+
c3 = ConfigIncludeOverride.C(x=1, y=2, z=0.1)
|
|
858
|
+
|
|
859
|
+
# x is in iter_group which is included (overrides exclusion)
|
|
860
|
+
# so different x should give different partial identifiers
|
|
861
|
+
pid1 = get_partial_identifier(c1, ConfigIncludeOverride.partial)
|
|
862
|
+
pid2 = get_partial_identifier(c2, ConfigIncludeOverride.partial)
|
|
863
|
+
assert pid1 != pid2, "Include should override exclude - x should be included"
|
|
864
|
+
|
|
865
|
+
# y is in model_group which is excluded (not included)
|
|
866
|
+
# so different y should give SAME partial identifiers
|
|
867
|
+
pid3 = get_partial_identifier(c3, ConfigIncludeOverride.partial)
|
|
868
|
+
assert pid1 == pid3, "y is excluded - different y should give same partial ID"
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
def test_partial_identifier_exclude_all():
|
|
872
|
+
"""Test exclude_all option"""
|
|
873
|
+
|
|
874
|
+
class ConfigExcludeAll(Config):
|
|
875
|
+
# Exclude all, but include model_group
|
|
876
|
+
partial = subparameters(exclude_all=True, include_groups=[model_group])
|
|
877
|
+
x: Param[int] = field(groups=[iter_group])
|
|
878
|
+
y: Param[int] = field(groups=[model_group])
|
|
879
|
+
z: Param[float] # No group
|
|
880
|
+
|
|
881
|
+
c1 = ConfigExcludeAll.C(x=1, y=1, z=0.1)
|
|
882
|
+
c2 = ConfigExcludeAll.C(x=2, y=1, z=0.1) # Different x (excluded)
|
|
883
|
+
c3 = ConfigExcludeAll.C(x=1, y=2, z=0.1) # Different y (included)
|
|
884
|
+
c4 = ConfigExcludeAll.C(x=1, y=1, z=0.2) # Different z (excluded - no group)
|
|
885
|
+
|
|
886
|
+
pid1 = get_partial_identifier(c1, ConfigExcludeAll.partial)
|
|
887
|
+
pid2 = get_partial_identifier(c2, ConfigExcludeAll.partial)
|
|
888
|
+
pid3 = get_partial_identifier(c3, ConfigExcludeAll.partial)
|
|
889
|
+
pid4 = get_partial_identifier(c4, ConfigExcludeAll.partial)
|
|
890
|
+
|
|
891
|
+
# x is excluded (in iter_group, not included) - same partial ID
|
|
892
|
+
assert pid1 == pid2, "x is excluded - should have same partial ID"
|
|
893
|
+
|
|
894
|
+
# y is included (in model_group) - different partial ID
|
|
895
|
+
assert pid1 != pid3, "y is included - should have different partial ID"
|
|
896
|
+
|
|
897
|
+
# z is excluded (no group, exclude_all=True) - same partial ID
|
|
898
|
+
assert (
|
|
899
|
+
pid1 == pid4
|
|
900
|
+
), "z (no group) is excluded by exclude_all - should have same partial ID"
|
|
901
|
+
|
|
902
|
+
|
|
903
|
+
def test_partial_identifier_exclude_no_group():
|
|
904
|
+
"""Test exclude_no_group option"""
|
|
905
|
+
|
|
906
|
+
class ConfigExcludeNoGroup(Config):
|
|
907
|
+
partial = subparameters(exclude_no_group=True)
|
|
908
|
+
x: Param[int] = field(groups=[iter_group])
|
|
909
|
+
y: Param[float] # No group
|
|
910
|
+
|
|
911
|
+
c1 = ConfigExcludeNoGroup.C(x=1, y=0.1)
|
|
912
|
+
c2 = ConfigExcludeNoGroup.C(x=2, y=0.1) # Different x (has group - not excluded)
|
|
913
|
+
c3 = ConfigExcludeNoGroup.C(x=1, y=0.2) # Different y (no group - excluded)
|
|
914
|
+
|
|
915
|
+
pid1 = get_partial_identifier(c1, ConfigExcludeNoGroup.partial)
|
|
916
|
+
pid2 = get_partial_identifier(c2, ConfigExcludeNoGroup.partial)
|
|
917
|
+
pid3 = get_partial_identifier(c3, ConfigExcludeNoGroup.partial)
|
|
918
|
+
|
|
919
|
+
# x has a group, so it's NOT excluded by exclude_no_group
|
|
920
|
+
assert pid1 != pid2, "x has group - should have different partial ID"
|
|
921
|
+
|
|
922
|
+
# y has no group, so it IS excluded by exclude_no_group
|
|
923
|
+
assert pid1 == pid3, "y has no group - should have same partial ID"
|