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