edsl 0.1.36.dev2__py3-none-any.whl → 0.1.36.dev6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (257) hide show
  1. edsl/Base.py +303 -298
  2. edsl/BaseDiff.py +260 -260
  3. edsl/TemplateLoader.py +24 -24
  4. edsl/__init__.py +47 -47
  5. edsl/__version__.py +1 -1
  6. edsl/agents/Agent.py +804 -800
  7. edsl/agents/AgentList.py +337 -337
  8. edsl/agents/Invigilator.py +222 -222
  9. edsl/agents/InvigilatorBase.py +294 -294
  10. edsl/agents/PromptConstructor.py +312 -311
  11. edsl/agents/__init__.py +3 -3
  12. edsl/agents/descriptors.py +86 -86
  13. edsl/agents/prompt_helpers.py +129 -129
  14. edsl/auto/AutoStudy.py +117 -117
  15. edsl/auto/StageBase.py +230 -230
  16. edsl/auto/StageGenerateSurvey.py +178 -178
  17. edsl/auto/StageLabelQuestions.py +125 -125
  18. edsl/auto/StagePersona.py +61 -61
  19. edsl/auto/StagePersonaDimensionValueRanges.py +88 -88
  20. edsl/auto/StagePersonaDimensionValues.py +74 -74
  21. edsl/auto/StagePersonaDimensions.py +69 -69
  22. edsl/auto/StageQuestions.py +73 -73
  23. edsl/auto/SurveyCreatorPipeline.py +21 -21
  24. edsl/auto/utilities.py +224 -224
  25. edsl/base/Base.py +289 -289
  26. edsl/config.py +149 -149
  27. edsl/conjure/AgentConstructionMixin.py +152 -152
  28. edsl/conjure/Conjure.py +62 -62
  29. edsl/conjure/InputData.py +659 -659
  30. edsl/conjure/InputDataCSV.py +48 -48
  31. edsl/conjure/InputDataMixinQuestionStats.py +182 -182
  32. edsl/conjure/InputDataPyRead.py +91 -91
  33. edsl/conjure/InputDataSPSS.py +8 -8
  34. edsl/conjure/InputDataStata.py +8 -8
  35. edsl/conjure/QuestionOptionMixin.py +76 -76
  36. edsl/conjure/QuestionTypeMixin.py +23 -23
  37. edsl/conjure/RawQuestion.py +65 -65
  38. edsl/conjure/SurveyResponses.py +7 -7
  39. edsl/conjure/__init__.py +9 -9
  40. edsl/conjure/naming_utilities.py +263 -263
  41. edsl/conjure/utilities.py +201 -201
  42. edsl/conversation/Conversation.py +238 -238
  43. edsl/conversation/car_buying.py +58 -58
  44. edsl/conversation/mug_negotiation.py +81 -81
  45. edsl/conversation/next_speaker_utilities.py +93 -93
  46. edsl/coop/PriceFetcher.py +54 -58
  47. edsl/coop/__init__.py +2 -2
  48. edsl/coop/coop.py +849 -815
  49. edsl/coop/utils.py +131 -131
  50. edsl/data/Cache.py +527 -527
  51. edsl/data/CacheEntry.py +228 -228
  52. edsl/data/CacheHandler.py +149 -149
  53. edsl/data/RemoteCacheSync.py +84 -0
  54. edsl/data/SQLiteDict.py +292 -292
  55. edsl/data/__init__.py +4 -4
  56. edsl/data/orm.py +10 -10
  57. edsl/data_transfer_models.py +73 -73
  58. edsl/enums.py +173 -173
  59. edsl/exceptions/__init__.py +50 -50
  60. edsl/exceptions/agents.py +40 -40
  61. edsl/exceptions/configuration.py +16 -16
  62. edsl/exceptions/coop.py +10 -2
  63. edsl/exceptions/data.py +14 -14
  64. edsl/exceptions/general.py +34 -34
  65. edsl/exceptions/jobs.py +33 -33
  66. edsl/exceptions/language_models.py +63 -63
  67. edsl/exceptions/prompts.py +15 -15
  68. edsl/exceptions/questions.py +91 -91
  69. edsl/exceptions/results.py +26 -26
  70. edsl/exceptions/surveys.py +34 -34
  71. edsl/inference_services/AnthropicService.py +87 -87
  72. edsl/inference_services/AwsBedrock.py +115 -115
  73. edsl/inference_services/AzureAI.py +217 -217
  74. edsl/inference_services/DeepInfraService.py +18 -18
  75. edsl/inference_services/GoogleService.py +156 -156
  76. edsl/inference_services/GroqService.py +20 -20
  77. edsl/inference_services/InferenceServiceABC.py +147 -119
  78. edsl/inference_services/InferenceServicesCollection.py +72 -68
  79. edsl/inference_services/MistralAIService.py +123 -123
  80. edsl/inference_services/OllamaService.py +18 -18
  81. edsl/inference_services/OpenAIService.py +224 -224
  82. edsl/inference_services/TestService.py +89 -89
  83. edsl/inference_services/TogetherAIService.py +170 -170
  84. edsl/inference_services/models_available_cache.py +118 -94
  85. edsl/inference_services/rate_limits_cache.py +25 -25
  86. edsl/inference_services/registry.py +39 -39
  87. edsl/inference_services/write_available.py +10 -10
  88. edsl/jobs/Answers.py +56 -56
  89. edsl/jobs/Jobs.py +1112 -1089
  90. edsl/jobs/__init__.py +1 -1
  91. edsl/jobs/buckets/BucketCollection.py +63 -63
  92. edsl/jobs/buckets/ModelBuckets.py +65 -65
  93. edsl/jobs/buckets/TokenBucket.py +248 -248
  94. edsl/jobs/interviews/Interview.py +651 -633
  95. edsl/jobs/interviews/InterviewExceptionCollection.py +99 -90
  96. edsl/jobs/interviews/InterviewExceptionEntry.py +182 -164
  97. edsl/jobs/interviews/InterviewStatistic.py +63 -63
  98. edsl/jobs/interviews/InterviewStatisticsCollection.py +25 -25
  99. edsl/jobs/interviews/InterviewStatusDictionary.py +78 -78
  100. edsl/jobs/interviews/InterviewStatusLog.py +92 -92
  101. edsl/jobs/interviews/ReportErrors.py +66 -66
  102. edsl/jobs/interviews/interview_status_enum.py +9 -9
  103. edsl/jobs/runners/JobsRunnerAsyncio.py +337 -343
  104. edsl/jobs/runners/JobsRunnerStatus.py +332 -332
  105. edsl/jobs/tasks/QuestionTaskCreator.py +242 -242
  106. edsl/jobs/tasks/TaskCreators.py +64 -64
  107. edsl/jobs/tasks/TaskHistory.py +441 -425
  108. edsl/jobs/tasks/TaskStatusLog.py +23 -23
  109. edsl/jobs/tasks/task_status_enum.py +163 -163
  110. edsl/jobs/tokens/InterviewTokenUsage.py +27 -27
  111. edsl/jobs/tokens/TokenUsage.py +34 -34
  112. edsl/language_models/LanguageModel.py +718 -718
  113. edsl/language_models/ModelList.py +102 -102
  114. edsl/language_models/RegisterLanguageModelsMeta.py +184 -184
  115. edsl/language_models/__init__.py +2 -2
  116. edsl/language_models/fake_openai_call.py +15 -15
  117. edsl/language_models/fake_openai_service.py +61 -61
  118. edsl/language_models/registry.py +137 -137
  119. edsl/language_models/repair.py +156 -156
  120. edsl/language_models/unused/ReplicateBase.py +83 -83
  121. edsl/language_models/utilities.py +64 -64
  122. edsl/notebooks/Notebook.py +259 -259
  123. edsl/notebooks/__init__.py +1 -1
  124. edsl/prompts/Prompt.py +358 -358
  125. edsl/prompts/__init__.py +2 -2
  126. edsl/questions/AnswerValidatorMixin.py +289 -289
  127. edsl/questions/QuestionBase.py +616 -616
  128. edsl/questions/QuestionBaseGenMixin.py +161 -161
  129. edsl/questions/QuestionBasePromptsMixin.py +266 -266
  130. edsl/questions/QuestionBudget.py +227 -227
  131. edsl/questions/QuestionCheckBox.py +359 -359
  132. edsl/questions/QuestionExtract.py +183 -183
  133. edsl/questions/QuestionFreeText.py +113 -113
  134. edsl/questions/QuestionFunctional.py +159 -155
  135. edsl/questions/QuestionList.py +231 -231
  136. edsl/questions/QuestionMultipleChoice.py +286 -286
  137. edsl/questions/QuestionNumerical.py +153 -153
  138. edsl/questions/QuestionRank.py +324 -324
  139. edsl/questions/Quick.py +41 -41
  140. edsl/questions/RegisterQuestionsMeta.py +71 -71
  141. edsl/questions/ResponseValidatorABC.py +174 -174
  142. edsl/questions/SimpleAskMixin.py +73 -73
  143. edsl/questions/__init__.py +26 -26
  144. edsl/questions/compose_questions.py +98 -98
  145. edsl/questions/decorators.py +21 -21
  146. edsl/questions/derived/QuestionLikertFive.py +76 -76
  147. edsl/questions/derived/QuestionLinearScale.py +87 -87
  148. edsl/questions/derived/QuestionTopK.py +91 -91
  149. edsl/questions/derived/QuestionYesNo.py +82 -82
  150. edsl/questions/descriptors.py +418 -418
  151. edsl/questions/prompt_templates/question_budget.jinja +13 -13
  152. edsl/questions/prompt_templates/question_checkbox.jinja +32 -32
  153. edsl/questions/prompt_templates/question_extract.jinja +11 -11
  154. edsl/questions/prompt_templates/question_free_text.jinja +3 -3
  155. edsl/questions/prompt_templates/question_linear_scale.jinja +11 -11
  156. edsl/questions/prompt_templates/question_list.jinja +17 -17
  157. edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -33
  158. edsl/questions/prompt_templates/question_numerical.jinja +36 -36
  159. edsl/questions/question_registry.py +147 -147
  160. edsl/questions/settings.py +12 -12
  161. edsl/questions/templates/budget/answering_instructions.jinja +7 -7
  162. edsl/questions/templates/budget/question_presentation.jinja +7 -7
  163. edsl/questions/templates/checkbox/answering_instructions.jinja +10 -10
  164. edsl/questions/templates/checkbox/question_presentation.jinja +22 -22
  165. edsl/questions/templates/extract/answering_instructions.jinja +7 -7
  166. edsl/questions/templates/likert_five/answering_instructions.jinja +10 -10
  167. edsl/questions/templates/likert_five/question_presentation.jinja +11 -11
  168. edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -5
  169. edsl/questions/templates/linear_scale/question_presentation.jinja +5 -5
  170. edsl/questions/templates/list/answering_instructions.jinja +3 -3
  171. edsl/questions/templates/list/question_presentation.jinja +5 -5
  172. edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -9
  173. edsl/questions/templates/multiple_choice/question_presentation.jinja +11 -11
  174. edsl/questions/templates/numerical/answering_instructions.jinja +6 -6
  175. edsl/questions/templates/numerical/question_presentation.jinja +6 -6
  176. edsl/questions/templates/rank/answering_instructions.jinja +11 -11
  177. edsl/questions/templates/rank/question_presentation.jinja +15 -15
  178. edsl/questions/templates/top_k/answering_instructions.jinja +8 -8
  179. edsl/questions/templates/top_k/question_presentation.jinja +22 -22
  180. edsl/questions/templates/yes_no/answering_instructions.jinja +6 -6
  181. edsl/questions/templates/yes_no/question_presentation.jinja +11 -11
  182. edsl/results/Dataset.py +293 -281
  183. edsl/results/DatasetExportMixin.py +693 -693
  184. edsl/results/DatasetTree.py +145 -145
  185. edsl/results/Result.py +433 -431
  186. edsl/results/Results.py +1158 -1146
  187. edsl/results/ResultsDBMixin.py +238 -238
  188. edsl/results/ResultsExportMixin.py +43 -43
  189. edsl/results/ResultsFetchMixin.py +33 -33
  190. edsl/results/ResultsGGMixin.py +121 -121
  191. edsl/results/ResultsToolsMixin.py +98 -98
  192. edsl/results/Selector.py +118 -118
  193. edsl/results/__init__.py +2 -2
  194. edsl/results/tree_explore.py +115 -115
  195. edsl/scenarios/FileStore.py +443 -443
  196. edsl/scenarios/Scenario.py +507 -496
  197. edsl/scenarios/ScenarioHtmlMixin.py +59 -59
  198. edsl/scenarios/ScenarioList.py +1101 -1101
  199. edsl/scenarios/ScenarioListExportMixin.py +52 -52
  200. edsl/scenarios/ScenarioListPdfMixin.py +261 -261
  201. edsl/scenarios/__init__.py +2 -2
  202. edsl/shared.py +1 -1
  203. edsl/study/ObjectEntry.py +173 -173
  204. edsl/study/ProofOfWork.py +113 -113
  205. edsl/study/SnapShot.py +80 -80
  206. edsl/study/Study.py +528 -528
  207. edsl/study/__init__.py +4 -4
  208. edsl/surveys/DAG.py +148 -148
  209. edsl/surveys/Memory.py +31 -31
  210. edsl/surveys/MemoryPlan.py +244 -244
  211. edsl/surveys/Rule.py +324 -324
  212. edsl/surveys/RuleCollection.py +387 -387
  213. edsl/surveys/Survey.py +1772 -1769
  214. edsl/surveys/SurveyCSS.py +261 -261
  215. edsl/surveys/SurveyExportMixin.py +259 -259
  216. edsl/surveys/SurveyFlowVisualizationMixin.py +121 -121
  217. edsl/surveys/SurveyQualtricsImport.py +284 -284
  218. edsl/surveys/__init__.py +3 -3
  219. edsl/surveys/base.py +53 -53
  220. edsl/surveys/descriptors.py +56 -56
  221. edsl/surveys/instructions/ChangeInstruction.py +47 -47
  222. edsl/surveys/instructions/Instruction.py +51 -34
  223. edsl/surveys/instructions/InstructionCollection.py +77 -77
  224. edsl/templates/error_reporting/base.html +23 -23
  225. edsl/templates/error_reporting/exceptions_by_model.html +34 -34
  226. edsl/templates/error_reporting/exceptions_by_question_name.html +16 -16
  227. edsl/templates/error_reporting/exceptions_by_type.html +16 -16
  228. edsl/templates/error_reporting/interview_details.html +115 -115
  229. edsl/templates/error_reporting/interviews.html +9 -9
  230. edsl/templates/error_reporting/overview.html +4 -4
  231. edsl/templates/error_reporting/performance_plot.html +1 -1
  232. edsl/templates/error_reporting/report.css +73 -73
  233. edsl/templates/error_reporting/report.html +117 -117
  234. edsl/templates/error_reporting/report.js +25 -25
  235. edsl/tools/__init__.py +1 -1
  236. edsl/tools/clusters.py +192 -192
  237. edsl/tools/embeddings.py +27 -27
  238. edsl/tools/embeddings_plotting.py +118 -118
  239. edsl/tools/plotting.py +112 -112
  240. edsl/tools/summarize.py +18 -18
  241. edsl/utilities/SystemInfo.py +28 -28
  242. edsl/utilities/__init__.py +22 -22
  243. edsl/utilities/ast_utilities.py +25 -25
  244. edsl/utilities/data/Registry.py +6 -6
  245. edsl/utilities/data/__init__.py +1 -1
  246. edsl/utilities/data/scooter_results.json +1 -1
  247. edsl/utilities/decorators.py +77 -77
  248. edsl/utilities/gcp_bucket/cloud_storage.py +96 -96
  249. edsl/utilities/interface.py +627 -627
  250. edsl/utilities/repair_functions.py +28 -28
  251. edsl/utilities/restricted_python.py +70 -70
  252. edsl/utilities/utilities.py +391 -391
  253. {edsl-0.1.36.dev2.dist-info → edsl-0.1.36.dev6.dist-info}/LICENSE +21 -21
  254. {edsl-0.1.36.dev2.dist-info → edsl-0.1.36.dev6.dist-info}/METADATA +1 -1
  255. edsl-0.1.36.dev6.dist-info/RECORD +279 -0
  256. edsl-0.1.36.dev2.dist-info/RECORD +0 -278
  257. {edsl-0.1.36.dev2.dist-info → edsl-0.1.36.dev6.dist-info}/WHEEL +0 -0
