bead 0.1.0__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 (231) hide show
  1. bead/__init__.py +11 -0
  2. bead/__main__.py +11 -0
  3. bead/active_learning/__init__.py +15 -0
  4. bead/active_learning/config.py +231 -0
  5. bead/active_learning/loop.py +566 -0
  6. bead/active_learning/models/__init__.py +24 -0
  7. bead/active_learning/models/base.py +852 -0
  8. bead/active_learning/models/binary.py +910 -0
  9. bead/active_learning/models/categorical.py +943 -0
  10. bead/active_learning/models/cloze.py +862 -0
  11. bead/active_learning/models/forced_choice.py +956 -0
  12. bead/active_learning/models/free_text.py +773 -0
  13. bead/active_learning/models/lora.py +365 -0
  14. bead/active_learning/models/magnitude.py +835 -0
  15. bead/active_learning/models/multi_select.py +795 -0
  16. bead/active_learning/models/ordinal_scale.py +811 -0
  17. bead/active_learning/models/peft_adapter.py +155 -0
  18. bead/active_learning/models/random_effects.py +639 -0
  19. bead/active_learning/selection.py +354 -0
  20. bead/active_learning/strategies.py +391 -0
  21. bead/active_learning/trainers/__init__.py +26 -0
  22. bead/active_learning/trainers/base.py +210 -0
  23. bead/active_learning/trainers/data_collator.py +172 -0
  24. bead/active_learning/trainers/dataset_utils.py +261 -0
  25. bead/active_learning/trainers/huggingface.py +304 -0
  26. bead/active_learning/trainers/lightning.py +324 -0
  27. bead/active_learning/trainers/metrics.py +424 -0
  28. bead/active_learning/trainers/mixed_effects.py +551 -0
  29. bead/active_learning/trainers/model_wrapper.py +509 -0
  30. bead/active_learning/trainers/registry.py +104 -0
  31. bead/adapters/__init__.py +11 -0
  32. bead/adapters/huggingface.py +61 -0
  33. bead/behavioral/__init__.py +116 -0
  34. bead/behavioral/analytics.py +646 -0
  35. bead/behavioral/extraction.py +343 -0
  36. bead/behavioral/merging.py +343 -0
  37. bead/cli/__init__.py +11 -0
  38. bead/cli/active_learning.py +513 -0
  39. bead/cli/active_learning_commands.py +779 -0
  40. bead/cli/completion.py +359 -0
  41. bead/cli/config.py +624 -0
  42. bead/cli/constraint_builders.py +286 -0
  43. bead/cli/deployment.py +859 -0
  44. bead/cli/deployment_trials.py +493 -0
  45. bead/cli/deployment_ui.py +332 -0
  46. bead/cli/display.py +378 -0
  47. bead/cli/items.py +960 -0
  48. bead/cli/items_factories.py +776 -0
  49. bead/cli/list_constraints.py +714 -0
  50. bead/cli/lists.py +490 -0
  51. bead/cli/main.py +430 -0
  52. bead/cli/models.py +877 -0
  53. bead/cli/resource_loaders.py +621 -0
  54. bead/cli/resources.py +1036 -0
  55. bead/cli/shell.py +356 -0
  56. bead/cli/simulate.py +840 -0
  57. bead/cli/templates.py +1158 -0
  58. bead/cli/training.py +1080 -0
  59. bead/cli/utils.py +614 -0
  60. bead/cli/workflow.py +1273 -0
  61. bead/config/__init__.py +68 -0
  62. bead/config/active_learning.py +1009 -0
  63. bead/config/config.py +192 -0
  64. bead/config/defaults.py +118 -0
  65. bead/config/deployment.py +217 -0
  66. bead/config/env.py +147 -0
  67. bead/config/item.py +45 -0
  68. bead/config/list.py +193 -0
  69. bead/config/loader.py +149 -0
  70. bead/config/logging.py +42 -0
  71. bead/config/model.py +49 -0
  72. bead/config/paths.py +46 -0
  73. bead/config/profiles.py +320 -0
  74. bead/config/resources.py +47 -0
  75. bead/config/serialization.py +210 -0
  76. bead/config/simulation.py +206 -0
  77. bead/config/template.py +238 -0
  78. bead/config/validation.py +267 -0
  79. bead/data/__init__.py +65 -0
  80. bead/data/base.py +87 -0
  81. bead/data/identifiers.py +97 -0
  82. bead/data/language_codes.py +61 -0
  83. bead/data/metadata.py +270 -0
  84. bead/data/range.py +123 -0
  85. bead/data/repository.py +358 -0
  86. bead/data/serialization.py +249 -0
  87. bead/data/timestamps.py +89 -0
  88. bead/data/validation.py +349 -0
  89. bead/data_collection/__init__.py +11 -0
  90. bead/data_collection/jatos.py +223 -0
  91. bead/data_collection/merger.py +154 -0
  92. bead/data_collection/prolific.py +198 -0
  93. bead/deployment/__init__.py +5 -0
  94. bead/deployment/distribution.py +402 -0
  95. bead/deployment/jatos/__init__.py +1 -0
  96. bead/deployment/jatos/api.py +200 -0
  97. bead/deployment/jatos/exporter.py +210 -0
  98. bead/deployment/jspsych/__init__.py +9 -0
  99. bead/deployment/jspsych/biome.json +44 -0
  100. bead/deployment/jspsych/config.py +411 -0
  101. bead/deployment/jspsych/generator.py +598 -0
  102. bead/deployment/jspsych/package.json +51 -0
  103. bead/deployment/jspsych/pnpm-lock.yaml +2141 -0
  104. bead/deployment/jspsych/randomizer.py +299 -0
  105. bead/deployment/jspsych/src/lib/list-distributor.test.ts +327 -0
  106. bead/deployment/jspsych/src/lib/list-distributor.ts +1282 -0
  107. bead/deployment/jspsych/src/lib/randomizer.test.ts +232 -0
  108. bead/deployment/jspsych/src/lib/randomizer.ts +367 -0
  109. bead/deployment/jspsych/src/plugins/cloze-dropdown.ts +252 -0
  110. bead/deployment/jspsych/src/plugins/forced-choice.ts +265 -0
  111. bead/deployment/jspsych/src/plugins/plugins.test.ts +141 -0
  112. bead/deployment/jspsych/src/plugins/rating.ts +248 -0
  113. bead/deployment/jspsych/src/slopit/index.ts +9 -0
  114. bead/deployment/jspsych/src/types/jatos.d.ts +256 -0
  115. bead/deployment/jspsych/src/types/jspsych.d.ts +228 -0
  116. bead/deployment/jspsych/templates/experiment.css +1 -0
  117. bead/deployment/jspsych/templates/experiment.js.template +289 -0
  118. bead/deployment/jspsych/templates/index.html +51 -0
  119. bead/deployment/jspsych/templates/randomizer.js +241 -0
  120. bead/deployment/jspsych/templates/randomizer.js.template +313 -0
  121. bead/deployment/jspsych/trials.py +723 -0
  122. bead/deployment/jspsych/tsconfig.json +23 -0
  123. bead/deployment/jspsych/tsup.config.ts +30 -0
  124. bead/deployment/jspsych/ui/__init__.py +1 -0
  125. bead/deployment/jspsych/ui/components.py +383 -0
  126. bead/deployment/jspsych/ui/styles.py +411 -0
  127. bead/dsl/__init__.py +80 -0
  128. bead/dsl/ast.py +168 -0
  129. bead/dsl/context.py +178 -0
  130. bead/dsl/errors.py +71 -0
  131. bead/dsl/evaluator.py +570 -0
  132. bead/dsl/grammar.lark +81 -0
  133. bead/dsl/parser.py +231 -0
  134. bead/dsl/stdlib.py +929 -0
  135. bead/evaluation/__init__.py +13 -0
  136. bead/evaluation/convergence.py +485 -0
  137. bead/evaluation/interannotator.py +398 -0
  138. bead/items/__init__.py +40 -0
  139. bead/items/adapters/__init__.py +70 -0
  140. bead/items/adapters/anthropic.py +224 -0
  141. bead/items/adapters/api_utils.py +167 -0
  142. bead/items/adapters/base.py +216 -0
  143. bead/items/adapters/google.py +259 -0
  144. bead/items/adapters/huggingface.py +1074 -0
  145. bead/items/adapters/openai.py +323 -0
  146. bead/items/adapters/registry.py +202 -0
  147. bead/items/adapters/sentence_transformers.py +224 -0
  148. bead/items/adapters/togetherai.py +309 -0
  149. bead/items/binary.py +515 -0
  150. bead/items/cache.py +558 -0
  151. bead/items/categorical.py +593 -0
  152. bead/items/cloze.py +757 -0
  153. bead/items/constructor.py +784 -0
  154. bead/items/forced_choice.py +413 -0
  155. bead/items/free_text.py +681 -0
  156. bead/items/generation.py +432 -0
  157. bead/items/item.py +396 -0
  158. bead/items/item_template.py +787 -0
  159. bead/items/magnitude.py +573 -0
  160. bead/items/multi_select.py +621 -0
  161. bead/items/ordinal_scale.py +569 -0
  162. bead/items/scoring.py +448 -0
  163. bead/items/validation.py +723 -0
  164. bead/lists/__init__.py +30 -0
  165. bead/lists/balancer.py +263 -0
  166. bead/lists/constraints.py +1067 -0
  167. bead/lists/experiment_list.py +286 -0
  168. bead/lists/list_collection.py +378 -0
  169. bead/lists/partitioner.py +1141 -0
  170. bead/lists/stratification.py +254 -0
  171. bead/participants/__init__.py +73 -0
  172. bead/participants/collection.py +699 -0
  173. bead/participants/merging.py +312 -0
  174. bead/participants/metadata_spec.py +491 -0
  175. bead/participants/models.py +276 -0
  176. bead/resources/__init__.py +29 -0
  177. bead/resources/adapters/__init__.py +19 -0
  178. bead/resources/adapters/base.py +104 -0
  179. bead/resources/adapters/cache.py +128 -0
  180. bead/resources/adapters/glazing.py +508 -0
  181. bead/resources/adapters/registry.py +117 -0
  182. bead/resources/adapters/unimorph.py +796 -0
  183. bead/resources/classification.py +856 -0
  184. bead/resources/constraint_builders.py +329 -0
  185. bead/resources/constraints.py +165 -0
  186. bead/resources/lexical_item.py +223 -0
  187. bead/resources/lexicon.py +744 -0
  188. bead/resources/loaders.py +209 -0
  189. bead/resources/template.py +441 -0
  190. bead/resources/template_collection.py +707 -0
  191. bead/resources/template_generation.py +349 -0
  192. bead/simulation/__init__.py +29 -0
  193. bead/simulation/annotators/__init__.py +15 -0
  194. bead/simulation/annotators/base.py +175 -0
  195. bead/simulation/annotators/distance_based.py +135 -0
  196. bead/simulation/annotators/lm_based.py +114 -0
  197. bead/simulation/annotators/oracle.py +182 -0
  198. bead/simulation/annotators/random.py +181 -0
  199. bead/simulation/dsl_extension/__init__.py +3 -0
  200. bead/simulation/noise_models/__init__.py +13 -0
  201. bead/simulation/noise_models/base.py +42 -0
  202. bead/simulation/noise_models/random_noise.py +82 -0
  203. bead/simulation/noise_models/systematic.py +132 -0
  204. bead/simulation/noise_models/temperature.py +86 -0
  205. bead/simulation/runner.py +144 -0
  206. bead/simulation/strategies/__init__.py +23 -0
  207. bead/simulation/strategies/base.py +123 -0
  208. bead/simulation/strategies/binary.py +103 -0
  209. bead/simulation/strategies/categorical.py +123 -0
  210. bead/simulation/strategies/cloze.py +224 -0
  211. bead/simulation/strategies/forced_choice.py +127 -0
  212. bead/simulation/strategies/free_text.py +105 -0
  213. bead/simulation/strategies/magnitude.py +116 -0
  214. bead/simulation/strategies/multi_select.py +129 -0
  215. bead/simulation/strategies/ordinal_scale.py +131 -0
  216. bead/templates/__init__.py +27 -0
  217. bead/templates/adapters/__init__.py +17 -0
  218. bead/templates/adapters/base.py +128 -0
  219. bead/templates/adapters/cache.py +178 -0
  220. bead/templates/adapters/huggingface.py +312 -0
  221. bead/templates/combinatorics.py +103 -0
  222. bead/templates/filler.py +605 -0
  223. bead/templates/renderers.py +177 -0
  224. bead/templates/resolver.py +178 -0
  225. bead/templates/strategies.py +1806 -0
  226. bead/templates/streaming.py +195 -0
  227. bead-0.1.0.dist-info/METADATA +212 -0
  228. bead-0.1.0.dist-info/RECORD +231 -0
  229. bead-0.1.0.dist-info/WHEEL +4 -0
  230. bead-0.1.0.dist-info/entry_points.txt +2 -0
  231. bead-0.1.0.dist-info/licenses/LICENSE +21 -0
