tensorcode 0.1.0a1__py3-none-any.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.
- tensorcode/__init__.py +84 -0
- tensorcode/actions.py +137 -0
- tensorcode/answer_type.py +222 -0
- tensorcode/awareness.py +344 -0
- tensorcode/backends/__init__.py +0 -0
- tensorcode/backends/builtin.py +167 -0
- tensorcode/backends/hf_local.py +89 -0
- tensorcode/backends/linear.py +133 -0
- tensorcode/backends/neural.py +361 -0
- tensorcode/causal.py +262 -0
- tensorcode/change.py +566 -0
- tensorcode/chunking.py +195 -0
- tensorcode/cognition.py +311 -0
- tensorcode/context.py +97 -0
- tensorcode/control.py +291 -0
- tensorcode/cues.py +192 -0
- tensorcode/expectation.py +270 -0
- tensorcode/frames.py +232 -0
- tensorcode/language/__init__.py +36 -0
- tensorcode/language/chart.py +558 -0
- tensorcode/language/discourse.py +132 -0
- tensorcode/language/domains/__init__.py +0 -0
- tensorcode/language/domains/desktop.py +552 -0
- tensorcode/language/english.py +459 -0
- tensorcode/language/features.py +112 -0
- tensorcode/language/generate.py +574 -0
- tensorcode/language/grammar.py +893 -0
- tensorcode/language/semantics.py +349 -0
- tensorcode/learning/__init__.py +30 -0
- tensorcode/learning/certificate.py +148 -0
- tensorcode/learning/induce.py +304 -0
- tensorcode/learning/library.py +217 -0
- tensorcode/learning/literals.py +126 -0
- tensorcode/learning/verify.py +253 -0
- tensorcode/memory.py +303 -0
- tensorcode/metacognition.py +351 -0
- tensorcode/ops.py +207 -0
- tensorcode/outcomes.py +99 -0
- tensorcode/permanence.py +376 -0
- tensorcode/priming.py +191 -0
- tensorcode/py.typed +0 -0
- tensorcode/quantity.py +311 -0
- tensorcode/records.py +728 -0
- tensorcode/relation.py +771 -0
- tensorcode/runtime.py +471 -0
- tensorcode/semantics_bridge.py +308 -0
- tensorcode/social.py +380 -0
- tensorcode/temporal.py +189 -0
- tensorcode/wants.py +185 -0
- tensorcode-0.1.0a1.dist-info/METADATA +196 -0
- tensorcode-0.1.0a1.dist-info/RECORD +53 -0
- tensorcode-0.1.0a1.dist-info/WHEEL +4 -0
- tensorcode-0.1.0a1.dist-info/licenses/LICENSE +21 -0
tensorcode/causal.py
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"""Causal claims, kept apart from evidential ones, and earned by intervention.
|
|
2
|
+
|
|
3
|
+
Provenance already answers "why do I believe this" — which source said it. It does not
|
|
4
|
+
answer "what makes this happen", and the two are routinely confused: a claim derived from
|
|
5
|
+
another is *supported* by it, not *caused* by it. So causation is its own record, and it
|
|
6
|
+
carries how it was learned:
|
|
7
|
+
|
|
8
|
+
* ``observational`` support means the two were seen together. That is a correlation, and
|
|
9
|
+
this module names it one.
|
|
10
|
+
* ``interventional`` support means the same state was run twice, once with the act and once
|
|
11
|
+
without, and the effect differed. An agent's own action is a real ``do(·)``, and a world
|
|
12
|
+
that can fork gives the untaken branch for free.
|
|
13
|
+
|
|
14
|
+
:func:`experiment` runs that controlled pair, :func:`learn` turns contrasts into claims
|
|
15
|
+
with an effect size, and :func:`counterfactual` records the branch that did not happen in a
|
|
16
|
+
scope of its own, so "what would have happened" never leaks into the shared world.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import hashlib
|
|
22
|
+
import json
|
|
23
|
+
from dataclasses import dataclass, field
|
|
24
|
+
from datetime import datetime, timezone
|
|
25
|
+
from typing import Any, Callable, Iterable, Mapping, Sequence
|
|
26
|
+
|
|
27
|
+
from .outcomes import Score, Unknown
|
|
28
|
+
from .records import Claim, Evidence, Ref, Store
|
|
29
|
+
|
|
30
|
+
Support = str # "interventional" | "observational"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _digest(payload: Any) -> str:
|
|
34
|
+
return hashlib.sha256(json.dumps(payload, sort_keys=True, default=str).encode()).hexdigest()[:12]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True)
|
|
38
|
+
class Contrast:
|
|
39
|
+
"""One aspect, observed with the act and without it, from the same starting state."""
|
|
40
|
+
|
|
41
|
+
cause: str
|
|
42
|
+
aspect: str
|
|
43
|
+
with_act: tuple[Any, ...]
|
|
44
|
+
without_act: tuple[Any, ...]
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def trials(self) -> int:
|
|
48
|
+
return min(len(self.with_act), len(self.without_act))
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def changed_with(self) -> float:
|
|
52
|
+
"""Fraction of with-act runs where this aspect ended up different from its control."""
|
|
53
|
+
if not self.with_act or not self.without_act:
|
|
54
|
+
return 0.0
|
|
55
|
+
pairs = zip(self.with_act, self.without_act)
|
|
56
|
+
return sum(1 for a, b in pairs if a != b) / self.trials
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def effect(self) -> float:
|
|
60
|
+
"""The average causal effect: how often the act, and only the act, moved this aspect."""
|
|
61
|
+
return self.changed_with
|
|
62
|
+
|
|
63
|
+
def describe(self) -> str:
|
|
64
|
+
return f"{self.cause} → {self.aspect}: {self.with_act[:1]} vs control {self.without_act[:1]} (effect {self.effect:.2f}, n={self.trials})"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass(frozen=True)
|
|
68
|
+
class Causal:
|
|
69
|
+
"""``cause`` brings about ``effect``, with the evidence that says so."""
|
|
70
|
+
|
|
71
|
+
cause: str
|
|
72
|
+
aspect: str
|
|
73
|
+
effect: Any
|
|
74
|
+
support: Support
|
|
75
|
+
strength: Score
|
|
76
|
+
trials: int
|
|
77
|
+
mechanism: str | None = None
|
|
78
|
+
enabling: tuple[str, ...] = () # conditions that must hold for the link to fire
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def ref(self) -> Ref:
|
|
82
|
+
return Ref(f"causal:{_digest([self.cause, self.aspect, str(self.effect), self.support])}")
|
|
83
|
+
|
|
84
|
+
def describe(self) -> str:
|
|
85
|
+
how = f" via {self.mechanism}" if self.mechanism else ""
|
|
86
|
+
needs = f" when {', '.join(self.enabling)}" if self.enabling else ""
|
|
87
|
+
return f"{self.cause} causes {self.aspect}={self.effect!r}{how}{needs} [{self.support}, {self.strength.value:.2f}, n={self.trials}]"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ------------------------------------------------------------- interventions
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def experiment(
|
|
94
|
+
*,
|
|
95
|
+
prepare: Callable[[], Any],
|
|
96
|
+
act: Callable[[Any], None],
|
|
97
|
+
observe: Callable[[Any], Mapping[str, Any]],
|
|
98
|
+
cause: str,
|
|
99
|
+
trials: int = 1,
|
|
100
|
+
settle: Callable[[Any], None] | None = None,
|
|
101
|
+
control: Callable[[Any], None] | None = None,
|
|
102
|
+
) -> list[Contrast]:
|
|
103
|
+
"""Run the same starting state with and without ``act``; report what differed.
|
|
104
|
+
|
|
105
|
+
``prepare`` returns a fresh copy of the world (a fork, a restored checkpoint), so the
|
|
106
|
+
two branches differ in exactly one thing: whether the act happened. Anything that moves
|
|
107
|
+
on its own — a clock, an animation, a scheduled event — moves in both branches and so
|
|
108
|
+
cancels out of the contrast, which is the whole point of running the control.
|
|
109
|
+
|
|
110
|
+
``control`` is what the untreated branch does *instead*: an innocuous act of the same
|
|
111
|
+
kind (a click on empty space, a step that changes nothing). Without it the control
|
|
112
|
+
branch does nothing at all, and then everything that follows from merely acting — a
|
|
113
|
+
logical clock, a step counter, a repaint — is scored as an effect of this act. Which
|
|
114
|
+
control is right depends on the question: leave it out to ask "what follows from doing
|
|
115
|
+
this rather than nothing", pass one to ask "what does *this* act do that any act would
|
|
116
|
+
not".
|
|
117
|
+
"""
|
|
118
|
+
with_runs: list[Mapping[str, Any]] = []
|
|
119
|
+
without_runs: list[Mapping[str, Any]] = []
|
|
120
|
+
for _ in range(max(1, trials)):
|
|
121
|
+
treated = prepare()
|
|
122
|
+
act(treated)
|
|
123
|
+
if settle:
|
|
124
|
+
settle(treated)
|
|
125
|
+
with_runs.append(dict(observe(treated)))
|
|
126
|
+
|
|
127
|
+
untreated = prepare()
|
|
128
|
+
if control:
|
|
129
|
+
control(untreated)
|
|
130
|
+
if settle:
|
|
131
|
+
settle(untreated)
|
|
132
|
+
without_runs.append(dict(observe(untreated)))
|
|
133
|
+
aspects = sorted({k for run in with_runs + without_runs for k in run})
|
|
134
|
+
return [
|
|
135
|
+
Contrast(cause, aspect, tuple(run.get(aspect) for run in with_runs), tuple(run.get(aspect) for run in without_runs))
|
|
136
|
+
for aspect in aspects
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def learn(contrasts: Iterable[Contrast], *, least_effect: float = 0.5, basis: str = "intervention") -> list[Causal]:
|
|
141
|
+
"""Causal claims from controlled contrasts. An aspect the act never moved is dropped."""
|
|
142
|
+
out: list[Causal] = []
|
|
143
|
+
for contrast in contrasts:
|
|
144
|
+
if contrast.trials == 0 or contrast.effect < least_effect:
|
|
145
|
+
continue
|
|
146
|
+
value = contrast.with_act[0]
|
|
147
|
+
out.append(Causal(
|
|
148
|
+
cause=contrast.cause, aspect=contrast.aspect, effect=value, support="interventional",
|
|
149
|
+
strength=Score(contrast.effect, "probability", basis=f"{basis}@n={contrast.trials}"),
|
|
150
|
+
trials=contrast.trials,
|
|
151
|
+
))
|
|
152
|
+
return out
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def correlations(observations: Sequence[tuple[set[str], Mapping[str, Any]]], *, cause: str,
|
|
156
|
+
least: float = 0.5, basis: str = "co-occurrence") -> list[Causal]:
|
|
157
|
+
"""The baseline an intervention has to beat: what merely co-occurs with the act.
|
|
158
|
+
|
|
159
|
+
Each observation is ``(acts_that_happened, aspects_observed)``. No control, so a
|
|
160
|
+
consequence of something else that always accompanies the act scores just as highly —
|
|
161
|
+
which is exactly the mistake :func:`experiment` exists to avoid.
|
|
162
|
+
"""
|
|
163
|
+
seen: dict[tuple[str, Any], int] = {}
|
|
164
|
+
total = 0
|
|
165
|
+
for acts, aspects in observations:
|
|
166
|
+
if cause not in acts:
|
|
167
|
+
continue
|
|
168
|
+
total += 1
|
|
169
|
+
for aspect, value in aspects.items():
|
|
170
|
+
seen[(aspect, _hashable(value))] = seen.get((aspect, _hashable(value)), 0) + 1
|
|
171
|
+
if not total:
|
|
172
|
+
return []
|
|
173
|
+
out: list[Causal] = []
|
|
174
|
+
for (aspect, value), hits in sorted(seen.items(), key=lambda kv: -kv[1]):
|
|
175
|
+
share = hits / total
|
|
176
|
+
if share >= least:
|
|
177
|
+
out.append(Causal(cause=cause, aspect=aspect, effect=value, support="observational",
|
|
178
|
+
strength=Score(share, "probability", basis=f"{basis}@n={total}"), trials=total))
|
|
179
|
+
return out
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def moved(before: Mapping[str, Any], after: Mapping[str, Any]) -> dict[str, str]:
|
|
183
|
+
"""Which aspects moved, as events rather than values.
|
|
184
|
+
|
|
185
|
+
Co-occurrence has to be counted over movements, not over exact values: an aspect whose
|
|
186
|
+
value is different every time (a clock, a counter) never repeats, so keying on the value
|
|
187
|
+
makes a perfectly reliable co-occurrence look like noise. This is the same lesson the
|
|
188
|
+
prediction measurement gave — what changed is learnable, what it became often is not.
|
|
189
|
+
"""
|
|
190
|
+
return {key: "changed" for key in sorted(set(before) | set(after)) if before.get(key) != after.get(key)}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _hashable(value: Any) -> Any:
|
|
194
|
+
if isinstance(value, (list, tuple)):
|
|
195
|
+
return tuple(_hashable(v) for v in value)
|
|
196
|
+
if isinstance(value, dict):
|
|
197
|
+
return tuple(sorted((k, _hashable(v)) for k, v in value.items()))
|
|
198
|
+
return value
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
# -------------------------------------------------------------------- claims
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def tell_causal(mind: Store, causal: Causal, *, source: Ref, observed_at: datetime | None = None,
|
|
205
|
+
derived_from: Sequence[str] = ()) -> Ref:
|
|
206
|
+
"""Record a causal link as claims that say how it was learned."""
|
|
207
|
+
at = observed_at or datetime.now(timezone.utc)
|
|
208
|
+
ref = causal.ref
|
|
209
|
+
evidence = Evidence(source=source, observed_at=at, method=f"causal:{causal.support}",
|
|
210
|
+
confidence=causal.strength, derived_from=tuple(derived_from))
|
|
211
|
+
mind.tell(Claim(ref, "is_a", "causal_link"), evidence)
|
|
212
|
+
mind.tell(Claim(ref, "cause", causal.cause), evidence)
|
|
213
|
+
mind.tell(Claim(ref, "aspect", causal.aspect), evidence)
|
|
214
|
+
mind.tell(Claim(ref, "effect", causal.effect), evidence)
|
|
215
|
+
mind.tell(Claim(ref, "support", causal.support), evidence)
|
|
216
|
+
mind.tell(Claim(ref, "trials", causal.trials), evidence)
|
|
217
|
+
if causal.mechanism:
|
|
218
|
+
mind.tell(Claim(ref, "mechanism", causal.mechanism), evidence)
|
|
219
|
+
for condition in causal.enabling:
|
|
220
|
+
mind.tell(Claim(ref, "enabled_by", condition), evidence)
|
|
221
|
+
return ref
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def causes_of(mind: Store, aspect: str, *, interventional_only: bool = False) -> list[Ref]:
|
|
225
|
+
"""Recorded causes of an aspect, strongest support first."""
|
|
226
|
+
links = [r.claim.subject for r in mind.claims(predicate="aspect", object=aspect)]
|
|
227
|
+
out = []
|
|
228
|
+
for ref in links:
|
|
229
|
+
support = next((r.claim.object for r in mind.claims(ref, "support")), None)
|
|
230
|
+
if interventional_only and support != "interventional":
|
|
231
|
+
continue
|
|
232
|
+
out.append((0 if support == "interventional" else 1, ref))
|
|
233
|
+
return [ref for _, ref in sorted(out, key=lambda pair: pair[0])]
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def counterfactual(mind: Store, *, name: str, facts: Iterable[tuple[Ref, str, Any]], source: Ref,
|
|
237
|
+
because: str, observed_at: datetime | None = None) -> Ref:
|
|
238
|
+
"""Record the branch that did not happen, in a scope of its own.
|
|
239
|
+
|
|
240
|
+
A counterfactual is real knowledge — it is what makes an effect size meaningful — but it
|
|
241
|
+
is not true of the world, so it lives in its own scope and never answers a question
|
|
242
|
+
about what is.
|
|
243
|
+
"""
|
|
244
|
+
at = observed_at or datetime.now(timezone.utc)
|
|
245
|
+
scope = Ref(f"scope:counterfactual:{name}")
|
|
246
|
+
evidence = Evidence(source=source, observed_at=at, method="counterfactual")
|
|
247
|
+
mind.tell(Claim(scope, "is_a", "counterfactual"), evidence)
|
|
248
|
+
mind.tell(Claim(scope, "because", because), evidence)
|
|
249
|
+
for subject, predicate, value in facts:
|
|
250
|
+
mind.tell(Claim(subject, predicate, value, scope=scope), evidence)
|
|
251
|
+
return scope
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def distinguish(contrasts: Iterable[Contrast], correlated: Iterable[Causal]) -> dict[str, str]:
|
|
255
|
+
"""Which co-occurring aspects the intervention actually vindicated.
|
|
256
|
+
|
|
257
|
+
Returns aspect → "caused" | "merely_correlated", which is the judgement a correlational
|
|
258
|
+
record cannot make on its own.
|
|
259
|
+
"""
|
|
260
|
+
caused = {c.aspect for c in contrasts if c.trials and c.effect >= 0.5}
|
|
261
|
+
seen = {c.aspect for c in correlated}
|
|
262
|
+
return {aspect: ("caused" if aspect in caused else "merely_correlated") for aspect in sorted(seen | caused)}
|