🇯🇵 日本語版 README
contingency-dsl is a short text notation for schedules of reinforcement and Pavlovian conditioning procedures, such as Conc(VI 30-s, VI 60-s, COD=2-s) or Pair.ForwardDelay(cs=Tone, us=Shock, isi=10s, cs_duration=12s). This repository is its specification: the grammar, the JSON format of a parsed expression, and a set of test cases. It is the common language of OperantKit: the parser, the schedule engines, and the Method-section tools all read the same expressions.
In short
- A schedule fits in a sentence or a table cell and states its parameters, such as the changeover delay (COD) and the limited hold, instead of leaving them to the program.
- The notation follows the abbreviations already used in Method sections (
FR 5,VI 60-s,Conc(...),Mult(...)). - Every parser, in any language, can be checked against the same 1,065 test cases.
A Method section that says "concurrent VI 30 s VI 60 s" often leaves out how the changeover delay was handled or how the intervals were generated, and every lab's program fills the gap differently. Writing the procedure in a notation with a grammar makes those choices explicit and lets other people rerun the same procedure.
This repository contains no code. To use the notation from Python, install the parser contingency-dsl-py (Python 3.11 or newer). The packages are not on PyPI yet, so install from GitHub:
git clone https://github.com/OperantKit/contingency-dsl
cd contingency-dsl
pip install git+https://github.com/OperantKit/contingency-dsl-py jsonschemajsonschema is only needed to check a parsed expression against the JSON Schema, as in the example below.
Parse a concurrent schedule, check the result against this repository's JSON Schema, and replay one of the test cases. Run it from the root of the clone:
import json
from pathlib import Path
from contingency_dsl import ParseError, parse, to_dict
from jsonschema import Draft202012Validator
schema = json.loads(Path("schema/operant/ast.schema.json").read_text(encoding="utf-8"))
validator = Draft202012Validator(schema)
ast = to_dict(parse("Conc(VI30s, VI60s, COD=2s)"))
print(ast["schedule"]["params"]) # {'COD': {'value': 2.0, 'time_unit': 's'}}
print(validator.is_valid(ast)) # True
ast["schedule"]["components"][0]["dist"] = "X" # not a schedule distribution
print(validator.is_valid(ast)) # False
cases = json.loads(Path("conformance/operant/errors.json").read_text(encoding="utf-8"))
case = next(c for c in cases if c["id"] == "error_compound_one_component")
print(case["input"], case["error"]) # Conc(FR5) {'type': 'ParseError', 'code': 'INSUFFICIENT_COMPONENTS'}
try:
parse(case["input"])
except ParseError as error:
print(error) # line 1, col 1: Conc requires at least 2 componentsTo run a parsed schedule against responses, use contingency-py. To turn it into a Method-section paragraph, use contingency-dsl2procedure; to extract it from published Method text, use contingency-procedure2dsl.
A few expressions:
FR5 -- fixed ratio 5
Conc(VI30s, VI60s, COD=2s) -- concurrent VI VI with a 2-s changeover delay
Chain(FR5, FI30s) -- chained FR 5 FI 30 s
FI30 LH10 -- FI 30 s with a 10-s limited hold
Pair.ForwardDelay(cs=Tone, us=Shock, isi=10s, cs_duration=12s)
More examples, grouped by layer, are in Syntax examples. The specification is organized in layers:
| Layer | What it describes |
|---|---|
| Foundations | The meta-grammar and the types shared by every layer (contingency, time scales, stimuli, context) |
| Operant | Three-term contingencies: ratio, interval, time, differential, compound, and progressive-ratio schedules; aversive control (Sidman avoidance, discriminated avoidance, escape, response cost); stateful schedules (percentile, adjusting, interlocking); trial-based procedures (matching-to-sample, go/no-go); limited hold, timeout, reinforcement delay |
| Respondent | Two-term (CS-US) contingencies: Pair.*, Extinction, CSOnly, USOnly, Contingency, TrulyRandom, ExplicitlyUnpaired, Compound, Serial, ITI, Differential |
| Composed | Procedures built from operant and respondent parts, such as conditioned suppression, Pavlovian-instrumental transfer, autoshaping, and omission |
| Experiment | Multi-phase designs, with phases and contexts as part of the program |
| Annotation | Metadata in the Method-section categories (Subjects, Apparatus, Procedure, Measurement), such as @reinforcer, @sd, @species, @apparatus |
Deeper Pavlovian procedures (blocking, overshadowing, latent inhibition, renewal, reinstatement, occasion setting, and others) are specified in the companion package contingency-respondent-dsl.
spec/en/,spec/ja/: the formal specification of each layer (theory, grammar, primitives, design rationale)schema/: the EBNF grammar of each layer and the JSON Schemas for the parsed formconformance/: the test cases (input text and the expected parsed form or error)docs/en/,docs/ja/: user documentationscripts/: converters from the EBNF grammar to Langium and Tree-sitter
Parsers live in separate packages:
- contingency-dsl-py — Python reference parser (sole reference implementation)
- Syntax guide: the notation step by step
- Syntax examples: examples for every layer
- Use cases: what each construct is for, with citations
- Annotations:
@reinforcer,@punisher,@sd,@cs,@us,@species,@apparatus, and the other metadata keywords - Paper examples: published studies written in the notation
- Tooling: Tree-sitter and Langium grammars
- Specification: architecture, design philosophy, operant grammar, respondent grammar, foundations grammar
conformance/ holds 1,065 test cases in 57 JSON files; 370 of them expect an error. Each case pairs an input with the expected parsed form or the expected error, so a parser in any language can be checked against the same inputs. The format and the directory layout are described in conformance/README.md.
The Python parser's test suite reads the cases from a contingency-dsl clone next to it:
git clone https://github.com/OperantKit/contingency-dsl
git clone https://github.com/OperantKit/contingency-dsl-py
cd contingency-dsl-py
pip install -e '.[test]'
pytest tests/test_conformance.py- contingency-dsl-py does not accept every construct specified here. It ships six annotation extensions; the respondent annotations used in the composed examples (
@cs,@us) are specified here but are not accepted by those extensions. conformance/foundations/has no cases: existing lexical tests use operant schedule keywords and are filed underconformance/operant/instead (why).
dist/ (the generated Langium and Tree-sitter grammars) is not tracked. Regenerate it from schema/**/grammar.ebnf:
./scripts/gen-langium.sh # dist/langium/contingency-dsl.langium
./scripts/gen-treesitter.sh # dist/tree-sitter/grammar.jsEvery python block in this README and in README.ja.md is run by scripts/check_readme_examples.py. Notes for AI coding agents are in AGENTS.md.
Cite this repository with CITATION.cff. MIT License; see LICENSE.