simz 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.
- simz-0.1.0/.gitignore +175 -0
- simz-0.1.0/PKG-INFO +58 -0
- simz-0.1.0/README.md +38 -0
- simz-0.1.0/pyproject.toml +36 -0
- simz-0.1.0/src/sim/__init__.py +0 -0
- simz-0.1.0/src/sim/ai/__init__.py +10 -0
- simz-0.1.0/src/sim/ai/brain.py +29 -0
- simz-0.1.0/src/sim/ai/context.py +18 -0
- simz-0.1.0/src/sim/ai/memory.py +230 -0
- simz-0.1.0/src/sim/common/__init__.py +0 -0
- simz-0.1.0/src/sim/common/ds/__init__.py +0 -0
- simz-0.1.0/src/sim/common/ds/generational.py +266 -0
- simz-0.1.0/src/sim/common/ds/running_stats.py +68 -0
- simz-0.1.0/src/sim/common/enum.py +75 -0
- simz-0.1.0/src/sim/common/extensions.py +87 -0
- simz-0.1.0/src/sim/common/formatting.py +41 -0
- simz-0.1.0/src/sim/common/logging.py +75 -0
- simz-0.1.0/src/sim/common/math.py +196 -0
- simz-0.1.0/src/sim/config.py +29 -0
- simz-0.1.0/src/sim/ecs/__init__.py +0 -0
- simz-0.1.0/src/sim/ecs/component.py +81 -0
- simz-0.1.0/src/sim/ecs/core.py +314 -0
- simz-0.1.0/src/sim/ecs/query.py +72 -0
- simz-0.1.0/src/sim/ecs/system.py +25 -0
simz-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py,cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
#Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
#uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
#poetry.lock
|
|
109
|
+
|
|
110
|
+
# pdm
|
|
111
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
112
|
+
#pdm.lock
|
|
113
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
114
|
+
# in version control.
|
|
115
|
+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
|
116
|
+
.pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
121
|
+
__pypackages__/
|
|
122
|
+
|
|
123
|
+
# Celery stuff
|
|
124
|
+
celerybeat-schedule
|
|
125
|
+
celerybeat.pid
|
|
126
|
+
|
|
127
|
+
# SageMath parsed files
|
|
128
|
+
*.sage.py
|
|
129
|
+
|
|
130
|
+
# Environments
|
|
131
|
+
.env
|
|
132
|
+
.venv
|
|
133
|
+
env/
|
|
134
|
+
venv/
|
|
135
|
+
ENV/
|
|
136
|
+
env.bak/
|
|
137
|
+
venv.bak/
|
|
138
|
+
|
|
139
|
+
# Spyder project settings
|
|
140
|
+
.spyderproject
|
|
141
|
+
.spyproject
|
|
142
|
+
|
|
143
|
+
# Rope project settings
|
|
144
|
+
.ropeproject
|
|
145
|
+
|
|
146
|
+
# mkdocs documentation
|
|
147
|
+
/site
|
|
148
|
+
|
|
149
|
+
# mypy
|
|
150
|
+
.mypy_cache/
|
|
151
|
+
.dmypy.json
|
|
152
|
+
dmypy.json
|
|
153
|
+
|
|
154
|
+
# ruff
|
|
155
|
+
.ruff_cache/
|
|
156
|
+
|
|
157
|
+
# Pyre type checker
|
|
158
|
+
.pyre/
|
|
159
|
+
|
|
160
|
+
# pytype static type analyzer
|
|
161
|
+
.pytype/
|
|
162
|
+
|
|
163
|
+
# Cython debug symbols
|
|
164
|
+
cython_debug/
|
|
165
|
+
|
|
166
|
+
# PyCharm
|
|
167
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
168
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
169
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
170
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
171
|
+
#.idea/
|
|
172
|
+
|
|
173
|
+
# PyPI configuration file
|
|
174
|
+
.pypirc
|
|
175
|
+
dist/
|
simz-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: simz
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A lightweight ECS-based simulation engine
|
|
5
|
+
Project-URL: Repository, https://github.com/danield137/projectzero
|
|
6
|
+
Author-email: Daniel Dror <danield137@gmail.com>
|
|
7
|
+
License: Copyright (c) [Daniel Dror;danield137@gmail.com]
|
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to use, copy, modify, and distribute the Software for non-commercial, non-profit purposes only.
|
|
9
|
+
Commercial use, including but not limited to use by for-profit organizations or individuals for business purposes, is not permitted without obtaining a separate commercial license from the copyright holder.
|
|
10
|
+
|
|
11
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY...
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Topic :: Games/Entertainment :: Simulation
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Requires-Dist: numpy>=2.2.4
|
|
18
|
+
Requires-Dist: termcolor>=2.5.0
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# sim
|
|
22
|
+
|
|
23
|
+
A lightweight ECS (Entity Component System) based simulation engine.
|
|
24
|
+
|
|
25
|
+
## Features
|
|
26
|
+
|
|
27
|
+
- **ECS Core** β Entity-Component-System architecture with generational IDs and efficient queries
|
|
28
|
+
- **AI Module** β Brain/context/memory abstractions for agent behavior
|
|
29
|
+
- **Common Utilities** β Math helpers, logging, data structures (generational containers, running stats)
|
|
30
|
+
- **Configurable** β Runtime configuration for simulation parameters
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install simz
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from sim.ecs.core import ECS
|
|
42
|
+
from sim.ecs.component import Component
|
|
43
|
+
from sim.ecs.system import System
|
|
44
|
+
|
|
45
|
+
# Define components
|
|
46
|
+
class Position(Component):
|
|
47
|
+
def __init__(self, x: float, y: float):
|
|
48
|
+
self.x = x
|
|
49
|
+
self.y = y
|
|
50
|
+
|
|
51
|
+
# Create ECS and entities
|
|
52
|
+
ecs = ECS()
|
|
53
|
+
ecs.create_entity("player", [Position(0, 0)])
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## License
|
|
57
|
+
|
|
58
|
+
Non-commercial use only. See [LICENSE](LICENSE) for details.
|
simz-0.1.0/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# sim
|
|
2
|
+
|
|
3
|
+
A lightweight ECS (Entity Component System) based simulation engine.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **ECS Core** β Entity-Component-System architecture with generational IDs and efficient queries
|
|
8
|
+
- **AI Module** β Brain/context/memory abstractions for agent behavior
|
|
9
|
+
- **Common Utilities** β Math helpers, logging, data structures (generational containers, running stats)
|
|
10
|
+
- **Configurable** β Runtime configuration for simulation parameters
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install simz
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from sim.ecs.core import ECS
|
|
22
|
+
from sim.ecs.component import Component
|
|
23
|
+
from sim.ecs.system import System
|
|
24
|
+
|
|
25
|
+
# Define components
|
|
26
|
+
class Position(Component):
|
|
27
|
+
def __init__(self, x: float, y: float):
|
|
28
|
+
self.x = x
|
|
29
|
+
self.y = y
|
|
30
|
+
|
|
31
|
+
# Create ECS and entities
|
|
32
|
+
ecs = ECS()
|
|
33
|
+
ecs.create_entity("player", [Position(0, 0)])
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## License
|
|
37
|
+
|
|
38
|
+
Non-commercial use only. See [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "simz"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "A lightweight ECS-based simulation engine"
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "Daniel Dror", email = "danield137@gmail.com"},
|
|
7
|
+
]
|
|
8
|
+
dependencies = [
|
|
9
|
+
"numpy>=2.2.4",
|
|
10
|
+
"termcolor>=2.5.0",
|
|
11
|
+
]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
readme = "README.md"
|
|
14
|
+
license = {text = """
|
|
15
|
+
Copyright (c) [Daniel Dror;danield137@gmail.com]
|
|
16
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to use, copy, modify, and distribute the Software for non-commercial, non-profit purposes only.
|
|
17
|
+
Commercial use, including but not limited to use by for-profit organizations or individuals for business purposes, is not permitted without obtaining a separate commercial license from the copyright holder.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY...
|
|
20
|
+
"""}
|
|
21
|
+
classifiers = [
|
|
22
|
+
"Development Status :: 3 - Alpha",
|
|
23
|
+
"Intended Audience :: Developers",
|
|
24
|
+
"Programming Language :: Python :: 3",
|
|
25
|
+
"Topic :: Games/Entertainment :: Simulation",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Repository = "https://github.com/danield137/projectzero"
|
|
30
|
+
|
|
31
|
+
[build-system]
|
|
32
|
+
requires = ["hatchling"]
|
|
33
|
+
build-backend = "hatchling.build"
|
|
34
|
+
|
|
35
|
+
[tool.hatch.build.targets.wheel]
|
|
36
|
+
packages = ["src/sim"]
|
|
File without changes
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import enum
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Generic, TypeVar
|
|
5
|
+
|
|
6
|
+
from sim.ai import ActionStep
|
|
7
|
+
from sim.ai.context import BrainContext
|
|
8
|
+
from sim.ai.memory import Memory
|
|
9
|
+
|
|
10
|
+
TGoal = TypeVar("TGoal", bound=enum.Enum)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(slots=True)
|
|
14
|
+
class GoalSelector(abc.ABC, Generic[TGoal]):
|
|
15
|
+
@abc.abstractmethod
|
|
16
|
+
def select_goal(self, ctx: BrainContext) -> TGoal: ...
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(slots=True)
|
|
20
|
+
class Planner(abc.ABC, Generic[TGoal]):
|
|
21
|
+
@abc.abstractmethod
|
|
22
|
+
def make_plan(self, ctx: BrainContext, goal: str) -> list[ActionStep]: ...
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(slots=True)
|
|
26
|
+
class Brain(Generic[TGoal]):
|
|
27
|
+
goal_selector: GoalSelector[TGoal]
|
|
28
|
+
planner: Planner[TGoal]
|
|
29
|
+
memory: Memory
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from sim.ai.memory import Memory, MemoryData
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass(slots=True, frozen=True)
|
|
7
|
+
class BrainContext:
|
|
8
|
+
"""
|
|
9
|
+
Context for the AI brain, providing necessary data for decision-making.
|
|
10
|
+
All "sensory" data is normalized to a range of 0.0 to 1.0.
|
|
11
|
+
This context is used by the AI engine to make decisions and plans.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
simulation_time: int
|
|
15
|
+
eid: int
|
|
16
|
+
etype: str
|
|
17
|
+
memory_data: MemoryData
|
|
18
|
+
memory_engine: Memory
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import enum
|
|
3
|
+
import heapq
|
|
4
|
+
import math
|
|
5
|
+
import random
|
|
6
|
+
from collections.abc import Callable, Iterator
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from sim.common.math import Vector, cosine
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(slots=True)
|
|
14
|
+
class MemoryFact:
|
|
15
|
+
"""
|
|
16
|
+
*uid* : unique and stable identifier
|
|
17
|
+
*t0* : tick when the fact was first encoded
|
|
18
|
+
*value*: **opaque payload** (Memory never inspects this)
|
|
19
|
+
*ctx* : optional context vector for similarity search
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
uid: str
|
|
23
|
+
tag: str
|
|
24
|
+
t0: int
|
|
25
|
+
value: Any
|
|
26
|
+
ctx: Vector | None = field(init=False, default=None)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class MemoryType(str, enum.Enum):
|
|
30
|
+
PERFECT = "PERFECT"
|
|
31
|
+
IMPERFECT = "IMPERFECT"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(slots=True)
|
|
35
|
+
class MemoryData:
|
|
36
|
+
ltm: dict[str, MemoryFact] = field(default_factory=dict[str, MemoryFact])
|
|
37
|
+
stm: list[MemoryFact] = field(default_factory=list[MemoryFact])
|
|
38
|
+
strength: dict[str, float] = field(default_factory=dict[str, float])
|
|
39
|
+
cue: dict[str, set[str]] = field(default_factory=dict[str, set[str]])
|
|
40
|
+
rng_state: int = field(default=0xDEADBEEF) # splitmix state
|
|
41
|
+
|
|
42
|
+
def all(self) -> Iterator[MemoryFact]:
|
|
43
|
+
if self.stm:
|
|
44
|
+
yield from self.stm
|
|
45
|
+
if self.ltm.values():
|
|
46
|
+
yield from self.ltm.values()
|
|
47
|
+
|
|
48
|
+
def exists(self, uid: str) -> bool:
|
|
49
|
+
"""Check if a fact with the given UID exists in either STM or LTM."""
|
|
50
|
+
return uid in self.ltm or any(fact.uid == uid for fact in self.stm)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass(slots=True, frozen=True)
|
|
54
|
+
class MemQuery:
|
|
55
|
+
fact_type: type[MemoryFact] | None = None
|
|
56
|
+
uid: str | None = None
|
|
57
|
+
where: Callable[[MemoryFact], bool] | None = None
|
|
58
|
+
ctx: Vector | None = None # current context for activation boost
|
|
59
|
+
k: int = 5
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
def tag_eq(tag: str) -> "MemQuery":
|
|
63
|
+
"""Create a query for a specific tag."""
|
|
64
|
+
return MemQuery(where=lambda f: f.tag == tag)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass(slots=True)
|
|
68
|
+
class Memory(abc.ABC):
|
|
69
|
+
@abc.abstractmethod
|
|
70
|
+
def remember(self, md: MemoryData, fact: MemoryFact) -> None: ...
|
|
71
|
+
|
|
72
|
+
@abc.abstractmethod
|
|
73
|
+
def recall(self, md: MemoryData, q: MemQuery, now: int) -> list[MemoryFact]: ...
|
|
74
|
+
|
|
75
|
+
@abc.abstractmethod
|
|
76
|
+
def tick(self, md: MemoryData, dt: float, now: int) -> None: ...
|
|
77
|
+
|
|
78
|
+
@abc.abstractmethod
|
|
79
|
+
def forget(self, md: MemoryData, uid: str) -> None: ...
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# ββββ PerfectMemory ββββββββββββββββββββββββββββββββββββββββ
|
|
83
|
+
class PerfectMemory(Memory):
|
|
84
|
+
def remember(self, md: MemoryData, fact: MemoryFact) -> None:
|
|
85
|
+
md.ltm[fact.uid] = fact
|
|
86
|
+
|
|
87
|
+
def recall(self, md: MemoryData, q: MemQuery, now: int) -> list[MemoryFact]:
|
|
88
|
+
cand = (fact for facts in (md.ltm.values(), md.stm) for fact in facts)
|
|
89
|
+
if q.where:
|
|
90
|
+
cand = filter(q.where, cand)
|
|
91
|
+
return list(cand)[: q.k]
|
|
92
|
+
|
|
93
|
+
def forget(self, md: MemoryData, uid: str) -> None:
|
|
94
|
+
"""Forget a fact by UID."""
|
|
95
|
+
md.ltm.pop(uid, None)
|
|
96
|
+
md.stm = [f for f in md.stm if f.uid != uid]
|
|
97
|
+
for s in md.cue.values():
|
|
98
|
+
s.discard(uid)
|
|
99
|
+
|
|
100
|
+
def tick(self, md: MemoryData, dt: float, now: int) -> None:
|
|
101
|
+
# noβop for perfect memory
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# ββββ ImperfectMemory knobs & logic ββββββββββββββββββββββββββ
|
|
106
|
+
class ImperfectMemory(Memory):
|
|
107
|
+
ENCODE_NOISE_P = 0.10
|
|
108
|
+
STM_SPAN_TICKS = 300
|
|
109
|
+
INTERFERENCE_P = 0.40
|
|
110
|
+
DECAY_HALF_LIFE = 30_000
|
|
111
|
+
RETRIEVAL_NOISE_SD = 0.25
|
|
112
|
+
|
|
113
|
+
def remember(self, md: MemoryData, fact: MemoryFact) -> None:
|
|
114
|
+
rng = random.Random(md.rng_state)
|
|
115
|
+
if rng.random() < self.ENCODE_NOISE_P:
|
|
116
|
+
# fact = {**fact, "noisy": True} # your distortion
|
|
117
|
+
...
|
|
118
|
+
md.stm.append(fact)
|
|
119
|
+
md.rng_state = rng.getrandbits(64)
|
|
120
|
+
|
|
121
|
+
def tick(self, md: MemoryData, dt: float, now: int) -> None:
|
|
122
|
+
# STM β LTM + interference
|
|
123
|
+
while md.stm and now - md.stm[0].t0 >= self.STM_SPAN_TICKS:
|
|
124
|
+
f = md.stm.pop(0)
|
|
125
|
+
bucket = f.uid.split("-", 1)[0]
|
|
126
|
+
md.cue.setdefault(bucket, set()).add(f.uid)
|
|
127
|
+
md.ltm[f.uid] = f
|
|
128
|
+
md.strength[f.uid] = md.strength.get(f.uid, 0.01) + 1.0
|
|
129
|
+
|
|
130
|
+
# decay
|
|
131
|
+
k = 0.5 ** (dt / self.DECAY_HALF_LIFE)
|
|
132
|
+
dead: list[str] = []
|
|
133
|
+
for uid, s in md.strength.items():
|
|
134
|
+
s *= k
|
|
135
|
+
if s < 0.02:
|
|
136
|
+
dead.append(uid)
|
|
137
|
+
md.strength[uid] = s
|
|
138
|
+
for uid in dead:
|
|
139
|
+
md.ltm.pop(uid, None)
|
|
140
|
+
for s in md.cue.values():
|
|
141
|
+
s.discard(uid)
|
|
142
|
+
|
|
143
|
+
def recall(self, md: MemoryData, q: MemQuery, now: int) -> list[MemoryFact]:
|
|
144
|
+
rng = random.Random(md.rng_state)
|
|
145
|
+
result_heap: list[
|
|
146
|
+
tuple[float, MemoryFact]
|
|
147
|
+
] = [] # Will store (-score, fact) pairs for min-heap behavior with max scores
|
|
148
|
+
|
|
149
|
+
# Process candidates from both LTM and STM without materializing full lists
|
|
150
|
+
def process_fact(f: MemoryFact):
|
|
151
|
+
if q.where and not q.where(f):
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
base = md.strength.get(f.uid, 0.01)
|
|
155
|
+
act = math.log(base / (now - f.t0 or 1e-9))
|
|
156
|
+
sim = 0.0
|
|
157
|
+
if q.ctx and f.ctx is not None:
|
|
158
|
+
sim = cosine(q.ctx, f.ctx)
|
|
159
|
+
noise = rng.gauss(0.0, self.RETRIEVAL_NOISE_SD)
|
|
160
|
+
score = act + sim + noise
|
|
161
|
+
|
|
162
|
+
# Use negative score for min-heap to function as max-heap
|
|
163
|
+
if len(result_heap) < q.k:
|
|
164
|
+
heapq.heappush(result_heap, (-score, f))
|
|
165
|
+
elif -score > result_heap[0][0]:
|
|
166
|
+
heapq.heappushpop(result_heap, (-score, f))
|
|
167
|
+
|
|
168
|
+
# Process long-term memory
|
|
169
|
+
for f in md.ltm.values():
|
|
170
|
+
process_fact(f)
|
|
171
|
+
|
|
172
|
+
# Process short-term memory
|
|
173
|
+
for f in md.stm:
|
|
174
|
+
process_fact(f)
|
|
175
|
+
|
|
176
|
+
md.rng_state = rng.getrandbits(64)
|
|
177
|
+
|
|
178
|
+
# Extract results in descending order of score
|
|
179
|
+
results = [f for _, f in sorted(result_heap, key=lambda x: x[0])]
|
|
180
|
+
return results
|
|
181
|
+
|
|
182
|
+
def forget(self, md: MemoryData, uid: str) -> None:
|
|
183
|
+
"""Forget a fact by UID."""
|
|
184
|
+
md.ltm.pop(uid, None)
|
|
185
|
+
md.stm = [f for f in md.stm if f.uid != uid]
|
|
186
|
+
for s in md.cue.values():
|
|
187
|
+
s.discard(uid)
|
|
188
|
+
md.strength.pop(uid, None)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# shared singleton instances
|
|
192
|
+
_perfect_memory = PerfectMemory()
|
|
193
|
+
_imperfect_memory = ImperfectMemory()
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def get_memory(memory_type: MemoryType) -> Memory:
|
|
197
|
+
if memory_type == MemoryType.PERFECT:
|
|
198
|
+
return _perfect_memory
|
|
199
|
+
return _imperfect_memory
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
# ββββ faΓ§ade dispatchers ββββββββββββββββββββββββββββββββββββββ
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class MemWriter:
|
|
206
|
+
@staticmethod
|
|
207
|
+
def write(md: MemoryData, memory_type: str, fact: MemoryFact) -> None:
|
|
208
|
+
if memory_type == MemoryType.PERFECT:
|
|
209
|
+
_perfect_memory.remember(md, fact)
|
|
210
|
+
elif memory_type == MemoryType.IMPERFECT:
|
|
211
|
+
_imperfect_memory.remember(md, fact)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class MemReader:
|
|
215
|
+
@staticmethod
|
|
216
|
+
def read(md: MemoryData, memory_type: str, q: MemQuery, now: int) -> list[MemoryFact]:
|
|
217
|
+
if memory_type == MemoryType.PERFECT:
|
|
218
|
+
return _perfect_memory.recall(md, q, now)
|
|
219
|
+
if memory_type == MemoryType.IMPERFECT:
|
|
220
|
+
return _imperfect_memory.recall(md, q, now)
|
|
221
|
+
return [] # empty list if memory type is unknown
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class MemHousekeeping:
|
|
225
|
+
@staticmethod
|
|
226
|
+
def tick(md: MemoryData, memory_type: str, dt: float, now: int) -> None:
|
|
227
|
+
if memory_type == MemoryType.PERFECT:
|
|
228
|
+
_perfect_memory.tick(md, dt, now)
|
|
229
|
+
else:
|
|
230
|
+
_imperfect_memory.tick(md, dt, now)
|
|
File without changes
|
|
File without changes
|