meds-algorithm 0.4.0a4__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 (46) hide show
  1. meds_algorithm/ARCHITECTURE.md +376 -0
  2. meds_algorithm/CONTRACT.md +567 -0
  3. meds_algorithm/__init__.py +94 -0
  4. meds_algorithm/algorithms.py +157 -0
  5. meds_algorithm/artifacts/__init__.py +1 -0
  6. meds_algorithm/artifacts/inputs.py +38 -0
  7. meds_algorithm/artifacts/manifest.py +142 -0
  8. meds_algorithm/artifacts/publication.py +68 -0
  9. meds_algorithm/artifacts/references.py +79 -0
  10. meds_algorithm/artifacts/results.py +76 -0
  11. meds_algorithm/configuration.py +64 -0
  12. meds_algorithm/conformance/__init__.py +1 -0
  13. meds_algorithm/conformance/assertions.py +62 -0
  14. meds_algorithm/conformance/end_to_end.py +150 -0
  15. meds_algorithm/conformance/meds_dev.py +127 -0
  16. meds_algorithm/conformance/reports.py +49 -0
  17. meds_algorithm/conformance/signal.py +141 -0
  18. meds_algorithm/conformance/types.py +29 -0
  19. meds_algorithm/contracts/__init__.py +1 -0
  20. meds_algorithm/contracts/commands.py +182 -0
  21. meds_algorithm/contracts/enums.py +28 -0
  22. meds_algorithm/data/__init__.py +1 -0
  23. meds_algorithm/data/index.py +56 -0
  24. meds_algorithm/data/labels.py +55 -0
  25. meds_algorithm/data/predicates.py +60 -0
  26. meds_algorithm/data/validation.py +25 -0
  27. meds_algorithm/evaluation/__init__.py +1 -0
  28. meds_algorithm/evaluation/predictions.py +56 -0
  29. meds_algorithm/integrations/__init__.py +1 -0
  30. meds_algorithm/integrations/meds_dev.py +51 -0
  31. meds_algorithm/requests.py +262 -0
  32. meds_algorithm/runner.py +232 -0
  33. meds_algorithm/runtime/__init__.py +1 -0
  34. meds_algorithm/runtime/process.py +50 -0
  35. meds_algorithm/runtime/provenance.py +31 -0
  36. meds_algorithm/runtime/resources.py +97 -0
  37. meds_algorithm/serialization.py +122 -0
  38. meds_algorithm/spec.py +23 -0
  39. meds_algorithm/tasks/__init__.py +1 -0
  40. meds_algorithm/tasks/spec.py +136 -0
  41. meds_algorithm/version.py +3 -0
  42. meds_algorithm-0.4.0a4.dist-info/METADATA +212 -0
  43. meds_algorithm-0.4.0a4.dist-info/RECORD +46 -0
  44. meds_algorithm-0.4.0a4.dist-info/WHEEL +4 -0
  45. meds_algorithm-0.4.0a4.dist-info/entry_points.txt +4 -0
  46. meds_algorithm-0.4.0a4.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,376 @@
