morphtree 2.1.0__py3-none-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. morphtree/__init__.py +28 -0
  2. morphtree/__main__.py +3 -0
  3. morphtree/_experimental/__init__.py +1 -0
  4. morphtree/_experimental/admission.py +364 -0
  5. morphtree/_experimental/base.py +215 -0
  6. morphtree/_experimental/component_cache_v2.py +263 -0
  7. morphtree/_experimental/evolution_report.py +307 -0
  8. morphtree/_experimental/execution_v2.py +697 -0
  9. morphtree/_experimental/facade.py +289 -0
  10. morphtree/_experimental/integrated.py +748 -0
  11. morphtree/_experimental/learning.py +418 -0
  12. morphtree/_experimental/manifest.json +204 -0
  13. morphtree/_experimental/numeric_fixture.py +21 -0
  14. morphtree/_experimental/parameter_contract.py +38 -0
  15. morphtree/_experimental/postfit.py +51 -0
  16. morphtree/_experimental/preprocessing.py +133 -0
  17. morphtree/_experimental/processes.py +191 -0
  18. morphtree/_experimental/producer.py +542 -0
  19. morphtree/_experimental/product.py +156 -0
  20. morphtree/_experimental/product_cli.py +43 -0
  21. morphtree/_experimental/roundtrip_v3.py +311 -0
  22. morphtree/_experimental/runtime_io.py +65 -0
  23. morphtree/_experimental/sampling.py +26 -0
  24. morphtree/_experimental/support.py +281 -0
  25. morphtree/_experimental/target.py +59 -0
  26. morphtree/_experimental/training.py +53 -0
  27. morphtree/_experimental/worker.py +682 -0
  28. morphtree/_installed.py +17 -0
  29. morphtree/_native/__init__.py +1 -0
  30. morphtree/_native/bin/win_amd64/morphtree_kernels.dll +0 -0
  31. morphtree/_native/buffers.py +82 -0
  32. morphtree/_native/loader.py +39 -0
  33. morphtree/_native/manifest.json +29 -0
  34. morphtree/_native/runtime.py +48 -0
  35. morphtree/_native/src/histogram.c +61 -0
  36. morphtree/_native/src/traversal.c +51 -0
  37. morphtree/api.py +388 -0
  38. morphtree/api_compat.py +65 -0
  39. morphtree/backend.py +162 -0
  40. morphtree/booster.py +252 -0
  41. morphtree/cli.py +52 -0
  42. morphtree/config.py +262 -0
  43. morphtree/control.py +20 -0
  44. morphtree/data.py +300 -0
  45. morphtree/diagnostics.py +158 -0
  46. morphtree/estimators.py +156 -0
  47. morphtree/examples.py +25 -0
  48. morphtree/exporting.py +224 -0
  49. morphtree/legacy_artifacts.py +32 -0
  50. morphtree/metrics.py +156 -0
  51. morphtree/model.py +1010 -0
  52. morphtree/mutations.py +205 -0
  53. morphtree/native_lab_backend.py +79 -0
  54. morphtree/numpy_backend.py +180 -0
  55. morphtree/optuna_tuner.py +154 -0
  56. morphtree/persistence.py +414 -0
  57. morphtree/prediction_output.py +35 -0
  58. morphtree/prepared_bins.py +82 -0
  59. morphtree/public.py +106 -0
  60. morphtree/resource_plan.py +170 -0
  61. morphtree/scalable.py +781 -0
  62. morphtree/scalable_backend.py +74 -0
  63. morphtree/scalable_checkpoint.py +129 -0
  64. morphtree/scalable_data.py +255 -0
  65. morphtree/scalable_receipts.py +92 -0
  66. morphtree/scalable_target.py +68 -0
  67. morphtree/scalable_variants.py +133 -0
  68. morphtree/sklearn_view.py +79 -0
  69. morphtree/source_markers.py +25 -0
  70. morphtree/species.py +47 -0
  71. morphtree/temporary.py +65 -0
  72. morphtree/training_fragment.py +130 -0
  73. morphtree/tree.py +231 -0
  74. morphtree/tuning.py +150 -0
  75. morphtree-2.1.0.dist-info/METADATA +212 -0
  76. morphtree-2.1.0.dist-info/RECORD +79 -0
  77. morphtree-2.1.0.dist-info/WHEEL +4 -0
  78. morphtree-2.1.0.dist-info/entry_points.txt +2 -0
  79. morphtree-2.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,281 @@
