-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathmind.py
More file actions
277 lines (241 loc) · 12.5 KB
/
mind.py
File metadata and controls
277 lines (241 loc) · 12.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
import random
class Mind(object):
"""A person's mind."""
def __init__(self, person):
"""Initialize a Mind object."""
self.person = person
if self.person.mother: # Person object
self.memory = self._init_memory()
else: # PersonExNihilo object
self.memory = self._init_ex_nihilo_memory()
self.mental_models = {}
# A mind's preoccupation is an entity that this person is currently preoccupied
# by, e.g., someone for whom this person is trying to fill in missing belief facets
self.preoccupation = None
self.thoughts = []
self.salient_action_selector = None
self.receptors = {} # Dictionary mapping signal names to corresponding signal receptors
self.synapses = {}
def __str__(self):
"""Return string representation."""
return "Mind of {person}".format(person=self.person.name)
def _init_memory(self):
"""Determine a person's base memory capability, given their parents'."""
config = self.person.game.config
if random.random() < config.memory_heritability:
takes_after = random.choice([self.person.mother, self.person.father])
memory = random.normalvariate(takes_after.mind.memory, config.memory_heritability_sd)
else:
takes_after = None
memory = random.normalvariate(config.memory_mean, config.memory_sd)
if self.person.male: # Men have slightly worse memory (studies show)
memory -= config.memory_sex_diff
if memory > config.memory_cap:
memory = config.memory_cap
elif memory < config.memory_floor_at_birth:
memory = config.memory_floor_at_birth
feature_object = Feature(value=memory, inherited_from=takes_after)
return feature_object
def _init_ex_nihilo_memory(self):
"""Determine this person's base memory capability."""
config = self.person.game.config
memory = random.normalvariate(config.memory_mean, config.memory_sd)
if self.person.male: # Men have slightly worse memory (studies show)
memory -= config.memory_sex_diff
if memory > config.memory_cap:
memory = config.memory_cap
elif memory < config.memory_floor:
memory = config.memory_floor
feature_object = Feature(value=memory, inherited_from=None)
return feature_object
@property
def recent_thoughts(self):
"""Return the N most recent thoughts by this person, where N is specified in config.py."""
number_of_recent_thoughts = self.person.game.config.number_of_recent_thoughts
return self.thoughts[:number_of_recent_thoughts]
def last_thought_had_signal(self, signal_name):
"""Return this person's last thought."""
if self.thoughts:
return signal_name in self.thoughts[-1].signals
else:
return False
def closest_match(self, features, entity_type='person'):
"""Match a set of features describing an entity against this person's mental models to return
a closest match.
"""
# TODO check for partial matches, express multiple matches (if there are multiple)
assert features, "A person's mind.closest_match() method was called with no indexing features."
# Build a lambda function that will match mental models against the given features
matches_description = lambda mental_model: all(
str(mental_model.get_facet_to_this_belief_of_type(feature[0])) == feature[1]
for feature in features if feature
)
# Collect all matches
all_matches = [
p for p in self.mental_models if p.type == entity_type and matches_description(self.mental_models[p])
]
if not all_matches:
return None
# Return the most salient match (meaning most salient to this person)
most_salient_match = max(all_matches, key=lambda match: self.person.salience_of_other_people.get(match, 0.0))
return most_salient_match
def associate(self, artifact):
"""Associate the signals emitting from an artifact with salient notions in this mind to
produce a set of stimuli, which may elicit a thought.
@param artifact: The artifact that this person has encountered.
"""
stimuli = {}
# Start with the artifact signals
for signal, strength in artifact.signals:
if signal not in stimuli:
stimuli[signal] = 0
stimuli[signal] += strength
# Boost the signals according to any corresponding signal receptors that
# this person may have
for signal, _ in artifact.signals:
if signal in self.receptors:
stimuli[signal] += self.receptors[signal].voltage
# Generate an action potential, which will propagate activity across this
# receptors synapses to potentially bring in more stimuli
for activated_signal, activated_signal_voltage in self.receptors[signal].activate():
if activated_signal not in stimuli:
stimuli[activated_signal] = 0
stimuli[activated_signal] += activated_signal_voltage
return stimuli
def elicit_thought(self, stimuli):
"""Elicit a thought from a set of stimuli"""
# Request a thought from Productionist by association with the stimuli
elicited_thought = (
self.person.game.thought_productionist.target_association(thinker=self.person, stimuli=stimuli)
)
return elicited_thought
# def wander(self):
# """Let this mind wander."""
# a_thought = Thoughts.a_thought(mind=self)
# if a_thought:
# self.think(a_thought)
def think(self, thought):
"""Think a thought."""
self.thoughts.append(thought)
thought.realize()
thought.execute()
def update_receptor_voltages_and_synapse_weights(self, voltage_updates):
"""Update the voltages of this person's signal receptors."""
# Add receptors for all the new signals that have elicited a thought in
# this mind for the first time
for signal in voltage_updates:
if signal not in self.receptors:
self.receptors[signal] = Receptor(mind=self, signal=signal)
# Now, update receptor voltages and instantiate new synapses (or strengthen
# existing ones)
for signal, voltage_update in voltage_updates.iteritems():
# Update voltages
self.receptors[signal].voltage += voltage_update
# Instantiate/strengthen synapses (i.e., increase their weights) for all pairs of signals
for other_signal in voltage_updates:
if other_signal != signal:
# If no such synapse yet exists, instantiate one
if tuple(sorted([signal, other_signal])) not in self.synapses:
receptors = (self.receptors[signal], self.receptors[other_signal])
Synapse(receptors=receptors)
self.receptors[signal].synapses[self.receptors[other_signal]].strengthen()
class Feature(float):
"""A feature representing a person's memory capability and metadata about that."""
def __init__(self, value, inherited_from):
"""Initialize a Feature object.
@param value: A float representing the value, on a scale from -1 to 1, of a
person's memory capability.
@param inherited_from: The parent from whom this memory capability was
inherited, if any.
"""
super(Feature, self).__init__()
self.inherited_from = inherited_from
def __new__(cls, value, inherited_from):
"""Do float stuff."""
return float.__new__(cls, value)
class Receptor(object):
"""A signal receptor in the mind of a person.
Signal receptors correspond to individual environmental signals (e.g.,
'work' or 'birth' or a particular person or bar in the world) and have
a voltage. Higher receptor voltages make their associated signals more
salient to an individual, which makes the person more likely to have
thoughts that are elicited by the signal.
Receptors connect to one another via synapses, which are pathways that
connect receptors and facilitate activity propagation across them. When
a person has a thought that is associated with multiple signals, synapses
connecting the corresponding receptors are constructed pairwise. The more
often signal receptors are activated simultaneously (by a thought associated
with both signals being thunk), the stronger the weight of the synapse
becomes.
When a signal in the world activates its receptor in the mind of a person,
an action potential will be generated in that receptor, which will propagate
across its synapses to in turn activate connected receptors. This makes
signals become associated in the mind of characters to the degree that
they have co-occurred in their cumulative subjective experience of the
world. (This is a loose operationalization of long-term potentiation.)
"""
def __init__(self, mind, signal):
"""Initialize a Receptor object."""
self.mind = mind
self.signal = signal # ID of the signal (e.g., 'job' or a character object's ID in memory)
self.voltage = 0
self.synapses = {}
def __str__(self):
"""Return string representation."""
return "A receptor for '{signal_name}' signals in the mind of {owner} (voltage: {voltage})".format(
signal_name=self.signal,
owner=self.mind.person.name,
voltage=self.voltage
)
def activate(self):
"""Activate this signal receptor to generate an action potential that will propagate across its synapses.
Specifically, this method returns a set of receptors activated by the action potential, along
with the associated synapse weights.
"""
activations = set()
for synapse in self.synapses.values():
other_receptor = synapse.other_receptor(receptor=self)
activated_signal = other_receptor.signal
activated_signal_weight = synapse.config.action_potential_signal_weight_multiplier * synapse.weight
activations.add((activated_signal, activated_signal_weight))
return activations
def most_associated_signals(self, n=3, excluding=None):
"""Return the signals associated with n heaviest synapses connected to this receptor."""
excluding = excluding if excluding else []
synapses_sorted_by_weight = sorted(
{s for s in self.synapses if s.signal not in excluding}, key=lambda s: self.synapses[s].weight
)
return [s.signal for s in synapses_sorted_by_weight][:n]
class Synapse(object):
"""A synapse in the mind of a person that connects two signal receptors and has a weight."""
def __init__(self, receptors):
"""Initialize a Synapse object."""
# We'll need to make a lot of calls to config -- amortize this by setting it
# as an attribute on this class
self.config = list(receptors)[0].mind.person.game.config
self.receptors = receptors
self.weight = self.config.signal_receptor_synapse_starting_weight
# Update the .synapses attribute of the receptors
receptor, other_receptor = receptors
receptor.synapses[other_receptor] = self
other_receptor.synapses[receptor] = self
# Update the .synapses attribute of the mind
mind_synapses_key = tuple(sorted([receptors[0].signal, receptors[1].signal]))
self.receptors[0].mind.synapses[mind_synapses_key] = self
def __str__(self):
"""Return string representation."""
return (
"A synapse between receptors for '{receptor1}' and '{receptor2}' signals in the mind "
"of {owner} (weight: {weight})".format(
receptor1=self.receptors[0].signal,
receptor2=self.receptors[1].signal,
owner=self.receptors[0].mind.person.name,
weight=self.weight
)
)
def other_receptor(self, receptor):
"""Return the receptor incident on this synpase that is not the given receptor."""
return self.receptors[0] if self.receptors[0] is not receptor else self.receptors[1]
def strengthen(self):
"""Strengthen this synapse."""
self.weight += self.config.signal_receptor_synapse_weight_increase_increment