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,289 @@
1
+ import copy, math, json, tempfile, os
2
+ from ..temporary import OwnedTemporaryDirectory
3
+ from pathlib import Path
4
+ from dataclasses import asdict, replace
5
+ from morphtree.config import AlgorithmGenome, HyperParams
6
+ from morphtree.persistence import read_json, write_json, LoadLimits
7
+ from morphtree.model import MorphTreeClassifier, MorphTreeRegressor, MorphTreeMulticlassClassifier
8
+ from morphtree.metrics import point_losses
9
+ from .admission import digest, sha, AdmissionError
10
+ from .runtime_io import artifact_path, configure_temp, reference, publish
11
+ NATIVE = {'binary': MorphTreeClassifier, 'multiclass': MorphTreeMulticlassClassifier, 'regression': MorphTreeRegressor}
12
+ TASKS = ('binary', 'multiclass', 'regression')
13
+ from .base import Base as Previous, PARAMS
14
+ from .preprocessing import TrainEncoder
15
+ from .runtime_io import require, explicit_input, configure
16
+
17
+ class VariantUnavailable(ValueError):
18
+ pass
19
+
20
+ class Control(Previous):
21
+
22
+ def __init__(self, *, task='auto', families='auto', morphing='auto', tuning='auto', preset='balanced', metric='auto', time_budget='auto', n_trials='auto', n_jobs='auto', random_state=None, verbose=0, capacity=None):
23
+ self.capacity = copy.deepcopy(capacity)
24
+ super().__init__(task=task, families=families, morphing=morphing, tuning=tuning, preset=preset, metric=metric, time_budget=time_budget, n_trials=n_trials, n_jobs=n_jobs, random_state=random_state, verbose=verbose)
25
+
26
+ def _validate(self):
27
+ super()._validate()
28
+ if self.capacity is not None:
29
+ if type(self.capacity) is not dict or not set(self.capacity) <= {'max_leaves', 'max_depth', 'n_estimators'}:
30
+ raise ValueError('capacity supports finite max_leaves/max_depth/n_estimators; no inactive options')
31
+ for name, value in self.capacity.items():
32
+ if type(value) is not int or not 1 <= value <= {'max_leaves': 16, 'max_depth': 8, 'n_estimators': 8}[name]:
33
+ raise ValueError('Finite integer structural capacity required')
34
+
35
+ def get_params(self, deep=True):
36
+ return copy.deepcopy({k: getattr(self, k) for k in (*PARAMS, 'capacity')})
37
+
38
+ def set_params(self, **values):
39
+ if set(values) - set(self.get_params()):
40
+ raise ValueError('Unknown estimator parameter')
41
+ new = type(self)(**self.get_params() | values)
42
+ self.__dict__ = new.__dict__
43
+ return self
44
+
45
+ def report(self):
46
+ value = super().report()
47
+ value['variants'] = self.available_variants()
48
+ return value
49
+
50
+ def available_variants(self):
51
+ self._fitted()
52
+ return {k: {'origin': v['origin'], 'params': copy.deepcopy(v['recipe']['params']), 'exportable': v['exportable'], 'evidence': v['evidence'], 'SEARCH_primary': v.get('SEARCH_primary')} for k, v in self._variants.items()}
53
+
54
+ def _variant(self, name):
55
+ self._fitted()
56
+ name = self._selected_variant if name == 'selected' else name
57
+ if name not in self._variants:
58
+ raise VariantUnavailable('Requested variant has no retained, verified weights; a separately budgeted fit may be needed')
59
+ v = self._variants[name]
60
+ if not v['exportable']:
61
+ raise VariantUnavailable('Software replay cannot be exported as a newly trained pure/tuned variant')
62
+ obj = Previous(**{k: v for k, v in self.get_params().items() if k != 'capacity'})
63
+ for k in ('encoder_', 'task_', 'n_features_in_', 'classes_', 'feature_names_in_'):
64
+ if hasattr(self, k):
65
+ setattr(obj, k, copy.deepcopy(getattr(self, k)))
66
+ obj.model_ = v['model']
67
+ obj.best_recipe_ = copy.deepcopy(v['recipe'])
68
+ obj.best_family_ = v['recipe']['family']
69
+ obj.best_params_ = copy.deepcopy(v['recipe']['params'])
70
+ obj.report_ = self.report()
71
+ obj.report_.update(exported_variant=name, SEARCH_primary=v.get('SEARCH_primary'), best_family=obj.best_family_)
72
+ obj.stop_reason_ = self.stop_reason_
73
+ return obj
74
+
75
+ def export_python(self, path, *, variant='selected'):
76
+ self._variant(variant).export_python(path)
77
+
78
+ def save_recipe(self, path, *, variant='selected'):
79
+ self._variant(variant).save_recipe(path)
80
+
81
+ def save(self, path, *, variant='all'):
82
+ self._fitted()
83
+ configure_temp()
84
+ names = list(self._variants) if variant == 'all' else [self._selected_variant if variant == 'selected' else variant]
85
+ packed = {}
86
+ origins = {}
87
+ with OwnedTemporaryDirectory() as tmp:
88
+ for name in names:
89
+ obj = self._variant(name)
90
+ p = artifact_path(Path(tmp) / (name + '.json'))
91
+ obj.save(p)
92
+ packed[name] = read_json(p, LoadLimits(max_bytes=32 * 1024 ** 2))
93
+ origins[name] = {'origin': self._variants[name]['origin'], 'evidence': self._variants[name]['evidence']}
94
+ body = {'schema': {'comp009-frozen-form-v1': 'comp009-native-control-v1', 'comp003-frozen-form-v1': 'comp003-native-control-v1', 'comp002-frozen-form-v1': 'comp002-native-control-v1', 'product002-frozen-form-v1': 'product002-native-control-v1', 'product003-frozen-form-v1': 'product003-native-control-v1', 'product006-frozen-form-v1': 'product006-native-control-v1'}.get(next(iter(packed.values()))['recipe']['schema'], 'morph027-native-control-v1'), 'constructor': self.get_params(), 'selected_variant': self._selected_variant if self._selected_variant in names else names[0], 'variants': packed, 'origins': origins}
95
+ if len(json.dumps(body, allow_nan=False).encode('utf8')) > 32 * 1024 ** 2:
96
+ raise ValueError('Bounded total variant payload required')
97
+ path = artifact_path(path)
98
+ if path.exists():
99
+ raise FileExistsError('Never overwrite research model evidence')
100
+ write_json(path, body)
101
+
102
+ @classmethod
103
+ def load(cls, path):
104
+ data = read_json(artifact_path(path), LoadLimits(max_bytes=32 * 1024 ** 2))
105
+ if set(data) != {'schema', 'constructor', 'selected_variant', 'variants', 'origins'} or data['schema'] not in ('morph027-native-control-v1', 'comp002-native-control-v1', 'comp003-native-control-v1', 'comp009-native-control-v1', 'product002-native-control-v1', 'product003-native-control-v1', 'product006-native-control-v1'):
106
+ raise ValueError('Exact bounded private control schema required')
107
+ vs = data['variants']
108
+ origins = data['origins']
109
+ if type(vs) is not dict or not vs or (not set(vs) <= {'pure', 'tuned'}) or (set(origins) != set(vs)) or (data['selected_variant'] not in vs):
110
+ raise ValueError('Bounded named variants required')
111
+ obj = cls(**data['constructor'])
112
+ configure_temp()
113
+ loaded = {}
114
+ with OwnedTemporaryDirectory() as tmp:
115
+ for name, body in vs.items():
116
+ if type(body) is not dict:
117
+ raise ValueError('Each model variant must be a JSON object')
118
+ o = origins[name]
119
+ if {'morph027-native-control-v1': 'morph026-native-estimator-v1', 'comp002-native-control-v1': 'comp002-native-estimator-v1', 'comp009-native-control-v1': 'comp009-native-estimator-v1', 'comp003-native-control-v1': 'comp003-native-estimator-v1', 'product002-native-control-v1': 'product002-native-estimator-v1', 'product003-native-control-v1': 'product003-native-estimator-v1', 'product006-native-control-v1': 'product006-native-estimator-v1'}[data['schema']] != body.get('schema'):
120
+ raise ValueError('Mixed experimental envelope versions')
121
+ if data['schema'] == 'morph027-native-control-v1' and 'COMP002_' in str(o):
122
+ raise ValueError('Never relabel fixture as historical training')
123
+ if set(o) != {'origin', 'evidence'} or o['origin'] not in ('PRE_FINAL_NUMERIC_SEARCH', 'VERIFIED_B0_UNTUNED', 'POST_FINAL_NUMERIC_SEARCH') or (not isinstance(o['evidence'], str)) or ('MOCK' in o['evidence']):
124
+ raise ValueError('Explicit genuine variant provenance required')
125
+ if name == 'pure' and o['origin'] == 'POST_FINAL_NUMERIC_SEARCH' or (name == 'tuned' and o['origin'] != 'POST_FINAL_NUMERIC_SEARCH'):
126
+ raise ValueError('Never relabel tuned weights as pure')
127
+ p = artifact_path(Path(tmp) / (name + '.json'))
128
+ write_json(p, body)
129
+ v = Previous.load(p)
130
+ if v.task_ != body['recipe']['task']:
131
+ raise ValueError('Changed task')
132
+ expected = HyperParams(**v.best_params_)
133
+ for m in v.model_.components:
134
+ actual = asdict(m.params_)
135
+ want = asdict(expected if v.best_family_ == 'boost' else replace(expected, n_estimators=1, learning_rate=1.0, row_fraction=1.0))
136
+ if actual != want:
137
+ raise ValueError('Recipe parameters do not describe the actual weights')
138
+ if name == 'tuned' and (not v.report_.get('trials') or not _numeric_evidence(data['schema'], o, v.report_)):
139
+ raise ValueError('No executed numeric trajectory for tuned variant')
140
+ loaded[name] = v
141
+ v = loaded[data['selected_variant']]
142
+ for other in loaded.values():
143
+ if (other.task_, other.classes_ if hasattr(other, 'classes_') else (), other.best_recipe_['structure'], other.best_recipe_['training_identity']) != (v.task_, v.classes_ if hasattr(v, 'classes_') else (), v.best_recipe_['structure'], v.best_recipe_['training_identity']):
144
+ raise ValueError('Variants must share the same frozen structure/data/map')
145
+ for k in ('model_', 'encoder_', 'task_', 'n_features_in_', 'classes_', 'feature_names_in_', 'best_family_', 'best_recipe_', 'best_params_', 'stop_reason_', 'report_'):
146
+ if hasattr(v, k):
147
+ setattr(obj, k, getattr(v, k))
148
+ obj.structure_ = copy.deepcopy(v.best_recipe_['structure'])
149
+ obj._selected_variant = data['selected_variant']
150
+ obj._variants = {k: {'model': v.model_, 'recipe': copy.deepcopy(v.best_recipe_), **origins[k], 'exportable': True, 'SEARCH_primary': v.report_.get('SEARCH_primary')} for k, v in loaded.items()}
151
+ return obj
152
+
153
+ def export_bundle(self, directory, *, variants=('pure',)):
154
+ if type(variants) is not tuple or not variants or len(set(variants)) != len(variants):
155
+ raise ValueError('Distinct variant tuple required')
156
+ for name in variants:
157
+ self._variant(name)
158
+ directory = artifact_path(directory)
159
+ if directory.exists():
160
+ raise FileExistsError('New export directory required')
161
+ directory.mkdir(parents=True)
162
+ out = []
163
+ for name in variants:
164
+ p = directory / (name + '.json')
165
+ self.save(p, variant=name)
166
+ out.append(reference(p))
167
+ p = directory / (name + '-recipe.json')
168
+ self.save_recipe(p, variant=name)
169
+ out.append(reference(p))
170
+ p = directory / (name + '-predictor.py')
171
+ self.export_python(p, variant=name)
172
+ out.append(reference(p))
173
+ p = directory / 'EXPORT.json'
174
+ write_json(p, {'schema': 'morph027-inference-export-v1', 'status': 'COMPLETE', 'artifacts': out, 'retraining_procedure': 'NOT_SUPPORTED', 'new_fits': 0, 'report': self.report()})
175
+ return reference(p)
176
+
177
+ def export_retrainable(self, path):
178
+ raise NotImplementedError('Only inference standalone and declarative recipe are qualified; generic retraining admission is missing')
179
+
180
+ class MorphTree(Control):
181
+ """Installed retained-model facade. No fresh training implementation."""
182
+
183
+ @classmethod
184
+ def load(cls, path):
185
+ source = explicit_input(path)
186
+ data = read_json(source, LoadLimits(max_bytes=32 * 1024 ** 2))
187
+ if data.get('schema') == 'morph027-native-control-v1':
188
+ obj = super().load(source)
189
+ else:
190
+ v = Previous.load(source)
191
+ expected = HyperParams(**v.best_params_)
192
+ for member in v.model_.components:
193
+ want = expected if v.best_family_ == 'boost' else replace(expected, n_estimators=1, learning_rate=1.0, row_fraction=1.0)
194
+ if asdict(member.params_) != asdict(want):
195
+ raise ValueError('Recipe versus actual retained native parameters')
196
+ obj = cls(**v.get_params())
197
+ obj.__dict__.update(v.__dict__)
198
+ obj.capacity = None
199
+ obj._selected_variant = 'pure'
200
+ obj._variants = {'pure': {'model': v.model_, 'recipe': copy.deepcopy(v.best_recipe_), 'origin': 'VERIFIED_B0_UNTUNED', 'evidence': 'EXPLICIT_SUPPLIED_RETAINED_INPUT_NOT_NEW_FIT', 'exportable': True, 'SEARCH_primary': v.report_.get('SEARCH_primary')}}
201
+ engine, r = require(('load', 'replay'))
202
+ obj._admitted_component = next((v for v in r['components'] if artifact_path(engine.q['input_root']) / v['path'] == artifact_path(path)))
203
+ obj._check_admission(('load', 'replay'))
204
+ return obj
205
+
206
+ def _check_admission(self, operations):
207
+ e, r = require(operations)
208
+ actual = {'family': self.best_family_, 'members': len(self.model_.components), 'stages': self.best_params_['n_estimators'] if self.best_family_ == 'boost' else 1, 'classes': len(getattr(self, 'classes_', ())) or 1, 'genome_sha256': digest(self.best_recipe_['structure']['genome'])}
209
+ if r['task'] != self.task_ or r['recipe'] != actual or self._admitted_component not in r['components']:
210
+ raise ValueError('Model/request actual task, form or component mismatch')
211
+
212
+ def fit(self, X, y):
213
+ self.last_attempt_ = {'status': 'BLOCKED', 'new_fits': 0, 'new_trials': 0, 'reason': 'NEW_INSTALLED_TRAINING_REQUIRES_QUALIFIED_BOUNDARY_AND_NEW_OWNER_CONSENT'}
214
+ raise AdmissionError(self.last_attempt_)
215
+
216
+ def predict(self, X):
217
+ self._check_admission(('infer', 'replay'))
218
+ return super().predict(X)
219
+
220
+ def predict_proba(self, X):
221
+ self._check_admission(('infer', 'replay'))
222
+ return super().predict_proba(X)
223
+
224
+ def save(self, path, *, variant='all'):
225
+ self._check_admission(('save',))
226
+ e, r = require(('save',))
227
+ super().save(path, variant=variant)
228
+ return e.record_output(path)
229
+
230
+ def save_recipe(self, path, *, variant='selected'):
231
+ self._check_admission(('export',))
232
+ e, r = require(('export',))
233
+ super().save_recipe(path, variant=variant)
234
+ return e.record_output(path)
235
+
236
+ def export_python(self, path, *, variant='selected'):
237
+ self._check_admission(('export',))
238
+ e, r = require(('export',))
239
+ super().export_python(path, variant=variant)
240
+ return e.record_output(path)
241
+
242
+ def export_bundle(self, *a, **k):
243
+ raise NotImplementedError('Use separately admitted save/export operations; no broad bundle capability')
244
+
245
+ def recipe(self):
246
+ self._fitted()
247
+ return copy.deepcopy(self.best_recipe_)
248
+
249
+ def _numeric_evidence(schema, origin, report):
250
+ if schema=='product006-native-control-v1':
251
+ rows=[e for e in report.get('events',[]) if e.get('operation')=='COMP003_ACCOUNTING_AND_SCOPE']
252
+ if len(rows)!=1:return False
253
+ row=rows[0];counts=row['counts'];n=len(report.get('trials',[]))
254
+ if not (origin['evidence']=='USER_API_NATIVE_EXECUTION_NOT_SCIENTIFIC_CONFIRMATION'
255
+ and row['details']['trial_evidence']=='USER_DATA_REAL_OPTUNA_NOT_SCIENTIFIC_CONFIRMATION'
256
+ and 1<=n<=4 and n<=counts['Optuna_trials']<=4):return False
257
+ if counts['Optuna_trials']==n and counts['physical_fits_started']==counts['physical_fits_completed']:return True
258
+ p=report.get('morphing',{}).get('verified_report',{})
259
+ recovery=p.get('budget_recovery',{})
260
+ if (recovery.get('schema')!='product006-verified-budget-recovery-v1'
261
+ or recovery.get('phase_completion')!='PARTIAL_BUDGET_VALID_ANCHOR_RETURNED'
262
+ or p.get('counts',{}).get('real_trials_completed')!=n
263
+ or p['counts'].get('real_trials_created')!=counts['Optuna_trials']):return False
264
+ abandoned=recovery.get('unadopted_components',{})
265
+ incomplete=counts['physical_fits_started']-counts['physical_fits_completed']
266
+ return (incomplete>=0 and incomplete==sum(not x['native_completed'] for x in abandoned.values())
267
+ and all(x.get('used_in_final_model') is False for x in abandoned.values())
268
+ and any(e.get('kind')=='SEARCH_STOPPED_BUDGET' for e in p.get('events',[])))
269
+
270
+ if schema == 'product003-native-control-v1':
271
+ rows = [e for e in report.get('events', []) if e.get('operation') == 'COMP003_ACCOUNTING_AND_SCOPE']
272
+ if len(rows) != 1:
273
+ return False
274
+ row = rows[0]
275
+ counts = row['counts']
276
+ n = len(report.get('trials', []))
277
+ return origin['evidence'] == 'USER_API_NATIVE_EXECUTION_NOT_SCIENTIFIC_CONFIRMATION' and row['details']['trial_evidence'] == 'USER_DATA_REAL_OPTUNA_NOT_SCIENTIFIC_CONFIRMATION' and (1 <= n <= 4) and (counts['Optuna_trials'] == n) and (counts['physical_fits_started'] == counts['physical_fits_completed'])
278
+ if schema != 'comp003-native-control-v1':
279
+ return report.get('trial_evidence') in ('EXECUTOR_BOUND_NUMERIC_CALLBACKS', 'AUTHENTICATED_HISTORICAL_REAL_TPE4')
280
+ rows = [e for e in report.get('events', []) if e.get('operation') == 'COMP003_ACCOUNTING_AND_SCOPE']
281
+ if len(rows) != 1:
282
+ return False
283
+ e = rows[0]
284
+ kind = e['details']['trial_evidence']
285
+ counts = e['counts']
286
+ n = len(report['trials'])
287
+ if 'SOFTWARE_FIXTURE' in origin['evidence']:
288
+ return kind == 'SIMULATED_OPTUNA_FIXTURE_NOT_REAL_TRIALS' and counts['trials'] == 0 and (counts['simulated_trials'] == n) and (n == 2)
289
+ return origin['evidence'] == 'COMP003_EXACT_OWNER_ADMITTED_NATIVE_TRAINING' and kind == 'EXACT_OWNER_BOUND_REAL_OPTUNA_TRIALS' and (counts['trials'] == n) and (n == 2) and (counts['simulated_trials'] == 0)