edsl 0.1.39.dev2__py3-none-any.whl → 0.1.39.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 (334) hide show
  1. edsl/Base.py +332 -385
  2. edsl/BaseDiff.py +260 -260
  3. edsl/TemplateLoader.py +24 -24
  4. edsl/__init__.py +49 -57
  5. edsl/__version__.py +1 -1
  6. edsl/agents/Agent.py +867 -1079
  7. edsl/agents/AgentList.py +413 -551
  8. edsl/agents/Invigilator.py +233 -285
  9. edsl/agents/InvigilatorBase.py +270 -254
  10. edsl/agents/PromptConstructor.py +354 -252
  11. edsl/agents/__init__.py +3 -2
  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 +157 -177
  27. edsl/conversation/Conversation.py +290 -290
  28. edsl/conversation/car_buying.py +58 -59
  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 +1028 -1090
  35. edsl/coop/utils.py +131 -131
  36. edsl/data/Cache.py +555 -562
  37. edsl/data/CacheEntry.py +233 -230
  38. edsl/data/CacheHandler.py +149 -170
  39. edsl/data/RemoteCacheSync.py +78 -78
  40. edsl/data/SQLiteDict.py +292 -292
  41. edsl/data/__init__.py +4 -5
  42. edsl/data/orm.py +10 -10
  43. edsl/data_transfer_models.py +73 -74
  44. edsl/enums.py +175 -195
  45. edsl/exceptions/BaseException.py +21 -21
  46. edsl/exceptions/__init__.py +54 -54
  47. edsl/exceptions/agents.py +42 -54
  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 -109
  57. edsl/exceptions/results.py +29 -29
  58. edsl/exceptions/scenarios.py +22 -29
  59. edsl/exceptions/surveys.py +37 -37
  60. edsl/inference_services/AnthropicService.py +87 -84
  61. edsl/inference_services/AwsBedrock.py +120 -118
  62. edsl/inference_services/AzureAI.py +217 -215
  63. edsl/inference_services/DeepInfraService.py +18 -18
  64. edsl/inference_services/GoogleService.py +148 -139
  65. edsl/inference_services/GroqService.py +20 -20
  66. edsl/inference_services/InferenceServiceABC.py +147 -80
  67. edsl/inference_services/InferenceServicesCollection.py +97 -122
  68. edsl/inference_services/MistralAIService.py +123 -120
  69. edsl/inference_services/OllamaService.py +18 -18
  70. edsl/inference_services/OpenAIService.py +224 -221
  71. edsl/inference_services/PerplexityService.py +163 -160
  72. edsl/inference_services/TestService.py +89 -92
  73. edsl/inference_services/TogetherAIService.py +170 -170
  74. edsl/inference_services/models_available_cache.py +118 -118
  75. edsl/inference_services/rate_limits_cache.py +25 -25
  76. edsl/inference_services/registry.py +41 -41
  77. edsl/inference_services/write_available.py +10 -10
  78. edsl/jobs/Answers.py +56 -43
  79. edsl/jobs/Jobs.py +898 -757
  80. edsl/jobs/JobsChecks.py +147 -172
  81. edsl/jobs/JobsPrompts.py +268 -270
  82. edsl/jobs/JobsRemoteInferenceHandler.py +239 -287
  83. edsl/jobs/__init__.py +1 -1
  84. edsl/jobs/buckets/BucketCollection.py +63 -104
  85. edsl/jobs/buckets/ModelBuckets.py +65 -65
  86. edsl/jobs/buckets/TokenBucket.py +251 -283
  87. edsl/jobs/interviews/Interview.py +661 -358
  88. edsl/jobs/interviews/InterviewExceptionCollection.py +99 -99
  89. edsl/jobs/interviews/InterviewExceptionEntry.py +186 -186
  90. edsl/jobs/interviews/InterviewStatistic.py +63 -63
  91. edsl/jobs/interviews/InterviewStatisticsCollection.py +25 -25
  92. edsl/jobs/interviews/InterviewStatusDictionary.py +78 -78
  93. edsl/jobs/interviews/InterviewStatusLog.py +92 -92
  94. edsl/jobs/interviews/ReportErrors.py +66 -66
  95. edsl/jobs/interviews/interview_status_enum.py +9 -9
  96. edsl/jobs/runners/JobsRunnerAsyncio.py +466 -421
  97. edsl/jobs/runners/JobsRunnerStatus.py +330 -330
  98. edsl/jobs/tasks/QuestionTaskCreator.py +242 -244
  99. edsl/jobs/tasks/TaskCreators.py +64 -64
  100. edsl/jobs/tasks/TaskHistory.py +450 -449
  101. edsl/jobs/tasks/TaskStatusLog.py +23 -23
  102. edsl/jobs/tasks/task_status_enum.py +163 -161
  103. edsl/jobs/tokens/InterviewTokenUsage.py +27 -27
  104. edsl/jobs/tokens/TokenUsage.py +34 -34
  105. edsl/language_models/KeyLookup.py +30 -0
  106. edsl/language_models/LanguageModel.py +668 -571
  107. edsl/language_models/ModelList.py +155 -153
  108. edsl/language_models/RegisterLanguageModelsMeta.py +184 -184
  109. edsl/language_models/__init__.py +3 -2
  110. edsl/language_models/fake_openai_call.py +15 -15
  111. edsl/language_models/fake_openai_service.py +61 -61
  112. edsl/language_models/registry.py +190 -180
  113. edsl/language_models/repair.py +156 -156
  114. edsl/language_models/unused/ReplicateBase.py +83 -0
  115. edsl/language_models/utilities.py +64 -65
  116. edsl/notebooks/Notebook.py +258 -263
  117. edsl/notebooks/__init__.py +1 -1
  118. edsl/prompts/Prompt.py +362 -352
  119. edsl/prompts/__init__.py +2 -2
  120. edsl/questions/AnswerValidatorMixin.py +289 -334
  121. edsl/questions/QuestionBase.py +664 -509
  122. edsl/questions/QuestionBaseGenMixin.py +161 -165
  123. edsl/questions/QuestionBasePromptsMixin.py +217 -221
  124. edsl/questions/QuestionBudget.py +227 -227
  125. edsl/questions/QuestionCheckBox.py +359 -359
  126. edsl/questions/QuestionExtract.py +182 -182
  127. edsl/questions/QuestionFreeText.py +114 -113
  128. edsl/questions/QuestionFunctional.py +166 -166
  129. edsl/questions/QuestionList.py +231 -229
  130. edsl/questions/QuestionMultipleChoice.py +286 -330
  131. edsl/questions/QuestionNumerical.py +153 -151
  132. edsl/questions/QuestionRank.py +324 -314
  133. edsl/questions/Quick.py +41 -41
  134. edsl/questions/RegisterQuestionsMeta.py +71 -71
  135. edsl/questions/ResponseValidatorABC.py +174 -200
  136. edsl/questions/SimpleAskMixin.py +73 -74
  137. edsl/questions/__init__.py +26 -27
  138. edsl/questions/compose_questions.py +98 -98
  139. edsl/questions/decorators.py +21 -21
  140. edsl/questions/derived/QuestionLikertFive.py +76 -76
  141. edsl/questions/derived/QuestionLinearScale.py +87 -90
  142. edsl/questions/derived/QuestionTopK.py +93 -93
  143. edsl/questions/derived/QuestionYesNo.py +82 -82
  144. edsl/questions/descriptors.py +413 -427
  145. edsl/questions/prompt_templates/question_budget.jinja +13 -13
  146. edsl/questions/prompt_templates/question_checkbox.jinja +32 -32
  147. edsl/questions/prompt_templates/question_extract.jinja +11 -11
  148. edsl/questions/prompt_templates/question_free_text.jinja +3 -3
  149. edsl/questions/prompt_templates/question_linear_scale.jinja +11 -11
  150. edsl/questions/prompt_templates/question_list.jinja +17 -17
  151. edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -33
  152. edsl/questions/prompt_templates/question_numerical.jinja +36 -36
  153. edsl/questions/question_registry.py +177 -177
  154. edsl/questions/settings.py +12 -12
  155. edsl/questions/templates/budget/answering_instructions.jinja +7 -7
  156. edsl/questions/templates/budget/question_presentation.jinja +7 -7
  157. edsl/questions/templates/checkbox/answering_instructions.jinja +10 -10
  158. edsl/questions/templates/checkbox/question_presentation.jinja +22 -22
  159. edsl/questions/templates/extract/answering_instructions.jinja +7 -7
  160. edsl/questions/templates/likert_five/answering_instructions.jinja +10 -10
  161. edsl/questions/templates/likert_five/question_presentation.jinja +11 -11
  162. edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -5
  163. edsl/questions/templates/linear_scale/question_presentation.jinja +5 -5
  164. edsl/questions/templates/list/answering_instructions.jinja +3 -3
  165. edsl/questions/templates/list/question_presentation.jinja +5 -5
  166. edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -9
  167. edsl/questions/templates/multiple_choice/question_presentation.jinja +11 -11
  168. edsl/questions/templates/numerical/answering_instructions.jinja +6 -6
  169. edsl/questions/templates/numerical/question_presentation.jinja +6 -6
  170. edsl/questions/templates/rank/answering_instructions.jinja +11 -11
  171. edsl/questions/templates/rank/question_presentation.jinja +15 -15
  172. edsl/questions/templates/top_k/answering_instructions.jinja +8 -8
  173. edsl/questions/templates/top_k/question_presentation.jinja +22 -22
  174. edsl/questions/templates/yes_no/answering_instructions.jinja +6 -6
  175. edsl/questions/templates/yes_no/question_presentation.jinja +11 -11
  176. edsl/results/CSSParameterizer.py +108 -108
  177. edsl/results/Dataset.py +424 -587
  178. edsl/results/DatasetExportMixin.py +731 -653
  179. edsl/results/DatasetTree.py +275 -295
  180. edsl/results/Result.py +465 -451
  181. edsl/results/Results.py +1165 -1172
  182. edsl/results/ResultsDBMixin.py +238 -0
  183. edsl/results/ResultsExportMixin.py +43 -45
  184. edsl/results/ResultsFetchMixin.py +33 -33
  185. edsl/results/ResultsGGMixin.py +121 -121
  186. edsl/results/ResultsToolsMixin.py +98 -98
  187. edsl/results/Selector.py +135 -145
  188. edsl/results/TableDisplay.py +198 -125
  189. edsl/results/__init__.py +2 -2
  190. edsl/results/table_display.css +77 -77
  191. edsl/results/tree_explore.py +115 -115
  192. edsl/scenarios/FileStore.py +632 -511
  193. edsl/scenarios/Scenario.py +601 -498
  194. edsl/scenarios/ScenarioHtmlMixin.py +64 -65
  195. edsl/scenarios/ScenarioJoin.py +127 -131
  196. edsl/scenarios/ScenarioList.py +1287 -1430
  197. edsl/scenarios/ScenarioListExportMixin.py +52 -45
  198. edsl/scenarios/ScenarioListPdfMixin.py +261 -239
  199. edsl/scenarios/__init__.py +4 -3
  200. edsl/shared.py +1 -1
  201. edsl/study/ObjectEntry.py +173 -173
  202. edsl/study/ProofOfWork.py +113 -113
  203. edsl/study/SnapShot.py +80 -80
  204. edsl/study/Study.py +528 -521
  205. edsl/study/__init__.py +4 -4
  206. edsl/surveys/DAG.py +148 -148
  207. edsl/surveys/Memory.py +31 -31
  208. edsl/surveys/MemoryPlan.py +244 -244
  209. edsl/surveys/Rule.py +326 -327
  210. edsl/surveys/RuleCollection.py +387 -385
  211. edsl/surveys/Survey.py +1801 -1229
  212. edsl/surveys/SurveyCSS.py +261 -273
  213. edsl/surveys/SurveyExportMixin.py +259 -259
  214. edsl/surveys/{SurveyFlowVisualization.py → SurveyFlowVisualizationMixin.py} +179 -181
  215. edsl/surveys/SurveyQualtricsImport.py +284 -284
  216. edsl/surveys/__init__.py +3 -5
  217. edsl/surveys/base.py +53 -53
  218. edsl/surveys/descriptors.py +56 -60
  219. edsl/surveys/instructions/ChangeInstruction.py +49 -48
  220. edsl/surveys/instructions/Instruction.py +65 -56
  221. edsl/surveys/instructions/InstructionCollection.py +77 -82
  222. edsl/templates/error_reporting/base.html +23 -23
  223. edsl/templates/error_reporting/exceptions_by_model.html +34 -34
  224. edsl/templates/error_reporting/exceptions_by_question_name.html +16 -16
  225. edsl/templates/error_reporting/exceptions_by_type.html +16 -16
  226. edsl/templates/error_reporting/interview_details.html +115 -115
  227. edsl/templates/error_reporting/interviews.html +19 -19
  228. edsl/templates/error_reporting/overview.html +4 -4
  229. edsl/templates/error_reporting/performance_plot.html +1 -1
  230. edsl/templates/error_reporting/report.css +73 -73
  231. edsl/templates/error_reporting/report.html +117 -117
  232. edsl/templates/error_reporting/report.js +25 -25
  233. edsl/tools/__init__.py +1 -1
  234. edsl/tools/clusters.py +192 -192
  235. edsl/tools/embeddings.py +27 -27
  236. edsl/tools/embeddings_plotting.py +118 -118
  237. edsl/tools/plotting.py +112 -112
  238. edsl/tools/summarize.py +18 -18
  239. edsl/utilities/SystemInfo.py +28 -28
  240. edsl/utilities/__init__.py +22 -22
  241. edsl/utilities/ast_utilities.py +25 -25
  242. edsl/utilities/data/Registry.py +6 -6
  243. edsl/utilities/data/__init__.py +1 -1
  244. edsl/utilities/data/scooter_results.json +1 -1
  245. edsl/utilities/decorators.py +77 -77
  246. edsl/utilities/gcp_bucket/cloud_storage.py +96 -96
  247. edsl/utilities/interface.py +627 -627
  248. edsl/utilities/naming_utilities.py +263 -263
  249. edsl/utilities/repair_functions.py +28 -28
  250. edsl/utilities/restricted_python.py +70 -70
  251. edsl/utilities/utilities.py +424 -436
  252. {edsl-0.1.39.dev2.dist-info → edsl-0.1.39.dev3.dist-info}/LICENSE +21 -21
  253. {edsl-0.1.39.dev2.dist-info → edsl-0.1.39.dev3.dist-info}/METADATA +10 -12
  254. edsl-0.1.39.dev3.dist-info/RECORD +277 -0
  255. edsl/agents/QuestionInstructionPromptBuilder.py +0 -128
  256. edsl/agents/QuestionOptionProcessor.py +0 -172
  257. edsl/agents/QuestionTemplateReplacementsBuilder.py +0 -137
  258. edsl/coop/CoopFunctionsMixin.py +0 -15
  259. edsl/coop/ExpectedParrotKeyHandler.py +0 -125
  260. edsl/exceptions/inference_services.py +0 -5
  261. edsl/inference_services/AvailableModelCacheHandler.py +0 -184
  262. edsl/inference_services/AvailableModelFetcher.py +0 -209
  263. edsl/inference_services/ServiceAvailability.py +0 -135
  264. edsl/inference_services/data_structures.py +0 -62
  265. edsl/jobs/AnswerQuestionFunctionConstructor.py +0 -188
  266. edsl/jobs/FetchInvigilator.py +0 -40
  267. edsl/jobs/InterviewTaskManager.py +0 -98
  268. edsl/jobs/InterviewsConstructor.py +0 -48
  269. edsl/jobs/JobsComponentConstructor.py +0 -189
  270. edsl/jobs/JobsRemoteInferenceLogger.py +0 -239
  271. edsl/jobs/RequestTokenEstimator.py +0 -30
  272. edsl/jobs/buckets/TokenBucketAPI.py +0 -211
  273. edsl/jobs/buckets/TokenBucketClient.py +0 -191
  274. edsl/jobs/decorators.py +0 -35
  275. edsl/jobs/jobs_status_enums.py +0 -9
  276. edsl/jobs/loggers/HTMLTableJobLogger.py +0 -304
  277. edsl/language_models/ComputeCost.py +0 -63
  278. edsl/language_models/PriceManager.py +0 -127
  279. edsl/language_models/RawResponseHandler.py +0 -106
  280. edsl/language_models/ServiceDataSources.py +0 -0
  281. edsl/language_models/key_management/KeyLookup.py +0 -63
  282. edsl/language_models/key_management/KeyLookupBuilder.py +0 -273
  283. edsl/language_models/key_management/KeyLookupCollection.py +0 -38
  284. edsl/language_models/key_management/__init__.py +0 -0
  285. edsl/language_models/key_management/models.py +0 -131
  286. edsl/notebooks/NotebookToLaTeX.py +0 -142
  287. edsl/questions/ExceptionExplainer.py +0 -77
  288. edsl/questions/HTMLQuestion.py +0 -103
  289. edsl/questions/LoopProcessor.py +0 -149
  290. edsl/questions/QuestionMatrix.py +0 -265
  291. edsl/questions/ResponseValidatorFactory.py +0 -28
  292. edsl/questions/templates/matrix/__init__.py +0 -1
  293. edsl/questions/templates/matrix/answering_instructions.jinja +0 -5
  294. edsl/questions/templates/matrix/question_presentation.jinja +0 -20
  295. edsl/results/MarkdownToDocx.py +0 -122
  296. edsl/results/MarkdownToPDF.py +0 -111
  297. edsl/results/TextEditor.py +0 -50
  298. edsl/results/smart_objects.py +0 -96
  299. edsl/results/table_data_class.py +0 -12
  300. edsl/results/table_renderers.py +0 -118
  301. edsl/scenarios/ConstructDownloadLink.py +0 -109
  302. edsl/scenarios/DirectoryScanner.py +0 -96
  303. edsl/scenarios/DocumentChunker.py +0 -102
  304. edsl/scenarios/DocxScenario.py +0 -16
  305. edsl/scenarios/PdfExtractor.py +0 -40
  306. edsl/scenarios/ScenarioSelector.py +0 -156
  307. edsl/scenarios/file_methods.py +0 -85
  308. edsl/scenarios/handlers/__init__.py +0 -13
  309. edsl/scenarios/handlers/csv.py +0 -38
  310. edsl/scenarios/handlers/docx.py +0 -76
  311. edsl/scenarios/handlers/html.py +0 -37
  312. edsl/scenarios/handlers/json.py +0 -111
  313. edsl/scenarios/handlers/latex.py +0 -5
  314. edsl/scenarios/handlers/md.py +0 -51
  315. edsl/scenarios/handlers/pdf.py +0 -68
  316. edsl/scenarios/handlers/png.py +0 -39
  317. edsl/scenarios/handlers/pptx.py +0 -105
  318. edsl/scenarios/handlers/py.py +0 -294
  319. edsl/scenarios/handlers/sql.py +0 -313
  320. edsl/scenarios/handlers/sqlite.py +0 -149
  321. edsl/scenarios/handlers/txt.py +0 -33
  322. edsl/surveys/ConstructDAG.py +0 -92
  323. edsl/surveys/EditSurvey.py +0 -221
  324. edsl/surveys/InstructionHandler.py +0 -100
  325. edsl/surveys/MemoryManagement.py +0 -72
  326. edsl/surveys/RuleManager.py +0 -172
  327. edsl/surveys/Simulator.py +0 -75
  328. edsl/surveys/SurveyToApp.py +0 -141
  329. edsl/utilities/PrettyList.py +0 -56
  330. edsl/utilities/is_notebook.py +0 -18
  331. edsl/utilities/is_valid_variable_name.py +0 -11
  332. edsl/utilities/remove_edsl_version.py +0 -24
  333. edsl-0.1.39.dev2.dist-info/RECORD +0 -352
  334. {edsl-0.1.39.dev2.dist-info → edsl-0.1.39.dev3.dist-info}/WHEEL +0 -0
