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,17 @@
1
+ import hashlib,json
2
+ from pathlib import Path
3
+ def activate(file):
4
+ package=Path(file).resolve().parent
5
+ m=json.loads((package/'_experimental/manifest.json').read_text('utf8'))
6
+ actual={p.relative_to(package).as_posix() for p in package.rglob('*') if p.is_file() and '__pycache__' not in p.parts}
7
+ if actual!=set(m['files'])|{'_experimental/manifest.json'}:raise ImportError('Missing or unexpected installed file')
8
+ for n,h in m['files'].items():
9
+ if hashlib.sha256((package/n).read_bytes()).hexdigest()!=h:raise ImportError('Modified installed component: '+n)
10
+ from .model import MorphTreeClassifier,MorphTreeRegressor,MorphTreeMulticlassClassifier,AutoMorphTree
11
+ from ._experimental.admission import AdmissionError
12
+ def denied(*a,**k):raise AdmissionError({'status':'BLOCKED','reason':'INSTALLED_NATIVE_TRAINING_UNQUALIFIED_ZERO_BUDGET'})
13
+ from ._experimental.execution_v2 import native_fit_entry
14
+ for cls in (MorphTreeClassifier,MorphTreeRegressor,MorphTreeMulticlassClassifier):cls.fit=native_fit_entry(cls.fit,cls)
15
+ AutoMorphTree.fit=denied
16
+ from ._experimental.facade import MorphTree
17
+ return MorphTree,{'scope':'RESEARCH_ONLY_ZERO_FIT','portable_inference':True,'physical_training':False}
@@ -0,0 +1 @@
1
+ """MorphTree-owned native execution support;no learning rules are selected here."""
@@ -0,0 +1,82 @@
1
+ """Owned compact buffers and a bounded content-keyed tree-layout cache."""
2
+ from array import array
3
+ from collections import OrderedDict
4
+ import ctypes,struct,sys,threading
5
+ from ..scalable_backend import Column
6
+
7
+ class UnsupportedBuffers(TypeError):pass
8
+
9
+ TREE_FIELDS=('leaf','values','feature','cut','missing','left','right')
10
+ def address(value):
11
+ # Never dispatch to an instance/subclass's overridable buffer_info method.
12
+ return ctypes.c_void_p(array.buffer_info(value)[0])
13
+
14
+ def columns_view(columns):
15
+ if type(columns) not in (list,tuple) or not columns:raise ValueError('Nonempty column sequence required')
16
+ if any(type(c) not in (array,Column) for c in columns):raise UnsupportedBuffers('Exact owned array/Column required')
17
+ rows=array.__len__(columns[0])
18
+ if any(c.typecode not in ('B','H','I') or c.itemsize not in (1,2,4) or array.__len__(c)!=rows for c in columns):raise UnsupportedBuffers('Aligned owned B/H/I columns required')
19
+ widths=array('I',(c.itemsize for c in columns));nominal=array('B',(bool(getattr(c,'nominal',False)) for c in columns))
20
+ pointers=(ctypes.c_void_p*len(columns))(*(array.buffer_info(c)[0] for c in columns))
21
+ return dict(rows=rows,widths=widths,nominal=nominal,pointers=pointers,owners=(*columns,widths,nominal))
22
+
23
+ def tree_key(nodes):
24
+ if type(nodes) not in (list,tuple):raise UnsupportedBuffers('Exact owned node list/tuple required')
25
+ if not nodes:raise ValueError('Nonempty declarative tree required')
26
+ # Include type and all numeric bits. True/1,-0/+0 and NaN payloads must not
27
+ # accidentally alias a previously checked layout. A changed tuple/list or
28
+ # even an intentional frozen-dataclass bypass produces a different key.
29
+ key=[]
30
+ for n in nodes:
31
+ if type(n.is_leaf) is not bool or type(n.missing_left) is not bool:raise ValueError('Typed tree flags required')
32
+ ints=(n.feature,n.threshold_bin,n.left,n.right)
33
+ if any(type(v) is not int or not -(2**63)<=v<2**63 for v in ints):raise ValueError('Int64 tree fields required')
34
+ key.append((n.is_leaf,struct.pack('=d',n.value),*ints,n.missing_left))
35
+ return tuple(key)
36
+
37
+ def pack_tree(key):
38
+ leaf=array('B');values=array('d');feature=array('q');cut=array('q');missing=array('B');left=array('q');right=array('q')
39
+ for is_leaf,value,f,c,l,r,m in key:
40
+ leaf.append(is_leaf);values.append(struct.unpack('=d',value)[0]);feature.append(f);cut.append(c);left.append(l);right.append(r);missing.append(m)
41
+ return dict(zip(TREE_FIELDS,(leaf,values,feature,cut,missing,left,right)))
42
+
43
+ def owned_bytes(key,packed):
44
+ # Conservative accounting includes key containers and scalar references,
45
+ # even when CPython interns/shares them. No input columns/models retained.
46
+ return (sys.getsizeof(key)+sum(sys.getsizeof(row)+sum(sys.getsizeof(v) for v in row) for row in key)
47
+ +sys.getsizeof(packed)+sum(sys.getsizeof(v) for v in packed.values())+256)
48
+
49
+ class TreeCache:
50
+ def __init__(self,max_bytes=4*1024**2):
51
+ if type(max_bytes) is not int or max_bytes<0:raise ValueError('Nonnegative cache byte allowance')
52
+ self.max_bytes=max_bytes;self.bytes=0;self.hits=0;self.misses=0;self.evictions=0;self._data=OrderedDict();self._lock=threading.RLock()
53
+ def prepare(self,nodes):
54
+ key=tree_key(nodes)
55
+ with self._lock:
56
+ if key in self._data:
57
+ self.hits+=1;packed,size=self._data.pop(key);self._data[key]=(packed,size);return packed
58
+ self.misses+=1;packed=pack_tree(key);size=owned_bytes(key,packed)
59
+ if size<=self.max_bytes:
60
+ while self._data and self.bytes+size>self.max_bytes:
61
+ _,(_,old)=self._data.popitem(last=False);self.bytes-=old;self.evictions+=1
62
+ self._data[key]=(packed,size);self.bytes+=size
63
+ return packed
64
+ def clear(self):
65
+ with self._lock:self._data.clear();self.bytes=0
66
+ def report(self):
67
+ with self._lock:return dict(bytes_accounted=self.bytes,max_bytes=self.max_bytes,entries=len(self._data),hits=self.hits,misses=self.misses,evictions=self.evictions,scope='Conservative Python object accounting;not measured RSS')
68
+
69
+ def histogram_view(column,indices,g,h,r,u,size):
70
+ if type(size) is not int or not 1<=size<=sys.maxsize//40:raise ValueError('Safe positive bin count required')
71
+ if type(column) not in (array,Column) or column.typecode not in ('B','H','I') or column.itemsize not in (1,2,4):raise UnsupportedBuffers('Exact owned bin array/Column required')
72
+ values=(g,h,r,u)
73
+ if any(type(v) is not array or v.typecode!='d' or v.itemsize!=8 or array.__len__(v)!=array.__len__(column) for v in values):raise UnsupportedBuffers('Exact aligned binary64 arrays required')
74
+ copied=not (type(indices) is array and indices.typecode=='Q' and indices.itemsize==8)
75
+ if copied:
76
+ if type(indices) not in (list,tuple,range,array):raise UnsupportedBuffers('Exact bounded node indices required')
77
+ ids=array('Q')
78
+ for i in indices:
79
+ if type(i) is not int or not 0<=i<array.__len__(column):raise ValueError('Invalid row index')
80
+ ids.append(i)
81
+ else:ids=indices
82
+ return dict(ids=ids,owners=(column,ids,*values),copy_bytes=len(ids)*8 if copied else 0)
@@ -0,0 +1,39 @@
1
+ """Load only the installed package's hash-bound library;never compile at import."""
2
+ import ctypes,hashlib,json,platform,struct,sys,threading
3
+ from pathlib import Path
4
+ from .runtime import Runtime
5
+ _LOCK=threading.RLock();_RUNTIME=None;_INFO=None
6
+ def platform_key():
7
+ if struct.calcsize('P')!=8 or platform.machine().lower() not in ('amd64','x86_64'):raise ImportError('This native build requires64bit x86_64')
8
+ if sys.platform=='win32':return 'win_amd64'
9
+ if sys.platform.startswith('linux'):return 'linux_x86_64'
10
+ raise ImportError('No qualified native artifact for this operating system')
11
+ def artifact():
12
+ root=Path(__file__).resolve().parent;manifest=root/'manifest.json'
13
+ if not manifest.is_file():raise ImportError('Native library missing:install the matching experimental wheel or build from source with an existing C toolchain')
14
+ v=json.loads(manifest.read_text('utf8'));expected={'schema','ABI','platform','library','binary_sha256','sources','floating_point','runtime_compilation','build_settings'}
15
+ if type(v) is not dict or set(v)!=expected or v['schema']!='MORPHTREE_NATIVE_ABI_1' or v['ABI']!=1 or v['platform']!=platform_key() or v['runtime_compilation'] is not False:raise ImportError('Native artifact capability mismatch')
16
+ name='morphtree_kernels.dll' if sys.platform=='win32' else 'libmorphtree_kernels.so'
17
+ if v['library']!='bin/'+v['platform']+'/'+name or set(v['sources'])!={'src/histogram.c','src/traversal.c'}:raise ImportError('Unexpected native artifact path/source contract')
18
+ p=root/v['library']
19
+ for item in (p,*p.parents):
20
+ if not item.is_relative_to(root):break
21
+ if item.is_symlink() or getattr(item,'is_junction',lambda:False)():raise ImportError('Native paths cannot be links')
22
+ if not p.is_file() or hashlib.sha256(p.read_bytes()).hexdigest()!=v['binary_sha256']:raise ImportError('Native binary integrity failure')
23
+ for n,h in v['sources'].items():
24
+ source=root/n
25
+ if source.is_symlink() or not source.is_file() or hashlib.sha256(source.read_bytes()).hexdigest()!=h:raise ImportError('Native source integrity failure')
26
+ return p,v
27
+ def get_runtime():
28
+ global _RUNTIME,_INFO
29
+ with _LOCK:
30
+ if _RUNTIME is None:
31
+ p,v=artifact()
32
+ # Retain GIL;Windows lookup uses the verified DLL directory and
33
+ # standard loader directories,never the current working directory.
34
+ library=ctypes.PyDLL(str(p),winmode=0x1100) if sys.platform=='win32' else ctypes.PyDLL(str(p),mode=ctypes.RTLD_LOCAL)
35
+ _RUNTIME=Runtime(library);_INFO=v
36
+ return _RUNTIME
37
+ def runtime_info():
38
+ get_runtime()
39
+ return dict(platform=_INFO['platform'],binary_sha256=_INFO['binary_sha256'],ABI=_INFO['ABI'],runtime_compilation=False,cache=_RUNTIME.trees.report())
@@ -0,0 +1,29 @@
1
+ {
2
+ "ABI": 1,
3
+ "binary_sha256": "1c577f636a74d873942d31ab81a1278eda8096252531c52520d00db2471d2a14",
4
+ "build_settings": {
5
+ "CRT": "static /MT",
6
+ "compiler_sha256": "b0a1818ebae6da7f18230a84e76ce839212584ac4c2ab55b97a0d5268085da57",
7
+ "flags": [
8
+ "/nologo",
9
+ "/LD",
10
+ "/std:c11",
11
+ "/W4",
12
+ "/WX",
13
+ "/O2",
14
+ "/fp:strict",
15
+ "/MT",
16
+ "/Brepro"
17
+ ],
18
+ "setup_sha256": "6b516d8fcf543c14b2d861e1f45661e0029230fe0dc48e86ce78522801822209"
19
+ },
20
+ "floating_point": "binary64 ordered / strict,without reassociation or contracted multiply-add",
21
+ "library": "bin/win_amd64/morphtree_kernels.dll",
22
+ "platform": "win_amd64",
23
+ "runtime_compilation": false,
24
+ "schema": "MORPHTREE_NATIVE_ABI_1",
25
+ "sources": {
26
+ "src/histogram.c": "4b05c05dab1de35c2b8c7cee69cf743b51f746e1d0a14b782df73d3ce959bf39",
27
+ "src/traversal.c": "487d8ca46c96faded18d99dcda0abcb3c8a0fa783070a7da23dd5692a7a69406"
28
+ }
29
+ }
@@ -0,0 +1,48 @@
1
+ """Integrated ABI execution;immutable keys,per-call buffers and retained GIL."""
2
+ from array import array
3
+ from contextlib import ExitStack
4
+ import ctypes
5
+ from .buffers import TreeCache,columns_view,histogram_view,TREE_FIELDS,address,UnsupportedBuffers
6
+ from ..scalable_backend import Column
7
+
8
+ class Runtime:
9
+ def __init__(self,histogram_library,traversal_library=None,*,cache_bytes=4*1024**2):
10
+ # The loader,not a model packet,chooses and verifies these handles.
11
+ self.histogram_library=histogram_library;self.traversal_library=traversal_library or histogram_library
12
+ self.histogram_library.mt_histogram_abi_version.restype=ctypes.c_uint32
13
+ self.traversal_library.mt_traversal_abi_version.restype=ctypes.c_uint32
14
+ if self.histogram_library.mt_histogram_abi_version()!=1 or self.traversal_library.mt_traversal_abi_version()!=1:raise ImportError('Unsupported MorphTree native ABI')
15
+ f=self.histogram_library.mt_histogram_f64_v1;f.argtypes=[ctypes.c_void_p,ctypes.c_uint32,ctypes.c_uint64,ctypes.c_void_p,ctypes.c_uint64,*([ctypes.c_void_p]*4),ctypes.c_uint64,ctypes.c_void_p,ctypes.c_uint64];f.restype=ctypes.c_int;self.histogram_function=f
16
+ f=self.traversal_library.mt_predict_tree_f64_v1;f.argtypes=[ctypes.c_void_p]*3+[ctypes.c_uint64]*2+[ctypes.c_void_p]*7+[ctypes.c_uint64,ctypes.c_void_p,ctypes.c_uint64];f.restype=ctypes.c_int;self.traversal_function=f
17
+ self.trees=TreeCache(cache_bytes)
18
+ def histogram(self,column,indices,g,h,r,u,size):
19
+ # Pin caller-owned arrays before validating lengths or running any
20
+ # conversion. Concurrent resize must not invalidate a checked shape.
21
+ if type(column) not in (array,Column) or any(type(v) is not array for v in (g,h,r,u)):
22
+ raise UnsupportedBuffers('Exact owned bin and statistic arrays required')
23
+ with ExitStack() as pins:
24
+ for value in (column,g,h,r,u):pins.enter_context(memoryview(value))
25
+ if type(indices) is array:pins.enter_context(memoryview(indices))
26
+ p=histogram_view(column,indices,g,h,r,u,size);owners=p['owners']
27
+ pins.enter_context(memoryview(p['ids']))
28
+ output=array('d',[0.])*(5*size)
29
+ pointers=[address(v) for v in owners]
30
+ code=self.histogram_function(pointers[0],column.itemsize,array.__len__(column),pointers[1],array.__len__(p['ids']),*pointers[2:],size,address(output),len(output))
31
+ if code:raise ValueError('Native histogram rejected request/status='+str(code))
32
+ from ..scalable_backend import Hist
33
+ return Hist([output[i*size:(i+1)*size] for i in range(5)],getattr(column,'nominal',False)),p['copy_bytes']
34
+ def predict_tree(self,nodes,columns):
35
+ if type(columns) not in (list,tuple) or not columns:raise ValueError('Nonempty column sequence required')
36
+ columns=tuple(columns)
37
+ if any(type(c) not in (array,Column) for c in columns):raise UnsupportedBuffers('Exact owned array/Column required')
38
+ # Snapshot the container and pin columns before nominal.__bool__ or
39
+ # a node value's numeric conversion can execute Python callbacks.
40
+ with ExitStack() as pins:
41
+ for value in columns:pins.enter_context(memoryview(value))
42
+ c=columns_view(columns);p=self.trees.prepare(nodes)
43
+ for value in (c['widths'],c['nominal'],*(p[k] for k in TREE_FIELDS)):
44
+ pins.enter_context(memoryview(value))
45
+ output=array('d',[0.])*c['rows'];pointer=address
46
+ code=self.traversal_function(c['pointers'],pointer(c['widths']),pointer(c['nominal']),len(columns),c['rows'],*(pointer(p[k]) for k in TREE_FIELDS),array.__len__(p['leaf']),pointer(output),len(output))
47
+ if code:raise ValueError('Native traversal rejected request/status='+str(code))
48
+ return list(output)
@@ -0,0 +1,61 @@
1
+ /* MorphTree-owned prototype: ordered five-plane histogram, ABI v1.
2
+ * No scoring policy, split rule, sampling, tree growth or learner is compiled
3
+ * into this primitive. Caller supplies stable owned buffers; output never aliases
4
+ * an input. No fast-math, reassociation or reduction reordering is permitted.
5
+ * SOURCE ONLY until a real compiler/build/test result is recorded.
6
+ */
7
+ #include <stddef.h>
8
+ #include <stdint.h>
9
+ #include <float.h>
10
+ #include <string.h>
11
+
12
+ _Static_assert(sizeof(double) == 8 && DBL_MANT_DIG == 53 && FLT_RADIX == 2,
13
+ "MorphTree histogram ABI requires binary64 doubles");
14
+ _Static_assert(sizeof(size_t) == 8, "Prototype currently qualified for 64-bit ABI only");
15
+
16
+ #if defined(_WIN32)
17
+ #define MT_EXPORT __declspec(dllexport)
18
+ #else
19
+ #define MT_EXPORT __attribute__((visibility("default")))
20
+ #endif
21
+
22
+ enum { MT_OK=0, MT_BAD_ARGUMENT=1, MT_BAD_INDEX=2, MT_BAD_BIN=3, MT_OVERFLOW=4 };
23
+
24
+ MT_EXPORT uint32_t mt_histogram_abi_version(void) { return 1u; }
25
+
26
+ static uint32_t read_bin(const void *bins, uint32_t width, uint64_t row) {
27
+ /* memcpy also avoids alignment/aliasing undefined behavior at the ABI edge. */
28
+ if (width == 1u) return ((const uint8_t *)bins)[row];
29
+ if (width == 2u) { uint16_t v; memcpy(&v, (const uint8_t *)bins + row*2u, 2u); return v; }
30
+ { uint32_t v; memcpy(&v, (const uint8_t *)bins + row*4u, 4u); return v; }
31
+ }
32
+
33
+ MT_EXPORT int mt_histogram_f64_v1(
34
+ const void *bins, uint32_t width, uint64_t rows,
35
+ const uint64_t *ids, uint64_t selected,
36
+ const double *g, const double *h, const double *r, const double *u,
37
+ uint64_t bin_count, double *out, uint64_t output_doubles) {
38
+ if ((width!=1u && width!=2u && width!=4u) || bin_count==0u || !out)
39
+ return MT_BAD_ARGUMENT;
40
+ if (bin_count > SIZE_MAX/(5u*sizeof(double)) || rows > SIZE_MAX/width)
41
+ return MT_OVERFLOW;
42
+ if (output_doubles < 5u*bin_count) return MT_BAD_ARGUMENT;
43
+ if (selected && (!bins || !ids || !g || !h || !r || !u)) return MT_BAD_ARGUMENT;
44
+ /* Validate first: ordinary invalid requests do not alter the output.
45
+ * The Python prototype retains the GIL and buffer exports throughout. */
46
+ for (uint64_t k=0; k<selected; ++k) {
47
+ const uint64_t i=ids[k];
48
+ if (i>=rows) return MT_BAD_INDEX;
49
+ if (read_bin(bins,width,i)>=bin_count) return MT_BAD_BIN;
50
+ }
51
+ memset(out,0,(size_t)(5u*bin_count*sizeof(double)));
52
+ double *counts=out, *gs=out+bin_count, *hs=out+2u*bin_count;
53
+ double *rs=out+3u*bin_count, *us=out+4u*bin_count;
54
+ for (uint64_t k=0; k<selected; ++k) {
55
+ const uint64_t i=ids[k];
56
+ const uint32_t b=read_bin(bins,width,i);
57
+ counts[b]+=1.0;
58
+ gs[b]+=g[i]; hs[b]+=h[i]; rs[b]+=r[i]; us[b]+=u[i];
59
+ }
60
+ return MT_OK;
61
+ }
@@ -0,0 +1,51 @@
1
+ /* MorphTree-owned traversal ABI1:read an existing declarative tree only.
2
+ * No training,split policy or program selection is encoded here.
3
+ * Stable owned nonaliasing caller buffers;bounded traversal even for cycles.
4
+ */
5
+ #include <stdint.h>
6
+ #include <stddef.h>
7
+ #include <string.h>
8
+ #include <float.h>
9
+ _Static_assert(sizeof(size_t)==8 && sizeof(double)==8 && DBL_MANT_DIG==53,"64bit binary64 ABI required");
10
+ #if defined(_WIN32)
11
+ #define MT_EXPORT __declspec(dllexport)
12
+ #else
13
+ #define MT_EXPORT __attribute__((visibility("default")))
14
+ #endif
15
+ MT_EXPORT uint32_t mt_traversal_abi_version(void){return 1u;}
16
+ static uint32_t bin_at(const void *column,uint32_t width,uint64_t row){
17
+ if(width==1u)return ((const uint8_t*)column)[row];
18
+ if(width==2u){uint16_t v;memcpy(&v,(const uint8_t*)column+row*2u,2u);return v;}
19
+ {uint32_t v;memcpy(&v,(const uint8_t*)column+row*4u,4u);return v;}
20
+ }
21
+ MT_EXPORT int mt_predict_tree_f64_v1(
22
+ const void *const *columns,const uint32_t *widths,const uint8_t *nominal,
23
+ uint64_t features,uint64_t rows,const uint8_t *leaf,const double *values,
24
+ const int64_t *feature,const int64_t *cut,const uint8_t *missing_left,
25
+ const int64_t *left,const int64_t *right,uint64_t nodes,double *out,uint64_t output_size){
26
+ if(!features || !nodes || nodes>INT64_MAX || rows>SIZE_MAX/8u || output_size<rows
27
+ || !columns || !widths || !nominal || !leaf || !values || !feature || !cut
28
+ || !missing_left || !left || !right || (rows && !out))return 1;
29
+ for(uint64_t f=0;f<features;f++){
30
+ if((widths[f]!=1u && widths[f]!=2u && widths[f]!=4u) || nominal[f]>1u
31
+ || rows>SIZE_MAX/widths[f] || (rows && !columns[f]))return 1;
32
+ }
33
+ /* Validate every index before any possible column/node read. */
34
+ for(uint64_t k=0;k<nodes;k++){
35
+ if(leaf[k]>1u || missing_left[k]>1u)return 1;
36
+ if(!leaf[k] && (feature[k]<0 || (uint64_t)feature[k]>=features || cut[k]<0
37
+ || (uint64_t)cut[k]>UINT32_MAX || left[k]<0 || right[k]<0
38
+ || (uint64_t)left[k]>=nodes || (uint64_t)right[k]>=nodes))return 2;
39
+ }
40
+ for(uint64_t row=0;row<rows;row++){
41
+ uint64_t k=0;int reached=0;
42
+ for(uint64_t hop=0;hop<=nodes;hop++){
43
+ if(leaf[k]){out[row]=values[k];reached=1;break;}
44
+ uint64_t f=(uint64_t)feature[k];uint32_t b=bin_at(columns[f],widths[f],row);
45
+ int go_left=nominal[f] ? b==(uint32_t)cut[k] : b==0u ? missing_left[k] : b<=(uint32_t)cut[k];
46
+ k=(uint64_t)(go_left ? left[k] : right[k]);
47
+ }
48
+ if(!reached)return 3; /* Cyclic graph,no unbounded native loop. */
49
+ }
50
+ return 0;
51
+ }