-
Notifications
You must be signed in to change notification settings - Fork 0
/
Game_fns.py
3110 lines (2356 loc) · 92.8 KB
/
Game_fns.py
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
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
'''
Vowel evolution program driver.
Run in PYTHON 3 (!)
with Vowel, Prototype, Word, Agent, Convention, graphics files in same directory
import time, random, re from Python library
Last update November 2017 HJMS
HOW TO RUN
Use 'run' (f5) from IDLE, or
execute this file while the others^ are in same directory, or use
$python3 Game_fns.py
in Linux. (Using the terminal is much faster)
Python3 and TKinter must be installed
The menu will print defaults and the commands you can enter at the prompt.
Use 'demo' if it's your first time and you want to be walked through setup of a game.
Use 'run' if you just want to see what happens with default parameters.
'''
import Vowel, Convention, Agent, Prototype, profile, Word
from time import ctime
from graphics import *
import re, datetime
from random import sample, choice
class Game_fns:
'''Game Class
Population starts as empty list for groups, which are lists of Agents of same age/stage.
Game runs through number of 'cycles' one 'step' at a time.
Each step exposes agents to Prototypes, appends a new group to the population, updates Prototypes.
One cycle is the number of steps required for the incoming group to complete its lifespan'''
def __init__(self):
'''Sets default parameters'''
self.languages= self.set_languages()
self.total_interactions = 0
self.num_cycles = 1 #cycle = follow a group from introduction to removal i.e. birth to death
self.cycle_lim = self.num_cycles
self.population_max = 10000 #population should never go this high unless you're running on Blue Gene
self.growth_rate = .03 #population growth rate (min. 2 agents/step)
self.age_limit = 40 #Age at which agents are removed.
al = self.age_limit
self.max_groups = al #max length of self.population
pm = self.population_max #population won't grow beyond this size
self.anc_group_size = 150 #ancestor group (first group with matching repertoires) recommended 60+
self.g_size = max([2, int( (pm-self.anc_group_size)/al )]) #num. of agents in each age group. If you want to use large numbers, turn off show
self.show = False #True -> more information is displayed. Turning this off makes runtime shorter
self.color_on = True #True -> prototypes and vowels are color-coded in graphic reports
self.length_flag = True #True -> all agents discriminate long vs short vowels
self.pause_time = 1 #number of seconds to show step reports (0 to wait for click)
self.perception = 1.0 #margin within which agents recognize matches (uniform and static for all agents)
self.phone_radius = 0 #margin within which agents form internal representation
self.phone_radius_noise = 0.25 #margin within which agents produce their internal representation when speaking
self.prox = -1 #controls the sensitivity of vowels in agent's reps
#PROX NOTE: prox is in ERB units and is added to vowel.weight
self.num_adults = self.anc_group_size
self.num_children = 0
self.num_babies = 0
self.adapt_perc = 0 #amount agents modify perception from feedback (0 will disable)
self.social = 0 #social prestige ranking levels (0 will diable)
self.lang_fn = "welsh"
self.base = self.languages[self.lang_fn] #base convention, which can be overwritten in the menu interface
self.lex_size = 50 #override the Convention default
self.convention = Convention.Convention(self.show, self.color_on, self.lex_size) #Convention tracks the prototypes
self.convention.str_to_protos(self.base)#Convention reads in a string and splits into keys
self.use_ipa_symbols = True
if self.use_ipa_symbols:
self.convention.plot = self.convention.plot_symbols
else:
self.convention.plot = self.convention.plot_spots
self.min_carrier_p = .25 #population size * min_carrier_p = minimum carriers
self.age_adult = int(self.age_limit/10) + 1 #at 10% lifespan, agents purge their vowels and stop listening
self.contact_agents = 50 #limit on number of "non-family" agents a listener hears at each step
self.contact_words = 25 #limit on how many words a listener heard from each "non-family" contact at each step
self.fam_size = 1
self.micro = False
self.micro_agent = None
self.num_repeats = 40 #MULTIPLE INTERACTIONS PER FAM MEMBER
self.sample_report = self.percept_sampling
self.find_prototypes = self.percept_protos
self.avg_adults_only = True
self.sampling_lim = 50
self.margin_watcher = False
self.curr_cycle = 0
self.str_buf = []
self.armchair_var = False
self.functional_load = 5
'''
SOME NOTES ON THE MARGIN MECHANICS
ex. perception = .75
When checking their rep. for a match to incoming signal, agents check the distance between their vowels and the signal
If the distance is less than .75, they will recognize it as a match
If no matches are found, agent will produce a randomized imitation within phone_radius ERB of the original.
Two vowels will conflict if they are within (vowel.weight + prox) ERB of each other
e.g. with a five vowel system {i, e, o, u, retracted_a} and prox add-on set to .75 :
vowel weight will be ~.2
average proximity for each vowel will be
.2 + .75 = .95 ERB
Which means any two Agent's Vowels within .95 ERB will "crowd" each other.
See Agent.py class.
A negative prox will effectively disable conflict micromanagement.
'''
def set_languages(self):
'''
Sets up a dictionary of vowel systems.
Entries are intended to be used as presets
which correspond to natural languages.
consonants are defined separately
'''
languages = dict()
languages["english"] = "i:, I, e:, epsilon, ae, u:, horseshoe, o:, open_o:, wedge, script_a:"
languages["spanish"] = "i, e, retracted_a, u, o"
#DEVELOPER NOTE ABOUT LANGUAGE PRESETS
#the following "languages" are all unreviewed approximations,
# sources: wikipedia,
# UCPI ( http://zimmer.csufresno.edu/~sfulop/UCPI/UCPI%20index.html ),
# and this site ( http://gesc19764.pwp.blueyonder.co.uk/vowels/vowel_systems.html )
# So far, the most interesting ones to watch are Dutch, Danish, and Swedish.
# You can adjust these yourself by altering the lines below and then running the program as normal.
# You can also add as many languages as you want.
# Note that prototype names which include ':' are long (250 ms)
# The complete "master set" can be viewed in Convention.py
# TIP: You can use command "languages" from the menu
# And it will iterate through these languages so you can review them
languages["russian"] = "i, epsilon, u, o, retracted_a, barred_i, retracted_a, e"
languages["italian"] = "i, e, epsilon, retracted_a, o, open_o, u"
languages["german"] = "i:, y, I, Y, e, o_slash, epsilon:, u, o, horseshoe, oe, retracted_a:, script_a, open_o"
languages["netherlandicdutch"] = "i, i:, e:, I, y, y:, Y, epsilon, epsilon:, oe:, retracted_a:, script_a, Y, u, u:, o:, open_o, open_o:"
languages["ancientgreek"] = "i, i:, y, y:, e, e:, retracted_a, retracted_a:, epsilon:, u, u:, o, o:"
languages["albanian"] = "i, y, e, retracted_a, u, o, schwa"
languages["swedish"] = "i:, y:, I, Y, e, e:, epsilon, epsilon:, o_slash, barred_o, rev_eps, oe, retracted_a, script_a, o:, u, u:, barred_u, barred_u:, open_o"
languages["japanese"] = "i, epsilon, turned_m, u, retracted_a, o, open_o"
languages["bengali"] = "i, epsilon, e, ae, retracted_a, script_a, horseshoe, o, open_o, u"
languages["arabic"] = "i, retracted_a, u"
languages["bulgarian"] = "i, e, rev_e, retracted_a, o, u"
languages["cantonese"] = "i, y, epsilon, oe, turned_a, retracted_a:, u, open_o"
languages["catalan"] = "i, e, epsilon, retracted_a, schwa, o, u, open_o"
languages["chichewa"] = "i, epsilon, retracted_a, o, u"
languages["chipewyan"] = "i, y, I, e, o_slash, oe, epsilon, ae, retracted_a, schwa, wedge, o, script_a, u"
languages["czech"] = "i, i:, I, e:, e, epsilon, epsilon:, schwa, retracted_a, retracted_a:, script_a, script_a:, open_o:, o, u"
languages["danish"] = "i, i:, y, y:, I, I:, epsilon, epsilon:, o_slash:, o_slash, e, e:, oe:, oe, ae:, ae, schwa, schwa:, horseshoe:, horseshoe, u, u:, o, o:, open_o, open_o:, script_a, script_a:"
languages["lyonnaisfrench"] = "i, epsilon, oe, o_slash, e, oe, ae, schwa, retracted_a, script_a, rev_script_a, o, u, open_o"
languages["hakka"] = "i, e, epsilon, ae, schwa, retracted_a, u, horseshoe, o, open_o, script_a"
languages["icelandic"] = "i, e, epsilon, o_slash, oe, u, o, script_a"
languages["idoma"] = "i, e, epsilon, retracted_a, u, o, open_o"
languages["korean"] = "i, I, e, epsilon, schwa, retracted_a, o, u"
languages["latvian"] = "i, I, e, e:, epsilon:, epsilon, wedge, retracted_a:, retracted_a, u, u:, horseshoe, horseshoe:, open_o, script_a, script_a:"
languages["malay"] = "i, I, e, epsilon, retracted_a, wedge, schwa, u, o, turned_m, open_o"
languages["norwegian"] = "i, y, I, e, o_slash, epsilon, ae, schwa, retracted_a, u, o, turned_m, o, open_o, script_a"
languages["zulu"] = "i, e, epsilon, schwa, retracted_a, u, horseshoe, o, open_o"
languages["slovak"] = "i, i:, epsilon:, epsilon, retracted_a, retracted_a:, u, u:, open_o, open_o:"
languages["tibetan"] = "i, y, I, e, o_slash, epsilon, retracted_a, wedge, u, horseshoe, o, open_o"
languages["tigrinya"] = "i, e, epsilon, retracted_a, schwa, u, o, open_o"
languages["turkish"] = "i, y, epsilon, oe, retracted_a, turned_m, u, open_o"
languages["ukranian"] = "i, I, epsilon, a, u, o"
languages["vietnamese"] = "i, e, epsilon, wedge, a, a:, turned_m, u, Y, o, open_o"
languages["xumi"] = "i:, e:, epsilon, barred_o, u, o:, script_a, retracted_a, wedge"
languages["oldenglish"] = "i, i:, y, y:, e, e:, ae, ae:, o_slash, o_slash:, script_a, script_a:, u, u:, o:, o"
languages["welsh"] = "i:, I, e:, epsilon, retracted_a, script_a:, barred_i, barred_i:, schwa, schwa:, open_o, horseshoe, u:, o:"
languages["arrernte"] = "retracted_a, schwa"
languages["finnish"] = "i, i:, y, y:, u, u:, e, e:, o_slash, o_slash:, epsilon, epsilon:, a, a:, u, u:, o, o:"
languages["karaja"] = "i, I, e, epsilon, wedge, barred_i, retracted_a, open_o, o, horseshoe, u"
languages["hidatsa"] = "i, u, i:, u:, e:, o:, a, a:"
#languages["yourlanguage"] = ""
####### ADD LANGUAGES ABOVE THIS LINE ########
#the names should be all lowercase alpha-num characters and there CANNOT (!) be any duplicates*
# *as in, no keyword (e.g. "spanish") can be used twice, but different languages may use the same set of vowels
#The following are for testing the values in the Convention IPA set;
# they aren't meant to be used as languages,
languages["high"] = "i, y, barred_i, barred_u, u, horseshoe, turned_m"
languages["mid"] = "I, Y, e, o_slash, barred_o, schwa, rams_horn, o, open_o, wedge, lil_bum, rev_e"
languages["low"] = "OE, epsilon, oe, ae, rev_eps, a, rev_script_a, turned_a, retracted_a, script_a"
languages["front"] = "i, y, I, e, o_slash, epsilon, ae, OE"
languages["central"] = "barred_i, barred_u, barred_o, rev_eps, lil_bum, rev_e, schwa, turned_a, retracted_a"
languages["back"] = "u, wedge, horseshoe, turned_m, rams_horn, o, open_o, script_a, rev_script_a"
high = languages["high"]
mid = languages["mid"]
low = languages["low"]
languages["short"] = ", ".join([high, mid, low])
languages["long"] = languages["short"].replace(",", ":,")+":"
languages["ipa"] = ", ".join([languages["short"], languages["long"]])
return languages
#############################
# STEP METHODS #
#############################
def step(self):
'''
Passage of time in steps.
Number of steps controlled by lifespan (age_limit) and cycles (num_cycles)
i.e. each agent takes age_limit steps from birth to death
'''
if self.show:
print("Stepping...")
self.reproduce() #append list of new agents to population
self.diffuse() #signal vowels in convention to all agents
self.increment() #advances every current agents' development. Insert argument for number of years
self.charon() #removes agents who have completed all cycles and increments cycle count
def get_prestige(self):
if self.social:
levels = range(self.social)
return choice(levels)
else:
return 0
def reproduce(self):
'''
Adds a new group of Agents with age = 0 to population--
babies eager to learn their parents' vowels and grow up to speak words.
Population size controlled by growth_rate
'''
popul = self.population
pot_fam = [k for k in self.get_sampling().keys()]
fam_size = min([self.fam_size, len(pot_fam)])
gr = self.growth_rate
pm = self.population_max
ta = self.total_agents
al = self.age_limit
#gs = self.g_size
#gs = max([1, int( (pm-ta) / (al - len(popul)) )] )
gs = max([2, int(ta * gr)]) #add at least two new agents every step
self.g_size = gs
if (ta+gs) <= pm: #population growth control. Peak population size = g_size * max_groups
a = Agent.Agent
gc = self.g_count+1
lf = self.length_flag
bp = self.perception
pm = self.prox
prod = self.phone_radius
pn = self.phone_radius_noise
ap = self.adapt_perc
gp = self.get_prestige
babies = [a(gc, i, bp, pm, prod, pn, lf, ap, gp()) for i in range(gs)]
popul.append(babies)
self.total_agents += gs
self.g_count = gc
#assign constant "nuclear family" to baby
for baby in babies:
baby.fam = sample(pot_fam, fam_size)
self.num_adults = sum([len(g) for g in self.population if g[0].age >= self.age_adult])
self.num_babies = len(self.population[-1])
self.num_children = self.total_agents - (self.num_adults + self.num_babies)
self.num_learners = self.num_babies + self.num_children
def diffuse(self):
'''
Transmits lexicon to Agents,
which will lead to the words being replicated in Agents' vocabularies.
'''
gs = self.g_size
c = self.convention
s = self.get_sampling()
proto_list = c.proto_dict.keys()
adult_age = int(self.age_limit/10)+1
children = (g for g in self.population if g[0].age < adult_age)
ta = self.total_agents
rpt = self.num_repeats #MULTIPLE INTERACTIONS PER FAM MEMBER
#"micro" mode: pick a random agent to monitor closely
if (self.micro and not self.micro_agent):
babies = self.population[-1]
self.micro_agent = choice(babies)
self.micro_agent.chosen = True
if not s:
#something has gone wrong; probably crazy parameters used
print("The language has died--agents were unable to pass on the lexicon.")
print("If you could please save and send your shell session or parameter values to")
print(" hannahjmscott@gmail.com ")
print("the developer will be super grateful and reply with a funny picture or something.")
self.curr_cycle = self.num_cycles+1
return
ca = self.contact_agents
cw = self.contact_words
s_keys = [s for s in s.keys()]
sample_size = int(min([self.lex_size, (len(s_keys)/4), ca]))
t = self.transmit
#iterate through population
for g in children:
for a in g:
#time0 = datetime.datetime.now()
#get sample_size random words from population
random_speakers = sample(s_keys, sample_size)
#random_words = [sample(s[rsp], min([len(s[rsp]), cw]) ) for rsp in random_speakers if len(s[rsp])]
#cw is the limit on how many words to get (0 for the whole vocab)
random_words = ( self.sample_agent_words(s[rsp], cw) for rsp in random_speakers if len(s[rsp]))
for rws in random_words:
for rw in rws:
#time1 = datetime.datetime.now()
#dif1 = time1 - time0
#time6 = datetime.datetime.now()
t(a, rw) #show the random words to the learner
#time7 = datetime.datetime.now()
#dif4 = time7 - time6
#print(dif4)
#time2 = datetime.datetime.now()
#get "family" input
family = [f for f in a.family if f in s]
#replace the dead family members (after a brief moment of respectful silence)
if len(family) < self.fam_size:
dead = self.fam_size - len(family)
new_fam = sample( [sk for sk in s_keys if sk != a.name and sk not in family and len(s[sk])], dead)
family.extend(new_fam)
for i in range(rpt):
fam_words = (self.sample_agent_words(s[member]) for member in family)
for ws in fam_words:
for w in ws:
#time3 = datetime.datetime.now()
#dif2 = time3 - time2
t(a, w)
#time4 = datetime.datetime.now()
#print(dif1+dif2)
if self.show:
self.sample_report() #draw population repertoires
self.proto_report() #draw current Prototypes
#time5 = datetime.datetime.now()
#dif3 = time5 - time4
#total_overhead = dif1 + dif2 + dif3
#print(total_overhead)
def sample_agent_words(self, vocab, lim = 0):
vg = [w for w in vocab.values()]
if len(vg):
if lim:
return sample(vg, lim)
return vg
return None
def get_sampling(self):
'''
Returns a dictionary of utterances for this step
i.e. {"agent name": [Word]}
'''
sampling = dict()
l = len(self.population)
if l < 3:
p = [self.population[0]]
else:
p = self.population
for g in p:
for a in g:
#sampling[a.name] = [w for w in a.idio.values()]
sampling[a.name] = a.idio
return sampling
def transmit(self, agent, n):
'''signals a word n to agent'''
self.total_interactions += 1
if self.armchair_var:
im = agent.call_matchers(n)
else:
im = agent.call_matchers_nh(n)
return
def increment(self):
'''increase age of every live agent'''
popul = self.population
adult_age = self.age_adult
for g in popul:
for a in g:
a.inc_age()
if a.age is adult_age:
a.purge_vwls()
chosen = self.micro_agent
if (chosen and chosen.age > adult_age):
print("\nAgent", chosen.name, "is all grown up!")
#chosen.print_rep()
#chosen.print_vocab()
#self.convention.draw_agent_margins(chosen)
self.draw_proto_margins()
chosen.chosen = False
self.micro_agent = None
def charon(self):
'''Removes the group of agents who have completed all stages.
Increments cycle counter for game'''
popul = self.population
oldest = popul[0]
if not oldest: #something has gone wrong
self.curr_cycle = self.num_cycles+1
print("Error in charon: the babies have been abandoned!")
return
a = oldest[0].age
age_lim = self.age_limit
d = []
#if the oldest group has reached the lifespan limit, remove them
#and inc the cycle counter if the new oldest group is 1 year under lifespan limit
if a >= age_lim:
d = popul.pop(0)
self.total_agents -= len(d)
del d
if(popul[0][0].age >= (age_lim - 1)):
self.curr_cycle += 1
if self.show:
print("Cycle ", self.curr_cycle, "completed.")
if self.avg_adults_only:
min_age_avg = self.age_adult
else:
min_age_avg = 0
#if self.show:
#time0 = datetime.datetime.now()
#self.find_prototypes(min_age_avg, self.age_limit)
#time1 = datetime.datetime.now()
#dif1 = time1 - time0
#print(dif1)
def percept_protos(self, min_age = 0, max_age = 100):
'''
Gets the average internal representation for each word in lexicon.
Shifting of vowels is enabled by updating the mean center for each word.
For each word, the Agents' vocabularies are scanned and
the Vowels used by Agents for that word are collected and averaged.
This updates the Convention's proto_dict.
'''
c = self.convention
agents = [g for g in self.population if g[0].age > min_age]
pd = c.proto_dict
adult_vowels = []
lex = c.lexicon.items()
for (w_id, word) in lex:
nuc = word.nucleus
word_vowels = (a.idio[w_id].percept for g in agents for a in g if w_id in a.idio)
if word_vowels:
word_proto = c.get_word_prototype(word_vowels, w_id) # word
adult_vowels.append(word_proto)
else:
print("RIP", word)
r = self.perception + self.prox
c.group_word_protos(r, adult_vowels)
return 1
def vowel_protos(self, min_age = 0, max_age = 100):
'''
Gets the average pronunciation for each word in lexicon.
Shifting of vowels is enabled by updating the mean center for each word.
For each word, the Agents' vocabularies are scanned and
the Vowels used by Agents for that word are collected and averaged.
This updates the Convention's proto_dict.
Accesses the agent's word's vowel, which may be dynamically generated
'''
c = self.convention
agents = [g for g in self.population if g[0].age > min_age]
#pd = c.proto_dict
adult_vowels = []
lex = c.lexicon.items()
for (w_id, word) in lex:
nuc = word.nucleus
word_vowels = ((a.idio[w_id]).get_vowel() for g in agents for a in g if w_id in a.idio)
if word_vowels:
word_proto = c.get_word_prototype(word_vowels, w_id)
adult_vowels.append(word_proto)
else:
print("RIP", word)
r = self.perception + self.prox
c.group_word_protos(r, adult_vowels)
return 1
#################################
#REPORTING METHODS #
#################################
######## GRAPHICAL
def proto_report(self):
'''
Plots the current convention in vowel space.
The convention is shown as a number of spots in the vowel space,
where each spot is the averaged pronunciation of the vowel in a word.
This method also prints an update on the game state in the shell,
which lists the number of live Agents (adults, children, babies),
the current mean centers for each word prototype,
and the cycle counter.
'''
if self.avg_adults_only:
min_age_avg = self.age_adult
else:
min_age_avg = 0
self.find_prototypes(min_age_avg, self.age_limit)
c = self.convention
sl = []
sol = sl.append
protos = sorted(c.proto_dict.values(), key = lambda proto: (proto.name.split("]["))[1])
ta = self.total_agents
num_adults = sum([len(g) for g in self.population if g[0].age >= self.age_adult])
num_babies = len(self.population[-1])
num_children = ta - (num_adults + num_babies)
s0 = "STEP COMPLETED. "
#print number of adults, children, babies, and cycles
s = ("\n{0} live agents ({1} babies, {2} children, {3} adults)\n".format(ta, num_babies, num_children, num_adults))
sol(s0+s)
print(s)
message = "Mean centers of adult agents' vowels (Cycle "+str(self.curr_cycle)+")"
mcp = self.min_carrier_p
min_carriers = (ta * mcp)
if (self.micro and self.micro_agent):
self.micro_agent.print_rep()
self.micro_agent.print_vocab()
else: #macro mode: show averages
s_header = "{0:25}{1}".format("WORD", "AVERAGE VOWEL")
sol(s_header)
for p in protos:
si = "{0:25} {1}".format(p.name, str(p))
sol(si)
print(si)
#draw the graphic
c.plot(self.pause_time, min_carriers, message)
if self.margin_watcher:
#c.draw_avg_margins(protos, self.perception, self.prox)
c.draw_base_margins(protos, self.perception, self.prox)
self.str_buf.append("\n".join(sl))
return 1
def vowel_sampling(self):
'''
Plots the protos of every agent (overlaid) in the vowel space.
This 'sampling' shows the population's Vowels,
where each Vowel is represented by a dot in the output window.
This also updates the centered label in the window,
which shows the number of live Agents,
the Agents' perception,
and the user-set prox supplement.
'''
from random import sample
lf = self.length_flag
p = self.population
c = self.convention
adult = self.age_adult
if self.avg_adults_only:
min_age_avg = self.age_adult
else:
min_age_avg = 0
self.find_prototypes(min_age_avg, self.age_limit)
#get all Agents' Vowels that are being used in Words
all_reps = set() #will be a set of Vowels
all_agents = []
for g in p:
if ((not self.avg_adults_only) or g[0].age >= self.age_adult):
all_agents.extend(g)
pop_sample = sample( all_agents, self.sampling_lim)
for a in pop_sample:
for w in a.idio.values():
#retrieve protos i.e. vowels as they are spoken (dynamically generated)
all_reps.add(w.get_vowel())
#Update the center label and plot the vowels
if all_reps:
s = "Live agents = {0}, Perception = {1}, Prox add-on = {2}, Phone radius = {3}, Vowel noise = {4}".format(self.total_agents, self.perception, self.prox, self.phone_radius, self.phone_radius_noise)
if len(all_reps) < 2:
c.plot_sets([all_reps], self.pause_time, s)
c.plot_sets(all_reps, self.pause_time, s)
if (self.micro and self.micro_agent):
c.plot_micro(self.micro_agent)
return 1
def percept_sampling(self):
'''
Plots the percepts of all agents (overlaid) in the vowel space.
This 'sampling' shows the population's Vowels,
where each Vowel is represented by a dot in the output window.
This also updates the centered label in the window,
which shows the number of live Agents,
the Agents' perception,
and the user-set prox supplement.
'''
lf = self.length_flag
p = self.population
c = self.convention
adult = self.age_adult
if self.avg_adults_only:
min_age_avg = self.age_adult
else:
min_age_avg = 0
self.find_prototypes(min_age_avg, self.age_limit)
#get all Agents' Vowels that are being used in Words
all_reps = set() #will be a set of Vowels
all_agents = []
for g in p:
if ((not self.avg_adults_only) or g[0].age >= self.age_adult):
all_agents.extend(g)
sample_size = min([self.sampling_lim, len(all_agents)])
pop_sample = sample( all_agents, sample_size)
for a in pop_sample:
for w in a.idio.values():
#retrieve percepts i.e. internal representation of vowels in agents' minds
all_reps.add(w.percept)
#Update the center label and plot the vowels
if all_reps:
s = "Live agents = {0}, Perception = {1}, Prox add-on = {2}, Phone radius = {3}, Vowel noise = {4}".format(self.total_agents, self.perception, self.prox, self.phone_radius, self.phone_radius_noise)
if len(all_reps) < 2:
c.plot_sets([all_reps], self.pause_time, s)
c.plot_sets(all_reps, self.pause_time, s)
if (self.micro and self.micro_agent):
c.plot_micro(self.micro_agent)
del all_reps
return 1
def init_report(self, pause = 0):
'''
Plots base convention in vowel space at the beginning of a simulation.
pause is the number of seconds window stays open.
Enter 0 to wait for mouse before continuing. (default)
'''
#set up the graphical output window
c = self.convention
s = str(len(self.base.split(", ")))
label = (s+" vowels; "+("%.4f" %self.perception))
c.draw_win(label)
#wait for user to be ready before starting
c.plot(pause, 1, "Starting Prototypes. (Click in window to continue)")
return 1
def final_report(self):
'''
Shows the final sampling and mean center results.
Closes the Convention window when user clicks in it.
'''
c = self.convention
ta = self.total_agents
min_carriers = (ta * .1) + 1
lf = self.length_flag
fs = self.fam_size
ca = self.contact_agents
cw = self.contact_words
nc = self.curr_cycle
cps = "Family = {0}, Contacts = {1}, Words = {2}, Cycles = {3}".format(fs, ca, cw, nc)
c.set_param_str(cps)
if self.avg_adults_only:
min_age_avg = self.age_adult
else:
min_age_avg = 0
self.find_prototypes(min_age_avg, self.age_limit)
self.sample_report()
if c.proto_dict.values():
c.plot(0, min_carriers, "End results. (Click in window to continue.)")
if (self.micro and self.micro_agent):
c.plot_micro(self.micro_agent)
if not self.show:
c.close_win()
self.count_near_splits() #counts the number of agents with lexical splits
return 1
def displacement_report(self, save = None):
'''
Calls the current Convention's show_displacement method
which plots the original and current averages of prototypes
'''
if self.avg_adults_only:
min_age_avg = self.age_adult
else:
min_age_avg = 0
self.find_prototypes(min_age_avg, self.age_limit)
conv = self.convention
p = str(self.perception)
b = self.base
mcp = self.min_carrier_p
mc = (self.total_agents * mcp)
protos = [p for p in conv.proto_dict.values() if p.carriers > mc]
lf = self.length_flag
fs = self.fam_size
ca = self.contact_agents
cw = self.contact_words
nc = self.curr_cycle
l1 = "Live agents = {0}, Perception = {1}, Prox add-on = {2}, Phone radius = {3}, Vowel noise = {4}".format(self.total_agents, self.perception, self.prox, self.phone_radius, self.phone_radius_noise)
cps = "Family = {0}, Contacts = {1}, Words = {2}, Cycles = {3}".format(fs, ca, cw, nc)
conv.set_param_str(cps)
if save:
fn = self.file_name(save)+"_shift"
conv.show_displacement(l1, protos, 1)
conv.save_win(fn)
conv.close_win()
elif self.margin_watcher:
#conv.draw_avg_margins(protos, self.perception, self.prox, True)
conv.draw_base_margins(protos, self.perception, self.prox, True)
else:
conv.show_displacement(l1, protos, 0)
return 1
def draw_proto_margins(self, save = None):
'''Calls the current Convention's draw_proto_margins metretracted_a
which draws the convention prototypes,
the perceptual margin as a radius around the prototype, and
the average proximity margin as a radius around the prototype'''
if (self.micro and self.micro_agent):
return self.draw_agent_margins(self.micro_agent)
else:
c = self.convention
if (not c.lexicon.keys()):
c.strap_protos()
perc = self.perception
prox = self.prox
if prox < 0:
prox = 0
c.draw_proto_margins(perc, prox, False)
if save:
fn = self.file_name(save)+"_margins"
c.save_win(fn)
else:
cfn = None
c.win.getMouse()
c.close_win()
return 1
def file_name(self, pref=""):
perc_s = str(self.perception).replace(".", "")
prox_s = str(self.prox).replace(".", "")
phone_s = str(self.phone_radius).replace(".", "")
v_s = str(self.phone_radius_noise).replace(".", "")
fam_s = str(self.fam_size)
friend_s = str(self.contact_agents)
rpts = str(self.num_repeats)
cc = "{0:03d}".format(self.curr_cycle)
if self.armchair_var:
li = [pref+fam_s+rpts, friend_s, perc_s, prox_s, phone_s, v_s, "rmchr", cc]
else:
li = [pref+fam_s+rpts, friend_s, perc_s, prox_s, phone_s, v_s, cc]
cfn = ("_".join(li))
return cfn
def draw_agent_margins(self, agent):
c = self.convention
c.draw_agent_margins(agent)
if not self.show:
c.close_win()
return 1
def redraw_last(self, save = None):
'''
Plots the most recent sampling of the live agents' vowels.
Show == True is not required for this to be called.
If user closes the window, they can use this
to re-open and get last sampling.
Closes the window on mouse click.
If called by write_image(),
it will save the window as a gif
then close the window
'''
c = self.convention
self.sample_report()
ta = self.total_agents
message = "Mean centers of adult agents' vowels (Cycle "+str(self.curr_cycle)+")"
mcp = self.min_carrier_p
min_carriers = 1 #(ta * mcp)
#Draw the results and
#either save, then close the window, or
#show and wait for mouse before closing it.
if save:
if self.use_ipa_symbols:
c.plot = c.plot_spots
self.proto_report()
fn = self.file_name(save)
c.save_win(fn)
c.plot = c.plot_symbols
else:
c.plot(0, min_carriers, message)
c.close_win()
return 1
######## TEXT-ONLY
def shifting_report(self, lang = ""):
'''
Prints a table listing the convention's Prototypes.
Shows the IPA formant values,
the current mean center,
the current weight, and
the displacement (from ipa i.e. self.base, not last run)
for each word in the lexicon.
'''
s_out_li = [] #save output to write to file later
sol = s_out_li.append
if self.avg_adults_only:
min_age_avg = self.age_adult
else:
min_age_avg = 0
self.find_prototypes(min_age_avg, self.age_limit)
ta = self.total_agents - self.g_size #don't count the babies
cv = self.convention.proto_dict.values()
pl = sorted(cv, key=lambda proto: (proto.name.split("]["))[1]) #sorts the Prototypes by f1
bpd = self.convention.base_proto_dict
bvd = self.convention.base_vowel_dict
s1 = "Resulting Prototypes with {0} agent perception, {1} proximity margin".format(self.perception, self.prox)
sol("\n\n"+s1)
print(s1)
s2 = "{0:25}{1:18}{2:18}{3:16}".format("Name", "Original", "Current", "Displacement")
sol(s2)
print(s2)
base = self.base.split(", ")
for p in pl:
p_class = (p.name.split("][")[1])
#p_class = "".join(re.findall("[a-zA-Z_:]+", p.name))
if p_class in base:
original = bvd[p_class]
shift = "{0:.4f}".format(p.euc(original))
o = original
else:
shift = "{:12}".format(" --")
o = "{:18}".format(" --")
#r = "{0:.0f}%".format((p.carriers/ta)*100) #retention
name = p.name
si = "{0:25}{1:18}{2:18}{3:16}".format(name, str(o), str(p), str(shift))
sol(si)
print(si)
s_out = "\n".join(s_out_li)
#self.str_buf.append(s_out)
if lang:
fn = self.file_name(lang)+".txt"
else:
fn = ctime().replace(" ", "_")+".txt"
fn = fn.replace(":", "")
print("Saving to file", fn)
f = open(fn, "a")
f.write(s_out)
f.close()
del s_out