proact-py 0.1.0__tar.gz
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.
- proact_py-0.1.0/MANIFEST.in +2 -0
- proact_py-0.1.0/Makefile +32 -0
- proact_py-0.1.0/PKG-INFO +207 -0
- proact_py-0.1.0/README.md +197 -0
- proact_py-0.1.0/proact/__init__.py +5 -0
- proact_py-0.1.0/proact/engine.py +158 -0
- proact_py-0.1.0/proact/tests/__init__.py +0 -0
- proact_py-0.1.0/proact/tests/test_engine.py +143 -0
- proact_py-0.1.0/proact_py.egg-info/PKG-INFO +207 -0
- proact_py-0.1.0/proact_py.egg-info/SOURCES.txt +20 -0
- proact_py-0.1.0/proact_py.egg-info/dependency_links.txt +1 -0
- proact_py-0.1.0/proact_py.egg-info/requires.txt +3 -0
- proact_py-0.1.0/proact_py.egg-info/top_level.txt +1 -0
- proact_py-0.1.0/pyproject.toml +17 -0
- proact_py-0.1.0/setup.cfg +4 -0
- proact_py-0.1.0/setup.py +51 -0
- proact_py-0.1.0/src/eval.c +440 -0
- proact_py-0.1.0/src/main.c +240 -0
- proact_py-0.1.0/src/parse.c +463 -0
- proact_py-0.1.0/src/proact.h +143 -0
- proact_py-0.1.0/src/term.c +157 -0
- proact_py-0.1.0/src/unify.c +106 -0
proact_py-0.1.0/Makefile
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
CC ?= gcc
|
|
2
|
+
CFLAGS = -Wall -Wextra -O2 -std=c11
|
|
3
|
+
SRCDIR = src
|
|
4
|
+
SOURCES = $(SRCDIR)/term.c $(SRCDIR)/parse.c $(SRCDIR)/unify.c $(SRCDIR)/eval.c $(SRCDIR)/main.c
|
|
5
|
+
LIB_SOURCES = $(SRCDIR)/term.c $(SRCDIR)/parse.c $(SRCDIR)/unify.c $(SRCDIR)/eval.c
|
|
6
|
+
BUILDDIR = build
|
|
7
|
+
TARGET = $(BUILDDIR)/proact
|
|
8
|
+
|
|
9
|
+
all: $(TARGET)
|
|
10
|
+
|
|
11
|
+
$(BUILDDIR):
|
|
12
|
+
mkdir -p $(BUILDDIR)
|
|
13
|
+
|
|
14
|
+
$(TARGET): $(SOURCES) $(SRCDIR)/proact.h | $(BUILDDIR)
|
|
15
|
+
$(CC) $(CFLAGS) -I$(SRCDIR) $(SOURCES) -o $(TARGET)
|
|
16
|
+
|
|
17
|
+
test: test/test_proact.c $(LIB_SOURCES) $(SRCDIR)/proact.h | $(BUILDDIR)
|
|
18
|
+
$(CC) $(CFLAGS) -I$(SRCDIR) $(LIB_SOURCES) test/test_proact.c -o $(BUILDDIR)/test_proact
|
|
19
|
+
./$(BUILDDIR)/test_proact
|
|
20
|
+
|
|
21
|
+
coverage: test/test_proact.c $(LIB_SOURCES) $(SRCDIR)/proact.h | $(BUILDDIR)
|
|
22
|
+
$(CC) -Wall -Wextra -std=c11 -O0 --coverage -I$(SRCDIR) $(LIB_SOURCES) test/test_proact.c -o $(BUILDDIR)/test_proact_cov
|
|
23
|
+
ulimit -s unlimited && ./$(BUILDDIR)/test_proact_cov
|
|
24
|
+
lcov --capture --directory . --output-file $(BUILDDIR)/coverage.info --ignore-errors inconsistent
|
|
25
|
+
lcov --remove $(BUILDDIR)/coverage.info '*/test/*' --output-file $(BUILDDIR)/coverage.info --ignore-errors unused
|
|
26
|
+
lcov --list $(BUILDDIR)/coverage.info
|
|
27
|
+
|
|
28
|
+
clean:
|
|
29
|
+
rm -rf $(BUILDDIR)
|
|
30
|
+
rm -f $(SRCDIR)/*.gcda $(SRCDIR)/*.gcno
|
|
31
|
+
|
|
32
|
+
.PHONY: all test coverage clean
|
proact_py-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: proact-py
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Active logic meets Prolog — three-valued reasoning engine
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Requires-Python: >=3.8
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Provides-Extra: dev
|
|
9
|
+
Requires-Dist: pytest; extra == "dev"
|
|
10
|
+
|
|
11
|
+
# Proact
|
|
12
|
+
|
|
13
|
+
Prolog has two truth values: succeed and fail. Proact adds a third: **cont** — *work/reasoning in progress*.
|
|
14
|
+
|
|
15
|
+
Every goal returns a status. Conjunction is a sequence. Disjunction is a selector. Backtracking only triggers on failure — `cont` and `done` cut the search. Between ticks, queries returning `cont` are automatically re-evaluated while the database persists, giving you a stateless tick loop for free.
|
|
16
|
+
|
|
17
|
+
Applications:
|
|
18
|
+
|
|
19
|
+
- **Reasoning under uncertainty.** The three values map onto epistemic states: *verified*, *undetermined*, *refuted*. A conjunction of checks naturally implements iterative deepening — work focuses on the earliest unresolved premise. Unary operators give you cognitive control: `demote` means "don't commit yet" (done→cont); `promote` means "don't give up yet" (fail→cont). This is relevant to validation layers for LLM reasoning, where forcing every check into true/false is lossy compression of the actual epistemic state.
|
|
20
|
+
|
|
21
|
+
- **Behavior modeling.** Reactive agents, planners, robot controllers — anything where logic meets time. Prolog gives you unification, backtracking, and pattern matching. Active logic adds temporal flow. You get behavior trees with the expressiveness of logic programming, without the trees.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
pip install git+https://github.com/eelstork/proact.git
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Requires a C compiler (`gcc`, `clang`, or MSVC).
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from proact import Proact
|
|
33
|
+
|
|
34
|
+
p = Proact()
|
|
35
|
+
p.add("mortal(X) :- human(X). human(socrates).")
|
|
36
|
+
print(p.query("mortal(socrates)")) # Result(status='done')
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Tutorials
|
|
40
|
+
|
|
41
|
+
- [Reasoning under uncertainty](tutorial/reasoning.md) — from predicate logic to three-valued epistemics
|
|
42
|
+
- [Behavior modeling](tutorial/behavior.md) — behavior trees via logic programming
|
|
43
|
+
|
|
44
|
+
## Build
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
make
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Usage
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
./proact # REPL
|
|
54
|
+
./proact file.pa # consult file, then REPL
|
|
55
|
+
./proact file.pa -e "goal." # evaluate goal and exit
|
|
56
|
+
./proact --max-ticks 100 -e "g." # limit tick count
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## The Transform
|
|
60
|
+
|
|
61
|
+
### Conjunction (`,`) — Active logic AND / Sequence
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
fail cont done
|
|
65
|
+
fail fail fail fail
|
|
66
|
+
cont cont cont cont
|
|
67
|
+
done fail cont done
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
If the left goal is `cont`, the whole conjunction is `cont` — don't evaluate the right side yet.
|
|
71
|
+
|
|
72
|
+
### Disjunction (`;`) — Active logic OR / Selector
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
fail cont done
|
|
76
|
+
fail fail cont done
|
|
77
|
+
cont cont cont cont
|
|
78
|
+
done done done done
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
If the left goal is `done` or `cont`, don't try alternatives.
|
|
82
|
+
|
|
83
|
+
### Clause resolution
|
|
84
|
+
|
|
85
|
+
Multiple clauses for the same predicate provide choice points. Backtracking occurs **only on fail** — if a clause body returns `cont` or `done`, stop searching.
|
|
86
|
+
|
|
87
|
+
### Tick loop
|
|
88
|
+
|
|
89
|
+
Queries returning `cont` are automatically re-evaluated on the next tick with a fresh environment. The database (asserted facts) persists between ticks.
|
|
90
|
+
|
|
91
|
+
## Built-ins
|
|
92
|
+
|
|
93
|
+
| Predicate | Status | Description |
|
|
94
|
+
|---|---|---|
|
|
95
|
+
| `done` / `true` | done | Always succeeds |
|
|
96
|
+
| `fail` / `false` | fail | Always fails |
|
|
97
|
+
| `cont` | cont | Always continues |
|
|
98
|
+
| `inv(G)` / `\+ G` | swap done/fail, keep cont | Inverter |
|
|
99
|
+
| `condone(G)` | fail→done | Forgive failure |
|
|
100
|
+
| `promote(G)` | fail→cont, cont→done | Optimistic |
|
|
101
|
+
| `demote(G)` | done→cont, cont→fail | Pessimistic |
|
|
102
|
+
| `par_any(A,B)` | max(A,B) | Lenient parallel |
|
|
103
|
+
| `par_all(A,B)` | min(A,B) | Strict parallel |
|
|
104
|
+
| `tick(N)` | done | Unify N with current tick |
|
|
105
|
+
|
|
106
|
+
Plus standard Prolog: `write/1`, `writeln/1`, `nl`, `assert/1`, `retract/1`, `is/2`, `=/2`, `\=/2`, `</2`, `>/2`, `>=/2`, `=</2`, `call/1`, `consult/1`.
|
|
107
|
+
|
|
108
|
+
## Examples
|
|
109
|
+
|
|
110
|
+
### Wait N ticks, then succeed
|
|
111
|
+
|
|
112
|
+
```prolog
|
|
113
|
+
wait(N) :- tick(T), T >= N.
|
|
114
|
+
wait(N) :- tick(T), T < N, cont.
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
?- wait(3), writeln(hello).
|
|
119
|
+
[tick 0] cont
|
|
120
|
+
[tick 1] cont
|
|
121
|
+
[tick 2] cont
|
|
122
|
+
hello
|
|
123
|
+
done.
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Counter (stateful, using assert/retract)
|
|
127
|
+
|
|
128
|
+
```prolog
|
|
129
|
+
:- assert(count(0)).
|
|
130
|
+
|
|
131
|
+
count_to(Target) :- count(N), N >= Target.
|
|
132
|
+
count_to(Target) :-
|
|
133
|
+
count(N), N < Target,
|
|
134
|
+
retract(count(N)), N1 is N + 1, assert(count(N1)),
|
|
135
|
+
cont.
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Reactive behavior (condition-gated sequence)
|
|
139
|
+
|
|
140
|
+
```prolog
|
|
141
|
+
:- assert(agent_pos(0)).
|
|
142
|
+
|
|
143
|
+
move_to(X) :- agent_pos(P), P =:= X.
|
|
144
|
+
move_to(X) :- agent_pos(P), P < X,
|
|
145
|
+
retract(agent_pos(P)), P1 is P + 1, assert(agent_pos(P1)), cont.
|
|
146
|
+
|
|
147
|
+
patrol :-
|
|
148
|
+
agent_pos(P), P < 3, move_to(3), cont.
|
|
149
|
+
patrol :-
|
|
150
|
+
agent_pos(P), P >= 3, P < 5, move_to(5), cont.
|
|
151
|
+
patrol :-
|
|
152
|
+
agent_pos(P), P >= 5.
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Reasoning under uncertainty
|
|
156
|
+
|
|
157
|
+
```prolog
|
|
158
|
+
% reason.pa — three-valued validation for multi-step arguments
|
|
159
|
+
|
|
160
|
+
:- assert(known(premise_a)).
|
|
161
|
+
:- assert(investigation(premise_b, 2)).
|
|
162
|
+
:- assert(investigation(premise_c, 1)).
|
|
163
|
+
|
|
164
|
+
check(P) :- known(P).
|
|
165
|
+
check(P) :- known_false(P), fail.
|
|
166
|
+
check(P) :-
|
|
167
|
+
investigation(P, N), N > 0,
|
|
168
|
+
retract(investigation(P, N)), N1 is N - 1,
|
|
169
|
+
(N1 > 0 -> assert(investigation(P, N1)) ; assert(known(P))),
|
|
170
|
+
cont.
|
|
171
|
+
|
|
172
|
+
argument :- check(premise_a), check(premise_b), check(premise_c).
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
The conjunction *is* the search strategy — it focuses work on the earliest unresolved premise and doesn't waste computation on steps whose preconditions haven't been met:
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
[tick 0] a=done, b=cont → cont (c never reached)
|
|
179
|
+
[tick 1] a=done, b=done, c=cont → cont (work shifts to c)
|
|
180
|
+
[tick 2] a=done, b=done, c=done → done
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
The epistemic operators give cognitive control over the validation:
|
|
184
|
+
|
|
185
|
+
```prolog
|
|
186
|
+
cautious(X) :- demote(check(X)). % "verified but needs review" (done→cont)
|
|
187
|
+
keep_exploring(X) :- promote(check(X)). % "refuted but try harder" (fail→cont)
|
|
188
|
+
viable_theory :- par_any(hyp_a, hyp_b). % first hypothesis verified wins
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Relation to active-logic
|
|
192
|
+
|
|
193
|
+
This prototype implements the core operators from [activelogic-cs](https://github.com/active-logic/activelogic-cs) in a logic programming context:
|
|
194
|
+
|
|
195
|
+
| C# (activelogic-cs) | Proact | BT equivalent |
|
|
196
|
+
|---|---|---|
|
|
197
|
+
| `status && status` | `Goal, Goal` | Sequence |
|
|
198
|
+
| `status \|\| status` | `Goal ; Goal` | Selector |
|
|
199
|
+
| `+` (lenient) | `par_any(A, B)` | Parallel (any) |
|
|
200
|
+
| `*` (strict) | `par_all(A, B)` | Parallel (all) |
|
|
201
|
+
| `!status` | `inv(G)` | Inverter |
|
|
202
|
+
| `~status` | `condone(G)` | Condone |
|
|
203
|
+
| `+status` (unary) | `promote(G)` | Promoter |
|
|
204
|
+
| `-status` (unary) | `demote(G)` | Demoter |
|
|
205
|
+
| `cont` | `cont` | Running |
|
|
206
|
+
|
|
207
|
+
The key addition over Prolog is `cont` — a single built-in that bridges logic programming with reactive, tick-based behavior.
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# Proact
|
|
2
|
+
|
|
3
|
+
Prolog has two truth values: succeed and fail. Proact adds a third: **cont** — *work/reasoning in progress*.
|
|
4
|
+
|
|
5
|
+
Every goal returns a status. Conjunction is a sequence. Disjunction is a selector. Backtracking only triggers on failure — `cont` and `done` cut the search. Between ticks, queries returning `cont` are automatically re-evaluated while the database persists, giving you a stateless tick loop for free.
|
|
6
|
+
|
|
7
|
+
Applications:
|
|
8
|
+
|
|
9
|
+
- **Reasoning under uncertainty.** The three values map onto epistemic states: *verified*, *undetermined*, *refuted*. A conjunction of checks naturally implements iterative deepening — work focuses on the earliest unresolved premise. Unary operators give you cognitive control: `demote` means "don't commit yet" (done→cont); `promote` means "don't give up yet" (fail→cont). This is relevant to validation layers for LLM reasoning, where forcing every check into true/false is lossy compression of the actual epistemic state.
|
|
10
|
+
|
|
11
|
+
- **Behavior modeling.** Reactive agents, planners, robot controllers — anything where logic meets time. Prolog gives you unification, backtracking, and pattern matching. Active logic adds temporal flow. You get behavior trees with the expressiveness of logic programming, without the trees.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
pip install git+https://github.com/eelstork/proact.git
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Requires a C compiler (`gcc`, `clang`, or MSVC).
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from proact import Proact
|
|
23
|
+
|
|
24
|
+
p = Proact()
|
|
25
|
+
p.add("mortal(X) :- human(X). human(socrates).")
|
|
26
|
+
print(p.query("mortal(socrates)")) # Result(status='done')
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Tutorials
|
|
30
|
+
|
|
31
|
+
- [Reasoning under uncertainty](tutorial/reasoning.md) — from predicate logic to three-valued epistemics
|
|
32
|
+
- [Behavior modeling](tutorial/behavior.md) — behavior trees via logic programming
|
|
33
|
+
|
|
34
|
+
## Build
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
make
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
./proact # REPL
|
|
44
|
+
./proact file.pa # consult file, then REPL
|
|
45
|
+
./proact file.pa -e "goal." # evaluate goal and exit
|
|
46
|
+
./proact --max-ticks 100 -e "g." # limit tick count
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## The Transform
|
|
50
|
+
|
|
51
|
+
### Conjunction (`,`) — Active logic AND / Sequence
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
fail cont done
|
|
55
|
+
fail fail fail fail
|
|
56
|
+
cont cont cont cont
|
|
57
|
+
done fail cont done
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
If the left goal is `cont`, the whole conjunction is `cont` — don't evaluate the right side yet.
|
|
61
|
+
|
|
62
|
+
### Disjunction (`;`) — Active logic OR / Selector
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
fail cont done
|
|
66
|
+
fail fail cont done
|
|
67
|
+
cont cont cont cont
|
|
68
|
+
done done done done
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
If the left goal is `done` or `cont`, don't try alternatives.
|
|
72
|
+
|
|
73
|
+
### Clause resolution
|
|
74
|
+
|
|
75
|
+
Multiple clauses for the same predicate provide choice points. Backtracking occurs **only on fail** — if a clause body returns `cont` or `done`, stop searching.
|
|
76
|
+
|
|
77
|
+
### Tick loop
|
|
78
|
+
|
|
79
|
+
Queries returning `cont` are automatically re-evaluated on the next tick with a fresh environment. The database (asserted facts) persists between ticks.
|
|
80
|
+
|
|
81
|
+
## Built-ins
|
|
82
|
+
|
|
83
|
+
| Predicate | Status | Description |
|
|
84
|
+
|---|---|---|
|
|
85
|
+
| `done` / `true` | done | Always succeeds |
|
|
86
|
+
| `fail` / `false` | fail | Always fails |
|
|
87
|
+
| `cont` | cont | Always continues |
|
|
88
|
+
| `inv(G)` / `\+ G` | swap done/fail, keep cont | Inverter |
|
|
89
|
+
| `condone(G)` | fail→done | Forgive failure |
|
|
90
|
+
| `promote(G)` | fail→cont, cont→done | Optimistic |
|
|
91
|
+
| `demote(G)` | done→cont, cont→fail | Pessimistic |
|
|
92
|
+
| `par_any(A,B)` | max(A,B) | Lenient parallel |
|
|
93
|
+
| `par_all(A,B)` | min(A,B) | Strict parallel |
|
|
94
|
+
| `tick(N)` | done | Unify N with current tick |
|
|
95
|
+
|
|
96
|
+
Plus standard Prolog: `write/1`, `writeln/1`, `nl`, `assert/1`, `retract/1`, `is/2`, `=/2`, `\=/2`, `</2`, `>/2`, `>=/2`, `=</2`, `call/1`, `consult/1`.
|
|
97
|
+
|
|
98
|
+
## Examples
|
|
99
|
+
|
|
100
|
+
### Wait N ticks, then succeed
|
|
101
|
+
|
|
102
|
+
```prolog
|
|
103
|
+
wait(N) :- tick(T), T >= N.
|
|
104
|
+
wait(N) :- tick(T), T < N, cont.
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
?- wait(3), writeln(hello).
|
|
109
|
+
[tick 0] cont
|
|
110
|
+
[tick 1] cont
|
|
111
|
+
[tick 2] cont
|
|
112
|
+
hello
|
|
113
|
+
done.
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Counter (stateful, using assert/retract)
|
|
117
|
+
|
|
118
|
+
```prolog
|
|
119
|
+
:- assert(count(0)).
|
|
120
|
+
|
|
121
|
+
count_to(Target) :- count(N), N >= Target.
|
|
122
|
+
count_to(Target) :-
|
|
123
|
+
count(N), N < Target,
|
|
124
|
+
retract(count(N)), N1 is N + 1, assert(count(N1)),
|
|
125
|
+
cont.
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Reactive behavior (condition-gated sequence)
|
|
129
|
+
|
|
130
|
+
```prolog
|
|
131
|
+
:- assert(agent_pos(0)).
|
|
132
|
+
|
|
133
|
+
move_to(X) :- agent_pos(P), P =:= X.
|
|
134
|
+
move_to(X) :- agent_pos(P), P < X,
|
|
135
|
+
retract(agent_pos(P)), P1 is P + 1, assert(agent_pos(P1)), cont.
|
|
136
|
+
|
|
137
|
+
patrol :-
|
|
138
|
+
agent_pos(P), P < 3, move_to(3), cont.
|
|
139
|
+
patrol :-
|
|
140
|
+
agent_pos(P), P >= 3, P < 5, move_to(5), cont.
|
|
141
|
+
patrol :-
|
|
142
|
+
agent_pos(P), P >= 5.
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Reasoning under uncertainty
|
|
146
|
+
|
|
147
|
+
```prolog
|
|
148
|
+
% reason.pa — three-valued validation for multi-step arguments
|
|
149
|
+
|
|
150
|
+
:- assert(known(premise_a)).
|
|
151
|
+
:- assert(investigation(premise_b, 2)).
|
|
152
|
+
:- assert(investigation(premise_c, 1)).
|
|
153
|
+
|
|
154
|
+
check(P) :- known(P).
|
|
155
|
+
check(P) :- known_false(P), fail.
|
|
156
|
+
check(P) :-
|
|
157
|
+
investigation(P, N), N > 0,
|
|
158
|
+
retract(investigation(P, N)), N1 is N - 1,
|
|
159
|
+
(N1 > 0 -> assert(investigation(P, N1)) ; assert(known(P))),
|
|
160
|
+
cont.
|
|
161
|
+
|
|
162
|
+
argument :- check(premise_a), check(premise_b), check(premise_c).
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The conjunction *is* the search strategy — it focuses work on the earliest unresolved premise and doesn't waste computation on steps whose preconditions haven't been met:
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
[tick 0] a=done, b=cont → cont (c never reached)
|
|
169
|
+
[tick 1] a=done, b=done, c=cont → cont (work shifts to c)
|
|
170
|
+
[tick 2] a=done, b=done, c=done → done
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
The epistemic operators give cognitive control over the validation:
|
|
174
|
+
|
|
175
|
+
```prolog
|
|
176
|
+
cautious(X) :- demote(check(X)). % "verified but needs review" (done→cont)
|
|
177
|
+
keep_exploring(X) :- promote(check(X)). % "refuted but try harder" (fail→cont)
|
|
178
|
+
viable_theory :- par_any(hyp_a, hyp_b). % first hypothesis verified wins
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Relation to active-logic
|
|
182
|
+
|
|
183
|
+
This prototype implements the core operators from [activelogic-cs](https://github.com/active-logic/activelogic-cs) in a logic programming context:
|
|
184
|
+
|
|
185
|
+
| C# (activelogic-cs) | Proact | BT equivalent |
|
|
186
|
+
|---|---|---|
|
|
187
|
+
| `status && status` | `Goal, Goal` | Sequence |
|
|
188
|
+
| `status \|\| status` | `Goal ; Goal` | Selector |
|
|
189
|
+
| `+` (lenient) | `par_any(A, B)` | Parallel (any) |
|
|
190
|
+
| `*` (strict) | `par_all(A, B)` | Parallel (all) |
|
|
191
|
+
| `!status` | `inv(G)` | Inverter |
|
|
192
|
+
| `~status` | `condone(G)` | Condone |
|
|
193
|
+
| `+status` (unary) | `promote(G)` | Promoter |
|
|
194
|
+
| `-status` (unary) | `demote(G)` | Demoter |
|
|
195
|
+
| `cont` | `cont` | Running |
|
|
196
|
+
|
|
197
|
+
The key addition over Prolog is `cont` — a single built-in that bridges logic programming with reactive, tick-based behavior.
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""Subprocess wrapper for the proact binary."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
import subprocess
|
|
6
|
+
import tempfile
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ProactError(Exception):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _find_binary():
|
|
14
|
+
"""Find the proact binary — packaged, local build, or PATH."""
|
|
15
|
+
# 1. Bundled with pip install
|
|
16
|
+
pkg_dir = os.path.dirname(__file__)
|
|
17
|
+
for name in ("proact", "proact.exe"):
|
|
18
|
+
path = os.path.join(pkg_dir, name)
|
|
19
|
+
if os.path.isfile(path):
|
|
20
|
+
return path
|
|
21
|
+
|
|
22
|
+
# 2. Built locally (repo root/build/)
|
|
23
|
+
repo = os.path.dirname(pkg_dir)
|
|
24
|
+
for subdir in ("build", ""):
|
|
25
|
+
for name in ("proact", "proact.exe"):
|
|
26
|
+
path = os.path.join(repo, subdir, name)
|
|
27
|
+
if os.path.isfile(path):
|
|
28
|
+
return path
|
|
29
|
+
|
|
30
|
+
# 3. On PATH
|
|
31
|
+
from shutil import which
|
|
32
|
+
path = which("proact")
|
|
33
|
+
if path:
|
|
34
|
+
return path
|
|
35
|
+
|
|
36
|
+
raise ProactError(
|
|
37
|
+
"proact binary not found. "
|
|
38
|
+
"Run 'make' to build it or 'pip install .' to install the package."
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class Proact:
|
|
43
|
+
"""Three-valued reasoning engine.
|
|
44
|
+
|
|
45
|
+
Example::
|
|
46
|
+
|
|
47
|
+
p = Proact()
|
|
48
|
+
p.consult("rules.pa")
|
|
49
|
+
result = p.query("mortal(socrates)")
|
|
50
|
+
print(result.status) # 'done'
|
|
51
|
+
|
|
52
|
+
# Multi-tick evaluation
|
|
53
|
+
p.add("wait(3) :- tick(T), T >= 3.")
|
|
54
|
+
p.add("wait(3) :- tick(T), T < 3, cont.")
|
|
55
|
+
result = p.query("wait(3)", max_ticks=10)
|
|
56
|
+
print(result.ticks) # 4
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(self, binary=None, max_ticks=1000, max_depth=512, trace=False):
|
|
60
|
+
self._binary = binary or _find_binary()
|
|
61
|
+
self._files = []
|
|
62
|
+
self._inline = []
|
|
63
|
+
self.max_ticks = max_ticks
|
|
64
|
+
self.max_depth = max_depth
|
|
65
|
+
self.trace = trace
|
|
66
|
+
|
|
67
|
+
def consult(self, path):
|
|
68
|
+
"""Load a .pa file."""
|
|
69
|
+
if not os.path.isfile(path):
|
|
70
|
+
raise FileNotFoundError(f"No such file: {path}")
|
|
71
|
+
self._files.append(os.path.abspath(path))
|
|
72
|
+
return self
|
|
73
|
+
|
|
74
|
+
def add(self, source):
|
|
75
|
+
"""Add clauses/directives from a string."""
|
|
76
|
+
self._inline.append(source)
|
|
77
|
+
return self
|
|
78
|
+
|
|
79
|
+
def query(self, goal, max_ticks=None):
|
|
80
|
+
"""Evaluate a goal. Returns a Result."""
|
|
81
|
+
if not goal.endswith("."):
|
|
82
|
+
goal += "."
|
|
83
|
+
|
|
84
|
+
ticks = max_ticks or self.max_ticks
|
|
85
|
+
|
|
86
|
+
# Build a combined source file if we have inline clauses
|
|
87
|
+
tmp = None
|
|
88
|
+
files = list(self._files)
|
|
89
|
+
|
|
90
|
+
if self._inline:
|
|
91
|
+
tmp = tempfile.NamedTemporaryFile(
|
|
92
|
+
mode="w", suffix=".pa", delete=False
|
|
93
|
+
)
|
|
94
|
+
tmp.write("\n".join(self._inline) + "\n")
|
|
95
|
+
tmp.close()
|
|
96
|
+
files.append(tmp.name)
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
cmd = [self._binary] + files + [
|
|
100
|
+
"--max-ticks", str(ticks),
|
|
101
|
+
"-q", # quiet — we parse output ourselves
|
|
102
|
+
"-e", goal,
|
|
103
|
+
]
|
|
104
|
+
if self.trace:
|
|
105
|
+
cmd.append("--trace")
|
|
106
|
+
|
|
107
|
+
proc = subprocess.run(
|
|
108
|
+
cmd, capture_output=True, text=True, timeout=30,
|
|
109
|
+
)
|
|
110
|
+
return Result._parse(proc)
|
|
111
|
+
finally:
|
|
112
|
+
if tmp:
|
|
113
|
+
os.unlink(tmp.name)
|
|
114
|
+
|
|
115
|
+
def run(self, goal="main", max_ticks=None):
|
|
116
|
+
"""Shorthand for query(goal, max_ticks)."""
|
|
117
|
+
return self.query(goal, max_ticks)
|
|
118
|
+
|
|
119
|
+
def reset(self):
|
|
120
|
+
"""Clear all loaded files and inline clauses."""
|
|
121
|
+
self._files.clear()
|
|
122
|
+
self._inline.clear()
|
|
123
|
+
return self
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class Result:
|
|
127
|
+
"""Result of a proact query."""
|
|
128
|
+
|
|
129
|
+
__slots__ = ("status", "output", "returncode")
|
|
130
|
+
|
|
131
|
+
def __init__(self, status, output, returncode):
|
|
132
|
+
self.status = status
|
|
133
|
+
self.output = output
|
|
134
|
+
self.returncode = returncode
|
|
135
|
+
|
|
136
|
+
def _parse(proc):
|
|
137
|
+
stdout = proc.stdout or ""
|
|
138
|
+
stderr = proc.stderr or ""
|
|
139
|
+
output = (stdout + stderr).strip()
|
|
140
|
+
|
|
141
|
+
if proc.returncode == 0:
|
|
142
|
+
status = "done"
|
|
143
|
+
elif proc.returncode == 1:
|
|
144
|
+
status = "fail"
|
|
145
|
+
else:
|
|
146
|
+
status = "error"
|
|
147
|
+
|
|
148
|
+
return Result(status=status, output=output, returncode=proc.returncode)
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def ok(self):
|
|
152
|
+
return self.status == "done"
|
|
153
|
+
|
|
154
|
+
def __repr__(self):
|
|
155
|
+
return f"Result(status={self.status!r})"
|
|
156
|
+
|
|
157
|
+
def __bool__(self):
|
|
158
|
+
return self.ok
|
|
File without changes
|