bead/dsl/parser.py ADDED
@@ -0,0 +1,231 @@
1
+ """Constraint DSL parser.
2
+
3
+ This module provides the parser for constraint expressions using the Lark
4
+ parsing library. The parser converts constraint strings into AST nodes.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from pathlib import Path
10
+
11
+ from lark import Lark, Token, Transformer
12
+ from lark.exceptions import LarkError, UnexpectedCharacters, UnexpectedInput
13
+
14
+ from bead.dsl import ast
15
+ from bead.dsl.errors import ParseError
16
+
17
+ # Load grammar from file
18
+ _GRAMMAR_PATH = Path(__file__).parent / "grammar.lark"
19
+ _GRAMMAR = _GRAMMAR_PATH.read_text()
20
+
21
+ # Create Lark parser
22
+ _PARSER = Lark(
23
+ _GRAMMAR,
24
+ start="start",
25
+ parser="lalr", # Fast LALR parser
26
+ propagate_positions=True, # Track line/column info
27
+ keep_all_tokens=True, # Keep operator tokens
28
+ )
29
+
30
+
31
+ class ASTBuilder(Transformer): # type: ignore[type-arg]
32
+ """Transformer that converts Lark parse tree to AST nodes."""
33
+
34
+ def string_literal(self, items: list[Token]) -> ast.Literal:
35
+ """Transform string literal."""
36
+ token = items[0]
37
+ # Remove quotes
38
+ value = str(token.value)[1:-1]
39
+ return ast.Literal(value=value)
40
+
41
+ def number_literal(self, items: list[Token]) -> ast.Literal:
42
+ """Transform number literal."""
43
+ token = items[0]
44
+ value_str = str(token.value)
45
+ # Parse as int or float
46
+ value: int | float = (
47
+ int(value_str) if "." not in value_str else float(value_str)
48
+ )
49
+ return ast.Literal(value=value)
50
+
51
+ def true_literal(self, items: list[Token]) -> ast.Literal:
52
+ """Transform true literal."""
53
+ return ast.Literal(value=True)
54
+
55
+ def false_literal(self, items: list[Token]) -> ast.Literal:
56
+ """Transform false literal."""
57
+ return ast.Literal(value=False)
58
+
59
+ def variable(self, items: list[Token]) -> ast.Variable:
60
+ """Transform variable reference."""
61
+ name = str(items[0].value)
62
+ return ast.Variable(name=name)
63
+
64
+ def binary_op(self, items: list[Token | ast.ASTNode]) -> ast.BinaryOp:
65
+ """Transform binary operation."""
66
+ # Items: [left, operator_token, right]
67
+ left = items[0]
68
+ # Get operator from token
69
+ operator_token = items[1]
70
+ if isinstance(operator_token, Token):
71
+ operator = str(operator_token.value)
72
+ else:
73
+ operator = str(operator_token)
74
+ right = items[2]
75
+ return ast.BinaryOp(operator=operator, left=left, right=right)
76
+
77
+ def binary_op_not_in(self, items: list[Token | ast.ASTNode]) -> ast.BinaryOp:
78
+ """Transform 'not in' binary operation."""
79
+ # Items: [left, Token('not'), Token('in'), right]
80
+ # Filter to get only AST nodes
81
+ nodes = [item for item in items if not isinstance(item, Token)]
82
+ left = nodes[0]
83
+ right = nodes[1]
84
+ return ast.BinaryOp(operator="not in", left=left, right=right)
85
+
86
+ def unary_op(self, items: list[Token | ast.ASTNode]) -> ast.UnaryOp:
87
+ """Transform unary operation."""
88
+ # Items: [operator_token, operand]
89
+ operator_token = items[0]
90
+ if isinstance(operator_token, Token):
91
+ operator = str(operator_token.value)
92
+ else:
93
+ operator = str(operator_token)
94
+ operand = items[1]
95
+ return ast.UnaryOp(operator=operator, operand=operand)
96
+
97
+ def attribute_access(self, items: list[Token | ast.ASTNode]) -> ast.AttributeAccess:
98
+ """Transform attribute access."""
99
+ # Items: [object, dot_token, name_token]
100
+ obj = items[0]
101
+ # Last token is the attribute name
102
+ attribute = str(items[-1].value)
103
+ return ast.AttributeAccess(object=obj, attribute=attribute)
104
+
105
+ def subscript(self, items: list[Token | ast.ASTNode]) -> ast.Subscript:
106
+ """Transform subscript access."""
107
+ # Items: [object, lbracket_token, index_expr, rbracket_token]
108
+ obj = items[0]
109
+ # The index expression is items[1] (non-Token items)
110
+ index_expr = [item for item in items[1:] if not isinstance(item, Token)][0]
111
+ return ast.Subscript(object=obj, index=index_expr)
112
+
113
+ def function_call(
114
+ self, items: list[Token | ast.ASTNode | list[ast.ASTNode] | None]
115
+ ) -> ast.FunctionCall:
116
+ """Transform function call."""
117
+ # Items: [atom, lparen, arguments_list/None, rparen]
118
+ # The first item is the function expression (can be Variable or AttributeAccess)
119
+ if not items or not isinstance(items[0], ast.ASTNode):
120
+ raise ValueError("Function call must have a function expression")
121
+ function: ast.ASTNode = items[0]
122
+
123
+ # Arguments are in the list returned by the arguments rule (if present)
124
+ # Filter for non-Token, non-None items and flatten lists
125
+ arguments: list[ast.ASTNode] = []
126
+ for item in items[1:]:
127
+ if isinstance(item, list):
128
+ # This is the arguments list - arg is already ast.ASTNode
129
+ for arg in item:
130
+ arguments.append(arg)
131
+ elif isinstance(item, ast.ASTNode):
132
+ arguments.append(item)
133
+ return ast.FunctionCall(function=function, arguments=arguments)
134
+
135
+ def list_literal(
136
+ self, items: list[Token | ast.ASTNode | list[ast.ASTNode] | None]
137
+ ) -> ast.ListLiteral:
138
+ """Transform list literal."""
139
+ # Filter out bracket tokens and None, flatten lists
140
+ elements: list[ast.ASTNode] = []
141
+ for item in items:
142
+ if isinstance(item, list):
143
+ # This is the list_elements list - elem is already ast.ASTNode
144
+ for elem in item:
145
+ elements.append(elem)
146
+ elif isinstance(item, ast.ASTNode):
147
+ elements.append(item)
148
+ return ast.ListLiteral(elements=elements)
149
+
150
+ def arguments(self, items: list[Token | ast.ASTNode]) -> list[ast.ASTNode]:
151
+ """Transform function arguments, returning flat list."""
152
+ # Filter out comma tokens
153
+ return [item for item in items if isinstance(item, ast.ASTNode)]
154
+
155
+ def list_elements(self, items: list[Token | ast.ASTNode]) -> list[ast.ASTNode]:
156
+ """Transform list elements, returning flat list."""
157
+ # Filter out comma tokens
158
+ return [item for item in items if isinstance(item, ast.ASTNode)]
159
+
160
+ def atom(self, items: list[Token | ast.ASTNode]) -> ast.ASTNode:
161
+ """Transform atom (handles parenthesized expressions)."""
162
+ # If we have parentheses, items = [lparen_token, expr, rparen_token]
163
+ # Return just the expression (filter out tokens)
164
+ nodes = [item for item in items if isinstance(item, ast.ASTNode)]
165
+ if nodes:
166
+ return nodes[0]
167
+ # Fallback: if no AST nodes, try to get first item (shouldn't happen)
168
+ if items and isinstance(items[0], ast.ASTNode):
169
+ return items[0]
170
+ raise ValueError("Atom must contain an AST node")
171
+
172
+
173
+ def parse(expression: str) -> ast.ASTNode:
174
+ """Parse constraint expression into AST.
175
+
176
+ Parameters
177
+ ----------
178
+ expression : str
179
+ Constraint expression to parse.
180
+
181
+ Returns
182
+ -------
183
+ ast.ASTNode
184
+ Root node of the abstract syntax tree.
185
+
186
+ Raises
187
+ ------
188
+ ParseError
189
+ If the expression cannot be parsed.
190
+
191
+ Examples
192
+ --------
193
+ >>> node = parse("pos == 'VERB'")
194
+ >>> isinstance(node, ast.BinaryOp)
195
+ True
196
+ >>> node.operator
197
+ '=='
198
+ """
199
+ try:
200
+ # Parse with Lark
201
+ tree = _PARSER.parse(expression) # type: ignore[no-untyped-call]
202
+
203
+ # Transform to AST
204
+ transformer = ASTBuilder()
205
+ result = transformer.transform(tree)
206
+
207
+ if not isinstance(result, ast.ASTNode):
208
+ raise ParseError(
209
+ f"Parser returned unexpected type: {type(result)}", text=expression
210
+ )
211
+ return result
212
+
213
+ except UnexpectedCharacters as e:
214
+ # Handle invalid characters (must come before UnexpectedInput)
215
+ raise ParseError(
216
+ f"Unexpected character at position {e.column}",
217
+ line=e.line,
218
+ column=e.column,
219
+ text=expression,
220
+ ) from e
221
+ except UnexpectedInput as e:
222
+ # Handle UnexpectedToken (has .token) and UnexpectedEOF
223
+ token_str = getattr(e, "token", "unexpected input")
224
+ raise ParseError(
225
+ f"Unexpected input: {token_str}",
226
+ line=e.line,
227
+ column=e.column,
228
+ text=expression,
229
+ ) from e
230
+ except LarkError as e:
231
+ raise ParseError(f"Parse error: {e}", text=expression) from e