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.
- morphtree/__init__.py +28 -0
- morphtree/__main__.py +3 -0
- morphtree/_experimental/__init__.py +1 -0
- morphtree/_experimental/admission.py +364 -0
- morphtree/_experimental/base.py +215 -0
- morphtree/_experimental/component_cache_v2.py +263 -0
- morphtree/_experimental/evolution_report.py +307 -0
- morphtree/_experimental/execution_v2.py +697 -0
- morphtree/_experimental/facade.py +289 -0
- morphtree/_experimental/integrated.py +748 -0
- morphtree/_experimental/learning.py +418 -0
- morphtree/_experimental/manifest.json +204 -0
- morphtree/_experimental/numeric_fixture.py +21 -0
- morphtree/_experimental/parameter_contract.py +38 -0
- morphtree/_experimental/postfit.py +51 -0
- morphtree/_experimental/preprocessing.py +133 -0
- morphtree/_experimental/processes.py +191 -0
- morphtree/_experimental/producer.py +542 -0
- morphtree/_experimental/product.py +156 -0
- morphtree/_experimental/product_cli.py +43 -0
- morphtree/_experimental/roundtrip_v3.py +311 -0
- morphtree/_experimental/runtime_io.py +65 -0
- morphtree/_experimental/sampling.py +26 -0
- morphtree/_experimental/support.py +281 -0
- morphtree/_experimental/target.py +59 -0
- morphtree/_experimental/training.py +53 -0
- morphtree/_experimental/worker.py +682 -0
- morphtree/_installed.py +17 -0
- morphtree/_native/__init__.py +1 -0
- morphtree/_native/bin/win_amd64/morphtree_kernels.dll +0 -0
- morphtree/_native/buffers.py +82 -0
- morphtree/_native/loader.py +39 -0
- morphtree/_native/manifest.json +29 -0
- morphtree/_native/runtime.py +48 -0
- morphtree/_native/src/histogram.c +61 -0
- morphtree/_native/src/traversal.c +51 -0
- morphtree/api.py +388 -0
- morphtree/api_compat.py +65 -0
- morphtree/backend.py +162 -0
- morphtree/booster.py +252 -0
- morphtree/cli.py +52 -0
- morphtree/config.py +262 -0
- morphtree/control.py +20 -0
- morphtree/data.py +300 -0
- morphtree/diagnostics.py +158 -0
- morphtree/estimators.py +156 -0
- morphtree/examples.py +25 -0
- morphtree/exporting.py +224 -0
- morphtree/legacy_artifacts.py +32 -0
- morphtree/metrics.py +156 -0
- morphtree/model.py +1010 -0
- morphtree/mutations.py +205 -0
- morphtree/native_lab_backend.py +79 -0
- morphtree/numpy_backend.py +180 -0
- morphtree/optuna_tuner.py +154 -0
- morphtree/persistence.py +414 -0
- morphtree/prediction_output.py +35 -0
- morphtree/prepared_bins.py +82 -0
- morphtree/public.py +106 -0
- morphtree/resource_plan.py +170 -0
- morphtree/scalable.py +781 -0
- morphtree/scalable_backend.py +74 -0
- morphtree/scalable_checkpoint.py +129 -0
- morphtree/scalable_data.py +255 -0
- morphtree/scalable_receipts.py +92 -0
- morphtree/scalable_target.py +68 -0
- morphtree/scalable_variants.py +133 -0
- morphtree/sklearn_view.py +79 -0
- morphtree/source_markers.py +25 -0
- morphtree/species.py +47 -0
- morphtree/temporary.py +65 -0
- morphtree/training_fragment.py +130 -0
- morphtree/tree.py +231 -0
- morphtree/tuning.py +150 -0
- morphtree-2.1.0.dist-info/METADATA +212 -0
- morphtree-2.1.0.dist-info/RECORD +79 -0
- morphtree-2.1.0.dist-info/WHEEL +4 -0
- morphtree-2.1.0.dist-info/entry_points.txt +2 -0
- morphtree-2.1.0.dist-info/licenses/LICENSE +21 -0
morphtree/_installed.py
ADDED
|
@@ -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."""
|
|
Binary file
|
|
@@ -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
|
+
}
|