@@ -1,221 +0,0 @@
1
- from typing import Union, Optional, TYPE_CHECKING
2
- from edsl.exceptions.surveys import SurveyError
3
-
4
- if TYPE_CHECKING:
5
- from edsl.questions.QuestionBase import QuestionBase
6
-
7
- from edsl.exceptions.surveys import SurveyError, SurveyCreationError
8
- from edsl.surveys.Rule import Rule
9
- from edsl.surveys.base import RulePriority, EndOfSurvey
10
-
11
-
12
- class EditSurvey:
13
- def __init__(self, survey):
14
- self.survey = survey
15
-
16
- def move_question(self, identifier: Union[str, int], new_index: int) -> "Survey":
17
- if isinstance(identifier, str):
18
- if identifier not in self.survey.question_names:
19
- raise SurveyError(
20
- f"Question name '{identifier}' does not exist in the survey."
21
- )
22
- index = self.survey.question_name_to_index[identifier]
23
- elif isinstance(identifier, int):
24
- if identifier < 0 or identifier >= len(self.survey.questions):
25
- raise SurveyError(f"Index {identifier} is out of range.")
26
- index = identifier
27
- else:
28
- raise SurveyError(
29
- "Identifier must be either a string (question name) or an integer (question index)."
30
- )
31
-
32
- moving_question = self.survey._questions[index]
33
-
34
- new_survey = self.survey.delete_question(index)
35
- new_survey.add_question(moving_question, new_index)
36
- return new_survey
37
-
38
- def add_question(
39
- self, question: "QuestionBase", index: Optional[int] = None
40
- ) -> "Survey":
41
- if question.question_name in self.survey.question_names:
42
- raise SurveyCreationError(
43
- f"""Question name '{question.question_name}' already exists in survey. Existing names are {self.survey.question_names}."""
44
- )
45
- if index is None:
46
- index = len(self.survey.questions)
47
-
48
- if index > len(self.survey.questions):
49
- raise SurveyCreationError(
50
- f"Index {index} is greater than the number of questions in the survey."
51
- )
52
- if index < 0:
53
- raise SurveyCreationError(f"Index {index} is less than 0.")
54
-
55
- interior_insertion = index != len(self.survey.questions)
56
-
57
- # index = len(self.survey.questions)
58
- # TODO: This is a bit ugly because the user
59
- # doesn't "know" about _questions - it's generated by the
60
- # descriptor.
61
- self.survey._questions.insert(index, question)
62
-
63
- if interior_insertion:
64
- for question_name, old_index in self.survey._pseudo_indices.items():
65
- if old_index >= index:
66
- self.survey._pseudo_indices[question_name] = old_index + 1
67
-
68
- self.survey._pseudo_indices[question.question_name] = index
69
-
70
- ## Re-do question_name to index - this is done automatically
71
- # for question_name, old_index in self.survey.question_name_to_index.items():
72
- # if old_index >= index:
73
- # self.survey.question_name_to_index[question_name] = old_index + 1
74
-
75
- ## Need to re-do the rule collection and the indices of the questions
76
-
77
- ## If a rule is before the insertion index and next_q is also before the insertion index, no change needed.
78
- ## If the rule is before the insertion index but next_q is after the insertion index, increment the next_q by 1
79
- ## If the rule is after the insertion index, increment the current_q by 1 and the next_q by 1
80
-
81
- # using index + 1 presumes there is a next question
82
- if interior_insertion:
83
- for rule in self.survey.rule_collection:
84
- if rule.current_q >= index:
85
- rule.current_q += 1
86
- if rule.next_q >= index:
87
- rule.next_q += 1
88
-
89
- # add a new rule
90
- self.survey.rule_collection.add_rule(
91
- Rule(
92
- current_q=index,
93
- expression="True",
94
- next_q=index + 1,
95
- question_name_to_index=self.survey.question_name_to_index,
96
- priority=RulePriority.DEFAULT.value,
97
- )
98
- )
99
-
100
- # a question might be added before the memory plan is created
101
- # it's ok because the memory plan will be updated when it is created
102
- if hasattr(self.survey, "memory_plan"):
103
- self.survey.memory_plan.add_question(question)
104
-
105
- return self.survey
106
-
107
- def delete_question(self, identifier: Union[str, int]) -> "Survey":
108
- """
109
- Delete a question from the survey.
110
-
111
- :param identifier: The name or index of the question to delete.
112
- :return: The updated Survey object.
113
-
114
- >>> from edsl import QuestionMultipleChoice, Survey
115
- >>> q1 = QuestionMultipleChoice(question_text="Q1", question_options=["A", "B"], question_name="q1")
116
- >>> q2 = QuestionMultipleChoice(question_text="Q2", question_options=["C", "D"], question_name="q2")
117
- >>> s = Survey().add_question(q1).add_question(q2)
118
- >>> _ = s.delete_question("q1")
119
- >>> len(s.questions)
120
- 1
121
- >>> _ = s.delete_question(0)
122
- >>> len(s.questions)
123
- 0
124
- """
125
- if isinstance(identifier, str):
126
- if identifier not in self.survey.question_names:
127
- raise SurveyError(
128
- f"Question name '{identifier}' does not exist in the survey."
129
- )
130
- index = self.survey.question_name_to_index[identifier]
131
- elif isinstance(identifier, int):
132
- if identifier < 0 or identifier >= len(self.survey.questions):
133
- raise SurveyError(f"Index {identifier} is out of range.")
134
- index = identifier
135
- else:
136
- raise SurveyError(
137
- "Identifier must be either a string (question name) or an integer (question index)."
138
- )
139
-
140
- # Remove the question
141
- deleted_question = self.survey._questions.pop(index)
142
- del self.survey._pseudo_indices[deleted_question.question_name]
143
-
144
- # Update indices
145
- for question_name, old_index in self.survey._pseudo_indices.items():
146
- if old_index > index:
147
- self.survey._pseudo_indices[question_name] = old_index - 1
148
-
149
- # Update rules
150
- from .RuleCollection import RuleCollection
151
-
152
- new_rule_collection = RuleCollection()
153
- for rule in self.survey.rule_collection:
154
- if rule.current_q == index:
155
- continue # Remove rules associated with the deleted question
156
- if rule.current_q > index:
157
- rule.current_q -= 1
158
- if rule.next_q > index:
159
- rule.next_q -= 1
160
-
161
- if rule.next_q == index:
162
- if index == len(self.survey.questions):
163
- rule.next_q = EndOfSurvey
164
- else:
165
- rule.next_q = index
166
-
167
- new_rule_collection.add_rule(rule)
168
- self.survey.rule_collection = new_rule_collection
169
-
170
- # Update memory plan if it exists
171
- if hasattr(self.survey, "memory_plan"):
172
- self.survey.memory_plan.remove_question(deleted_question.question_name)
173
-
174
- return self.survey
175
-
176
- def add_instruction(
177
- self, instruction: Union["Instruction", "ChangeInstruction"]
178
- ) -> "Survey":
179
- """
180
- Add an instruction to the survey.
181
-
182
- :param instruction: The instruction to add to the survey.
183
-
184
- >>> from edsl import Instruction
185
- >>> from edsl.surveys.Survey import Survey
186
- >>> i = Instruction(text="Pay attention to the following questions.", name="intro")
187
- >>> s = Survey().add_instruction(i)
188
- >>> s._instruction_names_to_instructions
189
- {'intro': Instruction(name="intro", text="Pay attention to the following questions.")}
190
- >>> s._pseudo_indices
191
- {'intro': -0.5}
192
- """
193
- import math
194
-
195
- if instruction.name in self.survey._instruction_names_to_instructions:
196
- raise SurveyCreationError(
197
- f"""Instruction name '{instruction.name}' already exists in survey. Existing names are {self.survey._instruction_names_to_instructions.keys()}."""
198
- )
199
- self.survey._instruction_names_to_instructions[instruction.name] = instruction
200
-
201
- # was the last thing added an instruction or a question?
202
- if self.survey._pseudo_indices.last_item_was_instruction:
203
- pseudo_index = (
204
- self.survey._pseudo_indices.max_pseudo_index
205
- + (
206
- math.ceil(self.survey._pseudo_indices.max_pseudo_index)
207
- - self.survey._pseudo_indices.max_pseudo_index
208
- )
209
- / 2
210
- )
211
- else:
212
- pseudo_index = self.survey._pseudo_indices.max_pseudo_index + 1.0 / 2.0
213
- self.survey._pseudo_indices[instruction.name] = pseudo_index
214
-
215
- return self.survey
216
-
217
-
218
- if __name__ == "__main__":
219
- import doctest
220
-
221
- doctest.testmod()
@@ -1,100 +0,0 @@
1
- from typing import TYPE_CHECKING
2
- from dataclasses import dataclass
3
-
4
-
5
- @dataclass
6
- class SeparatedComponents:
7
- true_questions: list
8
- instruction_names_to_instructions: dict
9
- pseudo_indices: dict
10
-
11
-
12
- class InstructionHandler:
13
- def __init__(self, survey):
14
- self.survey = survey
15
-
16
- @staticmethod
17
- def separate_questions_and_instructions(questions_and_instructions: list) -> tuple:
18
- """
19
- The 'pseudo_indices' attribute is a dictionary that maps question names to pseudo-indices
20
- that are used to order questions and instructions in the survey.
21
- Only questions get real indices; instructions get pseudo-indices.
22
- However, the order of the pseudo-indices is the same as the order questions and instructions are added to the survey.
23
-
24
- We don't have to know how many instructions there are to calculate the pseudo-indices because they are
25
- calculated by the inverse of one minus the sum of 1/2^n for n in the number of instructions run so far.
26
-
27
- >>> from edsl import Survey
28
- >>> from edsl import Instruction
29
- >>> i = Instruction(text = "Pay attention to the following questions.", name = "intro")
30
- >>> i2 = Instruction(text = "How are you feeling today?", name = "followon_intro")
31
- >>> from edsl import QuestionFreeText; q1 = QuestionFreeText.example()
32
- >>> from edsl import QuestionMultipleChoice; q2 = QuestionMultipleChoice.example()
33
- >>> s = Survey([q1, i, i2, q2])
34
- >>> len(s._instruction_names_to_instructions)
35
- 2
36
- >>> s._pseudo_indices
37
- {'how_are_you': 0, 'intro': 0.5, 'followon_intro': 0.75, 'how_feeling': 1}
38
-
39
- >>> from edsl import ChangeInstruction
40
- >>> q3 = QuestionFreeText(question_text = "What is your favorite color?", question_name = "color")
41
- >>> i_change = ChangeInstruction(drop = ["intro"])
42
- >>> s = Survey([q1, i, q2, i_change, q3])
43
- >>> [i.name for i in s._relevant_instructions(q1)]
44
- []
45
- >>> [i.name for i in s._relevant_instructions(q2)]
46
- ['intro']
47
- >>> [i.name for i in s._relevant_instructions(q3)]
48
- []
49
-
50
- >>> i_change = ChangeInstruction(keep = ["poop"], drop = [])
51
- >>> s = Survey([q1, i, q2, i_change])
52
- Traceback (most recent call last):
53
- ...
54
- ValueError: ChangeInstruction change_instruction_0 references instruction poop which does not exist.
55
- """
56
- from edsl.surveys.instructions.Instruction import Instruction
57
- from edsl.surveys.instructions.ChangeInstruction import ChangeInstruction
58
- from edsl.questions.QuestionBase import QuestionBase
59
-
60
- true_questions = []
61
- instruction_names_to_instructions = {}
62
-
63
- num_change_instructions = 0
64
- pseudo_indices = {}
65
- instructions_run_length = 0
66
- for entry in questions_and_instructions:
67
- if isinstance(entry, Instruction) or isinstance(entry, ChangeInstruction):
68
- if isinstance(entry, ChangeInstruction):
69
- entry.add_name(num_change_instructions)
70
- num_change_instructions += 1
71
- for prior_instruction in entry.keep + entry.drop:
72
- if prior_instruction not in instruction_names_to_instructions:
73
- raise ValueError(
74
- f"ChangeInstruction {entry.name} references instruction {prior_instruction} which does not exist."
75
- )
76
- instructions_run_length += 1
77
- delta = 1 - 1.0 / (2.0**instructions_run_length)
78
- pseudo_index = (len(true_questions) - 1) + delta
79
- entry.pseudo_index = pseudo_index
80
- instruction_names_to_instructions[entry.name] = entry
81
- elif isinstance(entry, QuestionBase):
82
- pseudo_index = len(true_questions)
83
- instructions_run_length = 0
84
- true_questions.append(entry)
85
- else:
86
- raise ValueError(
87
- f"Entry {repr(entry)} is not a QuestionBase or an Instruction."
88
- )
89
-
90
- pseudo_indices[entry.name] = pseudo_index
91
-
92
- return SeparatedComponents(
93
- true_questions, instruction_names_to_instructions, pseudo_indices
94
- )
95
-
96
-
97
- if __name__ == "__main__":
98
- import doctest
99
-
100
- doctest.testmod()
@@ -1,72 +0,0 @@
1
- from __future__ import annotations
2
- from typing import Callable, Union, List, TYPE_CHECKING
3
-
4
- if TYPE_CHECKING:
5
- from edsl.questions.QuestionBase import QuestionBase
6
-
7
-
8
- class MemoryManagement:
9
- def __init__(self, survey):
10
- self.survey = survey
11
-
12
- def _set_memory_plan(self, prior_questions_func: Callable) -> None:
13
- """Set memory plan based on a provided function determining prior questions.
14
- :param prior_questions_func: A function that takes an index and returns a list of prior questions.
15
- """
16
- for i, question_name in enumerate(self.survey.question_names):
17
- self.survey.memory_plan.add_memory_collection(
18
- focal_question=question_name,
19
- prior_questions=prior_questions_func(i),
20
- )
21
-
22
- def add_targeted_memory(
23
- self,
24
- focal_question: Union[QuestionBase, str],
25
- prior_question: Union[QuestionBase, str],
26
- ) -> "Survey":
27
- """Add instructions to a survey than when answering focal_question.
28
-
29
- :param focal_question: The question that the agent is answering.
30
- :param prior_question: The question that the agent should remember when answering the focal question.
31
-
32
- Here we add instructions to a survey than when answering q2 they should remember q1:
33
- """
34
- focal_question_name = self.survey.question_names[
35
- self.survey._get_question_index(focal_question)
36
- ]
37
- prior_question_name = self.survey.question_names[
38
- self.survey._get_question_index(prior_question)
39
- ]
40
-
41
- self.survey.memory_plan.add_single_memory(
42
- focal_question=focal_question_name,
43
- prior_question=prior_question_name,
44
- )
45
-
46
- return self.survey
47
-
48
- def add_memory_collection(
49
- self,
50
- focal_question: Union[QuestionBase, str],
51
- prior_questions: List[Union[QuestionBase, str]],
52
- ) -> "Survey":
53
- """Add prior questions and responses so the agent has them when answering.
54
-
55
- This adds instructions to a survey than when answering focal_question, the agent should also remember the answers to prior_questions listed in prior_questions.
56
-
57
- :param focal_question: The question that the agent is answering.
58
- :param prior_questions: The questions that the agent should remember when answering the focal question.
59
- """
60
- focal_question_name = self.survey.question_names[
61
- self.survey._get_question_index(focal_question)
62
- ]
63
-
64
- prior_question_names = [
65
- self.survey.question_names[self.survey._get_question_index(prior_question)]
66
- for prior_question in prior_questions
67
- ]
68
-
69
- self.survey.memory_plan.add_memory_collection(
70
- focal_question=focal_question_name, prior_questions=prior_question_names
71
- )
72
- return self.survey
@@ -1,172 +0,0 @@
1
- from typing import Union, TYPE_CHECKING
2
-
3
- if TYPE_CHECKING:
4
- from edsl.questions.QuestionBase import QuestionBase
5
-
6
- from edsl.surveys.Rule import Rule
7
- from .base import RulePriority, EndOfSurvey
8
- from edsl.exceptions.surveys import SurveyError, SurveyCreationError
9
-
10
-
11
- class ValidatedString(str):
12
- def __new__(cls, content):
13
- if "<>" in content:
14
- raise SurveyCreationError(
15
- "The expression contains '<>', which is not allowed. You probably mean '!='."
16
- )
17
- return super().__new__(cls, content)
18
-
19
-
20
- class RuleManager:
21
- def __init__(self, survey):
22
- self.survey = survey
23
-
24
- def _get_question_index(
25
- self, q: Union["QuestionBase", str, EndOfSurvey.__class__]
26
- ) -> Union[int, EndOfSurvey.__class__]:
27
- """Return the index of the question or EndOfSurvey object.
28
-
29
- :param q: The question or question name to get the index of.
30
-
31
- It can handle it if the user passes in the question name, the question object, or the EndOfSurvey object.
32
-
33
- >>> from edsl.questions import QuestionFreeText
34
- >>> from edsl import Survey
35
- >>> s = Survey.example()
36
- >>> s._get_question_index("q0")
37
- 0
38
-
39
- This doesnt' work with questions that don't exist:
40
-
41
- >>> s._get_question_index("poop")
42
- Traceback (most recent call last):
43
- ...
44
- edsl.exceptions.surveys.SurveyError: Question name poop not found in survey. The current question names are {'q0': 0, 'q1': 1, 'q2': 2}.
45
- ...
46
- """
47
- if q == EndOfSurvey:
48
- return EndOfSurvey
49
- else:
50
- question_name = q if isinstance(q, str) else q.question_name
51
- if question_name not in self.survey.question_name_to_index:
52
- raise SurveyError(
53
- f"""Question name {question_name} not found in survey. The current question names are {self.survey.question_name_to_index}."""
54
- )
55
- return self.survey.question_name_to_index[question_name]
56
-
57
- def _get_new_rule_priority(
58
- self, question_index: int, before_rule: bool = False
59
- ) -> int:
60
- """Return the priority for the new rule.
61
-
62
- :param question_index: The index of the question to add the rule to.
63
- :param before_rule: Whether the rule is evaluated before the question is answered.
64
-
65
- >>> from edsl import Survey
66
- >>> s = Survey.example()
67
- >>> RuleManager(s)._get_new_rule_priority(0)
68
- 1
69
- """
70
- current_priorities = [
71
- rule.priority
72
- for rule in self.survey.rule_collection.applicable_rules(
73
- question_index, before_rule
74
- )
75
- ]
76
- if len(current_priorities) == 0:
77
- return RulePriority.DEFAULT.value + 1
78
-
79
- max_priority = max(current_priorities)
80
- # newer rules take priority over older rules
81
- new_priority = (
82
- RulePriority.DEFAULT.value
83
- if len(current_priorities) == 0
84
- else max_priority + 1
85
- )
86
- return new_priority
87
-
88
- def add_rule(
89
- self,
90
- question: Union["QuestionBase", str],
91
- expression: str,
92
- next_question: Union["QuestionBase", str, int],
93
- before_rule: bool = False,
94
- ) -> "Survey":
95
- """
96
- Add a rule to a Question of the Survey with the appropriate priority.
97
-
98
- :param question: The question to add the rule to.
99
- :param expression: The expression to evaluate.
100
- :param next_question: The next question to go to if the rule is true.
101
- :param before_rule: Whether the rule is evaluated before the question is answered.
102
-
103
-
104
- - The last rule added for the question will have the highest priority.
105
- - If there are no rules, the rule added gets priority -1.
106
- """
107
- question_index = self.survey._get_question_index(question) # Fix
108
-
109
- # Might not have the name of the next question yet
110
- if isinstance(next_question, int):
111
- next_question_index = next_question
112
- else:
113
- next_question_index = self._get_question_index(next_question)
114
-
115
- new_priority = self._get_new_rule_priority(question_index, before_rule) # fix
116
-
117
- self.survey.rule_collection.add_rule(
118
- Rule(
119
- current_q=question_index,
120
- expression=expression,
121
- next_q=next_question_index,
122
- question_name_to_index=self.survey.question_name_to_index,
123
- priority=new_priority,
124
- before_rule=before_rule,
125
- )
126
- )
127
-
128
- return self.survey
129
-
130
- def add_stop_rule(
131
- self, question: Union["QuestionBase", str], expression: str
132
- ) -> "Survey":
133
- """Add a rule that stops the survey.
134
- The rule is evaluated *after* the question is answered. If the rule is true, the survey ends.
135
-
136
- :param question: The question to add the stop rule to.
137
- :param expression: The expression to evaluate.
138
-
139
- If this rule is true, the survey ends.
140
-
141
- Here, answering "yes" to q0 ends the survey:
142
-
143
- >>> from edsl import Survey
144
- >>> s = Survey.example().add_stop_rule("q0", "q0 == 'yes'")
145
- >>> s.next_question("q0", {"q0": "yes"})
146
- EndOfSurvey
147
-
148
- By comparison, answering "no" to q0 does not end the survey:
149
-
150
- >>> s.next_question("q0", {"q0": "no"}).question_name
151
- 'q1'
152
-
153
- >>> s.add_stop_rule("q0", "q1 <> 'yes'")
154
- Traceback (most recent call last):
155
- ...
156
- edsl.exceptions.surveys.SurveyCreationError: The expression contains '<>', which is not allowed. You probably mean '!='.
157
- ...
158
- """
159
- expression = ValidatedString(expression)
160
- prior_question_appears = False
161
- for prior_question in self.survey.questions:
162
- if prior_question.question_name in expression:
163
- prior_question_appears = True
164
-
165
- if not prior_question_appears:
166
- import warnings
167
-
168
- warnings.warn(
169
- f"The expression {expression} does not contain any prior question names. This is probably a mistake."
170
- )
171
- self.survey.add_rule(question, expression, EndOfSurvey)
172
- return self.survey
edsl/surveys/Simulator.py DELETED
@@ -1,75 +0,0 @@
1
- from typing import Callable
2
-
3
-
4
- class Simulator:
5
- def __init__(self, survey):
6
- self.survey = survey
7
-
8
- @classmethod
9
- def random_survey(cls):
10
- """Create a random survey."""
11
- from edsl.questions import QuestionMultipleChoice, QuestionFreeText
12
- from random import choice
13
- from edsl.surveys.Survey import Survey
14
-
15
- num_questions = 10
16
- questions = []
17
- for i in range(num_questions):
18
- if choice([True, False]):
19
- q = QuestionMultipleChoice(
20
- question_text="nothing",
21
- question_name="q_" + str(i),
22
- question_options=list(range(3)),
23
- )
24
- questions.append(q)
25
- else:
26
- questions.append(
27
- QuestionFreeText(
28
- question_text="nothing", question_name="q_" + str(i)
29
- )
30
- )
31
- s = Survey(questions)
32
- start_index = choice(range(num_questions - 1))
33
- end_index = choice(range(start_index + 1, 10))
34
- s = s.add_rule(f"q_{start_index}", "True", f"q_{end_index}")
35
- question_to_delete = choice(range(num_questions))
36
- s.delete_question(f"q_{question_to_delete}")
37
- return s
38
-
39
- def simulate(self) -> dict:
40
- """Simulate the survey and return the answers."""
41
- i = self.survey.gen_path_through_survey()
42
- q = next(i)
43
- num_passes = 0
44
- while True:
45
- num_passes += 1
46
- try:
47
- answer = q._simulate_answer()
48
- q = i.send({q.question_name: answer["answer"]})
49
- except StopIteration:
50
- break
51
-
52
- if num_passes > 100:
53
- print("Too many passes.")
54
- raise Exception("Too many passes.")
55
- return self.survey.answers
56
-
57
- def create_agent(self) -> "Agent":
58
- """Create an agent from the simulated answers."""
59
- answers_dict = self.survey.simulate()
60
- from edsl.agents.Agent import Agent
61
-
62
- def construct_answer_dict_function(traits: dict) -> Callable:
63
- def func(self, question: "QuestionBase", scenario=None):
64
- return traits.get(question.question_name, None)
65
-
66
- return func
67
-
68
- return Agent(traits=answers_dict).add_direct_question_answering_method(
69
- construct_answer_dict_function(answers_dict)
70
- )
71
-
72
- def simulate_results(self) -> "Results":
73
- """Simulate the survey and return the results."""
74
- a = self.create_agent()
75
- return self.survey.by([a]).run()