discourse-sim 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.
- discourse_sim-0.1.0/LICENSE +21 -0
- discourse_sim-0.1.0/MANIFEST.in +8 -0
- discourse_sim-0.1.0/PKG-INFO +286 -0
- discourse_sim-0.1.0/README.md +246 -0
- discourse_sim-0.1.0/discourse_sim/__init__.py +29 -0
- discourse_sim-0.1.0/discourse_sim/agents/__init__.py +2 -0
- discourse_sim-0.1.0/discourse_sim/agents/agent.py +129 -0
- discourse_sim-0.1.0/discourse_sim/config.py +222 -0
- discourse_sim-0.1.0/discourse_sim/core.py +333 -0
- discourse_sim-0.1.0/discourse_sim/simulation/__init__.py +2 -0
- discourse_sim-0.1.0/discourse_sim/simulation/engine.py +381 -0
- discourse_sim-0.1.0/discourse_sim/timeline/__init__.py +2 -0
- discourse_sim-0.1.0/discourse_sim/timeline/timeline.py +79 -0
- discourse_sim-0.1.0/discourse_sim/tools/__init__.py +2 -0
- discourse_sim-0.1.0/discourse_sim/tools/search_tools.py +78 -0
- discourse_sim-0.1.0/discourse_sim/utils/__init__.py +2 -0
- discourse_sim-0.1.0/discourse_sim/utils/dataframes.py +89 -0
- discourse_sim-0.1.0/discourse_sim.egg-info/PKG-INFO +286 -0
- discourse_sim-0.1.0/discourse_sim.egg-info/SOURCES.txt +26 -0
- discourse_sim-0.1.0/discourse_sim.egg-info/dependency_links.txt +1 -0
- discourse_sim-0.1.0/discourse_sim.egg-info/requires.txt +16 -0
- discourse_sim-0.1.0/discourse_sim.egg-info/top_level.txt +1 -0
- discourse_sim-0.1.0/examples/custom_agent_kinds.py +58 -0
- discourse_sim-0.1.0/examples/dublin_march.py +88 -0
- discourse_sim-0.1.0/examples/minimal_usage.py +35 -0
- discourse_sim-0.1.0/pyproject.toml +59 -0
- discourse_sim-0.1.0/setup.cfg +4 -0
- discourse_sim-0.1.0/setup.py +25 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 dreji18
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: discourse-sim
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: LLM-augmented Agent-Based Social Simulation for modelling public discourse dynamics following a critical real-world event.
|
|
5
|
+
Author: dreji18
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/dreji18/discourse_sim
|
|
8
|
+
Project-URL: Repository, https://github.com/dreji18/discourse_sim
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/dreji18/discourse_sim/issues
|
|
10
|
+
Keywords: agent-based-modelling,social-simulation,LLM,public-discourse,opinion-dynamics,ABM,polarisation,NLP
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
14
|
+
Classifier: Topic :: Sociology
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
20
|
+
Classifier: Operating System :: OS Independent
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: numpy>=1.24
|
|
25
|
+
Requires-Dist: networkx>=3.0
|
|
26
|
+
Requires-Dist: tqdm>=4.64
|
|
27
|
+
Requires-Dist: pandas>=2.0
|
|
28
|
+
Requires-Dist: langchain-core>=0.1
|
|
29
|
+
Requires-Dist: langchain-community>=0.0.20
|
|
30
|
+
Requires-Dist: duckduckgo-search>=4.0
|
|
31
|
+
Provides-Extra: ollama
|
|
32
|
+
Requires-Dist: langchain-ollama>=0.1; extra == "ollama"
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: pytest; extra == "dev"
|
|
35
|
+
Requires-Dist: twine; extra == "dev"
|
|
36
|
+
Requires-Dist: build; extra == "dev"
|
|
37
|
+
Requires-Dist: ruff; extra == "dev"
|
|
38
|
+
Dynamic: license-file
|
|
39
|
+
Dynamic: requires-python
|
|
40
|
+
|
|
41
|
+
# discourse_sim
|
|
42
|
+
|
|
43
|
+
**LLM-augmented Agent-Based Social Simulation** for modelling public discourse dynamics following a critical real-world event.
|
|
44
|
+
|
|
45
|
+
Agents observe live news, reason about it, post to a simulated social network, and update multidimensional beliefs through peer influence and news salience — all powered by a local Ollama LLM.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Install the package
|
|
53
|
+
pip install -e .
|
|
54
|
+
|
|
55
|
+
# Install with Ollama support (recommended)
|
|
56
|
+
pip install -e ".[ollama]"
|
|
57
|
+
|
|
58
|
+
# Pull the default model
|
|
59
|
+
ollama pull mistral:7b-instruct-q4_0
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**All dependencies:**
|
|
63
|
+
```bash
|
|
64
|
+
pip install langchain-core langchain-community langchain-ollama \
|
|
65
|
+
duckduckgo-search networkx numpy tqdm pandas openpyxl
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Quick Start
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from discourse_sim import DiscourseSimulation
|
|
74
|
+
|
|
75
|
+
sim = DiscourseSimulation(
|
|
76
|
+
critical_event=(
|
|
77
|
+
"In late April 2025, Dublin saw a large anti-immigration march "
|
|
78
|
+
"from the Garden of Remembrance to the Custom House, with thousands "
|
|
79
|
+
"protesting immigration levels and housing pressure..."
|
|
80
|
+
),
|
|
81
|
+
event_date="2025-04-26",
|
|
82
|
+
topic="immigration in Ireland",
|
|
83
|
+
n_agents=100,
|
|
84
|
+
n_days=15,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
sim.run()
|
|
88
|
+
df = sim.to_dataframe() # one row per agent per day
|
|
89
|
+
df.to_excel("output.xlsx", index=False)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## All Parameters
|
|
95
|
+
|
|
96
|
+
### Required
|
|
97
|
+
|
|
98
|
+
| Parameter | Type | Description |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| `critical_event` | `str` | Plain-text description of the event injected into every agent prompt on every day |
|
|
101
|
+
| `event_date` | `str` | ISO date `"YYYY-MM-DD"` — this becomes Day 0 |
|
|
102
|
+
| `topic` | `str` | Subject domain, e.g. `"immigration in Ireland"`. Anchors search queries and scoring prompts |
|
|
103
|
+
|
|
104
|
+
### Agent Population
|
|
105
|
+
|
|
106
|
+
| Parameter | Type | Default | Description |
|
|
107
|
+
|---|---|---|---|
|
|
108
|
+
| `n_agents` | `int` | `100` | Number of synthetic agents |
|
|
109
|
+
| `n_days` | `int` | `15` | Simulation duration in days |
|
|
110
|
+
| `agent_distribution` | `dict` | Ireland 2025 empirical split | Proportions of each agent kind — must sum to 1.0 |
|
|
111
|
+
| `kind_priors` | `dict` | `DEFAULT_KIND_PRIORS` | Prior distributions for belief/attitude initialisation per kind |
|
|
112
|
+
|
|
113
|
+
**Default distribution (Ireland 2025):**
|
|
114
|
+
```python
|
|
115
|
+
{"centrist": 0.45, "far_right": 0.20, "pro_imm": 0.25, "media": 0.10}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Timeline
|
|
119
|
+
|
|
120
|
+
| Parameter | Type | Default | Description |
|
|
121
|
+
|---|---|---|---|
|
|
122
|
+
| `timeline` | `dict[int, str]` | `None` | Optional explicit daily news entries keyed by 0-based day index. Missing days auto-generated. If `None`, all days use live search. |
|
|
123
|
+
|
|
124
|
+
**Example:**
|
|
125
|
+
```python
|
|
126
|
+
timeline={
|
|
127
|
+
0: "[VERIFIED] The march took place at the Garden of Remembrance...",
|
|
128
|
+
4: "[VERIFIED] Government announced a deportation flight...",
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Any day not in the dict falls back to an auto-generated entry. Agents search live news on every day regardless of timeline.
|
|
133
|
+
|
|
134
|
+
### LLM & Network
|
|
135
|
+
|
|
136
|
+
| Parameter | Type | Default | Description |
|
|
137
|
+
|---|---|---|---|
|
|
138
|
+
| `ollama_model` | `str` | `"mistral:7b-instruct-q4_0"` | Any locally pulled Ollama model |
|
|
139
|
+
| `temperature` | `float` | `0.75` | Post generation temperature |
|
|
140
|
+
| `use_llm_scoring` | `bool` | `True` | If `False`, uses keyword heuristic for attitude scoring |
|
|
141
|
+
| `network_k` | `int` | `6` | Watts-Strogatz nearest-neighbour count |
|
|
142
|
+
| `network_p` | `float` | `0.3` | Watts-Strogatz rewiring probability |
|
|
143
|
+
| `network_seed` | `int` | `42` | Reproducibility seed for network and agents |
|
|
144
|
+
|
|
145
|
+
### Belief Update Keywords
|
|
146
|
+
|
|
147
|
+
| Parameter | Type | Default | Description |
|
|
148
|
+
|---|---|---|---|
|
|
149
|
+
| `threat_keywords` | `list[str]` | See config.py | Words in daily news that trigger `security_threat_belief` increase and `exposure` accumulation |
|
|
150
|
+
| `humanitarian_keywords` | `list[str]` | See config.py | Words that trigger `humanitarian_belief` increase |
|
|
151
|
+
|
|
152
|
+
Override these when simulating non-immigration topics:
|
|
153
|
+
```python
|
|
154
|
+
threat_keywords=["eviction", "homeless", "unaffordable", "crisis"],
|
|
155
|
+
humanitarian_keywords=["social housing", "rights", "family", "support"],
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Outputs
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
sim.run()
|
|
164
|
+
|
|
165
|
+
# Single DataFrame — one row per agent per day (recommended for analysis)
|
|
166
|
+
df = sim.to_dataframe()
|
|
167
|
+
|
|
168
|
+
# All three DataFrames
|
|
169
|
+
dfs = sim.to_dataframes()
|
|
170
|
+
dfs["history"] # one row per timestep — aggregate metrics
|
|
171
|
+
dfs["agents"] # one row per agent per timestep — full belief state
|
|
172
|
+
dfs["messages"] # one row per agent per timestep — posts + beliefs merged
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Column reference — `df_messages`
|
|
176
|
+
|
|
177
|
+
| Column | Description |
|
|
178
|
+
|---|---|
|
|
179
|
+
| `t` / `date` | Day index and calendar date |
|
|
180
|
+
| `agent_id` / `kind` / `quirk` | Agent identity |
|
|
181
|
+
| `message` | The generated social media post |
|
|
182
|
+
| `interpreted_score` | LLM-scored stance [-1, +1] |
|
|
183
|
+
| `attitude` | Agent attitude at end of day t |
|
|
184
|
+
| `mood` | Agent mood at end of day t |
|
|
185
|
+
| `exposure` | Cumulative threat narrative exposure |
|
|
186
|
+
| `economic_threat_belief` | Economic threat sub-belief |
|
|
187
|
+
| `cultural_threat_belief` | Cultural threat sub-belief |
|
|
188
|
+
| `security_threat_belief` | Security threat sub-belief |
|
|
189
|
+
| `humanitarian_belief` | Humanitarian weight sub-belief |
|
|
190
|
+
| `avg_attitude` | Population mean attitude that day |
|
|
191
|
+
| `polarization` | Mean edge-level attitude divergence |
|
|
192
|
+
| `news_summary` | First 120 chars of daily news entry |
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Custom Agent Kinds
|
|
197
|
+
|
|
198
|
+
Add any agent kind by extending `DEFAULT_KIND_PRIORS`:
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
from discourse_sim import DiscourseSimulation
|
|
202
|
+
from discourse_sim.config import DEFAULT_KIND_PRIORS
|
|
203
|
+
|
|
204
|
+
custom_priors = {
|
|
205
|
+
**DEFAULT_KIND_PRIORS,
|
|
206
|
+
"nationalist": {
|
|
207
|
+
"attitude": (0.55, 0.95),
|
|
208
|
+
"economic_threat": (0.2, 0.6),
|
|
209
|
+
"cultural_threat": (0.7, 1.0),
|
|
210
|
+
"humanitarian": (-0.6, 0.0),
|
|
211
|
+
"openness": (0.1, 0.35),
|
|
212
|
+
"emotional_react": (0.5, 0.85),
|
|
213
|
+
},
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
sim = DiscourseSimulation(
|
|
217
|
+
...,
|
|
218
|
+
agent_distribution={"centrist": 0.35, "nationalist": 0.30, "pro_imm": 0.35},
|
|
219
|
+
kind_priors=custom_priors,
|
|
220
|
+
)
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Architecture
|
|
226
|
+
|
|
227
|
+
```
|
|
228
|
+
discourse_sim/
|
|
229
|
+
├── __init__.py # Public API: DiscourseSimulation
|
|
230
|
+
├── core.py # DiscourseSimulation class — orchestrator
|
|
231
|
+
├── config.py # SimConfig dataclass — all parameters + validation
|
|
232
|
+
├── agents/
|
|
233
|
+
│ └── agent.py # Agent dataclass + make_agents() factory
|
|
234
|
+
├── tools/
|
|
235
|
+
│ └── search_tools.py # make_tools() — search, memory, sentiment
|
|
236
|
+
├── timeline/
|
|
237
|
+
│ └── timeline.py # Timeline class — user-supplied + auto-generated
|
|
238
|
+
├── simulation/
|
|
239
|
+
│ └── engine.py # generate_message(), score_message(), update_beliefs(), run_loop()
|
|
240
|
+
└── utils/
|
|
241
|
+
└── dataframes.py # build_dataframes() — history/agents/messages
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**Per-day flow for each agent:**
|
|
245
|
+
|
|
246
|
+
```
|
|
247
|
+
OBSERVE → search_event_news(query) # live DuckDuckGo
|
|
248
|
+
→ recall_agent_memory(posts_json) # last 5 posts
|
|
249
|
+
|
|
250
|
+
THINK → build prompt: event + profile + memory + search result + today's news
|
|
251
|
+
|
|
252
|
+
ACT → LLM call → post text (max 40 words)
|
|
253
|
+
|
|
254
|
+
SCORE → LLM call (temp=0.0) → float in [-1, +1]
|
|
255
|
+
|
|
256
|
+
UPDATE → news salience → sub-beliefs
|
|
257
|
+
→ peer pull → attitude pressure
|
|
258
|
+
→ mood shock → affective state
|
|
259
|
+
→ inertia → resistance to change
|
|
260
|
+
→ composite → attitude(t)
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Examples
|
|
266
|
+
|
|
267
|
+
| File | Description |
|
|
268
|
+
|---|---|
|
|
269
|
+
| `examples/dublin_march.py` | Full reproduction of the original Dublin April 2025 experiment |
|
|
270
|
+
| `examples/minimal_usage.py` | Minimal usage — no timeline, live search only |
|
|
271
|
+
| `examples/custom_agent_kinds.py` | Adding a custom `nationalist` kind with its own priors |
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## Citation
|
|
276
|
+
|
|
277
|
+
If you use this package in research, please cite:
|
|
278
|
+
|
|
279
|
+
```
|
|
280
|
+
@software{discourse_sim,
|
|
281
|
+
title = {discourse\_sim: LLM-Augmented Agent-Based Social Simulation of Discourse Dynamics},
|
|
282
|
+
author = {dreji18},
|
|
283
|
+
year = {2026},
|
|
284
|
+
note = {Python package}
|
|
285
|
+
}
|
|
286
|
+
```
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# discourse_sim
|
|
2
|
+
|
|
3
|
+
**LLM-augmented Agent-Based Social Simulation** for modelling public discourse dynamics following a critical real-world event.
|
|
4
|
+
|
|
5
|
+
Agents observe live news, reason about it, post to a simulated social network, and update multidimensional beliefs through peer influence and news salience — all powered by a local Ollama LLM.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Install the package
|
|
13
|
+
pip install -e .
|
|
14
|
+
|
|
15
|
+
# Install with Ollama support (recommended)
|
|
16
|
+
pip install -e ".[ollama]"
|
|
17
|
+
|
|
18
|
+
# Pull the default model
|
|
19
|
+
ollama pull mistral:7b-instruct-q4_0
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**All dependencies:**
|
|
23
|
+
```bash
|
|
24
|
+
pip install langchain-core langchain-community langchain-ollama \
|
|
25
|
+
duckduckgo-search networkx numpy tqdm pandas openpyxl
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from discourse_sim import DiscourseSimulation
|
|
34
|
+
|
|
35
|
+
sim = DiscourseSimulation(
|
|
36
|
+
critical_event=(
|
|
37
|
+
"In late April 2025, Dublin saw a large anti-immigration march "
|
|
38
|
+
"from the Garden of Remembrance to the Custom House, with thousands "
|
|
39
|
+
"protesting immigration levels and housing pressure..."
|
|
40
|
+
),
|
|
41
|
+
event_date="2025-04-26",
|
|
42
|
+
topic="immigration in Ireland",
|
|
43
|
+
n_agents=100,
|
|
44
|
+
n_days=15,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
sim.run()
|
|
48
|
+
df = sim.to_dataframe() # one row per agent per day
|
|
49
|
+
df.to_excel("output.xlsx", index=False)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## All Parameters
|
|
55
|
+
|
|
56
|
+
### Required
|
|
57
|
+
|
|
58
|
+
| Parameter | Type | Description |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| `critical_event` | `str` | Plain-text description of the event injected into every agent prompt on every day |
|
|
61
|
+
| `event_date` | `str` | ISO date `"YYYY-MM-DD"` — this becomes Day 0 |
|
|
62
|
+
| `topic` | `str` | Subject domain, e.g. `"immigration in Ireland"`. Anchors search queries and scoring prompts |
|
|
63
|
+
|
|
64
|
+
### Agent Population
|
|
65
|
+
|
|
66
|
+
| Parameter | Type | Default | Description |
|
|
67
|
+
|---|---|---|---|
|
|
68
|
+
| `n_agents` | `int` | `100` | Number of synthetic agents |
|
|
69
|
+
| `n_days` | `int` | `15` | Simulation duration in days |
|
|
70
|
+
| `agent_distribution` | `dict` | Ireland 2025 empirical split | Proportions of each agent kind — must sum to 1.0 |
|
|
71
|
+
| `kind_priors` | `dict` | `DEFAULT_KIND_PRIORS` | Prior distributions for belief/attitude initialisation per kind |
|
|
72
|
+
|
|
73
|
+
**Default distribution (Ireland 2025):**
|
|
74
|
+
```python
|
|
75
|
+
{"centrist": 0.45, "far_right": 0.20, "pro_imm": 0.25, "media": 0.10}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Timeline
|
|
79
|
+
|
|
80
|
+
| Parameter | Type | Default | Description |
|
|
81
|
+
|---|---|---|---|
|
|
82
|
+
| `timeline` | `dict[int, str]` | `None` | Optional explicit daily news entries keyed by 0-based day index. Missing days auto-generated. If `None`, all days use live search. |
|
|
83
|
+
|
|
84
|
+
**Example:**
|
|
85
|
+
```python
|
|
86
|
+
timeline={
|
|
87
|
+
0: "[VERIFIED] The march took place at the Garden of Remembrance...",
|
|
88
|
+
4: "[VERIFIED] Government announced a deportation flight...",
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Any day not in the dict falls back to an auto-generated entry. Agents search live news on every day regardless of timeline.
|
|
93
|
+
|
|
94
|
+
### LLM & Network
|
|
95
|
+
|
|
96
|
+
| Parameter | Type | Default | Description |
|
|
97
|
+
|---|---|---|---|
|
|
98
|
+
| `ollama_model` | `str` | `"mistral:7b-instruct-q4_0"` | Any locally pulled Ollama model |
|
|
99
|
+
| `temperature` | `float` | `0.75` | Post generation temperature |
|
|
100
|
+
| `use_llm_scoring` | `bool` | `True` | If `False`, uses keyword heuristic for attitude scoring |
|
|
101
|
+
| `network_k` | `int` | `6` | Watts-Strogatz nearest-neighbour count |
|
|
102
|
+
| `network_p` | `float` | `0.3` | Watts-Strogatz rewiring probability |
|
|
103
|
+
| `network_seed` | `int` | `42` | Reproducibility seed for network and agents |
|
|
104
|
+
|
|
105
|
+
### Belief Update Keywords
|
|
106
|
+
|
|
107
|
+
| Parameter | Type | Default | Description |
|
|
108
|
+
|---|---|---|---|
|
|
109
|
+
| `threat_keywords` | `list[str]` | See config.py | Words in daily news that trigger `security_threat_belief` increase and `exposure` accumulation |
|
|
110
|
+
| `humanitarian_keywords` | `list[str]` | See config.py | Words that trigger `humanitarian_belief` increase |
|
|
111
|
+
|
|
112
|
+
Override these when simulating non-immigration topics:
|
|
113
|
+
```python
|
|
114
|
+
threat_keywords=["eviction", "homeless", "unaffordable", "crisis"],
|
|
115
|
+
humanitarian_keywords=["social housing", "rights", "family", "support"],
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Outputs
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
sim.run()
|
|
124
|
+
|
|
125
|
+
# Single DataFrame — one row per agent per day (recommended for analysis)
|
|
126
|
+
df = sim.to_dataframe()
|
|
127
|
+
|
|
128
|
+
# All three DataFrames
|
|
129
|
+
dfs = sim.to_dataframes()
|
|
130
|
+
dfs["history"] # one row per timestep — aggregate metrics
|
|
131
|
+
dfs["agents"] # one row per agent per timestep — full belief state
|
|
132
|
+
dfs["messages"] # one row per agent per timestep — posts + beliefs merged
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Column reference — `df_messages`
|
|
136
|
+
|
|
137
|
+
| Column | Description |
|
|
138
|
+
|---|---|
|
|
139
|
+
| `t` / `date` | Day index and calendar date |
|
|
140
|
+
| `agent_id` / `kind` / `quirk` | Agent identity |
|
|
141
|
+
| `message` | The generated social media post |
|
|
142
|
+
| `interpreted_score` | LLM-scored stance [-1, +1] |
|
|
143
|
+
| `attitude` | Agent attitude at end of day t |
|
|
144
|
+
| `mood` | Agent mood at end of day t |
|
|
145
|
+
| `exposure` | Cumulative threat narrative exposure |
|
|
146
|
+
| `economic_threat_belief` | Economic threat sub-belief |
|
|
147
|
+
| `cultural_threat_belief` | Cultural threat sub-belief |
|
|
148
|
+
| `security_threat_belief` | Security threat sub-belief |
|
|
149
|
+
| `humanitarian_belief` | Humanitarian weight sub-belief |
|
|
150
|
+
| `avg_attitude` | Population mean attitude that day |
|
|
151
|
+
| `polarization` | Mean edge-level attitude divergence |
|
|
152
|
+
| `news_summary` | First 120 chars of daily news entry |
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Custom Agent Kinds
|
|
157
|
+
|
|
158
|
+
Add any agent kind by extending `DEFAULT_KIND_PRIORS`:
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
from discourse_sim import DiscourseSimulation
|
|
162
|
+
from discourse_sim.config import DEFAULT_KIND_PRIORS
|
|
163
|
+
|
|
164
|
+
custom_priors = {
|
|
165
|
+
**DEFAULT_KIND_PRIORS,
|
|
166
|
+
"nationalist": {
|
|
167
|
+
"attitude": (0.55, 0.95),
|
|
168
|
+
"economic_threat": (0.2, 0.6),
|
|
169
|
+
"cultural_threat": (0.7, 1.0),
|
|
170
|
+
"humanitarian": (-0.6, 0.0),
|
|
171
|
+
"openness": (0.1, 0.35),
|
|
172
|
+
"emotional_react": (0.5, 0.85),
|
|
173
|
+
},
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
sim = DiscourseSimulation(
|
|
177
|
+
...,
|
|
178
|
+
agent_distribution={"centrist": 0.35, "nationalist": 0.30, "pro_imm": 0.35},
|
|
179
|
+
kind_priors=custom_priors,
|
|
180
|
+
)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Architecture
|
|
186
|
+
|
|
187
|
+
```
|
|
188
|
+
discourse_sim/
|
|
189
|
+
├── __init__.py # Public API: DiscourseSimulation
|
|
190
|
+
├── core.py # DiscourseSimulation class — orchestrator
|
|
191
|
+
├── config.py # SimConfig dataclass — all parameters + validation
|
|
192
|
+
├── agents/
|
|
193
|
+
│ └── agent.py # Agent dataclass + make_agents() factory
|
|
194
|
+
├── tools/
|
|
195
|
+
│ └── search_tools.py # make_tools() — search, memory, sentiment
|
|
196
|
+
├── timeline/
|
|
197
|
+
│ └── timeline.py # Timeline class — user-supplied + auto-generated
|
|
198
|
+
├── simulation/
|
|
199
|
+
│ └── engine.py # generate_message(), score_message(), update_beliefs(), run_loop()
|
|
200
|
+
└── utils/
|
|
201
|
+
└── dataframes.py # build_dataframes() — history/agents/messages
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Per-day flow for each agent:**
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
OBSERVE → search_event_news(query) # live DuckDuckGo
|
|
208
|
+
→ recall_agent_memory(posts_json) # last 5 posts
|
|
209
|
+
|
|
210
|
+
THINK → build prompt: event + profile + memory + search result + today's news
|
|
211
|
+
|
|
212
|
+
ACT → LLM call → post text (max 40 words)
|
|
213
|
+
|
|
214
|
+
SCORE → LLM call (temp=0.0) → float in [-1, +1]
|
|
215
|
+
|
|
216
|
+
UPDATE → news salience → sub-beliefs
|
|
217
|
+
→ peer pull → attitude pressure
|
|
218
|
+
→ mood shock → affective state
|
|
219
|
+
→ inertia → resistance to change
|
|
220
|
+
→ composite → attitude(t)
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Examples
|
|
226
|
+
|
|
227
|
+
| File | Description |
|
|
228
|
+
|---|---|
|
|
229
|
+
| `examples/dublin_march.py` | Full reproduction of the original Dublin April 2025 experiment |
|
|
230
|
+
| `examples/minimal_usage.py` | Minimal usage — no timeline, live search only |
|
|
231
|
+
| `examples/custom_agent_kinds.py` | Adding a custom `nationalist` kind with its own priors |
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Citation
|
|
236
|
+
|
|
237
|
+
If you use this package in research, please cite:
|
|
238
|
+
|
|
239
|
+
```
|
|
240
|
+
@software{discourse_sim,
|
|
241
|
+
title = {discourse\_sim: LLM-Augmented Agent-Based Social Simulation of Discourse Dynamics},
|
|
242
|
+
author = {dreji18},
|
|
243
|
+
year = {2026},
|
|
244
|
+
note = {Python package}
|
|
245
|
+
}
|
|
246
|
+
```
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
discourse_sim
|
|
3
|
+
=============
|
|
4
|
+
LLM-augmented Agent-Based Social Simulation for modelling
|
|
5
|
+
public discourse dynamics following a critical real-world event.
|
|
6
|
+
|
|
7
|
+
Quick start
|
|
8
|
+
-----------
|
|
9
|
+
from discourse_sim import DiscourseSimulation
|
|
10
|
+
|
|
11
|
+
sim = DiscourseSimulation(
|
|
12
|
+
critical_event="In November 2023, Dublin city centre saw a major riot...",
|
|
13
|
+
event_date="2023-11-23",
|
|
14
|
+
n_agents=100,
|
|
15
|
+
n_days=15,
|
|
16
|
+
agent_distribution={"centrist": 0.45, "far_right": 0.20,
|
|
17
|
+
"pro_imm": 0.25, "media": 0.10},
|
|
18
|
+
topic="immigration in Ireland",
|
|
19
|
+
ollama_model="mistral:7b-instruct-q4_0",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
sim.run()
|
|
23
|
+
df = sim.to_dataframe()
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from discourse_sim.core import DiscourseSimulation
|
|
27
|
+
|
|
28
|
+
__all__ = ["DiscourseSimulation"]
|
|
29
|
+
__version__ = "0.1.0"
|