PyDecisionGraph 0.3.2.post1__cp315-cp315-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.
- decision_graph/__infra__.pxd +1 -0
- decision_graph/__init__.pxd +1 -0
- decision_graph/__init__.py +39 -0
- decision_graph/decision_tree/__init__.pxd +2 -0
- decision_graph/decision_tree/__init__.py +85 -0
- decision_graph/decision_tree/bake/__infra__.pxd +502 -0
- decision_graph/decision_tree/bake/__init__.pxd +502 -0
- decision_graph/decision_tree/bake/__init__.py +29 -0
- decision_graph/decision_tree/bake/c_action.cp315-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_action.h +324 -0
- decision_graph/decision_tree/bake/c_action.pxd +54 -0
- decision_graph/decision_tree/bake/c_action.pyi +126 -0
- decision_graph/decision_tree/bake/c_action.pyx +65 -0
- decision_graph/decision_tree/bake/c_allocator_protocol.cp315-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_allocator_protocol.pxd +24 -0
- decision_graph/decision_tree/bake/c_allocator_protocol.pyi +61 -0
- decision_graph/decision_tree/bake/c_allocator_protocol.pyx +109 -0
- decision_graph/decision_tree/bake/c_bake.cp315-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_bake.h +629 -0
- decision_graph/decision_tree/bake/c_bake.pxd +52 -0
- decision_graph/decision_tree/bake/c_bake.pyi +151 -0
- decision_graph/decision_tree/bake/c_bake.pyx +142 -0
- decision_graph/decision_tree/bake/c_collections.cp315-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_collections.h +692 -0
- decision_graph/decision_tree/bake/c_collections.pxd +52 -0
- decision_graph/decision_tree/bake/c_collections.pyi +193 -0
- decision_graph/decision_tree/bake/c_collections.pyx +161 -0
- decision_graph/decision_tree/bake/c_const.cp315-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_const.h +395 -0
- decision_graph/decision_tree/bake/c_const.pxd +56 -0
- decision_graph/decision_tree/bake/c_const.pyi +305 -0
- decision_graph/decision_tree/bake/c_const.pyx +278 -0
- decision_graph/decision_tree/bake/c_edge.cp315-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_edge.h +511 -0
- decision_graph/decision_tree/bake/c_edge.pxd +117 -0
- decision_graph/decision_tree/bake/c_edge.pyi +192 -0
- decision_graph/decision_tree/bake/c_edge.pyx +270 -0
- decision_graph/decision_tree/bake/c_eval.h +848 -0
- decision_graph/decision_tree/bake/c_eval.pxd +40 -0
- decision_graph/decision_tree/bake/c_expr.cp315-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_expr.h +1900 -0
- decision_graph/decision_tree/bake/c_expr.pxd +129 -0
- decision_graph/decision_tree/bake/c_expr.pyi +280 -0
- decision_graph/decision_tree/bake/c_expr.pyx +272 -0
- decision_graph/decision_tree/bake/c_hierarchy.cp315-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_hierarchy.h +731 -0
- decision_graph/decision_tree/bake/c_hierarchy.pxd +71 -0
- decision_graph/decision_tree/bake/c_hierarchy.pyi +409 -0
- decision_graph/decision_tree/bake/c_hierarchy.pyx +277 -0
- decision_graph/decision_tree/bake/c_logic_group.cp315-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_logic_group.h +1226 -0
- decision_graph/decision_tree/bake/c_logic_group.pxd +162 -0
- decision_graph/decision_tree/bake/c_logic_group.pyi +264 -0
- decision_graph/decision_tree/bake/c_logic_group.pyx +308 -0
- decision_graph/decision_tree/bake/c_node.cp315-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_node.h +2660 -0
- decision_graph/decision_tree/bake/c_node.pxd +381 -0
- decision_graph/decision_tree/bake/c_node.pyi +438 -0
- decision_graph/decision_tree/bake/c_node.pyx +639 -0
- decision_graph/decision_tree/bake/c_reconstruct.cp315-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_reconstruct.pxd +63 -0
- decision_graph/decision_tree/bake/c_reconstruct.pyi +60 -0
- decision_graph/decision_tree/bake/c_reconstruct.pyx +252 -0
- decision_graph/decision_tree/bake/c_var.cp315-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_var.h +2054 -0
- decision_graph/decision_tree/bake/c_var.pxd +205 -0
- decision_graph/decision_tree/bake/c_var.pyi +271 -0
- decision_graph/decision_tree/bake/c_var.pyx +228 -0
- decision_graph/decision_tree/capi/__infra__.pxd +33 -0
- decision_graph/decision_tree/capi/__init__.pxd +33 -0
- decision_graph/decision_tree/capi/__init__.py +57 -0
- decision_graph/decision_tree/capi/c_abc.cp315-win_amd64.pyd +0 -0
- decision_graph/decision_tree/capi/c_abc.pxd +246 -0
- decision_graph/decision_tree/capi/c_abc.pyi +770 -0
- decision_graph/decision_tree/capi/c_abc.pyx +1532 -0
- decision_graph/decision_tree/capi/c_collection.cp315-win_amd64.pyd +0 -0
- decision_graph/decision_tree/capi/c_collection.pxd +22 -0
- decision_graph/decision_tree/capi/c_collection.pyi +245 -0
- decision_graph/decision_tree/capi/c_collection.pyx +138 -0
- decision_graph/decision_tree/capi/c_node.cp315-win_amd64.pyd +0 -0
- decision_graph/decision_tree/capi/c_node.pxd +95 -0
- decision_graph/decision_tree/capi/c_node.pyi +492 -0
- decision_graph/decision_tree/capi/c_node.pyx +551 -0
- decision_graph/decision_tree/exc.py +189 -0
- decision_graph/decision_tree/native/__init__.py +57 -0
- decision_graph/decision_tree/native/abc.py +1167 -0
- decision_graph/decision_tree/native/collection.py +134 -0
- decision_graph/decision_tree/native/node.py +521 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_action.c +16320 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_action.h +324 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_allocator_protocol.c +12473 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_bake.c +16485 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_bake.h +629 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_collections.c +17381 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_collections.h +692 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_const.c +20815 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_const.h +395 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_edge.c +20696 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_edge.h +511 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_eval.h +848 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_expr.c +22065 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_expr.h +1900 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_hierarchy.c +23267 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_hierarchy.h +731 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_logic_group.c +22434 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_logic_group.h +1226 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_node.c +28385 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_node.h +2660 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_reconstruct.c +15955 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_var.c +16881 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_var.h +2054 -0
- decision_graph/includes/decision_graph/decision_tree/capi/c_abc.c +59196 -0
- decision_graph/includes/decision_graph/decision_tree/capi/c_collection.c +19967 -0
- decision_graph/includes/decision_graph/decision_tree/capi/c_node.c +39229 -0
- decision_graph/logic_group/__init__.py +25 -0
- decision_graph/logic_group/base.py +100 -0
- decision_graph/logic_group/pending_request.py +384 -0
- decision_graph/webui/__init__.py +24 -0
- decision_graph/webui/bake/__init__.py +65 -0
- decision_graph/webui/bake/app.py +488 -0
- decision_graph/webui/bake/static/d3.v7.min.js +2 -0
- decision_graph/webui/bake/static/script.js +1440 -0
- decision_graph/webui/bake/static/style.css +920 -0
- decision_graph/webui/bake/templates/index.html +183 -0
- decision_graph/webui/bake/templates/offline.html +184 -0
- decision_graph/webui/capi/__init__.py +54 -0
- decision_graph/webui/capi/app.py +366 -0
- decision_graph/webui/capi/main.py +53 -0
- decision_graph/webui/capi/static/d3.v7.min.js +2 -0
- decision_graph/webui/capi/static/script.js +1026 -0
- decision_graph/webui/capi/static/style.css +656 -0
- decision_graph/webui/capi/templates/index.html +93 -0
- decision_graph/webui/capi/templates/offline.html +94 -0
- decision_graph/webui/native/__init__.py +54 -0
- decision_graph/webui/native/app.py +366 -0
- decision_graph/webui/native/main.py +53 -0
- decision_graph/webui/native/static/d3.v7.min.js +2 -0
- decision_graph/webui/native/static/script.js +1026 -0
- decision_graph/webui/native/static/style.css +656 -0
- decision_graph/webui/native/templates/index.html +93 -0
- decision_graph/webui/native/templates/offline.html +94 -0
- pydecisiongraph-0.3.2.post1.dist-info/METADATA +136 -0
- pydecisiongraph-0.3.2.post1.dist-info/RECORD +148 -0
- pydecisiongraph-0.3.2.post1.dist-info/WHEEL +5 -0
- pydecisiongraph-0.3.2.post1.dist-info/licenses/LICENSE +373 -0
- pydecisiongraph-0.3.2.post1.dist-info/top_level.txt +2 -0
- tests/bake_c_evaluator.cp315-win_amd64.pyd +0 -0
- tests/bake_eval_probe.cp315-win_amd64.pyd +0 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from libc.stdint cimport uint64_t
|
|
2
|
+
from libcpp cimport bool as c_bool
|
|
3
|
+
|
|
4
|
+
from cbase.allocator_protocol cimport allocator_protocol
|
|
5
|
+
|
|
6
|
+
from .c_node cimport LogicNode, dcg_logic_group_manager, dcg_node
|
|
7
|
+
from .c_var cimport dcg_ret_code, dcg_var_t
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
cdef extern from "decision_graph/decision_tree/bake/c_logic_group.h":
|
|
11
|
+
ctypedef struct dcg_logic_group:
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
int c_dcg_lgm_install_breakpoint(dcg_logic_group_manager* mgr, dcg_breakpoint_node* breakpoint) noexcept nogil
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
cdef extern from "decision_graph/decision_tree/bake/c_hierarchy.h":
|
|
18
|
+
ctypedef struct dcg_node_eval_path:
|
|
19
|
+
dcg_node** node
|
|
20
|
+
dcg_var_t* eval_val
|
|
21
|
+
size_t capacity
|
|
22
|
+
size_t n_nodes
|
|
23
|
+
dcg_ret_code code
|
|
24
|
+
dcg_node* leaf
|
|
25
|
+
dcg_node* failed
|
|
26
|
+
uint64_t seq_id
|
|
27
|
+
|
|
28
|
+
ctypedef struct dcg_root_node:
|
|
29
|
+
dcg_node base
|
|
30
|
+
dcg_node_eval_path eval_path
|
|
31
|
+
c_bool inherit_contexts
|
|
32
|
+
|
|
33
|
+
ctypedef struct dcg_breakpoint_node:
|
|
34
|
+
dcg_node base
|
|
35
|
+
dcg_logic_group* break_from
|
|
36
|
+
c_bool await_connection
|
|
37
|
+
|
|
38
|
+
dcg_root_node* c_dcg_node_new_root(const char* repr, allocator_protocol* allocator) noexcept nogil
|
|
39
|
+
dcg_breakpoint_node* c_dcg_node_new_breakpoint(dcg_logic_group* break_from, const char* repr, allocator_protocol* allocator) noexcept nogil
|
|
40
|
+
int c_dcg_node_ctx_enter_breakpoint(dcg_node* node, dcg_logic_group_manager* mgr) noexcept nogil
|
|
41
|
+
dcg_breakpoint_node* c_dcg_node_get_breakpoint(const dcg_node* node) noexcept nogil
|
|
42
|
+
void c_dcg_node_free_root(dcg_root_node* node) noexcept nogil
|
|
43
|
+
void c_dcg_node_free_breakpoint(dcg_breakpoint_node* node) noexcept nogil
|
|
44
|
+
|
|
45
|
+
void c_dcg_node_free_generic(dcg_node* node) noexcept nogil
|
|
46
|
+
int c_dcg_node_teardown_root(dcg_node* node) noexcept nogil
|
|
47
|
+
int c_dcg_node_remove(dcg_node* node) noexcept nogil
|
|
48
|
+
int c_dcg_node_clear_children(dcg_node* node) noexcept nogil
|
|
49
|
+
int c_dcg_node_clean(dcg_node* node) noexcept nogil
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
cdef extern from "decision_graph/decision_tree/bake/c_eval.h":
|
|
53
|
+
int c_dcg_root_node_eval(dcg_root_node* root) noexcept nogil
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
cdef class NodeEvalPathView:
|
|
57
|
+
cdef const dcg_node_eval_path* header
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
cdef inline NodeEvalPathView c_from_header(const dcg_node_eval_path* path)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
cdef class RootLogicNode(LogicNode):
|
|
64
|
+
cdef readonly str name
|
|
65
|
+
cdef readonly NodeEvalPathView eval_path
|
|
66
|
+
|
|
67
|
+
cpdef object bake(self, bint validate_only=?)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
cdef class BreakpointNode(LogicNode):
|
|
71
|
+
cdef readonly object break_from
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
"""The hierarchy layer: the graph's entry point, and the inspection sink.
|
|
2
|
+
|
|
3
|
+
Two nodes that are not part of the decision the graph computes. A root is where
|
|
4
|
+
a graph is entered: it takes one branch, and entering it opens a context of its
|
|
5
|
+
own - the scopes around it are shelved while a build is inside. A breakpoint is
|
|
6
|
+
where an inspection stops: it breaks out of a group, and it resumes into the
|
|
7
|
+
next node entered outside it.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from collections.abc import Iterator
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from .c_bake import BakeReport
|
|
14
|
+
from .c_logic_group import LogicGroup
|
|
15
|
+
from .c_node import LogicNode
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class RootLogicNode(LogicNode):
|
|
19
|
+
"""The graph's entry point: the only node that has no parent.
|
|
20
|
+
|
|
21
|
+
A root takes exactly ONE branch - its single arm is the entry edge - and
|
|
22
|
+
entering it shelves the contexts around it, so the graph a build enters is
|
|
23
|
+
built in a context of its own.
|
|
24
|
+
|
|
25
|
+
It is also where evaluating a graph starts: ``eval`` walks from here to a
|
|
26
|
+
single leaf and keeps the record of how it got there, so the root is what a
|
|
27
|
+
caller asks to make a decision.
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
name: The root's name, as the build gave it.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(self, *, name: str = 'Entry Point', inherit_contexts: bool = False, **kwargs: Any) -> None:
|
|
34
|
+
"""Build a root.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
name: The root's name.
|
|
38
|
+
inherit_contexts: Whether it takes the contexts of the groups around
|
|
39
|
+
it rather than shelving them.
|
|
40
|
+
|
|
41
|
+
Raises:
|
|
42
|
+
MemoryError: When the block cannot be allocated.
|
|
43
|
+
"""
|
|
44
|
+
...
|
|
45
|
+
|
|
46
|
+
def eval(self) -> Any:
|
|
47
|
+
"""Evaluate the graph from this root, and record how it decided.
|
|
48
|
+
|
|
49
|
+
The whole walk, not one node - which is what a root does differently
|
|
50
|
+
from every other node, whose ``eval`` runs its own rule and nothing else.
|
|
51
|
+
Every node on the way is evaluated, the record of the last walk is
|
|
52
|
+
replaced, and the answer is where the walk came to rest: the leaf itself
|
|
53
|
+
when that leaf is an action, because the action IS the decision, and the
|
|
54
|
+
value in the leaf's slot otherwise.
|
|
55
|
+
|
|
56
|
+
The value a walk reads is the value AT THE MOMENT OF THE WALK: a read is
|
|
57
|
+
live, so the same graph decides differently when the store it reads
|
|
58
|
+
changes, and evaluating again is how a caller asks it to. What a walk
|
|
59
|
+
pins is the TYPE: a read is resolved to its entry's type at its first
|
|
60
|
+
evaluation, and a value of another type written to that entry afterwards
|
|
61
|
+
is not one it reports.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
The leaf's wrapper when the walk landed on an action, else the value
|
|
65
|
+
that leaf holds.
|
|
66
|
+
|
|
67
|
+
Raises:
|
|
68
|
+
EvalFailureError: When the walk could not reach a leaf - a read of an
|
|
69
|
+
entry with no value in it, a branch whose value selected no arm
|
|
70
|
+
and which has no fallback, or a node a hook refused. It names the
|
|
71
|
+
node that refused, with the code, the stage and what was running
|
|
72
|
+
(``node`` is the node the record reports as ``failed``), and a
|
|
73
|
+
hook's own exception travels as its cause. The record still holds
|
|
74
|
+
the nodes reached before it stopped.
|
|
75
|
+
"""
|
|
76
|
+
...
|
|
77
|
+
|
|
78
|
+
def __call__(self) -> Any:
|
|
79
|
+
"""Evaluate the graph - the same walk ``eval`` runs, written as a call.
|
|
80
|
+
|
|
81
|
+
A root is what a caller asks to make a decision, and asking it is the
|
|
82
|
+
call: ``root()`` and ``root.eval()`` answer with the same value for the
|
|
83
|
+
same graph and leave the same record. The call form is what a graph reads
|
|
84
|
+
as at the place a decision is wanted, and it is the one door the two
|
|
85
|
+
layers spell the same way.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
The leaf's wrapper when the walk landed on an action, else the value
|
|
89
|
+
that leaf holds.
|
|
90
|
+
|
|
91
|
+
Raises:
|
|
92
|
+
EvalFailureError: When the walk could not reach a leaf - the same
|
|
93
|
+
failures, reported the same way, as ``eval``.
|
|
94
|
+
"""
|
|
95
|
+
...
|
|
96
|
+
|
|
97
|
+
def bake(self, validate_only: bool = False) -> BakeReport:
|
|
98
|
+
"""Bake the graph: verify what an evaluation assumes, lock it, prepare it.
|
|
99
|
+
|
|
100
|
+
The last thing done to a graph before it is walked, and the pass every
|
|
101
|
+
evaluation is written assuming has run. What an evaluation assumes is
|
|
102
|
+
established here and nowhere else, because the hot path carries no check
|
|
103
|
+
for a malformed node:
|
|
104
|
+
|
|
105
|
+
- the graph's STRUCTURE - one parentless root, the arms in reading order,
|
|
106
|
+
an else last, an action below nothing, no cycles;
|
|
107
|
+
- every operand an operator takes has a component bound to it, and its
|
|
108
|
+
operand array is exactly as long as its arity - the rules run their
|
|
109
|
+
operands and read the workspace they filled with nothing checked in
|
|
110
|
+
between;
|
|
111
|
+
- every operator belongs to its node's arity, and no node in the graph is
|
|
112
|
+
one that has no evaluation at all (a call).
|
|
113
|
+
|
|
114
|
+
What the pass then does is hold the graph to what it verified: every node
|
|
115
|
+
it walked is frozen, so the structure is what it was, and every store the
|
|
116
|
+
graph reads is sealed, so no entry can appear in it afterwards. That
|
|
117
|
+
second half is the one an evaluation depends on - a resolved read holds a
|
|
118
|
+
reference into its store's block, and a block moves only when the store
|
|
119
|
+
grows. A store is sealed in SHAPE and not in contents: the entries it has
|
|
120
|
+
are the entries it will have, and a caller goes on writing values into
|
|
121
|
+
them, because a graph is baked once and fed many times.
|
|
122
|
+
|
|
123
|
+
Finally the record a walk fills has its room made in advance, sized for
|
|
124
|
+
what a walk from this root can need, so evaluating a baked graph
|
|
125
|
+
allocates nothing.
|
|
126
|
+
|
|
127
|
+
A bake is all or nothing: a graph that fails is not locked and not
|
|
128
|
+
prepared, so nothing about it changes. A bake that succeeds is
|
|
129
|
+
idempotent - asked again it locks nothing and seals nothing, which the
|
|
130
|
+
report says.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
validate_only: Whether to verify the graph and report without
|
|
134
|
+
locking or preparing it - the question "would this bake?" asked
|
|
135
|
+
without the commitment. Nothing about the graph changes.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
The report: what the pass found, and what it affected.
|
|
139
|
+
|
|
140
|
+
Raises:
|
|
141
|
+
BakeFailureError: When the graph cannot be baked - the code, the node
|
|
142
|
+
that produced it and the problems found are in the report the
|
|
143
|
+
failure carries, and nothing was locked or prepared.
|
|
144
|
+
"""
|
|
145
|
+
...
|
|
146
|
+
|
|
147
|
+
def get_breakpoint(self) -> BreakpointNode | None:
|
|
148
|
+
"""The breakpoint this graph left waiting, or None if it left none.
|
|
149
|
+
|
|
150
|
+
A breakpoint that has not resumed into anything yet IS a leaf - it is
|
|
151
|
+
where a branch stopped - so what this looks for is the first breakpoint
|
|
152
|
+
among the leaves: the one a build can enter to carry the graph on from
|
|
153
|
+
where it broke out.
|
|
154
|
+
|
|
155
|
+
That is what makes a graph assemblable from several places. One function
|
|
156
|
+
builds up to a break, this hands the break back, and the next function
|
|
157
|
+
takes it and builds the rest - so the two halves never have to be in the
|
|
158
|
+
same scope, or even the same function.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
The waiting breakpoint, or None when the graph has none.
|
|
162
|
+
"""
|
|
163
|
+
...
|
|
164
|
+
|
|
165
|
+
def to_html(self, file_name: str | None = None, with_eval: bool = True) -> None:
|
|
166
|
+
"""Render this graph to a standalone HTML file.
|
|
167
|
+
|
|
168
|
+
The file carries the stylesheet, the script and D3 inside it, so it
|
|
169
|
+
opens anywhere with no server and no network.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
file_name: Output HTML file name; when omitted, the file is named
|
|
173
|
+
after the root's ``repr``.
|
|
174
|
+
with_eval: Whether to include the last evaluation in the rendering.
|
|
175
|
+
"""
|
|
176
|
+
...
|
|
177
|
+
|
|
178
|
+
def show(self, **kwargs: Any) -> None:
|
|
179
|
+
"""Serve this graph in the layer's own web viewer and open a browser.
|
|
180
|
+
|
|
181
|
+
Arguments:
|
|
182
|
+
**kwargs: keyword arguments passed into ``decision_graph.webui.bake.show`` method.
|
|
183
|
+
"""
|
|
184
|
+
...
|
|
185
|
+
|
|
186
|
+
def watch(self, **kwargs: Any) -> None:
|
|
187
|
+
"""Stream this graph's changes to the layer's own web viewer.
|
|
188
|
+
|
|
189
|
+
The root is re-evaluated on a timer, and what changed between runs is
|
|
190
|
+
sent to the page as it happens.
|
|
191
|
+
|
|
192
|
+
Arguments:
|
|
193
|
+
**kwargs: keyword arguments passed into ``decision_graph.webui.bake.watch`` method.
|
|
194
|
+
"""
|
|
195
|
+
...
|
|
196
|
+
|
|
197
|
+
@property
|
|
198
|
+
def inherit_contexts(self) -> bool:
|
|
199
|
+
"""Whether this root takes the groups around it rather than shelving them.
|
|
200
|
+
|
|
201
|
+
Recorded on the root when it is built, and read back off the block. Note
|
|
202
|
+
that this layer's root does not shelve yet - its entering reserves its arm
|
|
203
|
+
and nothing else - so the flag is carried and reported but has no effect
|
|
204
|
+
on a build. The capi's root does shelve, which is where the two differ.
|
|
205
|
+
"""
|
|
206
|
+
...
|
|
207
|
+
|
|
208
|
+
@property
|
|
209
|
+
def name(self) -> str | None:
|
|
210
|
+
"""The root's name - None for a root rebuilt from C, which carries none."""
|
|
211
|
+
...
|
|
212
|
+
|
|
213
|
+
@property
|
|
214
|
+
def eval_path(self) -> NodeEvalPathView:
|
|
215
|
+
"""The record of how the last decision was made, as a view over it.
|
|
216
|
+
|
|
217
|
+
The root first and the leaf it came to rest on last, with every branch it
|
|
218
|
+
descended through in between - the very wrappers the build made, not
|
|
219
|
+
fresh views of them - plus the outcome: which code the walk ended with,
|
|
220
|
+
where it landed, what failed if anything did, and which run wrote it. It
|
|
221
|
+
is replaced by each walk, so what it holds is the last one; a walk that
|
|
222
|
+
could not finish leaves the nodes it got through and no leaf.
|
|
223
|
+
|
|
224
|
+
The view is taken once, when the root is built, and it reads the record
|
|
225
|
+
the root holds - so a root rebuilt from C carries one too, over the very
|
|
226
|
+
record the block has. It is a field rather than a computed value: asking
|
|
227
|
+
for it twice answers with the same view.
|
|
228
|
+
"""
|
|
229
|
+
...
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class NodeEvalPathView:
|
|
233
|
+
"""A read-only window onto ``dcg_node_eval_path``: the record of a walk.
|
|
234
|
+
|
|
235
|
+
A view is an ADDRESS, not a copy: it holds the record it was taken over, and
|
|
236
|
+
every read answers for what that record holds now. The record belongs to the
|
|
237
|
+
root whose walk wrote it - it is embedded in that root and its blocks are
|
|
238
|
+
nested under it - so the view must not outlive the root, which the layer
|
|
239
|
+
guarantees by handing one out only from the root it belongs to
|
|
240
|
+
(``RootLogicNode.eval_path``).
|
|
241
|
+
|
|
242
|
+
It cannot write: the view holds the record **const**, and every reader it
|
|
243
|
+
exposes reads through that pointer, so a write through a view does not
|
|
244
|
+
compile.
|
|
245
|
+
|
|
246
|
+
Attributes:
|
|
247
|
+
_header: The C record this view reads through.
|
|
248
|
+
"""
|
|
249
|
+
|
|
250
|
+
def __init__(self, address: int) -> None:
|
|
251
|
+
"""View the record at an address.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
address: Address of a ``dcg_node_eval_path``. The layer hands these
|
|
255
|
+
out itself (``RootLogicNode.eval_path``); nothing here checks that
|
|
256
|
+
the address is a record, and the view keeps nothing alive.
|
|
257
|
+
"""
|
|
258
|
+
...
|
|
259
|
+
|
|
260
|
+
def __repr__(self) -> str:
|
|
261
|
+
"""The view's own text: the count, the outcome and where the walk landed."""
|
|
262
|
+
...
|
|
263
|
+
|
|
264
|
+
def __len__(self) -> int:
|
|
265
|
+
"""How many nodes the walk reached."""
|
|
266
|
+
...
|
|
267
|
+
|
|
268
|
+
def __iter__(self) -> Iterator[LogicNode]:
|
|
269
|
+
"""Walk the entries: the nodes, in the order the walk reached them."""
|
|
270
|
+
...
|
|
271
|
+
|
|
272
|
+
def __getitem__(self, index: int) -> LogicNode:
|
|
273
|
+
"""The entry at an index, counting from the front or the back.
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
index: Which entry; negative counts from the end.
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
The wrapper for the node the walk reached there.
|
|
280
|
+
|
|
281
|
+
Raises:
|
|
282
|
+
IndexError: When there is no such entry.
|
|
283
|
+
"""
|
|
284
|
+
...
|
|
285
|
+
|
|
286
|
+
@property
|
|
287
|
+
def address(self) -> int:
|
|
288
|
+
"""The record's address - what this view reads through."""
|
|
289
|
+
...
|
|
290
|
+
|
|
291
|
+
@property
|
|
292
|
+
def nodes(self) -> list[LogicNode]:
|
|
293
|
+
"""Every entry, as a list: the nodes in the order the walk reached them."""
|
|
294
|
+
...
|
|
295
|
+
|
|
296
|
+
@property
|
|
297
|
+
def capacity(self) -> int:
|
|
298
|
+
"""The room the record has - how many entries it can hold before it grows."""
|
|
299
|
+
...
|
|
300
|
+
|
|
301
|
+
@property
|
|
302
|
+
def code(self) -> int:
|
|
303
|
+
"""The outcome, as the ``DCG_ERR_*`` code the walk ended with."""
|
|
304
|
+
...
|
|
305
|
+
|
|
306
|
+
@property
|
|
307
|
+
def code_name(self) -> str:
|
|
308
|
+
"""The outcome's stable name: ``'OK'``, ``'UNBOUND'``, ``'NO_MATCH'``, ..."""
|
|
309
|
+
...
|
|
310
|
+
|
|
311
|
+
@property
|
|
312
|
+
def leaf(self) -> LogicNode | None:
|
|
313
|
+
"""The node the walk came to rest on, or None when it never landed."""
|
|
314
|
+
...
|
|
315
|
+
|
|
316
|
+
@property
|
|
317
|
+
def failed(self) -> LogicNode | None:
|
|
318
|
+
"""The node that reported the failure, or None when nothing failed.
|
|
319
|
+
|
|
320
|
+
In a walk this is the node whose own evaluation refused - the one whose
|
|
321
|
+
stages say how far it got - which need not be the node a caller asked to
|
|
322
|
+
evaluate: a root that reached a branch which then failed did not fail.
|
|
323
|
+
"""
|
|
324
|
+
...
|
|
325
|
+
|
|
326
|
+
@property
|
|
327
|
+
def seq_id(self) -> int:
|
|
328
|
+
"""The id of the run the record belongs to: which walk wrote it."""
|
|
329
|
+
...
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
class BreakpointNode(LogicNode):
|
|
333
|
+
"""An inspection sink: evaluation stops here and resumes outside the group.
|
|
334
|
+
|
|
335
|
+
It borrows the group it breaks out of, and this wrapper is what holds that
|
|
336
|
+
group alive while the breakpoint stands - which is why a reconstruction
|
|
337
|
+
restores the group with it.
|
|
338
|
+
"""
|
|
339
|
+
|
|
340
|
+
def __init__(self, *, break_from: LogicGroup | None = None, repr: str | None = None, autogen: bool = True, **kwargs: Any) -> None:
|
|
341
|
+
"""Build a breakpoint.
|
|
342
|
+
|
|
343
|
+
Args:
|
|
344
|
+
break_from: The group it breaks out of, if any.
|
|
345
|
+
repr: Display text to copy.
|
|
346
|
+
autogen: Whether the builder generated it rather than a caller.
|
|
347
|
+
|
|
348
|
+
Raises:
|
|
349
|
+
MemoryError: When the block cannot be allocated.
|
|
350
|
+
"""
|
|
351
|
+
...
|
|
352
|
+
|
|
353
|
+
@classmethod
|
|
354
|
+
def break_(cls, break_from: LogicGroup, **kwargs: Any) -> BreakpointNode:
|
|
355
|
+
"""Break out of a group, and hand back the breakpoint that did it.
|
|
356
|
+
|
|
357
|
+
A break is a node: it takes the arm the build was about to fill, and the
|
|
358
|
+
build continues from IT rather than from the group it left. That is what
|
|
359
|
+
makes this the door for assembling a graph in pieces - one function
|
|
360
|
+
builds up to the break and returns, and the break it made is taken here
|
|
361
|
+
to carry on outside the group.
|
|
362
|
+
|
|
363
|
+
The breakpoint is built first and then installed, rather than made by the
|
|
364
|
+
install: the block that lands in the graph has to be the one this wrapper
|
|
365
|
+
holds, or the caller is handed a handle on a node that is not in the
|
|
366
|
+
graph.
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
break_from: The group being broken out of.
|
|
370
|
+
**kwargs: Passed to the constructor.
|
|
371
|
+
|
|
372
|
+
Returns:
|
|
373
|
+
The breakpoint, now in the graph and waiting to resume into the next
|
|
374
|
+
node built.
|
|
375
|
+
|
|
376
|
+
Raises:
|
|
377
|
+
RuntimeError: When the C layer cannot place it.
|
|
378
|
+
MemoryError: When the block cannot be allocated.
|
|
379
|
+
"""
|
|
380
|
+
...
|
|
381
|
+
|
|
382
|
+
def connect(self, child: LogicNode) -> None:
|
|
383
|
+
"""Connect a node as what this breakpoint resumes into.
|
|
384
|
+
|
|
385
|
+
A breakpoint resumes into exactly one node - which is what its arm is
|
|
386
|
+
for - so a second connection is refused rather than replacing the first.
|
|
387
|
+
|
|
388
|
+
Args:
|
|
389
|
+
child: Node to connect.
|
|
390
|
+
|
|
391
|
+
Raises:
|
|
392
|
+
RuntimeError: When the C layer refuses the link.
|
|
393
|
+
"""
|
|
394
|
+
...
|
|
395
|
+
|
|
396
|
+
@property
|
|
397
|
+
def linked_to(self) -> LogicNode | None:
|
|
398
|
+
"""The node this breakpoint resumes into, or None while it waits."""
|
|
399
|
+
...
|
|
400
|
+
|
|
401
|
+
@property
|
|
402
|
+
def break_from(self) -> LogicGroup | None:
|
|
403
|
+
"""The group this breakpoint breaks out of, or None when it names none."""
|
|
404
|
+
...
|
|
405
|
+
|
|
406
|
+
@property
|
|
407
|
+
def await_connection(self) -> bool:
|
|
408
|
+
"""Whether the breakpoint is waiting for the node it resumes into."""
|
|
409
|
+
...
|