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
morphtree/__init__.py ADDED
@@ -0,0 +1,28 @@
1
+ """MorphTree: own native strategies and a bounded evolving learning program."""
2
+ from .config import AlgorithmGenome,HyperParams,MorphTreeConfig,VERSION as __native_core_version__
3
+ from .model import MorphTreeClassifier as NativeMorphTreeClassifier,MorphTreeRegressor as NativeMorphTreeRegressor,MorphTreeMulticlassClassifier,AutoMorphTree
4
+ from .persistence import LoadLimits
5
+ from ._installed import activate as _activate_installed
6
+ _activate_installed(__file__)
7
+ from .api import MorphTree,TrainingNotEnabledError
8
+ from .estimators import MorphTreeClassifier,MorphTreeRegressor
9
+ __version__='2.1.0'
10
+ __all__=['MorphTreeClassifier','MorphTreeRegressor','MorphTree','load_model','AlgorithmGenome','HyperParams','MorphTreeConfig','LoadLimits','__version__']
11
+ __runtime__={'release_candidate':True,'backend':'automatic-native',
12
+ 'implementation':'Python with verified C kernels',
13
+ 'backend_policy':'C histograms and cached traversal; Python traversal below 128 rows',
14
+ 'native_loading':'Lazy; runtime_info() reports the loaded native library',
15
+ 'qualification':'Documented OS, interpreter, task and scale scope only',
16
+ 'reference_version':'2.1.0 (historical Python implementation)',
17
+ 'publication_approved':False,'scientific_training_authority':0}
18
+ def load_model(path,*,sha256=None):
19
+ """Load the public model with bounded JSON validation; a digest is optional."""
20
+ return MorphTree.load(path,sha256=sha256)
21
+
22
+ # Scalable public task facades; retain the legacy MorphTree/artifact decoder.
23
+ from .scalable import MorphTreeClassifier,MorphTreeRegressor
24
+ __runtime__.update(private_research=False,scope="ORDINARY_USER_WITHOUT_SCIENTIFIC_AUTHORITY")
25
+ def load_model(path,*,sha256=None):
26
+ """Load current and historical models with validated bounded JSON."""
27
+ from .scalable import _Scalable
28
+ return _Scalable.load(path,sha256=sha256)
morphtree/__main__.py ADDED
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ raise SystemExit(main())
@@ -0,0 +1 @@
1
+ """Private native runtime; legacy namespace retained for model compatibility."""
@@ -0,0 +1,364 @@
1
+ """Portable experimental admission, zero learning budget; no checkout imports.
2
+
3
+ The caller pins a qualification dossier out of band. A self-supplied matching
4
+ name/version/hash is not trust. This cooperative guard is not an OS sandbox.
5
+ """
6
+ import copy
7
+ import hashlib
8
+ import importlib
9
+ import json
10
+ import math
11
+ import os
12
+ import platform
13
+ import sys
14
+ import time
15
+ from contextvars import ContextVar
16
+ from contextlib import contextmanager
17
+ from pathlib import Path
18
+
19
+ OPERATIONS = frozenset(('inspect','load','infer','save','export','replay','optuna_import',
20
+ 'fit','trial','OOF','campaign','independent_evaluation','release_prepare'))
21
+ LEARNING = frozenset(('fit','trial','OOF','campaign'))
22
+ STATES = frozenset(('ADMITTED','REJECTED','BLOCKED','INVALID'))
23
+ CURRENT = ContextVar('morph030_exact_operation',default=None)
24
+
25
+
26
+ def digest(value):
27
+ return hashlib.sha256(json.dumps(value,sort_keys=True,separators=(',',':'),ensure_ascii=False,allow_nan=False).encode('utf8')).hexdigest()
28
+
29
+
30
+ def sha(path):
31
+ return hashlib.sha256(Path(path).read_bytes()).hexdigest()
32
+
33
+
34
+ def read(path,limit=32*1024**2,*,expected_sha256=None):
35
+ b=Path(path).read_bytes()
36
+ if len(b)>limit:raise ValueError('Bounded input exceeded')
37
+ if expected_sha256 is not None and hashlib.sha256(b).hexdigest()!=expected_sha256:raise ValueError('Changed bound input bytes')
38
+ def pairs(items):
39
+ d={}
40
+ for k,v in items:
41
+ if k in d:raise ValueError('Duplicate JSON key')
42
+ d[k]=v
43
+ return d
44
+ def bad(_):raise ValueError('Nonfinite JSON')
45
+ v=json.loads(b.decode('utf8'),object_pairs_hook=pairs,parse_constant=bad)
46
+ digest(v)
47
+ return v
48
+
49
+
50
+ def safe_path(root,value):
51
+ root=Path(root).resolve();p=Path(value)
52
+ p=p if p.is_absolute() else root/p
53
+ # Reject links/reparse redirections even when their final target is internal.
54
+ for a in (p,*p.parents):
55
+ if a.exists() and (a.is_symlink() or getattr(a.stat(),'st_file_attributes',0)&0x400):raise ValueError('Link/reparse path')
56
+ p=p.resolve()
57
+ if not p.is_relative_to(root):raise ValueError('Path outside explicit working directory')
58
+ return p
59
+
60
+
61
+ def environment():
62
+ return {'python':sys.version,'implementation':platform.python_implementation(),
63
+ 'platform':sys.platform,'executable_sha256':sha(sys.executable),'backend':'pure'}
64
+
65
+
66
+ def installed_identity(package,qualification):
67
+ package=Path(package).resolve();q=qualification
68
+ if q.get('usage')=='ORDINARY_USER_NOT_SCIENTIFIC':
69
+ from morphtree.api import _user_identity
70
+ return _user_identity(package,q)
71
+ if q.get('schema')!='morph030-qualified-installed-v1':raise ValueError('Unknown qualification schema')
72
+ if q.get('version')!='1.0.0rc4' or q.get('backend')!='pure':raise ValueError('Unqualified version/backend')
73
+ if environment()!=q['environment']:raise ValueError('Unqualified execution environment')
74
+ manifest=package/'_experimental/manifest.json'
75
+ m=read(manifest,expected_sha256=q['manifest_sha256'])
76
+ if m['version']!=q['version'] or digest(m)!=q['build_identity']:raise ValueError('Incoherent build identity')
77
+ actual={p.relative_to(package).as_posix() for p in package.rglob('*') if p.is_file() and '__pycache__' not in p.parts}
78
+ if actual!=set(m['files'])|{'_experimental/manifest.json'}:raise ValueError('Missing/unexpected installed component')
79
+ for name,h in m['files'].items():
80
+ if sha(safe_path(package,name))!=h:raise ValueError('Changed installed component: '+name)
81
+ infos=list(package.parent.glob('morphtree-*.dist-info'))
82
+ if len(infos)!=1:raise ValueError('Unique experimental distribution metadata required')
83
+ meta=infos[0]/'METADATA'
84
+ metadata=meta.read_bytes()
85
+ if hashlib.sha256(metadata).hexdigest()!=q['metadata_sha256']:raise ValueError('Changed metadata')
86
+ text=metadata.decode('utf8')
87
+ if 'Name: morphtree\n' not in text or 'Version: '+q['version']+'\n' not in text:raise ValueError('Metadata name/version mismatch')
88
+ # local pip records the supplied wheel hash, not merely the package name.
89
+ direct=read(infos[0]/'direct_url.json')
90
+ if direct.get('archive_info',{}).get('hashes',{}).get('sha256')!=q['wheel_sha256']:raise ValueError('Installed wheel provenance mismatch')
91
+ import morphtree
92
+ if Path(morphtree.__file__).resolve()!=package/'__init__.py':raise ValueError('Shadowed runtime')
93
+ for name,module in tuple(sys.modules.items()):
94
+ if name=='morphtree' or name.startswith('morphtree.'):
95
+ f=getattr(module,'__file__',None)
96
+ if f and not Path(f).resolve().is_relative_to(package):raise ValueError('Mixed native module origins')
97
+ return {'runtime':'morphtree-experimental-installed','version':q['version'],
98
+ 'artifact_sha256':q['wheel_sha256'],'build_identity':q['build_identity'],
99
+ 'manifest_sha256':q['manifest_sha256'],'backend':'pure','environment':environment()}
100
+
101
+
102
+ def validate_roles(source,expected):
103
+ """Role is bound to actual row content/order and trusted source declaration.
104
+
105
+ Origin/exposure is a provenance assertion pinned by the dossier, never
106
+ inferred from a filename. These checks cannot discover dishonest originals.
107
+ """
108
+ if source!=expected:raise ValueError('Source/role/provenance differs from qualified declaration')
109
+ if set(source)!={'origin','exposure','sealed','rows','partitions','OOF'}:raise ValueError('Exact source schema required')
110
+ if source['sealed'] or source['exposure'] not in ('DEV_EXPOSED','SOFTWARE_FIXTURE'):raise ValueError('Sealed/independent source not admitted')
111
+ rows=source['rows'];ids=[r['id'] for r in rows]
112
+ if not ids or len(set(ids))!=len(ids):raise ValueError('Unique original row IDs required')
113
+ # Aliased IDs cannot turn a row into two data roles.
114
+ contents=[digest({'X':r['X'],'y':r['y']}) for r in rows]
115
+ if len(set(contents))!=len(contents):raise ValueError('Ambiguous duplicate-row provenance requires another protocol')
116
+ parts=source['partitions']
117
+ if set(parts)!={'TRAIN','SEARCH','GUARD_FINAL'} or parts['GUARD_FINAL']:raise ValueError('No final GUARD access in this scope')
118
+ t,s=parts['TRAIN'],parts['SEARCH']
119
+ if not t or not s or len(set(t+s))!=len(t+s) or set(t+s)!=set(ids):raise ValueError('Exact disjoint TRAIN/SEARCH coverage required')
120
+ folds=source['OOF']
121
+ if folds:
122
+ held=[]
123
+ for f in folds:
124
+ if set(f)!={'fit','held','recipe_sha256'} or set(f['fit'])&set(f['held']) or set(f['fit'])|set(f['held'])!=set(t):raise ValueError('OOF must use disjoint TRAIN-only folds')
125
+ if len(set(f['fit']))!=len(f['fit']) or len(set(f['held']))!=len(f['held']) or not f['fit'] or not f['held']:raise ValueError('Nonempty unique OOF IDs')
126
+ held+=f['held']
127
+ if len(set(held))!=len(held) or set(held)!=set(t) or len({f['recipe_sha256'] for f in folds})!=1:raise ValueError('Fixed recipe/exact held coverage required')
128
+ return digest(source)
129
+
130
+
131
+ def capacity(recipe,task):
132
+ if recipe.get('schema')=='comp009-evolution-capacity-v1':
133
+ from morphtree.config import AlgorithmGenome
134
+ if set(recipe)!={'schema','initial','OOF','child_genome'} or task!='binary':
135
+ raise ValueError('Exact evolution capacity schema/task')
136
+ if recipe['OOF']!={'calls':8,'scalar_trees':8,'folds':2,'max_contexts':2} or recipe['child_genome']!=AlgorithmGenome(growth_policy='depthwise').to_dict():
137
+ raise ValueError('Exact cumulative OOF/child capacity required')
138
+ initial=recipe['initial']
139
+ if set(initial)!={'candidates'} or len(initial['candidates'])!=2:
140
+ raise ValueError('Two exact initial native forms')
141
+ forms=initial['candidates']
142
+ expected=[{'family':f,'members':1 if f=='boost' else 2,'stages':2 if f=='boost' else 1,
143
+ 'classes':2,'genome_sha256':digest(AlgorithmGenome().to_dict())} for f in ('boost','bag')]
144
+ if forms!=expected or capacity(initial,task)!=4:
145
+ raise ValueError('Fixed initial native capacity')
146
+ return 14
147
+ if set(recipe)=={'candidates','research_genome'}:
148
+ from morphtree.config import AlgorithmGenome
149
+ if task!='binary' or recipe['research_genome']!=AlgorithmGenome(growth_policy='depthwise').to_dict():
150
+ raise ValueError('Exact operative binary research genome required')
151
+ base=capacity({'candidates':recipe['candidates']},task)
152
+ return base+max(capacity(form,task) for form in recipe['candidates'])
153
+ if set(recipe)=={'candidates'}:
154
+ if type(recipe['candidates']) is not list or not 1<=len(recipe['candidates'])<=2:raise ValueError('Finite one/two native candidate path')
155
+ families=[p['family'] for p in recipe['candidates']]
156
+ if len(set(families))!=len(families):raise ValueError('No duplicate family budget alias')
157
+ return sum(capacity(p,task) for p in recipe['candidates'])
158
+ if set(recipe)!={'family','members','stages','classes','genome_sha256'} or recipe['family'] not in ('boost','bag'):raise ValueError('Bounded frozen native recipe required')
159
+ for n in ('members','stages','classes'):
160
+ if type(recipe[n]) is not int or recipe[n]<1:raise ValueError('Integer actual capacity required')
161
+ if recipe['family']=='boost' and recipe['members']!=1 or recipe['family']=='bag' and recipe['stages']!=1:raise ValueError('Reset bag versus boost capacity mismatch')
162
+ if task=='binary' and recipe['classes']!=2 or task=='regression' and recipe['classes']!=1 or task=='multiclass' and not 2<=recipe['classes']<=64:raise ValueError('Task/class capacity mismatch')
163
+ return recipe['members']*recipe['stages']*(recipe['classes'] if task=='multiclass' else 1)
164
+
165
+
166
+ class AdmissionError(RuntimeError):
167
+ def __init__(self,decision):
168
+ self.decision=decision
169
+ super().__init__(decision['status']+': '+decision['reason'])
170
+
171
+
172
+ class AdmissionEngine:
173
+ """One finite, durable, cooperative zero-fit capability ledger.
174
+
175
+ Admission authorizes exactly the supplied operation/request hash once. An
176
+ unfinished INTENT is never silently replayed; physical execution is closed.
177
+ """
178
+ def __init__(self,*,package,qualification_path,qualification_sha256,workdir):
179
+ self.q=read(qualification_path,expected_sha256=qualification_sha256);self.package=Path(package).resolve();self.workdir=Path(workdir).resolve()
180
+ if not self.workdir.is_dir():raise ValueError('Explicit existing work directory required')
181
+ self.identity=installed_identity(self.package,self.q);self._q_pin=digest(self.q)
182
+ self.ledger=safe_path(self.workdir,'ADMISSION.jsonl');self.stop=safe_path(self.workdir,'STOP');self.lock=safe_path(self.workdir,'WRITER.lock')
183
+ self.started=time.monotonic();self.deadline=self.q['deadline_utc_epoch']
184
+ public=self.q.get('usage')=='ORDINARY_USER_NOT_SCIENTIFIC'
185
+ if not public and (self.q['limits']['fits']!=0 or self.q['limits']['trials']!=0):raise ValueError('No training authority in MORPH030')
186
+ if public and (self.q['limits']['fits']!=128 or type(self.q['limits']['trials']) is not int or not 0<=self.q['limits']['trials']<=4):raise ValueError('Exact user caps required')
187
+ if self.q['scalar_cap']!=(2048 if public else 32) or not 0<self.q['limits']['operations']<=(256 if public else 100) or not 0<self.q['limits']['seconds']<=(300 if public else 600):raise ValueError('Exact finite MORPH030 profile required')
188
+
189
+ @contextmanager
190
+ def _lease(self):
191
+ fd=os.open(self.lock,os.O_CREAT|os.O_EXCL|os.O_WRONLY)
192
+ try:yield
193
+ finally:os.close(fd);self.lock.unlink()
194
+
195
+ def _events(self):
196
+ if not self.ledger.exists():return []
197
+ events=[];prev='0'*64
198
+ for line in self.ledger.read_text('utf8').splitlines():
199
+ v=json.loads(line);h=v.pop('sha256')
200
+ if v['previous']!=prev or digest(v)!=h:raise ValueError('Corrupt/partial admission journal; reconcile, never replay')
201
+ prev=h;events.append(v)
202
+ return events
203
+
204
+ def _event(self,kind,request_id,**values):
205
+ old=self._events();v={'event':kind,'request_id':request_id,'previous':digest(old[-1]) if old else '0'*64,'utc':time.time(),'monotonic':time.monotonic(),**values};v['sha256']=digest(v)
206
+ with self.ledger.open('a',encoding='utf8') as f:f.write(json.dumps(v,sort_keys=True,allow_nan=False)+'\n');f.flush();os.fsync(f.fileno())
207
+
208
+ def request(self,*,operation,task,recipe,source,role,protocol,authorization,components,requested_cost=None):
209
+ r={'runtime':copy.deepcopy(self.identity),'operation':operation,'task':task,'recipe':copy.deepcopy(recipe),
210
+ 'effective_capacity':capacity(recipe,task),'source':copy.deepcopy(source),'role':role,
211
+ 'protocol':protocol,'authorization':authorization,'components':copy.deepcopy(components),
212
+ 'budget':copy.deepcopy(self.q['limits']),'deadline':self.deadline,'requested_cost':requested_cost or {'fits':0,'trials':0}}
213
+ r['id']=digest(r);return r
214
+
215
+ def decide(self,r):
216
+ try:request_hash=digest(r)
217
+ except (ValueError,TypeError,RecursionError):request_hash=None
218
+ if type(r) is not dict or request_hash is None:return {'status':'INVALID','reason':'NONCANONICAL_REQUEST','request_sha256':request_hash,'operation':None,'permitted_operations':[],'physical_fits_authorized':0,'trials_authorized':0}
219
+ def result(status,reason):return {'status':status,'reason':reason,'request_sha256':request_hash,'operation':r.get('operation'),'permitted_operations':[r['operation']] if status=='ADMITTED' else [],'physical_fits_authorized':r.get('requested_cost',{}).get('fits',0) if status=='ADMITTED' and self.q.get('usage')=='ORDINARY_USER_NOT_SCIENTIFIC' else 0,'trials_authorized':r.get('requested_cost',{}).get('trials',0) if status=='ADMITTED' and self.q.get('usage')=='ORDINARY_USER_NOT_SCIENTIFIC' else 0}
220
+ try:
221
+ if digest(self.q)!=self._q_pin:return result('INVALID','MUTATED_QUALIFICATION')
222
+ if installed_identity(self.package,self.q)!=self.identity or r['runtime']!=self.identity:return result('REJECTED','RUNTIME_IDENTITY')
223
+ if set(r)!={'id','runtime','operation','task','recipe','effective_capacity','source','role','protocol','authorization','components','budget','deadline','requested_cost'}|({'component_key'} if self.q.get('usage')=='ORDINARY_USER_NOT_SCIENTIFIC' and r['operation'] in ('fit','trial') else set()):return result('INVALID','REQUEST_SCHEMA')
224
+ if r['id']!=digest({k:v for k,v in r.items() if k!='id'}):return result('INVALID','REQUEST_ID')
225
+ if r['operation'] not in OPERATIONS or r['task'] not in ('binary','multiclass','regression'):return result('INVALID','OPERATION_OR_TASK')
226
+ if self.stop.exists() or any(e['event']=='STOP' for e in self._events()):return result('BLOCKED','STOP_PERMANENT')
227
+ if time.time()>=self.deadline or time.monotonic()-self.started>=self.q['limits']['seconds'] or r['deadline']!=self.deadline:return result('BLOCKED','DEADLINE')
228
+ if r['protocol']!=self.q['protocol'] or r['authorization']!=self.q['authorization']['id']:return result('REJECTED','PROTOCOL_OR_AUTHORIZATION')
229
+ if digest(r['budget'])!=digest(self.q['limits']):return result('REJECTED','BUDGET_IDENTITY')
230
+ c=capacity(r['recipe'],r['task'])
231
+ if c!=r['effective_capacity']:return result('INVALID','MISREPORTED_CAPACITY')
232
+ if c>self.q['scalar_cap']:return result('REJECTED','CAPACITY_ABOVE_32_QUALIFIED_PROFILE')
233
+ key=digest(r['source'])
234
+ if key not in self.q['sources']:return result('REJECTED','UNKNOWN_SOURCE_OR_RELABELLED_ROLE')
235
+ _user_roles(r['source'],self.q['sources'][key]) if self.q.get('usage')=='ORDINARY_USER_NOT_SCIENTIFIC' else validate_roles(r['source'],self.q['sources'][key])
236
+ if r['role'] not in ('TRAIN','SEARCH','OOF','DEV'):return result('REJECTED','GUARD_OR_UNKNOWN_ROLE')
237
+ if r['role']=='OOF' and not r['source']['OOF']:return result('REJECTED','OOF_PROVENANCE_NOT_SUPPLIED')
238
+ if r['operation'] in LEARNING and r['role']!='TRAIN':return result('REJECTED','TRAINING_REQUIRES_TRAIN')
239
+ if r['operation']=='replay' and r['role']!='SEARCH':return result('REJECTED','REPLAY_REQUIRES_SEARCH')
240
+ if r['operation']=='independent_evaluation':return result('REJECTED','DEV_NOT_INDEPENDENT')
241
+ for ref in r['components']:
242
+ ev=self._events();completed={e['request_id'] for e in ev if e['event']=='COMPLETE'}
243
+ produced=[e['artifact'] for e in ev if e['event']=='ARTIFACT' and e['request_id'] in completed]
244
+ if ref not in self.q['components'] and ref not in produced:return result('REJECTED','UNQUALIFIED_COMPONENT')
245
+ contracts=self.q['component_contracts']
246
+ contract=contracts.get(ref['path'])
247
+ if contract is None:
248
+ contract=next((e['contract'] for e in ev if e['event']=='ARTIFACT' and e['artifact']==ref and e['request_id'] in completed),None)
249
+ if contract!={'task':r['task'],'recipe':r['recipe']}:return result('REJECTED','ACTUAL_COMPONENT_TASK_OR_CAPACITY')
250
+ p=safe_path(self.q['input_root'],ref['path'])
251
+ if p.stat().st_size!=ref['bytes'] or sha(p)!=ref['sha256']:return result('REJECTED','COMPONENT_HASH')
252
+ ev=self._events();used={e['request_id'] for e in ev if e['event']=='INTENT'}
253
+ if r['id'] in used:return result('BLOCKED','REQUEST_CONSUMED_OR_INCOMPLETE')
254
+ if len(used)>=self.q['limits']['operations']:return result('BLOCKED','OPERATION_BUDGET_EXHAUSTED')
255
+ if self.q.get('usage')=='ORDINARY_USER_NOT_SCIENTIFIC' and r['operation'] in ('fit','trial'):
256
+ cost=r['requested_cost'];wanted={'fits':int(r['operation']=='fit'),'trials':int(r['operation']=='trial')}
257
+ if cost!=wanted or any(type(v) is not int for v in cost.values()):return result('INVALID','USER_COST')
258
+ if r['operation'] not in self.q['authorization']['operations']:return result('REJECTED','USER_OPERATION')
259
+ key=r['component_key']
260
+ if type(key) is not str or len(key)!=64 or any(c not in '0123456789abcdef' for c in key):return result('INVALID','USER_COMPONENT_ID')
261
+ for unit in ('fits','trials'):
262
+ if sum(e.get(unit,0) for e in ev if e['event']=='INTENT')+cost[unit]>self.q['limits'][unit]:return result('BLOCKED','USER_CUMULATIVE_BUDGET')
263
+ return result('ADMITTED','EXACT_ORDINARY_USER_OPERATION_NOT_SCIENTIFIC')
264
+ if digest(r['requested_cost'])!=digest({'fits':0,'trials':0}):return result('BLOCKED','LEARNING_BUDGET_ZERO')
265
+ if r['operation'] in LEARNING:return result('BLOCKED','NEW_TRAINING_BOUNDARY_UNQUALIFIED_ZERO_BUDGET')
266
+ if r['operation'] not in self.q['authorization']['operations']:return result('REJECTED','OPERATION_NOT_AUTHORIZED')
267
+ return result('ADMITTED','EXACT_ZERO_FIT_OPERATION_ONLY')
268
+ except (KeyError,ValueError,TypeError,OSError) as e:return result('INVALID',type(e).__name__+': '+str(e))
269
+
270
+ @contextmanager
271
+ def operation(self,r):
272
+ with self._lease():
273
+ d=self.decide(r)
274
+ self._event('DECISION',r.get('id','INVALID'),decision=d)
275
+ if d['status']!='ADMITTED':raise AdmissionError(d)
276
+ self._event('INTENT',r['id'],request_sha256=digest(r),operation=r['operation'],fits=0,trials=0)
277
+ token=CURRENT.set((self,copy.deepcopy(r)))
278
+ try:
279
+ yield d
280
+ # Revalidate artifact/deadline/STOP after the callback without
281
+ # treating our own already persisted intent as a replay.
282
+ installed_identity(self.package,self.q)
283
+ if self.stop.exists() or any(e['event']=='STOP' for e in self._events()) or time.time()>=self.deadline or time.monotonic()-self.started>=self.q['limits']['seconds']:raise RuntimeError('STOP/deadline before completion')
284
+ self._event('COMPLETE',r['id'],fits=0,trials=0)
285
+ except BaseException as e:
286
+ self._event('INCOMPLETE',r['id'],error=type(e).__name__,fits=0,trials=0);raise
287
+ finally:CURRENT.reset(token)
288
+
289
+ @contextmanager
290
+ def fold_operation(self,r):
291
+ # The outer operation owns WRITER.lock across yield. Reuse that lease
292
+ # only for this declared TRAIN-fold child, not arbitrary nested access.
293
+ current=CURRENT.get()
294
+ if current is None or current[0] is not self:
295
+ raise ValueError('OOF child requires the active owned parent operation')
296
+ outer=current[1]
297
+ if outer['operation']!='inspect' or outer['role']!='TRAIN' or outer['recipe'].get('schema')!='comp009-evolution-capacity-v1' or r['operation']!='inspect' or r['role']!='OOF':
298
+ raise ValueError('Exact versioned parent/OOF inspect relationship')
299
+ if any(r['source'][k]!=outer['source'][k] for k in ('origin','rows','partitions','sealed','exposure')):
300
+ raise ValueError('OOF source differs from actual parent TRAIN/SEARCH')
301
+ d=self.decide(r);self._event('DECISION',r['id'],decision=d,parent_request=outer['id'])
302
+ if d['status']!='ADMITTED':raise AdmissionError(d)
303
+ self._event('INTENT',r['id'],request_sha256=digest(r),operation='inspect',parent_request=outer['id'],fits=0,trials=0)
304
+ token=CURRENT.set((self,copy.deepcopy(r)))
305
+ try:
306
+ yield d
307
+ installed_identity(self.package,self.q)
308
+ if self.stop.exists() or time.time()>=self.deadline or time.monotonic()-self.started>=self.q['limits']['seconds']:
309
+ raise RuntimeError('STOP/deadline before OOF completion')
310
+ self._event('COMPLETE',r['id'],fits=0,trials=0)
311
+ except BaseException as e:
312
+ self._event('INCOMPLETE',r['id'],error=type(e).__name__,fits=0,trials=0);raise
313
+ finally:CURRENT.reset(token)
314
+
315
+ @contextmanager
316
+ def user_learning(self,r):
317
+ current=CURRENT.get()
318
+ if self.q.get('usage')!='ORDINARY_USER_NOT_SCIENTIFIC' or current is None or current[0] is not self or current[1]['operation']!='inspect' or r['operation'] not in ('fit','trial'):
319
+ raise PermissionError('Exact ordinary-user parent inspect required')
320
+ d=self.decide(r);self._event('DECISION',r['id'],decision=d)
321
+ if d['status']!='ADMITTED':raise AdmissionError(d)
322
+ self._event('INTENT',r['id'],operation=r['operation'],fits=r['requested_cost']['fits'],trials=r['requested_cost']['trials'],domain='ORDINARY_USER_RESERVATION')
323
+ try:
324
+ yield d
325
+ self._event('COMPLETE',r['id'],domain='ORDINARY_USER_RESERVATION')
326
+ except BaseException as exc:
327
+ self._event('INCOMPLETE',r['id'],error=type(exc).__name__,domain='ORDINARY_USER_RESERVATION');raise
328
+
329
+ def record_output(self,path):
330
+ current=CURRENT.get()
331
+ if current is None or current[0] is not self or current[1]['operation'] not in ('save','export'):raise ValueError('Output requires current writer operation')
332
+ p=safe_path(self.q['input_root'],path)
333
+ ref={'path':p.relative_to(Path(self.q['input_root']).resolve()).as_posix(),'sha256':sha(p),'bytes':p.stat().st_size}
334
+ self._event('ARTIFACT',current[1]['id'],artifact=ref,contract={'task':current[1]['task'],'recipe':current[1]['recipe']})
335
+ return ref
336
+
337
+ def stop_permanently(self,reason):
338
+ with self._lease():
339
+ if not self.stop.exists():
340
+ with self.stop.open('x',encoding='utf8') as f:f.write(str(reason));f.flush();os.fsync(f.fileno())
341
+ self._event('STOP','CONTROLLER',reason=str(reason))
342
+
343
+ def optional_optuna(self,r):
344
+ if r['operation']!='optuna_import':raise ValueError('Import-only capability required')
345
+ with self.operation(r):
346
+ lock=self.q['optional_dependency']
347
+ root=Path(lock['root']).resolve()
348
+ for name,h in lock['files'].items():
349
+ if sha(safe_path(root,name))!=h:raise ValueError('Changed optional dependency lock')
350
+ import importlib.util
351
+ spec=importlib.util.find_spec('optuna')
352
+ if spec is None:raise ImportError('Optional Optuna absent')
353
+ if Path(spec.origin).resolve()!=root/'optuna/__init__.py':raise ValueError('Unknown Optuna origin')
354
+ module=importlib.import_module('optuna')
355
+ if module.__version__!=lock['version']:raise ValueError('Incompatible Optuna version')
356
+ return {'status':'ADMITTED_IMPORT_ONLY','version':module.__version__,'files_verified':len(lock['files']),'new_studies':0,'new_trials':0}
357
+
358
+
359
+ def user_qualification(package,root,source,protocol,seconds,user_limits):
360
+ from morphtree.api import _user_qualification
361
+ return _user_qualification(package,root,source,protocol,seconds,user_limits)
362
+ def _user_roles(source,expected):
363
+ from morphtree.api import _user_source
364
+ return _user_source(source,expected)
@@ -0,0 +1,215 @@
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 .support import FamilyModel
14
+ from .preprocessing import TrainEncoder, _m26_rows, _m26_scalar
15
+ PARAMS = ('task', 'families', 'morphing', 'tuning', 'preset', 'metric', 'time_budget', 'n_trials', 'n_jobs', 'random_state', 'verbose')
16
+
17
+ def _payloads(model):
18
+ configure_temp()
19
+ out = []
20
+ with OwnedTemporaryDirectory() as tmp:
21
+ for i, m in enumerate(model.components):
22
+ p = artifact_path(Path(tmp) / ('member-' + str(i) + '.json'))
23
+ m.save_model(p)
24
+ out.append(read_json(p))
25
+ return out
26
+
27
+ def _load_members(task, payloads):
28
+ if task not in NATIVE or type(payloads) is not list or (not 1 <= len(payloads) <= 32):
29
+ raise ValueError('Bounded known native family required')
30
+ if any(type(payload) is not dict for payload in payloads):
31
+ raise ValueError('Each native model payload must be a JSON object')
32
+ models = [NATIVE[task]._from_payload(p, limits=LoadLimits(max_bytes=16 * 1024 ** 2), backend='pure') for p in payloads]
33
+ return FamilyModel(task, models, [1 / len(models)] * len(models))
34
+
35
+ class Base:
36
+
37
+ 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):
38
+ for k, v in locals().copy().items():
39
+ if k != 'self':
40
+ setattr(self, k, v)
41
+ self._validate()
42
+
43
+ def _validate(self):
44
+ if self.task not in ('auto', 'classification', 'binary', 'multiclass', 'regression') or self.morphing not in ('auto', 'none') or self.tuning not in ('auto', 'none', 'optuna') or (self.preset != 'balanced'):
45
+ raise ValueError('Unsupported task/morphing/tuning/preset')
46
+ if self.families not in ('auto', 'boost', 'bag'):
47
+ raise ValueError('families must be auto, boost or bag')
48
+ if self.metric not in ('auto', 'logloss', 'mse'):
49
+ raise ValueError('Only task-consistent logloss/mse primary metrics are supported')
50
+ if self.n_jobs != 'auto' and (type(self.n_jobs) is not int or self.n_jobs != 1):
51
+ raise ValueError('Experimental serial scheduler supports n_jobs=1 or auto only')
52
+ if self.n_trials != 'auto' and (type(self.n_trials) is not int or not 1 <= self.n_trials <= 4):
53
+ raise ValueError('n_trials must be auto or 1..4')
54
+ if self.time_budget != 'auto' and (type(self.time_budget) not in (int, float) or not math.isfinite(self.time_budget) or (not 0 < self.time_budget <= 300)):
55
+ raise ValueError('Finite 0<time_budget<=300 required')
56
+ if self.random_state is not None and (type(self.random_state) is not int or not 0 <= self.random_state < 2 ** 31):
57
+ raise ValueError('random_state must be None or an integer in [0,2**31)')
58
+ if type(self.verbose) is not int or self.verbose not in (0, 1):
59
+ raise ValueError('verbose must be 0 or 1')
60
+
61
+ def get_params(self, deep=True):
62
+ return {k: getattr(self, k) for k in PARAMS}
63
+
64
+ def set_params(self, **values):
65
+ if set(values) - set(PARAMS):
66
+ raise ValueError('Unknown estimator parameter')
67
+ new = type(self)(**self.get_params() | values)
68
+ self.__dict__ = new.__dict__
69
+ return self
70
+
71
+ def _fitted(self):
72
+ if not hasattr(self, 'model_'):
73
+ raise RuntimeError('MorphTree experimental estimator is not fitted')
74
+
75
+ def predict_proba(self, X):
76
+ self._fitted()
77
+ from ..source_markers import controlled_source
78
+ if controlled_source(X):raise ValueError('Controlled sources cannot use ordinary legacy inference')
79
+ if self.task_ == 'regression':
80
+ raise ValueError('predict_proba is unavailable for regression')
81
+ p = self.model_.probabilities_or_values(self.encoder_.transform(X))
82
+ return [[1 - v, v] for v in p] if self.task_ == 'binary' else p
83
+
84
+ def predict(self, X):
85
+ self._fitted()
86
+ from ..source_markers import controlled_source
87
+ if controlled_source(X):raise ValueError('Controlled sources cannot use ordinary legacy inference')
88
+ if self.task_ == 'regression':
89
+ return self.model_.probabilities_or_values(self.encoder_.transform(X))
90
+ return [self.classes_[max(range(len(p)), key=p.__getitem__)] for p in self.predict_proba(X)]
91
+
92
+ def report(self):
93
+ self._fitted()
94
+ return copy.deepcopy(self.report_)
95
+
96
+ def save(self, path):
97
+ self._fitted()
98
+ path = artifact_path(path)
99
+ data = {'schema': {'comp009-frozen-form-v1': 'comp009-native-estimator-v1', 'comp003-frozen-form-v1': 'comp003-native-estimator-v1', 'comp002-frozen-form-v1': 'comp002-native-estimator-v1', 'product002-frozen-form-v1': 'product002-native-estimator-v1', 'product003-frozen-form-v1': 'product003-native-estimator-v1', 'product006-frozen-form-v1': 'product006-native-estimator-v1'}.get(self.best_recipe_['schema'], 'morph026-native-estimator-v1'), 'constructor': self.get_params(), 'recipe': copy.deepcopy(self.best_recipe_), 'classes': list(getattr(self, 'classes_', ())), 'report': self.report(), 'native_payloads': _payloads(self.model_)}
100
+ if len(json.dumps(data, allow_nan=False).encode('utf8')) > 32 * 1024 ** 2:
101
+ raise ValueError('Aggregate private model cap is 32MiB')
102
+ write_json(path, data)
103
+
104
+ def save_recipe(self, path):
105
+ """Declarative frozen form/params, no weights or self-executing training."""
106
+ self._fitted()
107
+ write_json(artifact_path(path), self.best_recipe_)
108
+
109
+ def export_python(self, path):
110
+ """Inference only; reuse native standalone runtime and exact preprocessing.
111
+
112
+ No fitter, search, Optuna, private TRAIN rows or acquisition credentials
113
+ are embedded. This is not a retrainable procedure or a pure/tuned pair.
114
+ """
115
+ import ast
116
+ import morphtree.exporting as exporting
117
+ from . import preprocessing
118
+ self._fitted()
119
+ payloads = _payloads(self.model_)
120
+ class_name = NATIVE[self.task_].__name__
121
+ text = exporting._bundle_runtime(inference_only=True, task=self.task_) + '\n'
122
+ from .. import source_markers
123
+ text += Path(source_markers.__file__).read_text('utf8') + '\n'
124
+ text += Path(preprocessing.__file__).read_text('utf8') + '\n'
125
+ text += '_M26_ENCODER = TrainEncoder.from_dict(' + repr(self.encoder_.to_dict()) + ')\n'
126
+ text += '_M26_TASK = ' + repr(self.task_) + '\n_M26_CLASSES = ' + repr(tuple(getattr(self, 'classes_', ()))) + '\n'
127
+ text += '_M26_MEMBERS = [' + ','.join((class_name + '._from_payload(json.loads(' + repr(json.dumps(p, allow_nan=False)) + '))' for p in payloads)) + ']\n'
128
+ text += "\ndef _m26_values(X):\n if controlled_source(X):raise ValueError('Controlled sources cannot use ordinary legacy standalone inference')\n rows=_M26_ENCODER.transform(X)\n cols=[m.predict(rows) if _M26_TASK=='regression' else m.predict_proba(rows) for m in _M26_MEMBERS]\n if _M26_TASK=='regression':return [math.fsum((1/len(cols))*c[i] for c in cols) for i in range(len(rows))]\n if _M26_TASK=='binary':\n p=[math.fsum((1/len(cols))*c[i][1] for c in cols) for i in range(len(rows))]\n return [[1-v,v] for v in p]\n return [[math.fsum((1/len(cols))*c[i][j] for c in cols) for j in range(len(_M26_CLASSES))] for i in range(len(rows))]\n\ndef predict_proba(X):\n if _M26_TASK=='regression':raise ValueError('Regression has no probabilities')\n return _m26_values(X)\n\ndef predict(X):\n p=_m26_values(X)\n return p if _M26_TASK=='regression' else [_M26_CLASSES[max(range(len(row)),key=row.__getitem__)] for row in p]\n"
129
+ ast.parse(text)
130
+ if len(text.encode('utf8')) > 32 * 1024 ** 2:
131
+ raise ValueError('Standalone cap is 32MiB')
132
+ from .runtime_io import publish
133
+ publish(path, text, lambda: None)
134
+
135
+ @classmethod
136
+ def load(cls, path):
137
+ d = read_json(artifact_path(path), LoadLimits(max_bytes=32 * 1024 ** 2))
138
+ if set(d) != {'schema', 'constructor', 'recipe', 'classes', 'report', 'native_payloads'} or d['schema'] not in ('morph026-native-estimator-v1', 'comp002-native-estimator-v1', 'comp003-native-estimator-v1', 'comp009-native-estimator-v1', 'product002-native-estimator-v1', 'product003-native-estimator-v1', 'product006-native-estimator-v1'):
139
+ raise ValueError('Exact research estimator schema required')
140
+ r = d['recipe']
141
+ expected = {'schema', 'task', 'family', 'structure', 'params', 'seed', 'training_identity', 'preprocessing', 'mutations'}
142
+ if type(r) is not dict or set(r) != expected or r['schema'] != {'morph026-native-estimator-v1': 'morph026-frozen-form-v1', 'comp002-native-estimator-v1': 'comp002-frozen-form-v1', 'comp009-native-estimator-v1': 'comp009-frozen-form-v1', 'comp003-native-estimator-v1': 'comp003-frozen-form-v1', 'product002-native-estimator-v1': 'product002-frozen-form-v1', 'product003-native-estimator-v1': 'product003-frozen-form-v1', 'product006-native-estimator-v1': 'product006-frozen-form-v1'}[d['schema']] or (r['task'] not in NATIVE) or (r['family'] not in ('boost', 'bag')) or (r['mutations'] != []):
143
+ raise ValueError('Unsupported private frozen form')
144
+ if type(r['structure']) is not dict or set(r['structure']) != ({'genome', 'classes', 'members', 'encoder', 'lineage'} if d['schema'] in ('comp003-native-estimator-v1', 'comp009-native-estimator-v1', 'product002-native-estimator-v1', 'product003-native-estimator-v1', 'product006-native-estimator-v1') else {'genome', 'classes', 'members', 'encoder'}) or type(d['report']) is not dict or (not isinstance(d['report'].get('stop_reason'), str)):
145
+ raise ValueError('Exact private structure/report required')
146
+ obj = cls(**d['constructor'])
147
+ encoder = TrainEncoder.from_dict(r['preprocessing'])
148
+ model = _load_members(r['task'], d['native_payloads'])
149
+ if any((m.n_features_in_ != encoder.width_ for m in model.components)) or (r['family'] == 'boost' and len(model.components) != 1) or r['structure']['members'] != len(model.components):
150
+ raise ValueError('Model/encoder/member shape mismatch')
151
+ HyperParams(**r['params'])
152
+ classes = d['classes']
153
+ if type(classes) is not list or any((type(c) not in (str, int, float) or (isinstance(c, str) and len(c) > 1024) for c in classes)) or (r['task'] == 'regression' and classes) or (r['task'] != 'regression' and (not 2 <= len(classes) <= 64 or not (all((isinstance(c, str) for c in classes)) or all((type(c) in (int, float) for c in classes))) or classes != sorted(set(classes)) or (r['task'] == 'binary' and len(classes) != 2))):
154
+ raise ValueError('Bounded target vocabulary required')
155
+ if r['task'] == 'multiclass' and any((list(m.classes_) != list(range(len(classes))) for m in model.components)):
156
+ raise ValueError('Native multiclass column alignment differs')
157
+ if r['structure'].get('encoder') != r['preprocessing'] or r['structure'].get('classes') != list(range(len(classes))) or (not _qualified_genome(d, model)):
158
+ raise ValueError('Changed frozen Base form/encoder/columns')
159
+ obj.model_ = model
160
+ obj.encoder_ = encoder
161
+ obj.task_ = r['task']
162
+ obj.n_features_in_ = len(encoder.state_['columns'])
163
+ obj.best_recipe_ = copy.deepcopy(r)
164
+ obj.best_family_ = r['family']
165
+ obj.best_params_ = copy.deepcopy(r['params'])
166
+ obj.report_ = copy.deepcopy(d['report'])
167
+ obj.stop_reason_ = d['report']['stop_reason']
168
+ if classes:
169
+ obj.classes_ = tuple(classes)
170
+ if encoder.state_['names'] is not None:
171
+ obj.feature_names_in_ = tuple(encoder.state_['names'])
172
+ return obj
173
+ Base._execution_check = lambda self: None
174
+ Base._record_event = lambda self, event: None
175
+
176
+ def _qualified_genome(data, model):
177
+ if data['schema'] in ('product002-native-estimator-v1', 'product003-native-estimator-v1', 'product006-native-estimator-v1'):
178
+ from morphtree.config import validate_task_genome
179
+ r = data['recipe']
180
+ g = validate_task_genome(r['task'], AlgorithmGenome(**r['structure']['genome']))
181
+ base = AlgorithmGenome()
182
+ depth = AlgorithmGenome(growth_policy='depthwise')
183
+ if g not in (base, depth):
184
+ return False
185
+ lineage = r['structure']['lineage']
186
+ if g == base:
187
+ if lineage != {}:
188
+ return False
189
+ else:
190
+ keys = {'gene', 'parent_packet_sha256', 'from', 'to', 'reason'}
191
+ if type(lineage) is not dict or set(lineage) != keys or lineage['gene'] != 'growth_policy' or (lineage['from'] != base.to_dict()) or (lineage['to'] != g.to_dict()) or (lineage['reason'] != 'OOF_HEURISTIC_HYPOTHESIS_NOT_CAUSAL_UTILITY') or (type(lineage['parent_packet_sha256']) is not str) or (len(lineage['parent_packet_sha256']) != 64) or any((c not in '0123456789abcdef' for c in lineage['parent_packet_sha256'])):
192
+ return False
193
+ return all((m.genome_.to_dict() == g.to_dict() for m in model.components))
194
+ recipe = data['recipe']
195
+ g = recipe['structure'].get('genome')
196
+ allowed = [AlgorithmGenome().to_dict()]
197
+ if data['schema'] in ('comp002-native-estimator-v1', 'comp003-native-estimator-v1', 'comp009-native-estimator-v1', 'product002-native-estimator-v1', 'product003-native-estimator-v1', 'product006-native-estimator-v1') and recipe['task'] == 'binary':
198
+ allowed.append(AlgorithmGenome(growth_policy='depthwise').to_dict())
199
+ if g not in allowed:
200
+ return False
201
+ if data['schema'] == 'comp009-native-estimator-v1':
202
+ lineage = recipe['structure']['lineage']
203
+ if g == allowed[0]:
204
+ if lineage != {}:
205
+ return False
206
+ elif type(lineage) is not dict or set(lineage) != {'gene', 'parent_packet_sha256', 'from', 'to', 'reason'} or lineage['gene'] != 'growth_policy' or (lineage['from'] != allowed[0]) or (lineage['to'] != g) or (lineage['reason'] != 'OOF_HEURISTIC_HYPOTHESIS_NOT_CAUSAL_UTILITY') or (type(lineage['parent_packet_sha256']) is not str) or (len(lineage['parent_packet_sha256']) != 64) or any((c not in '0123456789abcdef' for c in lineage['parent_packet_sha256'])):
207
+ return False
208
+ if data['schema'] == 'comp003-native-estimator-v1':
209
+ lineage = recipe['structure']['lineage']
210
+ if g == allowed[0]:
211
+ if lineage != {}:
212
+ return False
213
+ elif type(lineage) is not dict or set(lineage) != {'gene', 'parent_packet_sha256', 'from', 'to', 'reason'} or lineage['gene'] != 'growth_policy' or (lineage['from'] != allowed[0]) or (lineage['to'] != g) or (lineage['reason'] != 'STATIC_TECHNICAL_NOMINATION_NOT_DIAGNOSTIC_UTILITY') or (type(lineage['parent_packet_sha256']) is not str) or (len(lineage['parent_packet_sha256']) != 64) or any((c not in '0123456789abcdef' for c in lineage['parent_packet_sha256'])):
214
+ return False
215
+ return all((m.genome_.to_dict() == g for m in model.components)) if recipe['task'] == 'binary' else g == allowed[0]