1
+ import copy,math,json,tempfile,os
2
+ from pathlib import Path
3
+ from dataclasses import asdict,replace
4
+ from morphtree.config import AlgorithmGenome,HyperParams
5
+ from morphtree.persistence import read_json,write_json,LoadLimits
6
+ from morphtree.model import MorphTreeClassifier,MorphTreeRegressor,MorphTreeMulticlassClassifier
7
+ from morphtree.metrics import point_losses
8
+ from .admission import digest,sha,AdmissionError
9
+ from .runtime_io import artifact_path,configure_temp,reference,publish
10
+ NATIVE={'binary':MorphTreeClassifier,'multiclass':MorphTreeMulticlassClassifier,'regression':MorphTreeRegressor}
11
+ TASKS=('binary','multiclass','regression')
12
+ def point_risks(task, y, predictions):
13
+ if len(y) != len(predictions) or not y:
14
+ raise ValueError('Aligned nonempty risks required')
15
+ if task == 'binary':
16
+ return point_losses(y, predictions)
17
+ if task == 'regression':
18
+ values = [(a - b) ** 2 for a, b in zip(y, predictions)]
19
+ elif task == 'multiclass':
20
+ values = [-math.log(max(1e-15, row[label])) for row, label in zip(predictions, y)]
21
+ else:
22
+ raise ValueError('Known task required')
23
+ if any((not math.isfinite(v) for v in values)):
24
+ raise ValueError('Finite predictions required')
25
+ return values
26
+
27
+ def average(task, columns, weights):
28
+ if not columns or len(columns) != len(weights) or abs(math.fsum(weights) - 1) > 1e-12:
29
+ raise ValueError('Normalized ensemble required')
30
+ if task == 'multiclass':
31
+ return [[math.fsum((w * p[i][j] for w, p in zip(weights, columns))) for j in range(len(columns[0][i]))] for i in range(len(columns[0]))]
32
+ return [math.fsum((w * p[i] for w, p in zip(weights, columns))) for i in range(len(columns[0]))]
33
+
34
+ class FamilyModel:
35
+
36
+ def __init__(self, task, components, weights):
37
+ self.task, self.components, self.weights = (task, components, weights)
38
+
39
+ def probabilities_or_values(self, X):
40
+ columns = []
41
+ for m in self.components:
42
+ p = m.predict(X) if self.task == 'regression' else m.predict_proba(X)
43
+ if self.task == 'binary':
44
+ p = [row[1] for row in p]
45
+ columns.append(p)
46
+ return average(self.task, columns, self.weights)
47
+
48
+ def predict(self, X):
49
+ p = self.probabilities_or_values(X)
50
+ if self.task == 'regression':
51
+ return p
52
+ if self.task == 'binary':
53
+ return [int(v >= 0.5) for v in p]
54
+ return [max(range(len(v)), key=v.__getitem__) for v in p]
55
+ def prediction_contract(task, y, values):
56
+ if len(y) != len(values) or not y:
57
+ raise ValueError('Aligned predictions required')
58
+ if task == 'multiclass':
59
+ classes = sorted(set(y))
60
+ if classes != list(range(len(classes))):
61
+ raise ValueError('Explicit contiguous class vocabulary required')
62
+ if any((len(p) != len(classes) or any((not math.isfinite(q) or q < 0 for q in p)) or abs(math.fsum(p) - 1) > 1e-12 for p in values)):
63
+ raise ValueError('Aligned finite probability vectors required')
64
+ elif any((type(p) not in (int, float) or not math.isfinite(p) or (task == 'binary' and (not 0 <= p <= 1)) for p in values)):
65
+ raise ValueError('Finite task-appropriate predictions required')
66
+ return point_risks(task, y, values)
67
+ def primary(task, y, prediction):
68
+ losses = prediction_contract(task, y, prediction)
69
+ return math.fsum(losses) / len(losses)
70
+
71
+ class FrozenFinalizer:
72
+ """One fixed form, explicit numeric opportunity, retain on ties/failure.
73
+
74
+ verify(packet, SEARCH) must authenticate completed model requests and return
75
+ predictions from those exact artifacts. It may load, never silently refit.
76
+ No GUARD or DEV argument exists. Recipe contains weights/member layout/map/
77
+ operator; numeric fields cannot alter it. Callback exceptions propagate and
78
+ leave the previously completed incumbent intact, never a partial result.
79
+ """
80
+
81
+ def __init__(self, anchor, search, *, numeric_fields, max_trials, verify):
82
+ if type(max_trials) is not int or not 0 <= max_trials <= 8:
83
+ raise ValueError('Finite trial ceiling 0..8')
84
+ if search.get('role') != 'SEARCH' or set(search) != {'role', 'ids', 'X', 'y'}:
85
+ raise ValueError('Explicit SEARCH only')
86
+ if not search['ids'] or len(set(search['ids'])) != len(search['ids']) or len(search['ids']) != len(search['y']) or (len(search['X']) != len(search['y'])):
87
+ raise ValueError('Aligned unique SEARCH rows')
88
+ if not callable(verify) or not set(numeric_fields) <= set(anchor['params']) or 'n_estimators' in numeric_fields:
89
+ raise ValueError('Explicit verifier and numerical fields; tree budget frozen')
90
+ self.search = copy.deepcopy(search)
91
+ self.numeric_fields = frozenset(numeric_fields)
92
+ self.max_trials = max_trials
93
+ self.verify = verify
94
+ self.anchor = copy.deepcopy(anchor)
95
+ self.signature = self._signature(self.anchor)
96
+ self.trace = []
97
+ self.attempts = 0
98
+ self.incumbent = self._verified(self.anchor)
99
+ self.anchor_loss = self.incumbent['loss']
100
+
101
+ def _signature(self, packet):
102
+ return digest({k: packet[k] for k in ('task', 'family', 'recipe', 'learning_identity')} | {'fixed_params': {k: v for k, v in packet['params'].items() if k not in self.numeric_fields}})
103
+
104
+ def _verified(self, packet):
105
+ packet = copy.deepcopy(packet)
106
+ if self._signature(packet) != self.signature or packet['family'] not in ('boost', 'bag', 'blend'):
107
+ raise ValueError('Frozen family/recipe/data/source/budget changed')
108
+ if packet['task'] not in ('binary', 'multiclass', 'regression') or packet['learning_identity']['SEARCH'] != digest(self.search):
109
+ raise ValueError('Task or SEARCH identity mismatch')
110
+ predictions = self.verify(copy.deepcopy(packet), copy.deepcopy(self.search))
111
+ loss = primary(packet['task'], self.search['y'], predictions)
112
+ if not math.isfinite(loss) or loss < 0:
113
+ raise ValueError('Finite nonnegative primary risk')
114
+ return {'packet': packet, 'prediction': copy.deepcopy(predictions), 'loss': loss}
115
+
116
+ def offer(self, packet):
117
+ if self.attempts >= self.max_trials:
118
+ raise ValueError('Trial ceiling exhausted; no renewal')
119
+ self.attempts += 1
120
+ candidate = self._verified(packet)
121
+ if candidate['loss'] < self.incumbent['loss']:
122
+ self.incumbent = candidate
123
+ self.trace.append({'offered': packet['id'], 'SEARCH_primary': candidate['loss'], 'retained': self.incumbent['packet']['id']})
124
+ return copy.deepcopy(self.incumbent)
125
+
126
+ def run(self, propose):
127
+ """Actual prospective trial loop; the anchor never becomes a default.
128
+
129
+ A qualified callback may perform a fit outside this module. The frozen
130
+ anchor packet is supplied by value for exact seed/recipe propagation.
131
+ This method grants no fit budget or admission by itself.
132
+ """
133
+ while self.attempts < self.max_trials:
134
+ packet = propose(self.attempts, copy.deepcopy(self.anchor))
135
+ self.offer(packet)
136
+ result = self._verified(self.incumbent['packet'])
137
+ if result['prediction'] != self.incumbent['prediction'] or result['loss'] > self.anchor_loss:
138
+ raise ValueError('Nontransactional final handoff or lost anchor')
139
+ return copy.deepcopy(result)
140
+ class TransactionalFinalizer(FrozenFinalizer):
141
+ """Consume an opportunity durably BEFORE a callback can start learning.
142
+
143
+ A callback/verifier/checkpoint error poisons this instance. A new instance
144
+ cannot reconcile the outer worker's incomplete INTENT; the existing durable
145
+ unit/lease boundary remains mandatory. checkpoint is a trusted source-bound
146
+ synchronous writer, never an inference provider.
147
+ """
148
+
149
+ def __init__(self, *args, checkpoint, **kwargs):
150
+ if not callable(checkpoint):
151
+ raise ValueError('Synchronous checkpoint required')
152
+ self.checkpoint = checkpoint
153
+ self.failed = False
154
+ super().__init__(*args, **kwargs)
155
+
156
+ def _begin(self):
157
+ if self.failed or self.attempts >= self.max_trials:
158
+ raise RuntimeError('Terminal or exhausted finalizer; reconcile, never retry')
159
+ number = self.attempts
160
+ self.attempts += 1
161
+ event = {'status': 'INTENT', 'trial': number, 'anchor': self.anchor['id'], 'signature': self.signature}
162
+ self.trace.append(event)
163
+ try:
164
+ self.checkpoint(copy.deepcopy(event))
165
+ except BaseException:
166
+ self.failed = True
167
+ raise
168
+ return number
169
+
170
+ def _complete(self, packet):
171
+ candidate = self._verified(packet)
172
+ if candidate['loss'] < self.incumbent['loss']:
173
+ self.incumbent = candidate
174
+ event = {'status': 'COMPLETED', 'trial': self.attempts - 1, 'offered': packet['id'], 'SEARCH_primary': candidate['loss'], 'retained': self.incumbent['packet']['id']}
175
+ self.checkpoint(copy.deepcopy(event))
176
+ self.trace.append(event)
177
+ return copy.deepcopy(self.incumbent)
178
+
179
+ def offer(self, packet):
180
+ self._begin()
181
+ try:
182
+ return self._complete(packet)
183
+ except BaseException:
184
+ self.failed = True
185
+ raise
186
+
187
+ def run(self, propose):
188
+ if self.failed:
189
+ raise RuntimeError('Failed callback cannot replay')
190
+ while self.attempts < self.max_trials:
191
+ number = self._begin()
192
+ try:
193
+ self._complete(propose(number, copy.deepcopy(self.anchor)))
194
+ except BaseException:
195
+ self.failed = True
196
+ raise
197
+ try:
198
+ result = self._verified(self.incumbent['packet'])
199
+ if result['prediction'] != self.incumbent['prediction'] or result['loss'] > self.anchor_loss:
200
+ raise ValueError('Changed final artifact or lost anchor')
201
+ return copy.deepcopy(result)
202
+ except BaseException:
203
+ self.failed = True
204
+ raise
205
+ def risk(task, y, prediction, classes):
206
+ if task not in TASKS or not y or len(y) != len(prediction):
207
+ raise ValueError('Aligned finite task risk')
208
+ if task != 'multiclass':
209
+ if any((type(p) not in (int, float) or not math.isfinite(p) or (task == 'binary' and (not 0 <= p <= 1)) for p in prediction)):
210
+ raise ValueError('Finite native predictions')
211
+ values = point_risks(task, y, prediction)
212
+ else:
213
+ if type(classes) is not list or not classes or len(set(classes)) != len(classes):
214
+ raise ValueError('Explicit unique TRAIN class vocabulary')
215
+ mapping = {v: i for i, v in enumerate(classes)}
216
+ if any((v not in mapping for v in y)):
217
+ raise ValueError('Unknown SEARCH label')
218
+ if any((type(p) is not list or len(p) != len(classes) or any((type(q) not in (int, float) or not math.isfinite(q) or (not 0 <= q <= 1) for q in p)) or (abs(math.fsum(p) - 1) > 1e-12) for p in prediction)):
219
+ raise ValueError('TRAIN-aligned finite probability vectors')
220
+ values = [-math.log(max(1e-15, p[mapping[v]])) for v, p in zip(y, prediction)]
221
+ value = math.fsum(values) / len(values)
222
+ if not math.isfinite(value) or value < 0:
223
+ raise ValueError('Nonnegative finite risk')
224
+ return value
225
+
226
+ class TaskFinalizer(TransactionalFinalizer):
227
+ """Reuse the poisoned/durable coordinator; bind full TRAIN class columns."""
228
+
229
+ def _verified(self, value):
230
+ p = copy.deepcopy(value)
231
+ if self._signature(p) != self.signature or p['family'] not in ('boost', 'bag') or p['task'] not in TASKS or (p['learning_identity']['SEARCH'] != digest(self.search)):
232
+ raise ValueError('Frozen task/form/data changed')
233
+ pred = self.verify(copy.deepcopy(p), copy.deepcopy(self.search))
234
+ return {'packet': p, 'prediction': copy.deepcopy(pred), 'loss': risk(p['task'], self.search['y'], pred, p['recipe']['classes'])}
235
+
236
+ class FamilyChoice:
237
+ """One static post-evaluation family comparison, never a predictive policy.
238
+
239
+ All source/model acquisition costs belong to the caller. Failed or consumed
240
+ instances cannot be replayed; the verified anchor artifact remains separate.
241
+ """
242
+
243
+ def __init__(self, anchor, selection, *, checkpoint, verify=None):
244
+ if not callable(checkpoint) or not callable(verify) or selection.get('role') != 'SEARCH':
245
+ raise ValueError('SEARCH and synchronous verifier/journal required')
246
+ self.anchor = copy.deepcopy(anchor)
247
+ self.selection = copy.deepcopy(selection)
248
+ self.verify = verify
249
+ self.checkpoint = checkpoint
250
+ self.failed = False
251
+ self.done = False
252
+ self.attempts = 0
253
+ self.best = self._checked(self.anchor)
254
+ self.anchor_loss = self.best['loss']
255
+
256
+ def _checked(self, p):
257
+ if p['task'] != self.anchor['task'] or p['family'] not in ('boost', 'bag') or p['learning_identity'] != self.anchor['learning_identity']:
258
+ raise ValueError('Common actual TRAIN/SEARCH/task required')
259
+ pred = self.verify(copy.deepcopy(p), copy.deepcopy(self.selection))
260
+ return {'packet': copy.deepcopy(p), 'prediction': copy.deepcopy(pred), 'loss': risk(p['task'], self.selection['y'], pred, p['recipe']['classes'])}
261
+
262
+ def run(self, offer):
263
+ if self.failed or self.done:
264
+ raise RuntimeError('Terminal family decision; no replay')
265
+ try:
266
+ if offer['family'] == self.anchor['family']:
267
+ raise ValueError('Other native family only; numeric search is separate')
268
+ self.attempts += 1
269
+ self.checkpoint({'status': 'INTENT', 'offer': offer['id'], 'actual_anchor': self.anchor['id']})
270
+ candidate = self._checked(offer)
271
+ if candidate['loss'] < self.best['loss']:
272
+ self.best = candidate
273
+ self.checkpoint({'status': 'COMPLETED', 'retained': self.best['packet']['id'], 'loss': self.best['loss']})
274
+ final = self._checked(self.best['packet'])
275
+ if final['prediction'] != self.best['prediction'] or final['loss'] > self.anchor_loss:
276
+ raise ValueError('Changed final artifact or lost anchor')
277
+ self.done = True
278
+ return {**final, 'anchor_loss': self.anchor_loss, 'actual_anchor': self.anchor['id'], 'policy': 'POSTHOC_SEARCH_BOTH_FAMILIES_ALREADY_ACQUIRED', 'predictive_policy_utility_demonstrated': False}
279
+ except BaseException:
280
+ self.failed = True
281
+ raise
@@ -0,0 +1,59 @@
1
+ import math,random
2
+ from .sampling import class_groups
3
+ from .preprocessing import _m26_scalar
4
+ def detect_target(y, task):
5
+ if hasattr(y, 'tolist'):
6
+ y = y.tolist()
7
+ if not isinstance(y, (list, tuple)) or len(y) < 4:
8
+ raise ValueError('At least four scalar target values required')
9
+ values = [_m26_scalar(v) for v in y]
10
+ if any((v is None for v in values)):
11
+ raise ValueError('Missing target is unsupported')
12
+ strings = all((isinstance(v, str) for v in values))
13
+ numeric = all((type(v) is float for v in values))
14
+ if not (strings or numeric):
15
+ raise ValueError('Target must be consistently string or numeric')
16
+ classes = sorted(set(values))
17
+ if task == 'classification':
18
+ # Explicit classifier intent: continuous-valued labels remain classes.
19
+ # Only the binary/multiclass distinction is inferred here.
20
+ task = 'binary' if len(classes) == 2 else 'multiclass'
21
+ if task == 'auto':
22
+ if strings:
23
+ task = 'binary' if len(classes) == 2 else 'multiclass'
24
+ elif classes == [0.0, 1.0]:
25
+ task = 'binary'
26
+ elif len(classes) > 2 and any((not v.is_integer() for v in values)):
27
+ task = 'regression'
28
+ else:
29
+ raise ValueError('Ambiguous numeric target: specify task="regression", "binary" or "multiclass"')
30
+ if task == 'regression':
31
+ if not numeric:
32
+ raise ValueError('Regression requires finite numeric targets')
33
+ return (task, values, [])
34
+ if task not in ('binary', 'multiclass') or not 2 <= len(classes) <= 64 or (task == 'binary' and len(classes) != 2):
35
+ raise ValueError('Classification requires two (binary) or 2..64 (multiclass) classes')
36
+ return (task, [classes.index(v) for v in values], classes)
37
+
38
+ def _split(y, task, seed):
39
+ rng = random.Random(seed)
40
+ held = []
41
+ groups = [list(range(len(y)))] if task == 'regression' else class_groups(y)
42
+ for ids in groups:
43
+ if len(ids) < 2:
44
+ raise ValueError('Each class needs at least two rows for disjoint TRAIN/SEARCH')
45
+ rng.shuffle(ids)
46
+ held.extend(ids[:max(1, len(ids) // 4)])
47
+ train = sorted(set(range(len(y))) - set(held))
48
+ return (train, sorted(held))
49
+
50
+ class _NamedRows:
51
+ """Local duck-typed carrier; avoids optional pandas or implicit column loss."""
52
+
53
+ def __init__(self, rows, names):
54
+ self.rows = rows
55
+ if names is not None:
56
+ self.columns = names
57
+
58
+ def to_numpy(self):
59
+ return self.rows
@@ -0,0 +1,53 @@
1
+ """Existing native estimators/aggregation behind an UNADMITTED fit boundary.
2
+
3
+ No historical worker is relaunched. A future source-bound v2 worker must qualify
4
+ and supply execute_native before any physical training. No admission is issued
5
+ by constructing the estimator, by a deadline or by this module's existence.
6
+ """
7
+ from dataclasses import replace, asdict
8
+ # Resolve native learners at their implementation module. Public task facades
9
+ # may later use the same names without redirecting native work into themselves.
10
+ from ..model import MorphTreeClassifier, MorphTreeMulticlassClassifier, MorphTreeRegressor
11
+ from morphtree.config import AlgorithmGenome, HyperParams, MorphTreeConfig
12
+ from .support import FamilyModel
13
+ from .sampling import bootstrap
14
+
15
+ NATIVE={'binary':MorphTreeClassifier,'multiclass':MorphTreeMulticlassClassifier,'regression':MorphTreeRegressor}
16
+
17
+
18
+ class TrainingNotAdmitted(RuntimeError):
19
+ pass
20
+
21
+
22
+ class NativeExecutor:
23
+ evidence_level='NATIVE_PATH_IMPLEMENTED_NOT_FIT_ADMITTED'
24
+
25
+ def execute_native(self, estimator, X, y, *, identity, cancel):
26
+ # This boundary must be replaced ONLY by a separately qualified finite
27
+ # source/interpreter/protocol/TRAIN/budget-bound v2 execution adapter.
28
+ raise TrainingNotAdmitted('MORPH-026 has zero training budget; separate owner smoke authorization and qualified v2 worker required')
29
+
30
+ def fit_family(self, task, family, X, y, *, seed, params, check, identity, genome=None):
31
+ if task not in NATIVE or family not in ('boost','bag') or not isinstance(params,HyperParams) or params.n_estimators>8:
32
+ raise ValueError('Known native family/task and at most eight stages/members in this finite adapter')
33
+ genome=AlgorithmGenome() if genome is None else genome
34
+ from morphtree.config import validate_task_genome
35
+ validate_task_genome(task,genome)
36
+ check(); members=[]
37
+ rounds=params.n_estimators
38
+ for i in range(1 if family=='boost' else rounds):
39
+ check()
40
+ ids=list(range(len(y))) if family=='boost' else bootstrap(y,task,seed+i+1)
41
+ p=params if family=='boost' else replace(params,n_estimators=1,learning_rate=1.0,row_fraction=1.0)
42
+ m=NATIVE[task](genome=genome,config=MorphTreeConfig(seed=seed+i,enable_tuning=False,enable_morphing=False,refit_full=False),params=p,backend='pure')
43
+ # Actual existing native .fit, never a new tree implementation.
44
+ m=self.execute_native(m,[X[j] for j in ids],[y[j] for j in ids],identity={**identity,'member':i,'ordered_sample':ids,'params':asdict(p)},cancel=check)
45
+ check();members.append(m)
46
+ return FamilyModel(task,members,[1/len(members)]*len(members))
47
+
48
+
49
+ def native_fit_call(estimator, X, y, cancel):
50
+ """Delegate for a future admitted worker, not an authorization API."""
51
+ def cancelled():
52
+ cancel();return False
53
+ return estimator.fit(X,y,cancel=cancelled)