edsl/agents/AgentList.py CHANGED
@@ -1,337 +1,337 @@
1
- """A list of Agent objects.
2
-
3
- Example usage:
4
-
5
- .. code-block:: python
6
-
7
- al = AgentList([Agent.example(), Agent.example()])
8
- len(al)
9
- 2
10
-
11
- """
12
-
13
- from __future__ import annotations
14
- import csv
15
- import json
16
- from collections import UserList
17
- from typing import Any, List, Optional, Union
18
- from rich import print_json
19
- from rich.table import Table
20
- from simpleeval import EvalWithCompoundTypes
21
- from edsl.Base import Base
22
- from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
23
-
24
- from collections.abc import Iterable
25
-
26
-
27
- def is_iterable(obj):
28
- return isinstance(obj, Iterable)
29
-
30
-
31
- class AgentList(UserList, Base):
32
- """A list of Agents."""
33
-
34
- def __init__(self, data: Optional[list["Agent"]] = None):
35
- """Initialize a new AgentList.
36
-
37
- :param data: A list of Agents.
38
- """
39
- if data is not None:
40
- super().__init__(data)
41
- else:
42
- super().__init__()
43
-
44
- def shuffle(self, seed: Optional[str] = "edsl") -> AgentList:
45
- """Shuffle the AgentList.
46
-
47
- :param seed: The seed for the random number generator.
48
- """
49
- import random
50
-
51
- random.seed(seed)
52
- random.shuffle(self.data)
53
- return self
54
-
55
- def sample(self, n: int, seed="edsl") -> AgentList:
56
- """Return a random sample of agents.
57
-
58
- :param n: The number of agents to sample.
59
- :param seed: The seed for the random number generator.
60
- """
61
- import random
62
-
63
- random.seed(seed)
64
- return AgentList(random.sample(self.data, n))
65
-
66
- def rename(self, old_name, new_name):
67
- """Rename a trait in the AgentList.
68
-
69
- :param old_name: The old name of the trait.
70
- :param new_name: The new name of the trait.
71
- """
72
- for agent in self.data:
73
- agent.rename(old_name, new_name)
74
- return self
75
-
76
- def select(self, *traits) -> AgentList:
77
- """Selects agents with only the references traits.
78
-
79
- >>> from edsl.agents.Agent import Agent
80
- >>> al = AgentList([Agent(traits = {'a': 1, 'b': 1}), Agent(traits = {'a': 1, 'b': 2})])
81
- >>> al.select('a')
82
- AgentList([Agent(traits = {'a': 1}), Agent(traits = {'a': 1})])
83
-
84
- """
85
-
86
- if len(traits) == 1:
87
- traits_to_select = [list(traits)[0]]
88
- else:
89
- traits_to_select = list(traits)
90
-
91
- return AgentList([agent.select(*traits_to_select) for agent in self.data])
92
-
93
- def filter(self, expression: str) -> AgentList:
94
- """
95
- Filter a list of agents based on an expression.
96
-
97
- >>> from edsl.agents.Agent import Agent
98
- >>> al = AgentList([Agent(traits = {'a': 1, 'b': 1}), Agent(traits = {'a': 1, 'b': 2})])
99
- >>> al.filter("b == 2")
100
- AgentList([Agent(traits = {'a': 1, 'b': 2})])
101
- """
102
-
103
- def create_evaluator(agent: "Agent"):
104
- """Create an evaluator for the given result.
105
- The 'combined_dict' is a mapping of all values for that Result object.
106
- """
107
- return EvalWithCompoundTypes(names=agent.traits)
108
-
109
- try:
110
- # iterates through all the results and evaluates the expression
111
- new_data = [
112
- agent for agent in self.data if create_evaluator(agent).eval(expression)
113
- ]
114
- except Exception as e:
115
- print(f"Exception:{e}")
116
- raise Exception(f"Error in filter. Exception:{e}")
117
-
118
- return AgentList(new_data)
119
-
120
- @property
121
- def all_traits(self):
122
- d = {}
123
- for agent in self:
124
- d.update(agent.traits)
125
- return list(d.keys())
126
-
127
- @classmethod
128
- def from_csv(cls, file_path: str):
129
- """Load AgentList from a CSV file.
130
-
131
- >>> import csv
132
- >>> import os
133
- >>> with open('/tmp/agents.csv', 'w') as f:
134
- ... writer = csv.writer(f)
135
- ... _ = writer.writerow(['age', 'hair', 'height'])
136
- ... _ = writer.writerow([22, 'brown', 5.5])
137
- >>> al = AgentList.from_csv('/tmp/agents.csv')
138
- >>> al
139
- AgentList([Agent(traits = {'age': '22', 'hair': 'brown', 'height': '5.5'})])
140
- >>> os.remove('/tmp/agents.csv')
141
-
142
- :param file_path: The path to the CSV file.
143
- """
144
- from edsl.agents.Agent import Agent
145
-
146
- agent_list = []
147
- with open(file_path, "r") as f:
148
- reader = csv.DictReader(f)
149
- for row in reader:
150
- agent_list.append(Agent(row))
151
- return cls(agent_list)
152
-
153
- def translate_traits(self, values_codebook: dict[str, str]):
154
- """Translate traits to a new codebook.
155
-
156
- :param codebook: The new codebook.
157
- """
158
- for agent in self.data:
159
- agent.translate_traits(codebook)
160
- return self
161
-
162
- def remove_trait(self, trait: str):
163
- """Remove traits from the AgentList.
164
-
165
- :param traits: The traits to remove.
166
- >>> from edsl.agents.Agent import Agent
167
- >>> al = AgentList([Agent({'age': 22, 'hair': 'brown', 'height': 5.5}), Agent({'age': 22, 'hair': 'brown', 'height': 5.5})])
168
- >>> al.remove_trait('age')
169
- AgentList([Agent(traits = {'hair': 'brown', 'height': 5.5}), Agent(traits = {'hair': 'brown', 'height': 5.5})])
170
- """
171
- for agent in self.data:
172
- _ = agent.remove_trait(trait)
173
- return self
174
-
175
- def add_trait(self, trait, values):
176
- """Adds a new trait to every agent, with values taken from values.
177
-
178
- :param trait: The name of the trait.
179
- :param values: The valeues(s) of the trait. If a single value is passed, it is used for all agents.
180
-
181
- >>> al = AgentList.example()
182
- >>> al.add_trait('new_trait', 1)
183
- AgentList([Agent(traits = {'age': 22, 'hair': 'brown', 'height': 5.5, 'new_trait': 1}), Agent(traits = {'age': 22, 'hair': 'brown', 'height': 5.5, 'new_trait': 1})])
184
- >>> al.select('new_trait').to_scenario_list().to_list()
185
- [1, 1]
186
- >>> al.add_trait('new_trait', [1, 2, 3])
187
- Traceback (most recent call last):
188
- ...
189
- ValueError: The passed values have to be the same length as the agent list.
190
- """
191
- if not is_iterable(values):
192
- value = values
193
- for agent in self.data:
194
- agent.add_trait(trait, value)
195
- return self
196
-
197
- if len(values) != len(self):
198
- raise ValueError(
199
- "The passed values have to be the same length as the agent list."
200
- )
201
- for agent, value in zip(self.data, values):
202
- agent.add_trait(trait, value)
203
- return self
204
-
205
- @staticmethod
206
- def get_codebook(file_path: str):
207
- """Return the codebook for a CSV file.
208
-
209
- :param file_path: The path to the CSV file.
210
- """
211
- with open(file_path, "r") as f:
212
- reader = csv.DictReader(f)
213
- return {field: None for field in reader.fieldnames}
214
-
215
- def __hash__(self) -> int:
216
- from edsl.utilities.utilities import dict_hash
217
-
218
- data = self.to_dict()
219
- # data['agent_list'] = sorted(data['agent_list'], key=lambda x: dict_hash(x)
220
- return dict_hash(self._to_dict(sorted=True))
221
-
222
- def _to_dict(self, sorted=False):
223
- if sorted:
224
- data = self.data[:]
225
- data.sort(key=lambda x: hash(x))
226
- else:
227
- data = self.data
228
-
229
- return {"agent_list": [agent.to_dict() for agent in data]}
230
-
231
- def __eq__(self, other: AgentList) -> bool:
232
- return self._to_dict(sorted=True) == other._to_dict(sorted=True)
233
-
234
- @add_edsl_version
235
- def to_dict(self):
236
- """Return dictionary of AgentList to serialization."""
237
- return self._to_dict()
238
-
239
- def __repr__(self):
240
- return f"AgentList({self.data})"
241
-
242
- def print(self, format: Optional[str] = None):
243
- """Print the AgentList."""
244
- print_json(json.dumps(self._to_dict()))
245
-
246
- def _repr_html_(self):
247
- """Return an HTML representation of the AgentList."""
248
- from edsl.utilities.utilities import data_to_html
249
-
250
- return data_to_html(self.to_dict()["agent_list"])
251
-
252
- def to_scenario_list(self) -> "ScenarioList":
253
- """Return a list of scenarios."""
254
- from edsl.scenarios.ScenarioList import ScenarioList
255
- from edsl.scenarios.Scenario import Scenario
256
-
257
- return ScenarioList([Scenario(agent.traits) for agent in self.data])
258
-
259
- @classmethod
260
- @remove_edsl_version
261
- def from_dict(cls, data: dict) -> "AgentList":
262
- """Deserialize the dictionary back to an AgentList object.
263
-
264
- :param: data: A dictionary representing an AgentList.
265
- >>> from edsl.agents.Agent import Agent
266
- >>> al = AgentList([Agent.example(), Agent.example()])
267
- >>> al2 = AgentList.from_dict(al.to_dict())
268
- >>> al2 == al
269
- True
270
- """
271
- from edsl.agents.Agent import Agent
272
-
273
- agents = [Agent.from_dict(agent_dict) for agent_dict in data["agent_list"]]
274
- return cls(agents)
275
-
276
- @classmethod
277
- def example(cls, randomize: bool = False) -> AgentList:
278
- """
279
- Returns an example AgentList instance.
280
-
281
- :param randomize: If True, uses Agent's randomize method.
282
- """
283
- from edsl.agents.Agent import Agent
284
-
285
- return cls([Agent.example(randomize), Agent.example(randomize)])
286
-
287
- @classmethod
288
- def from_list(self, trait_name: str, values: List[Any]):
289
- """Create an AgentList from a list of values.
290
-
291
- :param trait_name: The name of the trait.
292
- :param values: A list of values.
293
- """
294
- from edsl.agents.Agent import Agent
295
-
296
- return AgentList([Agent({trait_name: value}) for value in values])
297
-
298
- def __mul__(self, other: AgentList) -> AgentList:
299
- """Takes the cross product of two AgentLists."""
300
- from itertools import product
301
-
302
- new_sl = []
303
- for s1, s2 in list(product(self, other)):
304
- new_sl.append(s1 + s2)
305
- return AgentList(new_sl)
306
-
307
- def code(self, string=True) -> Union[str, list[str]]:
308
- """Return code to construct an AgentList.
309
-
310
- >>> al = AgentList.example()
311
- >>> print(al.code())
312
- from edsl.agents.Agent import Agent
313
- from edsl.agents.AgentList import AgentList
314
- agent_list = AgentList([Agent(traits = {'age': 22, 'hair': 'brown', 'height': 5.5}), Agent(traits = {'age': 22, 'hair': 'brown', 'height': 5.5})])
315
- """
316
- lines = [
317
- "from edsl.agents.Agent import Agent",
318
- "from edsl.agents.AgentList import AgentList",
319
- ]
320
- lines.append(f"agent_list = AgentList({self.data})")
321
- if string:
322
- return "\n".join(lines)
323
- return lines
324
-
325
- def rich_print(self) -> Table:
326
- """Display an object as a rich table."""
327
- table = Table(title="AgentList")
328
- table.add_column("Agents", style="bold")
329
- for agent in self.data:
330
- table.add_row(agent.rich_print())
331
- return table
332
-
333
-
334
- if __name__ == "__main__":
335
- import doctest
336
-
337
- doctest.testmod(optionflags=doctest.ELLIPSIS)
1
+ """A list of Agent objects.
2
+
3
+ Example usage:
4
+
5
+ .. code-block:: python
6
+
7
+ al = AgentList([Agent.example(), Agent.example()])
8
+ len(al)
9
+ 2
10
+
11
+ """
12
+
13
+ from __future__ import annotations
14
+ import csv
15
+ import json
16
+ from collections import UserList
17
+ from typing import Any, List, Optional, Union
18
+ from rich import print_json
19
+ from rich.table import Table
20
+ from simpleeval import EvalWithCompoundTypes
21
+ from edsl.Base import Base
22
+ from edsl.utilities.decorators import add_edsl_version, remove_edsl_version
23
+
24
+ from collections.abc import Iterable
25
+
26
+
27
+ def is_iterable(obj):
28
+ return isinstance(obj, Iterable)
29
+
30
+
31
+ class AgentList(UserList, Base):
32
+ """A list of Agents."""
33
+
34
+ def __init__(self, data: Optional[list["Agent"]] = None):
35
+ """Initialize a new AgentList.
36
+
37
+ :param data: A list of Agents.
38
+ """
39
+ if data is not None:
40
+ super().__init__(data)
41
+ else:
42
+ super().__init__()
43
+
44
+ def shuffle(self, seed: Optional[str] = "edsl") -> AgentList:
45
+ """Shuffle the AgentList.
46
+
47
+ :param seed: The seed for the random number generator.
48
+ """
49
+ import random
50
+
51
+ random.seed(seed)
52
+ random.shuffle(self.data)
53
+ return self
54
+
55
+ def sample(self, n: int, seed="edsl") -> AgentList:
56
+ """Return a random sample of agents.
57
+
58
+ :param n: The number of agents to sample.
59
+ :param seed: The seed for the random number generator.
60
+ """
61
+ import random
62
+
63
+ random.seed(seed)
64
+ return AgentList(random.sample(self.data, n))
65
+
66
+ def rename(self, old_name, new_name):
67
+ """Rename a trait in the AgentList.
68
+
69
+ :param old_name: The old name of the trait.
70
+ :param new_name: The new name of the trait.
71
+ """
72
+ for agent in self.data:
73
+ agent.rename(old_name, new_name)
74
+ return self
75
+
76
+ def select(self, *traits) -> AgentList:
77
+ """Selects agents with only the references traits.
78
+
79
+ >>> from edsl.agents.Agent import Agent
80
+ >>> al = AgentList([Agent(traits = {'a': 1, 'b': 1}), Agent(traits = {'a': 1, 'b': 2})])
81
+ >>> al.select('a')
82
+ AgentList([Agent(traits = {'a': 1}), Agent(traits = {'a': 1})])
83
+
84
+ """
85
+
86
+ if len(traits) == 1:
87
+ traits_to_select = [list(traits)[0]]
88
+ else:
89
+ traits_to_select = list(traits)
90
+
91
+ return AgentList([agent.select(*traits_to_select) for agent in self.data])
92
+
93
+ def filter(self, expression: str) -> AgentList:
94
+ """
95
+ Filter a list of agents based on an expression.
96
+
97
+ >>> from edsl.agents.Agent import Agent
98
+ >>> al = AgentList([Agent(traits = {'a': 1, 'b': 1}), Agent(traits = {'a': 1, 'b': 2})])
99
+ >>> al.filter("b == 2")
100
+ AgentList([Agent(traits = {'a': 1, 'b': 2})])
101
+ """
102
+
103
+ def create_evaluator(agent: "Agent"):
104
+ """Create an evaluator for the given result.
105
+ The 'combined_dict' is a mapping of all values for that Result object.
106
+ """
107
+ return EvalWithCompoundTypes(names=agent.traits)
108
+
109
+ try:
110
+ # iterates through all the results and evaluates the expression
111
+ new_data = [
112
+ agent for agent in self.data if create_evaluator(agent).eval(expression)
113
+ ]
114
+ except Exception as e:
115
+ print(f"Exception:{e}")
116
+ raise Exception(f"Error in filter. Exception:{e}")
117
+
118
+ return AgentList(new_data)
119
+
120
+ @property
121
+ def all_traits(self):
122
+ d = {}
123
+ for agent in self:
124
+ d.update(agent.traits)
125
+ return list(d.keys())
126
+
127
+ @classmethod
128
+ def from_csv(cls, file_path: str):
129
+ """Load AgentList from a CSV file.
130
+
131
+ >>> import csv
132
+ >>> import os
133
+ >>> with open('/tmp/agents.csv', 'w') as f:
134
+ ... writer = csv.writer(f)
135
+ ... _ = writer.writerow(['age', 'hair', 'height'])
136
+ ... _ = writer.writerow([22, 'brown', 5.5])
137
+ >>> al = AgentList.from_csv('/tmp/agents.csv')
138
+ >>> al
139
+ AgentList([Agent(traits = {'age': '22', 'hair': 'brown', 'height': '5.5'})])
140
+ >>> os.remove('/tmp/agents.csv')
141
+
142
+ :param file_path: The path to the CSV file.
143
+ """
144
+ from edsl.agents.Agent import Agent
145
+
146
+ agent_list = []
147
+ with open(file_path, "r") as f:
148
+ reader = csv.DictReader(f)
149
+ for row in reader:
150
+ agent_list.append(Agent(row))
151
+ return cls(agent_list)
152
+
153
+ def translate_traits(self, values_codebook: dict[str, str]):
154
+ """Translate traits to a new codebook.
155
+
156
+ :param codebook: The new codebook.
157
+ """
158
+ for agent in self.data:
159
+ agent.translate_traits(codebook)
160
+ return self
161
+
162
+ def remove_trait(self, trait: str):
163
+ """Remove traits from the AgentList.
164
+
165
+ :param traits: The traits to remove.
166
+ >>> from edsl.agents.Agent import Agent
167
+ >>> al = AgentList([Agent({'age': 22, 'hair': 'brown', 'height': 5.5}), Agent({'age': 22, 'hair': 'brown', 'height': 5.5})])
168
+ >>> al.remove_trait('age')
169
+ AgentList([Agent(traits = {'hair': 'brown', 'height': 5.5}), Agent(traits = {'hair': 'brown', 'height': 5.5})])
170
+ """
171
+ for agent in self.data:
172
+ _ = agent.remove_trait(trait)
173
+ return self
174
+
175
+ def add_trait(self, trait, values):
176
+ """Adds a new trait to every agent, with values taken from values.
177
+
178
+ :param trait: The name of the trait.
179
+ :param values: The valeues(s) of the trait. If a single value is passed, it is used for all agents.
180
+
181
+ >>> al = AgentList.example()
182
+ >>> al.add_trait('new_trait', 1)
183
+ AgentList([Agent(traits = {'age': 22, 'hair': 'brown', 'height': 5.5, 'new_trait': 1}), Agent(traits = {'age': 22, 'hair': 'brown', 'height': 5.5, 'new_trait': 1})])
184
+ >>> al.select('new_trait').to_scenario_list().to_list()
185
+ [1, 1]
186
+ >>> al.add_trait('new_trait', [1, 2, 3])
187
+ Traceback (most recent call last):
188
+ ...
189
+ ValueError: The passed values have to be the same length as the agent list.
190
+ """
191
+ if not is_iterable(values):
192
+ value = values
193
+ for agent in self.data:
194
+ agent.add_trait(trait, value)
195
+ return self
196
+
197
+ if len(values) != len(self):
198
+ raise ValueError(
199
+ "The passed values have to be the same length as the agent list."
200
+ )
201
+ for agent, value in zip(self.data, values):
202
+ agent.add_trait(trait, value)
203
+ return self
204
+
205
+ @staticmethod
206
+ def get_codebook(file_path: str):
207
+ """Return the codebook for a CSV file.
208
+
209
+ :param file_path: The path to the CSV file.
210
+ """
211
+ with open(file_path, "r") as f:
212
+ reader = csv.DictReader(f)
213
+ return {field: None for field in reader.fieldnames}
214
+
215
+ def __hash__(self) -> int:
216
+ from edsl.utilities.utilities import dict_hash
217
+
218
+ data = self.to_dict()
219
+ # data['agent_list'] = sorted(data['agent_list'], key=lambda x: dict_hash(x)
220
+ return dict_hash(self._to_dict(sorted=True))
221
+
222
+ def _to_dict(self, sorted=False):
223
+ if sorted:
224
+ data = self.data[:]
225
+ data.sort(key=lambda x: hash(x))
226
+ else:
227
+ data = self.data
228
+
229
+ return {"agent_list": [agent.to_dict() for agent in data]}
230
+
231
+ def __eq__(self, other: AgentList) -> bool:
232
+ return self._to_dict(sorted=True) == other._to_dict(sorted=True)
233
+
234
+ @add_edsl_version
235
+ def to_dict(self):
236
+ """Return dictionary of AgentList to serialization."""
237
+ return self._to_dict()
238
+
239
+ def __repr__(self):
240
+ return f"AgentList({self.data})"
241
+
242
+ def print(self, format: Optional[str] = None):
243
+ """Print the AgentList."""
244
+ print_json(json.dumps(self._to_dict()))
245
+
246
+ def _repr_html_(self):
247
+ """Return an HTML representation of the AgentList."""
248
+ from edsl.utilities.utilities import data_to_html
249
+
250
+ return data_to_html(self.to_dict()["agent_list"])
251
+
252
+ def to_scenario_list(self) -> "ScenarioList":
253
+ """Return a list of scenarios."""
254
+ from edsl.scenarios.ScenarioList import ScenarioList
255
+ from edsl.scenarios.Scenario import Scenario
256
+
257
+ return ScenarioList([Scenario(agent.traits) for agent in self.data])
258
+
259
+ @classmethod
260
+ @remove_edsl_version
261
+ def from_dict(cls, data: dict) -> "AgentList":
262
+ """Deserialize the dictionary back to an AgentList object.
263
+
264
+ :param: data: A dictionary representing an AgentList.
265
+ >>> from edsl.agents.Agent import Agent
266
+ >>> al = AgentList([Agent.example(), Agent.example()])
267
+ >>> al2 = AgentList.from_dict(al.to_dict())
268
+ >>> al2 == al
269
+ True
270
+ """
271
+ from edsl.agents.Agent import Agent
272
+
273
+ agents = [Agent.from_dict(agent_dict) for agent_dict in data["agent_list"]]
274
+ return cls(agents)
275
+
276
+ @classmethod
277
+ def example(cls, randomize: bool = False) -> AgentList:
278
+ """
279
+ Returns an example AgentList instance.
280
+
281
+ :param randomize: If True, uses Agent's randomize method.
282
+ """
283
+ from edsl.agents.Agent import Agent
284
+
285
+ return cls([Agent.example(randomize), Agent.example(randomize)])
286
+
287
+ @classmethod
288
+ def from_list(self, trait_name: str, values: List[Any]):
289
+ """Create an AgentList from a list of values.
290
+
291
+ :param trait_name: The name of the trait.
292
+ :param values: A list of values.
293
+ """
294
+ from edsl.agents.Agent import Agent
295
+
296
+ return AgentList([Agent({trait_name: value}) for value in values])
297
+
298
+ def __mul__(self, other: AgentList) -> AgentList:
299
+ """Takes the cross product of two AgentLists."""
300
+ from itertools import product
301
+
302
+ new_sl = []
303
+ for s1, s2 in list(product(self, other)):
304
+ new_sl.append(s1 + s2)
305
+ return AgentList(new_sl)
306
+
307
+ def code(self, string=True) -> Union[str, list[str]]:
308
+ """Return code to construct an AgentList.
309
+
310
+ >>> al = AgentList.example()
311
+ >>> print(al.code())
312
+ from edsl.agents.Agent import Agent
313
+ from edsl.agents.AgentList import AgentList
314
+ agent_list = AgentList([Agent(traits = {'age': 22, 'hair': 'brown', 'height': 5.5}), Agent(traits = {'age': 22, 'hair': 'brown', 'height': 5.5})])
315
+ """
316
+ lines = [
317
+ "from edsl.agents.Agent import Agent",
318
+ "from edsl.agents.AgentList import AgentList",
319
+ ]
320
+ lines.append(f"agent_list = AgentList({self.data})")
321
+ if string:
322
+ return "\n".join(lines)
323
+ return lines
324
+
325
+ def rich_print(self) -> Table:
326
+ """Display an object as a rich table."""
327
+ table = Table(title="AgentList")
328
+ table.add_column("Agents", style="bold")
329
+ for agent in self.data:
330
+ table.add_row(agent.rich_print())
331
+ return table
332
+
333
+
334
+ if __name__ == "__main__":
335
+ import doctest
336
+
337
+ doctest.testmod(optionflags=doctest.ELLIPSIS)