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/stdlib.py ADDED
@@ -0,0 +1,929 @@
1
+ """Standard library functions for constraint DSL.
2
+
3
+ This module provides built-in functions that can be used in constraint
4
+ expressions. Functions are organized by category and registered with
5
+ the evaluation context.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import math
11
+ import random
12
+ from collections.abc import Callable
13
+ from typing import TYPE_CHECKING, TypeVar
14
+
15
+ if TYPE_CHECKING:
16
+ from bead.dsl.context import EvaluationContext
17
+ from bead.items.item import Item
18
+
19
+ # Type for DSL scalar values that can be compared/processed
20
+ DslScalar = str | int | float | bool | None
21
+
22
+ # Type for collections in DSL
23
+ DslCollection = list[DslScalar] | dict[str, DslScalar]
24
+
25
+ # Generic type for min/max/any/all operations
26
+ T = TypeVar("T")
27
+
28
+
29
+ # String functions
30
+ def len_(
31
+ s: str
32
+ | list[str | int | float | bool | None]
33
+ | dict[str, str | int | float | bool | None]
34
+ | tuple[str | int | float | bool | None, ...],
35
+ ) -> int:
36
+ """Return length of string or collection.
37
+
38
+ Parameters
39
+ ----------
40
+ s : str | list | dict | tuple
41
+ String or collection to measure.
42
+
43
+ Returns
44
+ -------
45
+ int
46
+ Length of string or collection.
47
+
48
+ Examples
49
+ --------
50
+ >>> len_("hello")
51
+ 5
52
+ >>> len_([1, 2, 3])
53
+ 3
54
+ """
55
+ return len(s)
56
+
57
+
58
+ def lower(s: str) -> str:
59
+ """Convert string to lowercase.
60
+
61
+ Parameters
62
+ ----------
63
+ s : str
64
+ String to convert.
65
+
66
+ Returns
67
+ -------
68
+ str
69
+ Lowercase string.
70
+
71
+ Examples
72
+ --------
73
+ >>> lower("HELLO")
74
+ 'hello'
75
+ """
76
+ return s.lower()
77
+
78
+
79
+ def upper(s: str) -> str:
80
+ """Convert string to uppercase.
81
+
82
+ Parameters
83
+ ----------
84
+ s : str
85
+ String to convert.
86
+
87
+ Returns
88
+ -------
89
+ str
90
+ Uppercase string.
91
+
92
+ Examples
93
+ --------
94
+ >>> upper("hello")
95
+ 'HELLO'
96
+ """
97
+ return s.upper()
98
+
99
+
100
+ def startswith(s: str, prefix: str) -> bool:
101
+ """Check if string starts with prefix.
102
+
103
+ Parameters
104
+ ----------
105
+ s : str
106
+ String to check.
107
+ prefix : str
108
+ Prefix to look for.
109
+
110
+ Returns
111
+ -------
112
+ bool
113
+ True if string starts with prefix.
114
+
115
+ Examples
116
+ --------
117
+ >>> startswith("hello", "hel")
118
+ True
119
+ >>> startswith("hello", "bye")
120
+ False
121
+ """
122
+ return s.startswith(prefix)
123
+
124
+
125
+ def endswith(s: str, suffix: str) -> bool:
126
+ """Check if string ends with suffix.
127
+
128
+ Parameters
129
+ ----------
130
+ s : str
131
+ String to check.
132
+ suffix : str
133
+ Suffix to look for.
134
+
135
+ Returns
136
+ -------
137
+ bool
138
+ True if string ends with suffix.
139
+
140
+ Examples
141
+ --------
142
+ >>> endswith("hello", "lo")
143
+ True
144
+ >>> endswith("hello", "hi")
145
+ False
146
+ """
147
+ return s.endswith(suffix)
148
+
149
+
150
+ def contains(s: str, substring: str) -> bool:
151
+ """Check if string contains substring.
152
+
153
+ Parameters
154
+ ----------
155
+ s : str
156
+ String to check.
157
+ substring : str
158
+ Substring to look for.
159
+
160
+ Returns
161
+ -------
162
+ bool
163
+ True if string contains substring.
164
+
165
+ Examples
166
+ --------
167
+ >>> contains("hello", "ell")
168
+ True
169
+ >>> contains("hello", "bye")
170
+ False
171
+ """
172
+ return substring in s
173
+
174
+
175
+ def replace(s: str, old: str, new: str) -> str:
176
+ """Replace occurrences of substring.
177
+
178
+ Parameters
179
+ ----------
180
+ s : str
181
+ String to modify.
182
+ old : str
183
+ Substring to replace.
184
+ new : str
185
+ Replacement substring.
186
+
187
+ Returns
188
+ -------
189
+ str
190
+ String with replacements.
191
+
192
+ Examples
193
+ --------
194
+ >>> replace("hello world", "world", "there")
195
+ 'hello there'
196
+ """
197
+ return s.replace(old, new)
198
+
199
+
200
+ def split(s: str, sep: str = " ") -> list[str]:
201
+ """Split string by separator.
202
+
203
+ Parameters
204
+ ----------
205
+ s : str
206
+ String to split.
207
+ sep : str
208
+ Separator string. Defaults to space.
209
+
210
+ Returns
211
+ -------
212
+ list[str]
213
+ List of substrings.
214
+
215
+ Examples
216
+ --------
217
+ >>> split("a,b,c", ",")
218
+ ['a', 'b', 'c']
219
+ """
220
+ return s.split(sep)
221
+
222
+
223
+ # Collection functions
224
+ def count(collection: str | list[DslScalar], item: DslScalar) -> int:
225
+ """Count occurrences of item in collection.
226
+
227
+ Parameters
228
+ ----------
229
+ collection : str | list[DslScalar]
230
+ Collection to search.
231
+ item : DslScalar
232
+ Item to count.
233
+
234
+ Returns
235
+ -------
236
+ int
237
+ Number of occurrences.
238
+
239
+ Examples
240
+ --------
241
+ >>> count([1, 2, 2, 3], 2)
242
+ 2
243
+ >>> count("hello", "l")
244
+ 2
245
+ """
246
+ return collection.count(item)
247
+
248
+
249
+ def sum_(collection: list[int | float]) -> int | float:
250
+ """Sum numeric collection.
251
+
252
+ Parameters
253
+ ----------
254
+ collection : list[int | float]
255
+ Collection of numbers.
256
+
257
+ Returns
258
+ -------
259
+ int | float
260
+ Sum of all numbers.
261
+
262
+ Examples
263
+ --------
264
+ >>> sum_([1, 2, 3])
265
+ 6
266
+ >>> sum_([1.5, 2.5])
267
+ 4.0
268
+ """
269
+ return sum(collection)
270
+
271
+
272
+ def min_(collection: list[DslScalar]) -> DslScalar:
273
+ """Return minimum value from collection.
274
+
275
+ Parameters
276
+ ----------
277
+ collection : list[DslScalar]
278
+ Collection to search.
279
+
280
+ Returns
281
+ -------
282
+ DslScalar
283
+ Minimum value.
284
+
285
+ Examples
286
+ --------
287
+ >>> min_([3, 1, 2])
288
+ 1
289
+ """
290
+ return min(collection)
291
+
292
+
293
+ def max_[T](collection: list[T]) -> T:
294
+ """Return maximum value from collection.
295
+
296
+ Parameters
297
+ ----------
298
+ collection : list[T]
299
+ Collection to search.
300
+
301
+ Returns
302
+ -------
303
+ T
304
+ Maximum value.
305
+
306
+ Examples
307
+ --------
308
+ >>> max_([3, 1, 2])
309
+ 3
310
+ """
311
+ return max(collection)
312
+
313
+
314
+ def any_[T](collection: list[T]) -> bool:
315
+ """Check if any element is truthy.
316
+
317
+ Parameters
318
+ ----------
319
+ collection : list[T]
320
+ Collection to check.
321
+
322
+ Returns
323
+ -------
324
+ bool
325
+ True if any element is truthy.
326
+
327
+ Examples
328
+ --------
329
+ >>> any_([False, True, False])
330
+ True
331
+ >>> any_([False, False])
332
+ False
333
+ """
334
+ return any(collection)
335
+
336
+
337
+ def all_[T](collection: list[T]) -> bool:
338
+ """Check if all elements are truthy.
339
+
340
+ Parameters
341
+ ----------
342
+ collection : list[T]
343
+ Collection to check.
344
+
345
+ Returns
346
+ -------
347
+ bool
348
+ True if all elements are truthy.
349
+
350
+ Examples
351
+ --------
352
+ >>> all_([True, True, True])
353
+ True
354
+ >>> all_([True, False, True])
355
+ False
356
+ """
357
+ return all(collection)
358
+
359
+
360
+ # Type checking functions
361
+ def is_str(value: DslScalar) -> bool:
362
+ """Check if value is a string.
363
+
364
+ Parameters
365
+ ----------
366
+ value : DslScalar
367
+ Value to check.
368
+
369
+ Returns
370
+ -------
371
+ bool
372
+ True if value is a string.
373
+
374
+ Examples
375
+ --------
376
+ >>> is_str("hello")
377
+ True
378
+ >>> is_str(42)
379
+ False
380
+ """
381
+ return isinstance(value, str)
382
+
383
+
384
+ def is_int(value: DslScalar) -> bool:
385
+ """Check if value is an integer.
386
+
387
+ Parameters
388
+ ----------
389
+ value : DslScalar
390
+ Value to check.
391
+
392
+ Returns
393
+ -------
394
+ bool
395
+ True if value is an integer.
396
+
397
+ Examples
398
+ --------
399
+ >>> is_int(42)
400
+ True
401
+ >>> is_int(42.0)
402
+ False
403
+ """
404
+ return isinstance(value, int) and not isinstance(value, bool)
405
+
406
+
407
+ def is_float(value: DslScalar) -> bool:
408
+ """Check if value is a float.
409
+
410
+ Parameters
411
+ ----------
412
+ value : DslScalar
413
+ Value to check.
414
+
415
+ Returns
416
+ -------
417
+ bool
418
+ True if value is a float.
419
+
420
+ Examples
421
+ --------
422
+ >>> is_float(42.0)
423
+ True
424
+ >>> is_float(42)
425
+ False
426
+ """
427
+ return isinstance(value, float)
428
+
429
+
430
+ def is_bool(value: DslScalar) -> bool:
431
+ """Check if value is a boolean.
432
+
433
+ Parameters
434
+ ----------
435
+ value : DslScalar
436
+ Value to check.
437
+
438
+ Returns
439
+ -------
440
+ bool
441
+ True if value is a boolean.
442
+
443
+ Examples
444
+ --------
445
+ >>> is_bool(True)
446
+ True
447
+ >>> is_bool(1)
448
+ False
449
+ """
450
+ return isinstance(value, bool)
451
+
452
+
453
+ def is_list(value: DslScalar | list[DslScalar]) -> bool:
454
+ """Check if value is a list.
455
+
456
+ Parameters
457
+ ----------
458
+ value : DslScalar | list[DslScalar]
459
+ Value to check.
460
+
461
+ Returns
462
+ -------
463
+ bool
464
+ True if value is a list.
465
+
466
+ Examples
467
+ --------
468
+ >>> is_list([1, 2, 3])
469
+ True
470
+ >>> is_list((1, 2, 3))
471
+ False
472
+ """
473
+ return isinstance(value, list)
474
+
475
+
476
+ # Conversion functions
477
+ def str_(value: DslScalar) -> str:
478
+ """Convert value to string.
479
+
480
+ Parameters
481
+ ----------
482
+ value : DslScalar
483
+ Value to convert.
484
+
485
+ Returns
486
+ -------
487
+ str
488
+ String representation of value.
489
+
490
+ Examples
491
+ --------
492
+ >>> str_(42)
493
+ '42'
494
+ >>> str_(True)
495
+ 'True'
496
+ """
497
+ return str(value)
498
+
499
+
500
+ # Math functions
501
+ def abs_(value: int | float) -> int | float:
502
+ """Return absolute value.
503
+
504
+ Parameters
505
+ ----------
506
+ value : int | float
507
+ Numeric value.
508
+
509
+ Returns
510
+ -------
511
+ int | float
512
+ Absolute value.
513
+
514
+ Examples
515
+ --------
516
+ >>> abs_(-5)
517
+ 5
518
+ >>> abs_(5)
519
+ 5
520
+ """
521
+ return abs(value)
522
+
523
+
524
+ def round_(value: float, ndigits: int = 0) -> float:
525
+ """Round numeric value.
526
+
527
+ Parameters
528
+ ----------
529
+ value : float
530
+ Value to round.
531
+ ndigits : int
532
+ Number of decimal places.
533
+
534
+ Returns
535
+ -------
536
+ float
537
+ Rounded value.
538
+
539
+ Examples
540
+ --------
541
+ >>> round_(3.14159, 2)
542
+ 3.14
543
+ """
544
+ return round(value, ndigits)
545
+
546
+
547
+ def floor(value: float) -> int:
548
+ """Return floor of value.
549
+
550
+ Parameters
551
+ ----------
552
+ value : float
553
+ Numeric value.
554
+
555
+ Returns
556
+ -------
557
+ int
558
+ Floor value.
559
+
560
+ Examples
561
+ --------
562
+ >>> floor(3.7)
563
+ 3
564
+ >>> floor(-3.7)
565
+ -4
566
+ """
567
+ return math.floor(value)
568
+
569
+
570
+ def ceil(value: float) -> int:
571
+ """Return ceiling of value.
572
+
573
+ Parameters
574
+ ----------
575
+ value : float
576
+ Numeric value.
577
+
578
+ Returns
579
+ -------
580
+ int
581
+ Ceiling value.
582
+
583
+ Examples
584
+ --------
585
+ >>> ceil(3.2)
586
+ 4
587
+ >>> ceil(-3.2)
588
+ -3
589
+ """
590
+ return math.ceil(value)
591
+
592
+
593
+ # Logic functions
594
+ def not_(value: DslScalar | list[DslScalar]) -> bool:
595
+ """Return logical negation of value.
596
+
597
+ Parameters
598
+ ----------
599
+ value : DslScalar | list[DslScalar]
600
+ Value to negate.
601
+
602
+ Returns
603
+ -------
604
+ bool
605
+ Logical negation.
606
+
607
+ Examples
608
+ --------
609
+ >>> not_(True)
610
+ False
611
+ >>> not_(False)
612
+ True
613
+ >>> not_(0)
614
+ True
615
+ """
616
+ return not value
617
+
618
+
619
+ # ============================================================================
620
+ # Simulation Functions
621
+ # ============================================================================
622
+
623
+
624
+ def sigmoid(x: float) -> float:
625
+ """Sigmoid activation function.
626
+
627
+ Converts unbounded value to probability in (0, 1).
628
+
629
+ Parameters
630
+ ----------
631
+ x : float
632
+ Input value.
633
+
634
+ Returns
635
+ -------
636
+ float
637
+ Sigmoid output in (0, 1).
638
+
639
+ Examples
640
+ --------
641
+ >>> sigmoid(0.0)
642
+ 0.5
643
+ >>> round(sigmoid(5.0), 3)
644
+ 0.993
645
+ >>> round(sigmoid(-5.0), 3)
646
+ 0.007
647
+ """
648
+ return 1.0 / (1.0 + math.exp(-x))
649
+
650
+
651
+ def softmax(values: list[float]) -> list[float]:
652
+ """Softmax function over list of values.
653
+
654
+ Converts list of scores to probability distribution.
655
+
656
+ Parameters
657
+ ----------
658
+ values : list[float]
659
+ Input scores.
660
+
661
+ Returns
662
+ -------
663
+ list[float]
664
+ Probability distribution (sums to 1.0).
665
+
666
+ Examples
667
+ --------
668
+ >>> probs = softmax([1.0, 2.0, 3.0])
669
+ >>> [round(p, 2) for p in probs]
670
+ [0.09, 0.24, 0.67]
671
+ """
672
+ if not values:
673
+ return []
674
+ exp_values = [math.exp(v) for v in values]
675
+ total = sum(exp_values)
676
+ return [e / total for e in exp_values]
677
+
678
+
679
+ def sample_categorical(probs: list[float], seed: int | None = None) -> int:
680
+ """Sample from categorical distribution.
681
+
682
+ Parameters
683
+ ----------
684
+ probs : list[float]
685
+ Probability distribution.
686
+ seed : int | None
687
+ Random seed.
688
+
689
+ Returns
690
+ -------
691
+ int
692
+ Sampled index (0-based).
693
+
694
+ Examples
695
+ --------
696
+ >>> sample_categorical([0.2, 0.5, 0.3], seed=42)
697
+ 1
698
+ """
699
+ if seed is not None:
700
+ random.seed(seed)
701
+ return random.choices(range(len(probs)), weights=probs)[0]
702
+
703
+
704
+ def add_noise(
705
+ value: float, noise_type: str, strength: float, seed: int | None = None
706
+ ) -> float:
707
+ """Add noise to a value.
708
+
709
+ Parameters
710
+ ----------
711
+ value : float
712
+ Original value.
713
+ noise_type : str
714
+ Type of noise ("gaussian", "uniform").
715
+ strength : float
716
+ Noise strength (stddev for gaussian, range for uniform).
717
+ seed : int | None
718
+ Random seed.
719
+
720
+ Returns
721
+ -------
722
+ float
723
+ Value with noise added.
724
+
725
+ Examples
726
+ --------
727
+ >>> result = add_noise(5.0, "gaussian", 0.1, seed=42)
728
+ >>> isinstance(result, float)
729
+ True
730
+ >>> result = add_noise(5.0, "uniform", 0.1, seed=42)
731
+ >>> isinstance(result, float)
732
+ True
733
+ """
734
+ if seed is not None:
735
+ random.seed(seed)
736
+
737
+ if noise_type == "gaussian":
738
+ return value + random.gauss(0, strength)
739
+ elif noise_type == "uniform":
740
+ return value + random.uniform(-strength, strength)
741
+ else:
742
+ return value
743
+
744
+
745
+ def model_output(
746
+ item: Item, key: str, default: DslScalar = None
747
+ ) -> DslScalar | list[float]:
748
+ """Extract model output from item.
749
+
750
+ Parameters
751
+ ----------
752
+ item : Item
753
+ Item with model outputs.
754
+ key : str
755
+ Key to extract (e.g., "lm_score", "embedding").
756
+ default : DslScalar
757
+ Default value if key not found.
758
+
759
+ Returns
760
+ -------
761
+ DslScalar | list[float]
762
+ Extracted value or default.
763
+
764
+ Examples
765
+ --------
766
+ >>> # Would work with actual Item object
767
+ >>> # model_output(item, "lm_score", default=0.0)
768
+ >>> # -12.4
769
+ """
770
+ if not hasattr(item, "model_outputs"):
771
+ return default
772
+
773
+ for output in item.model_outputs:
774
+ if output.operation == key or key in output.computation_metadata:
775
+ return output.output
776
+
777
+ # Try item_metadata as fallback
778
+ if hasattr(item, "item_metadata") and key in item.item_metadata:
779
+ return item.item_metadata[key]
780
+
781
+ return default
782
+
783
+
784
+ def distance(emb1: list[float], emb2: list[float], metric: str = "cosine") -> float:
785
+ """Compute distance between embeddings.
786
+
787
+ Parameters
788
+ ----------
789
+ emb1 : list[float]
790
+ First embedding.
791
+ emb2 : list[float]
792
+ Second embedding.
793
+ metric : str
794
+ Distance metric ("cosine", "euclidean", "manhattan").
795
+
796
+ Returns
797
+ -------
798
+ float
799
+ Distance value.
800
+
801
+ Examples
802
+ --------
803
+ >>> distance([1.0, 0.0], [0.0, 1.0], "cosine")
804
+ 1.0
805
+ >>> round(distance([1.0, 0.0], [0.0, 1.0], "euclidean"), 3)
806
+ 1.414
807
+ >>> distance([1.0, 0.0], [0.0, 1.0], "manhattan")
808
+ 2.0
809
+ """
810
+ if metric == "cosine":
811
+ dot = sum(a * b for a, b in zip(emb1, emb2, strict=True))
812
+ norm1 = math.sqrt(sum(a * a for a in emb1))
813
+ norm2 = math.sqrt(sum(b * b for b in emb2))
814
+ if norm1 == 0 or norm2 == 0:
815
+ return 1.0
816
+ return 1.0 - (dot / (norm1 * norm2))
817
+
818
+ elif metric == "euclidean":
819
+ return math.sqrt(sum((a - b) ** 2 for a, b in zip(emb1, emb2, strict=True)))
820
+
821
+ elif metric == "manhattan":
822
+ return sum(abs(a - b) for a, b in zip(emb1, emb2, strict=True))
823
+
824
+ else:
825
+ msg = f"Unknown metric: {metric}"
826
+ raise ValueError(msg)
827
+
828
+
829
+ def preference_prob(score1: float, score2: float, temperature: float = 1.0) -> float:
830
+ """Compute preference probability using sigmoid.
831
+
832
+ P(choose option 1) = sigmoid((score1 - score2) / temperature)
833
+
834
+ Parameters
835
+ ----------
836
+ score1 : float
837
+ Score for option 1.
838
+ score2 : float
839
+ Score for option 2.
840
+ temperature : float
841
+ Temperature for scaling.
842
+
843
+ Returns
844
+ -------
845
+ float
846
+ Probability of choosing option 1.
847
+
848
+ Examples
849
+ --------
850
+ >>> round(preference_prob(10.0, 5.0, temperature=1.0), 3)
851
+ 0.993
852
+ >>> round(preference_prob(10.0, 5.0, temperature=5.0), 2)
853
+ 0.73
854
+ """
855
+ return sigmoid((score1 - score2) / temperature)
856
+
857
+
858
+ # Type alias for DSL callable functions
859
+ DslFunction = Callable[..., DslScalar | list[DslScalar] | list[float]]
860
+
861
+ # Register simulation functions
862
+ SIMULATION_FUNCTIONS: dict[str, DslFunction] = {
863
+ "sigmoid": sigmoid,
864
+ "softmax": softmax,
865
+ "sample_categorical": sample_categorical,
866
+ "add_noise": add_noise,
867
+ "model_output": model_output,
868
+ "distance": distance,
869
+ "preference_prob": preference_prob,
870
+ }
871
+
872
+
873
+ # Registry
874
+ STDLIB_FUNCTIONS: dict[str, DslFunction] = {
875
+ # String functions
876
+ "len": len_,
877
+ "lower": lower,
878
+ "upper": upper,
879
+ "startswith": startswith,
880
+ "endswith": endswith,
881
+ "contains": contains,
882
+ "replace": replace,
883
+ "split": split,
884
+ # Collection functions
885
+ "count": count,
886
+ "sum": sum_,
887
+ "min": min_,
888
+ "max": max_,
889
+ "any": any_,
890
+ "all": all_,
891
+ # Type checking
892
+ "is_str": is_str,
893
+ "is_int": is_int,
894
+ "is_float": is_float,
895
+ "is_bool": is_bool,
896
+ "is_list": is_list,
897
+ # Conversion functions
898
+ "str": str_,
899
+ # Math functions
900
+ "abs": abs_,
901
+ "round": round_,
902
+ "floor": floor,
903
+ "ceil": ceil,
904
+ # Logic functions
905
+ "not": not_,
906
+ }
907
+
908
+ # Update STDLIB_FUNCTIONS with simulation functions
909
+ STDLIB_FUNCTIONS.update(SIMULATION_FUNCTIONS)
910
+
911
+
912
+ def register_stdlib(context: EvaluationContext) -> None:
913
+ """Register all standard library functions in context.
914
+
915
+ Parameters
916
+ ----------
917
+ context : EvaluationContext
918
+ Context to register functions in.
919
+
920
+ Examples
921
+ --------
922
+ >>> from bead.dsl.context import EvaluationContext
923
+ >>> ctx = EvaluationContext()
924
+ >>> register_stdlib(ctx)
925
+ >>> ctx.call_function("len", ["hello"])
926
+ 5
927
+ """
928
+ for name, func in STDLIB_FUNCTIONS.items():
929
+ context.set_function(name, func)