1
+ # Architecture and typed algorithm API
2
+
3
+ Core owns profile interfaces, typed requests, artifact references, serialization and execution.
4
+ Algorithm repositories own concrete algorithms, configurations, payloads and scientific compatibility.
5
+ The [contract](src/meds_algorithm/CONTRACT.md) remains normative. These diagrams describe implemented
6
+ classes. This source is also packaged in the wheel and rendered by the template documentation.
7
+
8
+ ## Profile hierarchy
9
+
10
+ Every profile requires preprocessing; only pretrained profiles require pretraining. A family can
11
+ inherit several profile ABCs when shared methods have the same semantics, or register separate
12
+ implementation classes that share internal components.
13
+
14
+ ```mermaid
15
+ classDiagram
16
+ class Algorithm {
17
+ <<abstract>>
18
+ name
19
+ config_type
20
+ config
21
+ preprocess_data(request)*
22
+ validate_inputs(request)
23
+ }
24
+ class SupervisedAlgorithm {
25
+ <<abstract>>
26
+ train_supervised(request)*
27
+ predict_supervised(request)*
28
+ }
29
+ class PretrainedAlgorithm {
30
+ <<abstract>>
31
+ pretrain(request)*
32
+ }
33
+ class FinetuneAlgorithm {
34
+ <<abstract>>
35
+ train_finetune(request)*
36
+ predict_finetune(request)*
37
+ }
38
+ class ProbeAlgorithm {
39
+ <<abstract>>
40
+ infer_embeddings(request)*
41
+ train_probe(request)*
42
+ predict_probe(request)*
43
+ }
44
+ class ZeroShotDirectAlgorithm {
45
+ <<abstract>>
46
+ predict_zero_shot_direct(request)*
47
+ }
48
+ class ZeroShotMaterializedAlgorithm {
49
+ <<abstract>>
50
+ infer_trajectories(request)*
51
+ predict_zero_shot_materialized(request)*
52
+ }
53
+ class PredictOnlyAlgorithm {
54
+ <<abstract>>
55
+ predict_only(request)*
56
+ }
57
+ Algorithm <|-- SupervisedAlgorithm
58
+ Algorithm <|-- PretrainedAlgorithm
59
+ Algorithm <|-- PredictOnlyAlgorithm
60
+ PretrainedAlgorithm <|-- FinetuneAlgorithm
61
+ PretrainedAlgorithm <|-- ProbeAlgorithm
62
+ PretrainedAlgorithm <|-- ZeroShotDirectAlgorithm
63
+ PretrainedAlgorithm <|-- ZeroShotMaterializedAlgorithm
64
+ ```
65
+
66
+ Each profile class declares its `profile` and ordered `steps`; method annotations identify request
67
+ types. Request fields define CLI arguments, while each request declares its output argument and
68
+ result artifact class. There is no independent step-definition registry. `ALGORITHM_PROFILES` and
69
+ `STEP_SPECS` are read-only, derived compatibility views for existing consumers.
70
+
71
+ ## Requests, configuration and artifacts
72
+
73
+ ```mermaid
74
+ classDiagram
75
+ class AlgorithmConfig {
76
+ <<frozen dataclass>>
77
+ additional_predicates_file
78
+ validate()
79
+ from_dict(values)
80
+ from_file(path)
81
+ save(path)
82
+ load(path)
83
+ }
84
+ class AlgorithmOwnedConfig {
85
+ <<frozen dataclass>>
86
+ algorithm parameters and defaults
87
+ }
88
+ class NNConfig {
89
+ <<frozen dataclass>>
90
+ capacity
91
+ learning_rate
92
+ batch_size
93
+ weight_decay
94
+ dropout
95
+ }
96
+ class AlgorithmOwnedNNConfig {
97
+ <<frozen dataclass>>
98
+ architecture-specific defaults and settings
99
+ }
100
+ class StepRequest {
101
+ <<frozen dataclass>>
102
+ output
103
+ execution
104
+ predicates_file
105
+ from_arguments(arguments)
106
+ save(path)
107
+ load(path)
108
+ }
109
+ class PredictSupervisedRequest {
110
+ data
111
+ algorithm
112
+ index
113
+ }
114
+ class ArtifactReference {
115
+ <<frozen dataclass>>
116
+ directory
117
+ kind
118
+ open(directory)
119
+ read_manifest()
120
+ }
121
+ class PreparedDataArtifact
122
+ class SupervisedAlgorithmArtifact
123
+ class PredictionsArtifact
124
+ class PredictionIndex {
125
+ directory
126
+ splits
127
+ read()
128
+ }
129
+ class ArtifactDestination {
130
+ directory
131
+ }
132
+ class ExecutionOptions {
133
+ seed
134
+ overwrite
135
+ }
136
+ AlgorithmConfig <|-- AlgorithmOwnedConfig
137
+ AlgorithmConfig <|-- NNConfig
138
+ NNConfig <|-- AlgorithmOwnedNNConfig
139
+ StepRequest <|-- PredictSupervisedRequest
140
+ ArtifactReference <|-- PreparedDataArtifact
141
+ ArtifactReference <|-- SupervisedAlgorithmArtifact
142
+ ArtifactReference <|-- PredictionsArtifact
143
+ PredictSupervisedRequest --> PreparedDataArtifact : data
144
+ PredictSupervisedRequest --> SupervisedAlgorithmArtifact : algorithm
145
+ PredictSupervisedRequest --> PredictionIndex : index
146
+ StepRequest --> ArtifactDestination : output
147
+ StepRequest --> ExecutionOptions : execution
148
+ ```
149
+
150
+ Artifact constructors normalize paths without loading payloads. `open`/`read_manifest` validate the
151
+ common envelope and artifact type. The runner validates references before algorithm code. Payload readers
152
+ remain algorithm-owned; core imposes no tensor, tokenizer, batching or dataframe representation.
153
+
154
+ `TrainingLabels.read` accepts only train/tuning. `PredictionIndex.read` projects keys at the I/O boundary.
155
+ `PredictionContext` explicitly selects train/tuning and checks overlap with evaluated keys. Zero-shot
156
+ requests carry `TaskSpec`. These interfaces make permissions clear; they are not a filesystem sandbox.
157
+
158
+ Define configuration in the algorithm repository:
159
+
160
+ ```python
161
+ from dataclasses import dataclass
162
+ from meds_algorithm import AlgorithmConfig
163
+
164
+
165
+ @dataclass(frozen=True)
166
+ class Config(AlgorithmConfig):
167
+ C: float = 1.0
168
+ max_iterations: int = 1000
169
+
170
+ def validate(self):
171
+ if self.C <= 0 or self.max_iterations < 1:
172
+ raise ValueError("C and max_iterations must be positive")
173
+ ```
174
+
175
+ Core validates field types, rejects unknown keys and nonfinite floats, and resolves defaults. Supported
176
+ values are strings, booleans, integers, finite floats, paths, enums, tuples, optional values and nested
177
+ frozen dataclasses. Mutable configuration fields and arbitrary Python objects are unsupported. JSON
178
+ arrays can initialize typed tuples. Override `validate`, not `__post_init__`, for algorithm constraints.
179
+
180
+ Use `config_file=/path/config.yaml` on the CLI for a JSON/YAML values mapping. Relative paths in that
181
+ file resolve against its parent. `Config.save` writes a versioned JSON envelope with an explicit class
182
+ identifier and all resolved values. `Config.load` checks the requested class and schema version; it
183
+ never imports code named by a file. Increment the algorithm's `schema_version` when serialized meaning
184
+ changes; unsupported versions fail without guessing migrations. Requests use the same serialization.
185
+ Constructed references use absolute paths; moving artifacts requires explicitly updating locations.
186
+
187
+ `additional_predicates_file` is an optional keyword-only configuration field inherited by concrete
188
+ configs. Preprocessing alone composes it with dataset predicates, rejects duplicate names/keys, and
189
+ supplies a resolved `request.predicates_file` to the hook and method. The runner preserves that file
190
+ inside prepared data with source/resolved digests; its contents are reserved. Later stages do not read
191
+ the additions, and `TaskSpec` continues to use the original task binding. See contract section 2.3.
192
+
193
+ ## Neural-network configuration and scaling experiments
194
+
195
+ `AlgorithmConfig` is the common parent. `NNConfig` is an optional, framework-independent child for
196
+ neural algorithms whose settings have the semantics below. It provides validation and serialization,
197
+ not a neural network, optimizer or training loop. Classical algorithms continue to extend
198
+ `AlgorithmConfig` directly.
199
+
200
+ | Setting | Meaning and validation |
201
+ |---|---|
202
+ | `capacity` | Positive finite multiplier relative to the algorithm's reference architecture; defaults to `1.0` |
203
+ | `learning_rate` | Positive finite base optimizer learning rate, before scheduling |
204
+ | `batch_size` | Positive integer examples per device per microbatch, before gradient accumulation |
205
+ | `weight_decay` | Nonnegative finite weight-decay coefficient; defaults to zero |
206
+ | `dropout` | Finite probability of dropping a unit, in `[0, 1)`; defaults to zero |
207
+
208
+ Core deliberately supplies no default learning rate or batch size. A concrete algorithm defines its
209
+ reference architecture and multiplier-to-dimensions mapping, supplies source-defined defaults, chooses
210
+ its optimizer and scheduler, and specifies which parameter
211
+ groups receive weight decay and where dropout is applied. Do not treat weight decay as an interchangeable
212
+ L2 loss penalty or dropout as a keep probability. If an architecture has several widths or dropout rates,
213
+ declare their meaning and additional typed fields locally; do not silently ignore inherited settings.
214
+ For a neural algorithm without meaningful capacity scaling or microbatch semantics, use `AlgorithmConfig`
215
+ with explicit local fields instead of inventing a mapping just to satisfy this optional interface.
216
+
217
+ For example, a transformer can scale its hidden dimension while keeping depth fixed. This illustrative
218
+ policy rounds down to a multiple of attention heads, with at least one dimension per head. The reference
219
+ width must already be divisible by the head count, so `capacity=1.0` preserves the reference architecture.
220
+ Other algorithms may scale depth, several dimensions, or target parameter counts; document and test the
221
+ exact mapping and rounding. `capacity=2.0` does **not** universally mean twice the parameters or FLOPs.
222
+
223
+ ```python
224
+ from dataclasses import dataclass, replace
225
+ from meds_algorithm import NNConfig
226
+
227
+
228
+ @dataclass(frozen=True)
229
+ class TransformerConfig(NNConfig):
230
+ learning_rate: float = 0.001
231
+ batch_size: int = 32
232
+ weight_decay: float = 0.01
233
+ dropout: float = 0.1
234
+ reference_width: int = 128
235
+ depth: int = 4
236
+ heads: int = 4
237
+
238
+ @property
239
+ def hidden_size(self) -> int:
240
+ return max(1, int(self.reference_width * self.capacity / self.heads)) * self.heads
241
+
242
+ def validate(self):
243
+ super().validate()
244
+ if self.heads < 1 or self.reference_width < 1 or self.reference_width % self.heads:
245
+ raise ValueError("reference_width must be positive and divisible by positive heads")
246
+ if self.depth < 1:
247
+ raise ValueError("depth must be positive")
248
+
249
+
250
+ config = TransformerConfig()
251
+ larger = replace(config, capacity=2.0)
252
+ assert larger.hidden_size == 256
253
+ larger.save("config.json")
254
+ assert TransformerConfig.load("config.json") == larger
255
+ ```
256
+
257
+ Override `validate()` with `super().validate()` when adding constraints to `NNConfig`.
258
+ Set the implementation's `config_type = TransformerConfig` and export the named config from its package.
259
+ The existing runner records every resolved field in `config.json` and the manifest. YAML uses the same
260
+ names; `lr` and `bs` aliases are intentionally not accepted:
261
+
262
+ ```yaml
263
+ capacity: 2.0
264
+ reference_width: 128
265
+ depth: 4
266
+ heads: 4
267
+ learning_rate: 0.001
268
+ batch_size: 32
269
+ weight_decay: 0.01
270
+ dropout: 0.1
271
+ ```
272
+
273
+ The capacity multiplier is an experimental input, not a comparable measured parameter count. For scaling-law
274
+ analysis, an implementation should separately report total and trainable parameters of the instantiated
275
+ network, resolved architecture dimensions, actual optimizer steps, consumed examples/events/tokens (with
276
+ units), and effective batch size. Record resolved dimensions in an algorithm-owned payload/report as well
277
+ as retaining the multiplier, reference settings and implementation revision in existing core provenance.
278
+ For fixed-size full microbatches, effective batch size is `batch_size × gradient_accumulation_steps ×
279
+ world_size`; uneven batches or dynamic token batching require actual measurements. Any accumulation,
280
+ distributed or token-budget settings belong in the concrete config. Core does not infer these quantities,
281
+ instantiate optimizers or enforce that an implementation uses a field; algorithm tests must verify the
282
+ mapping. There is no automatic scaling-law experiment runner or standardized scaling-results artifact yet.
283
+
284
+ ### Data scaling belongs to experiment orchestration
285
+
286
+ Recommended ownership is upstream: MEDS-DEV or another experiment runner selects the training cohort
287
+ and materializes or references an explicit subset before calling algorithms. This is an integration
288
+ recommendation, not a claim that MEDS-DEV already implements that sampler. No `data_fraction` field or
289
+ hidden sampler is added to `NNConfig`.
290
+
291
+ - Fix the split assignment first. Select **training subjects**, retaining their valid history, rather than
292
+ sampling arbitrary event rows. Keep tuning and evaluation keys fixed across a data-scaling sweep.
293
+ - Use deterministic nested subsets with a recorded selection seed and explicit membership digest/list.
294
+ Reuse the same subject membership across algorithms; a fraction and seed alone are not an identity.
295
+ - Record dataset revision, eligible and selected subjects/events, selection rule and temporal cutoffs
296
+ upstream. Scope learned preprocessing and label bundles consistently to the selected training cohort.
297
+ For pretraining versus fine-tuning, declare separately which population is being scaled.
298
+ - Let the algorithm report what was actually consumed after filtering/tokenization, plus repetitions and
299
+ training budget. Dataset size, unique token count and total tokens processed are different quantities;
300
+ choose whether a sweep fixes steps, epochs or compute and record that decision.
301
+
302
+ These cohort-selection and consumption-reporting integrations remain future work. The implemented shared
303
+ interface covers configuration and its existing persisted provenance; it does not perform data selection
304
+ or count network parameters. Avoid adding another dataset-selection configuration until its upstream
305
+ owner and identity format are agreed.
306
+
307
+ ## Shared execution
308
+
309
+ `AlgorithmRepository` associates profile names with implementation classes, creates a fresh instance
310
+ for every dispatch, converts CLI arguments into a request, and delegates to `StepRunner.run`. Python
311
+ callers can invoke the runner directly.
312
+
313
+ ```mermaid
314
+ sequenceDiagram
315
+ participant Caller as CLI or Python caller
316
+ participant Runner as StepRunner
317
+ participant Algorithm as Algorithm implementation
318
+ participant Publish as measured_artifact
319
+ Caller->>Runner: run(algorithm, step, request, profile)
320
+ Runner->>Runner: Validate types, paths, manifests, index and context
321
+ Runner->>Algorithm: validate_inputs(request) when no feature composition is needed
322
+ Runner->>Publish: Lock destination, create staging, start measurements
323
+ Publish-->>Runner: Staging directory
324
+ opt Preprocessing with additional predicates
325
+ Runner->>Runner: Save resolved feature predicates from validated source snapshots
326
+ Runner->>Algorithm: validate_inputs(request with resolved predicates)
327
+ end
328
+ Runner->>Algorithm: step(request with staging destination)
329
+ Algorithm-->>Runner: PayloadMetadata(format, version)
330
+ Runner->>Runner: Validate prediction keys/probabilities and zero-shot ledger
331
+ Runner->>Runner: Attach optional evaluation truth after scoring
332
+ Runner->>Runner: Save config.json and original request.json
333
+ Runner->>Publish: Successful context exit
334
+ Publish->>Publish: Write manifest and publish with rollback
335
+ Runner-->>Caller: Typed published artifact reference
336
+ ```
337
+
338
+ Algorithm methods write payloads only into `request.output.directory` and return `PayloadMetadata`. They
339
+ must not write the reserved `manifest.yaml`, `config.json` or `request.json`, or publish final directories.
340
+ Directly calling algorithm methods bypasses runner validation/publication; production and test adapters
341
+ must use the runner. Low-level publication helpers remain available for legacy implementations.
342
+
343
+ Prediction payloads are Parquet shards with keys and probabilities, without ground truth. The runner
344
+ validates their combined key set and probabilities, then optionally attaches evaluation labels. That
345
+ label path is a runner-only argument, never part of the algorithm's prediction request. Install the `data`
346
+ extra for typed prediction execution. Zero-shot outputs include `relaxations.yaml` at their root.
347
+
348
+ Outputs must not overlap inputs. Failures before publication preserve the previous published artifact;
349
+ filesystem/crash limitations remain as described in the README. The measured interval includes algorithm
350
+ execution and postprocessing/publication preparation, not initial input validation. Algorithms apply
351
+ `request.execution.seed` to relevant RNGs and subprocesses; core does not seed frameworks implicitly.
352
+
353
+ The default `Algorithm.validate_inputs` requires fitted-algorithm inputs to record the same algorithm name
354
+ and resolved configuration. Extend it for vocabulary, shape, payload-format and scientific checks.
355
+ Override that default deliberately for a documented policy, such as compatible external pretrained
356
+ algorithms. Reopen persisted artifacts; prediction must not depend on training on the same object.
357
+
358
+ Each successful artifact includes resolved configuration and original request, implementation identity,
359
+ explicit algorithm-checkout provenance, upstream manifest digests and a key-only index digest. Manifest
360
+ digests identify envelopes, not every payload byte. Dataset revisions and algorithm-specific content
361
+ identities remain algorithm/run responsibilities; serialization does not make external data immutable.
362
+ Replaying evaluation additionally requires the runner-only label path recorded in the manifest.
363
+
364
+ ## Ownership and compatibility
365
+
366
+ | Location | Owns |
367
+ |---|---|
368
+ | Core | Profile ABCs, request/artifact types, serialization, runner, shared validation and conformance |
369
+ | Template | Algorithm/config stubs, CLI, profile YAML, test adapters and operational files |
370
+ | Algorithm repository | Concrete typed config, algorithms/components, payload formats, compatibility and scientific tests |
371
+
372
+ Legacy `Command`/`LegacyAlgorithmRepository` retain their previous mapping-based behavior. They do not acquire
373
+ the runner's stronger validation/publication automatically. New generated repositories use
374
+ `AlgorithmRepository`. Contract version 4 uses algorithm-named package imports, CLI entry points,
375
+ artifact types and input/output arguments. Version 3 artifacts and serialized types are not accepted
376
+ under the new names by changing a version field. Existing repositories require an explicit migration.