uncertain-lang 0.1.0__tar.gz
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.
- uncertain_lang-0.1.0/.gitattributes +2 -0
- uncertain_lang-0.1.0/.github/workflows/smoke-test.yml +35 -0
- uncertain_lang-0.1.0/.gitignore +167 -0
- uncertain_lang-0.1.0/.python-version +1 -0
- uncertain_lang-0.1.0/PKG-INFO +193 -0
- uncertain_lang-0.1.0/README.md +182 -0
- uncertain_lang-0.1.0/docs/error-catalog.md +91 -0
- uncertain_lang-0.1.0/examples/bad_math.calc +2 -0
- uncertain_lang-0.1.0/examples/my_experiment.calc +25 -0
- uncertain_lang-0.1.0/examples/sensor.calc +12 -0
- uncertain_lang-0.1.0/pyproject.toml +22 -0
- uncertain_lang-0.1.0/run.bat +14 -0
- uncertain_lang-0.1.0/scripts/compare_uncertainties.py +33 -0
- uncertain_lang-0.1.0/scripts/monte_carlo_report.py +98 -0
- uncertain_lang-0.1.0/src/uncertain/__init__.py +1 -0
- uncertain_lang-0.1.0/src/uncertain/ast_nodes.py +52 -0
- uncertain_lang-0.1.0/src/uncertain/cli.py +52 -0
- uncertain_lang-0.1.0/src/uncertain/dependency.py +44 -0
- uncertain_lang-0.1.0/src/uncertain/diagnostics.py +58 -0
- uncertain_lang-0.1.0/src/uncertain/distributions.py +50 -0
- uncertain_lang-0.1.0/src/uncertain/evaluator.py +11 -0
- uncertain_lang-0.1.0/src/uncertain/lexer.py +87 -0
- uncertain_lang-0.1.0/src/uncertain/parser.py +193 -0
- uncertain_lang-0.1.0/src/uncertain/typechecker.py +141 -0
- uncertain_lang-0.1.0/test.bat +13 -0
- uncertain_lang-0.1.0/tests/conftest.py +4 -0
- uncertain_lang-0.1.0/tests/golden/reuse_error.calc +2 -0
- uncertain_lang-0.1.0/tests/golden/reuse_error.expected.txt +11 -0
- uncertain_lang-0.1.0/tests/test_diagnostics.py +26 -0
- uncertain_lang-0.1.0/tests/test_distributions.py +69 -0
- uncertain_lang-0.1.0/tests/test_evaluator.py +21 -0
- uncertain_lang-0.1.0/tests/test_fuzz.py +45 -0
- uncertain_lang-0.1.0/tests/test_parser.py +68 -0
- uncertain_lang-0.1.0/tests/test_performance.py +31 -0
- uncertain_lang-0.1.0/tests/test_typechecker_products.py +39 -0
- uncertain_lang-0.1.0/tests/test_typechecker_reuse.py +56 -0
- uncertain_lang-0.1.0/tests/test_typechecker_sums.py +48 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: Smoke Test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ "main" ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ "main" ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build-and-test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
- name: Set up Python
|
|
16
|
+
uses: actions/setup-python@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: "3.12"
|
|
19
|
+
|
|
20
|
+
- name: Install hatch and dependencies
|
|
21
|
+
run: |
|
|
22
|
+
python -m pip install --upgrade pip
|
|
23
|
+
pip install hatch
|
|
24
|
+
|
|
25
|
+
- name: Install package
|
|
26
|
+
run: |
|
|
27
|
+
pip install .
|
|
28
|
+
|
|
29
|
+
- name: Run Pytest
|
|
30
|
+
run: |
|
|
31
|
+
pytest tests/
|
|
32
|
+
|
|
33
|
+
- name: Smoke Test CLI
|
|
34
|
+
run: |
|
|
35
|
+
uncertain examples/my_experiment.calc
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py,cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
#Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# poetry
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
102
|
+
#poetry.lock
|
|
103
|
+
|
|
104
|
+
# pdm
|
|
105
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
106
|
+
#pdm.lock
|
|
107
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
108
|
+
# in version control.
|
|
109
|
+
# https://pdm.fming.dev/#use-with-ide
|
|
110
|
+
.pdm.toml
|
|
111
|
+
|
|
112
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
113
|
+
__pypackages__/
|
|
114
|
+
|
|
115
|
+
# Celery stuff
|
|
116
|
+
celerybeat-schedule
|
|
117
|
+
celerybeat.pid
|
|
118
|
+
|
|
119
|
+
# SageMath parsed files
|
|
120
|
+
*.sage.py
|
|
121
|
+
|
|
122
|
+
# Environments
|
|
123
|
+
.env
|
|
124
|
+
.venv
|
|
125
|
+
env/
|
|
126
|
+
venv/
|
|
127
|
+
ENV/
|
|
128
|
+
env.bak/
|
|
129
|
+
venv.bak/
|
|
130
|
+
|
|
131
|
+
# Spyder project settings
|
|
132
|
+
.spyderproject
|
|
133
|
+
.spyproject
|
|
134
|
+
|
|
135
|
+
# Rope project settings
|
|
136
|
+
.ropeproject
|
|
137
|
+
|
|
138
|
+
# mkdocs documentation
|
|
139
|
+
/site
|
|
140
|
+
|
|
141
|
+
# mypy
|
|
142
|
+
.mypy_cache/
|
|
143
|
+
.dmypy.json
|
|
144
|
+
dmypy.json
|
|
145
|
+
|
|
146
|
+
# Pyre type checker
|
|
147
|
+
.pyre/
|
|
148
|
+
|
|
149
|
+
# pytype static type analyzer
|
|
150
|
+
.pytype/
|
|
151
|
+
|
|
152
|
+
# Cython debug symbols
|
|
153
|
+
cython_debug/
|
|
154
|
+
|
|
155
|
+
# uv
|
|
156
|
+
uv.lock
|
|
157
|
+
|
|
158
|
+
# IDEs and Editors
|
|
159
|
+
.vscode/
|
|
160
|
+
.idea/
|
|
161
|
+
*.swp
|
|
162
|
+
*.swo
|
|
163
|
+
.DS_Store
|
|
164
|
+
|
|
165
|
+
# Design Documents
|
|
166
|
+
uncertain-implementation-plan.md
|
|
167
|
+
uncertainty-typed-language.md
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: uncertain-lang
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Requires-Dist: hypothesis>=6.126.0
|
|
7
|
+
Requires-Dist: numpy>=2.2.3
|
|
8
|
+
Requires-Dist: pytest>=8.3.4
|
|
9
|
+
Requires-Dist: uncertainties>=3.1.7
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# Uncertain
|
|
13
|
+
|
|
14
|
+
*A statically-typed arithmetic DSL where every value's type encodes its distributional uncertainty — and the compiler proves how that uncertainty compounds.*
|
|
15
|
+
|
|
16
|
+
## Why Uncertain?
|
|
17
|
+
|
|
18
|
+
When you write equations for physical measurements, sensor data, or statistical variables, those values are almost never exact—they are probability distributions.
|
|
19
|
+
|
|
20
|
+
If you reuse a variable in a normal programming language without explicitly tracking its mathematical correlation (e.g. `a * a`), your calculated variance will be silently understated, leading to overconfidence in faulty data.
|
|
21
|
+
|
|
22
|
+
**`Uncertain` catches these correlation bugs at compile-time by enforcing dependency tracking in its type system.**
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
### Prerequisites
|
|
29
|
+
- [uv](https://astral.sh/uv/) (Fast Python package and environment manager)
|
|
30
|
+
- Python 3.12+
|
|
31
|
+
|
|
32
|
+
### Installation
|
|
33
|
+
|
|
34
|
+
The compiler is available on PyPI. You can install it globally via `pip` or `uv`:
|
|
35
|
+
```bash
|
|
36
|
+
pip install uncertain
|
|
37
|
+
# or
|
|
38
|
+
uv tool install uncertain
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
If you want to install from source:
|
|
42
|
+
```bash
|
|
43
|
+
git clone https://github.com/yourusername/uncertain.git
|
|
44
|
+
cd uncertain
|
|
45
|
+
pip install .
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Running Scripts
|
|
49
|
+
|
|
50
|
+
Run an example calculation using the newly installed CLI:
|
|
51
|
+
```bash
|
|
52
|
+
uncertain examples/my_experiment.calc
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## The Hero Demo: Catching Correlation Bugs
|
|
58
|
+
|
|
59
|
+
Consider this simple program:
|
|
60
|
+
|
|
61
|
+
```calc
|
|
62
|
+
let a = sensor_read();
|
|
63
|
+
let variance_est = a * a;
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Running `uncertain` on this file immediately catches the hidden dependency reuse:
|
|
67
|
+
|
|
68
|
+
```text
|
|
69
|
+
error: uncertain reuse without correlation annotation
|
|
70
|
+
--> line 2:20
|
|
71
|
+
|
|
|
72
|
+
2 | let variance_est = a * a;
|
|
73
|
+
| ^^^^^ `a` appears twice in this product
|
|
74
|
+
|
|
|
75
|
+
= note: treating repeated occurrences of `a` as independent understates
|
|
76
|
+
the true variance of the result.
|
|
77
|
+
= help: use `square(a)` for the correct self-product variance formula,
|
|
78
|
+
or wrap with `correlated(..., ..., cov = ...)` if you have an explicit
|
|
79
|
+
covariance estimate.
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Catching Runtime Math Errors at Compile Time
|
|
83
|
+
Beyond type checking, `uncertain` uses the same beautiful diagnostic system to catch mathematical domain errors before your program even evaluates. For example, taking the square root of a distribution with a negative mean:
|
|
84
|
+
|
|
85
|
+
```calc
|
|
86
|
+
let a = sensor_read() - 15.0;
|
|
87
|
+
let b = sqrt(a);
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Yields a pinpointed math-domain error:
|
|
91
|
+
```text
|
|
92
|
+
error: math domain error
|
|
93
|
+
--> line 2:9
|
|
94
|
+
|
|
|
95
|
+
2 | let b = sqrt(a);
|
|
96
|
+
| ^^^^^^^ invalid operation
|
|
97
|
+
|
|
|
98
|
+
= note: cannot compute the square root of a distribution with a negative mean (-5.0)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
> **Note:** For a comprehensive list of all diagnostics emitted by the compiler, check out the [Error Catalog](docs/error-catalog.md).
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Head-to-Head: `uncertain` vs. Python's `uncertainties`
|
|
106
|
+
|
|
107
|
+
Python's popular `uncertainties` package is fantastic, but it operates entirely at *runtime* using linear approximations (the Delta method).
|
|
108
|
+
|
|
109
|
+
To see exactly why a compiler-enforced approach is safer and more precise, we've included a script that computes `a * a` where `a = 10.0 ± 2.0`.
|
|
110
|
+
|
|
111
|
+
Run it yourself:
|
|
112
|
+
```bash
|
|
113
|
+
uv run python scripts/compare_uncertainties.py
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**The Output:**
|
|
117
|
+
```text
|
|
118
|
+
1. Naive Hand Calculation (Assuming Independence):
|
|
119
|
+
Result: 100.00 ± 28.28
|
|
120
|
+
(DANGEROUS: Silently understates variance by ignoring correlation)
|
|
121
|
+
|
|
122
|
+
2. Python's `uncertainties` package (a * a):
|
|
123
|
+
Result: 100.00 ± 40.00
|
|
124
|
+
(BETTER: Detects correlation, but uses linear Taylor approximation, dropping higher-order terms.)
|
|
125
|
+
|
|
126
|
+
3. Uncertain DSL (forces `square(a)` at compile time):
|
|
127
|
+
Result: 100.00 ± 40.40
|
|
128
|
+
(PERFECT: Compiler caught the reuse, forced explicit intent, and used the exact higher-order formula.)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
This single comparison proves the core thesis of the project: `uncertainties` will happily compute a linear approximation of `a * a` without telling you. `uncertain` will throw a **compile-time error**, forcing you to explicitly choose `square(a)`, which in turn applies the *exact* mathematical formula for the variance of a squared distribution.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Language Guide
|
|
136
|
+
|
|
137
|
+
### Declaring Variables
|
|
138
|
+
All inputs are declared using the built-in `sensor_read()` function. The compiler infers the uncertainty, but you can also provide explicit type annotations to ensure your expectations match reality.
|
|
139
|
+
|
|
140
|
+
```calc
|
|
141
|
+
// Inferred type
|
|
142
|
+
let width = sensor_read();
|
|
143
|
+
|
|
144
|
+
// Explicitly type-checked distribution
|
|
145
|
+
let length: Measured<Normal(10.0, 1.0)> = sensor_read();
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Math & Operations
|
|
149
|
+
You can safely combine independent measurements using standard arithmetic. The compiler propagates the mean and standard deviation automatically using the Delta-method.
|
|
150
|
+
|
|
151
|
+
```calc
|
|
152
|
+
let w = sensor_read();
|
|
153
|
+
let h = sensor_read();
|
|
154
|
+
|
|
155
|
+
let perimeter = w + w + h + h;
|
|
156
|
+
let area = w * h;
|
|
157
|
+
let ratio = w / h;
|
|
158
|
+
let root = sqrt(w);
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Safe Variable Reuse
|
|
162
|
+
If you *must* multiply correlated variables, you must use mathematically safe functions provided by the language to bypass the compiler error:
|
|
163
|
+
|
|
164
|
+
```calc
|
|
165
|
+
// For perfect self-correlation (squaring a variable)
|
|
166
|
+
let w_squared = square(w);
|
|
167
|
+
|
|
168
|
+
// If you have a known covariance estimate for two distinct variables
|
|
169
|
+
let correlated_area = correlated(w, h, cov=0.5);
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Testing
|
|
175
|
+
|
|
176
|
+
The project has a highly robust, fuzzed test suite covering lexical analysis, recursive-descent parsing, bidirectional type checking, and hypothesis-driven property tests for the distribution math.
|
|
177
|
+
|
|
178
|
+
- **Fuzzing**: The parser and typechecker are subjected to thousands of randomly generated inputs and deep AST structures via `hypothesis` to ensure zero unhandled exceptions.
|
|
179
|
+
- **Performance**: The typechecker dependency-union performance is strictly validated against large generated programs.
|
|
180
|
+
|
|
181
|
+
To run the full test suite:
|
|
182
|
+
```bash
|
|
183
|
+
# Windows
|
|
184
|
+
.\test.bat
|
|
185
|
+
|
|
186
|
+
# Linux/macOS
|
|
187
|
+
uv run pytest tests/
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
To run the Monte Carlo simulation cross-validation (which proves our analytic Delta-method formulas match empirical random sampling):
|
|
191
|
+
```bash
|
|
192
|
+
uv run python scripts/monte_carlo_report.py
|
|
193
|
+
```
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# Uncertain
|
|
2
|
+
|
|
3
|
+
*A statically-typed arithmetic DSL where every value's type encodes its distributional uncertainty — and the compiler proves how that uncertainty compounds.*
|
|
4
|
+
|
|
5
|
+
## Why Uncertain?
|
|
6
|
+
|
|
7
|
+
When you write equations for physical measurements, sensor data, or statistical variables, those values are almost never exact—they are probability distributions.
|
|
8
|
+
|
|
9
|
+
If you reuse a variable in a normal programming language without explicitly tracking its mathematical correlation (e.g. `a * a`), your calculated variance will be silently understated, leading to overconfidence in faulty data.
|
|
10
|
+
|
|
11
|
+
**`Uncertain` catches these correlation bugs at compile-time by enforcing dependency tracking in its type system.**
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
### Prerequisites
|
|
18
|
+
- [uv](https://astral.sh/uv/) (Fast Python package and environment manager)
|
|
19
|
+
- Python 3.12+
|
|
20
|
+
|
|
21
|
+
### Installation
|
|
22
|
+
|
|
23
|
+
The compiler is available on PyPI. You can install it globally via `pip` or `uv`:
|
|
24
|
+
```bash
|
|
25
|
+
pip install uncertain
|
|
26
|
+
# or
|
|
27
|
+
uv tool install uncertain
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
If you want to install from source:
|
|
31
|
+
```bash
|
|
32
|
+
git clone https://github.com/yourusername/uncertain.git
|
|
33
|
+
cd uncertain
|
|
34
|
+
pip install .
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Running Scripts
|
|
38
|
+
|
|
39
|
+
Run an example calculation using the newly installed CLI:
|
|
40
|
+
```bash
|
|
41
|
+
uncertain examples/my_experiment.calc
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## The Hero Demo: Catching Correlation Bugs
|
|
47
|
+
|
|
48
|
+
Consider this simple program:
|
|
49
|
+
|
|
50
|
+
```calc
|
|
51
|
+
let a = sensor_read();
|
|
52
|
+
let variance_est = a * a;
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Running `uncertain` on this file immediately catches the hidden dependency reuse:
|
|
56
|
+
|
|
57
|
+
```text
|
|
58
|
+
error: uncertain reuse without correlation annotation
|
|
59
|
+
--> line 2:20
|
|
60
|
+
|
|
|
61
|
+
2 | let variance_est = a * a;
|
|
62
|
+
| ^^^^^ `a` appears twice in this product
|
|
63
|
+
|
|
|
64
|
+
= note: treating repeated occurrences of `a` as independent understates
|
|
65
|
+
the true variance of the result.
|
|
66
|
+
= help: use `square(a)` for the correct self-product variance formula,
|
|
67
|
+
or wrap with `correlated(..., ..., cov = ...)` if you have an explicit
|
|
68
|
+
covariance estimate.
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Catching Runtime Math Errors at Compile Time
|
|
72
|
+
Beyond type checking, `uncertain` uses the same beautiful diagnostic system to catch mathematical domain errors before your program even evaluates. For example, taking the square root of a distribution with a negative mean:
|
|
73
|
+
|
|
74
|
+
```calc
|
|
75
|
+
let a = sensor_read() - 15.0;
|
|
76
|
+
let b = sqrt(a);
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Yields a pinpointed math-domain error:
|
|
80
|
+
```text
|
|
81
|
+
error: math domain error
|
|
82
|
+
--> line 2:9
|
|
83
|
+
|
|
|
84
|
+
2 | let b = sqrt(a);
|
|
85
|
+
| ^^^^^^^ invalid operation
|
|
86
|
+
|
|
|
87
|
+
= note: cannot compute the square root of a distribution with a negative mean (-5.0)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
> **Note:** For a comprehensive list of all diagnostics emitted by the compiler, check out the [Error Catalog](docs/error-catalog.md).
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Head-to-Head: `uncertain` vs. Python's `uncertainties`
|
|
95
|
+
|
|
96
|
+
Python's popular `uncertainties` package is fantastic, but it operates entirely at *runtime* using linear approximations (the Delta method).
|
|
97
|
+
|
|
98
|
+
To see exactly why a compiler-enforced approach is safer and more precise, we've included a script that computes `a * a` where `a = 10.0 ± 2.0`.
|
|
99
|
+
|
|
100
|
+
Run it yourself:
|
|
101
|
+
```bash
|
|
102
|
+
uv run python scripts/compare_uncertainties.py
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**The Output:**
|
|
106
|
+
```text
|
|
107
|
+
1. Naive Hand Calculation (Assuming Independence):
|
|
108
|
+
Result: 100.00 ± 28.28
|
|
109
|
+
(DANGEROUS: Silently understates variance by ignoring correlation)
|
|
110
|
+
|
|
111
|
+
2. Python's `uncertainties` package (a * a):
|
|
112
|
+
Result: 100.00 ± 40.00
|
|
113
|
+
(BETTER: Detects correlation, but uses linear Taylor approximation, dropping higher-order terms.)
|
|
114
|
+
|
|
115
|
+
3. Uncertain DSL (forces `square(a)` at compile time):
|
|
116
|
+
Result: 100.00 ± 40.40
|
|
117
|
+
(PERFECT: Compiler caught the reuse, forced explicit intent, and used the exact higher-order formula.)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
This single comparison proves the core thesis of the project: `uncertainties` will happily compute a linear approximation of `a * a` without telling you. `uncertain` will throw a **compile-time error**, forcing you to explicitly choose `square(a)`, which in turn applies the *exact* mathematical formula for the variance of a squared distribution.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Language Guide
|
|
125
|
+
|
|
126
|
+
### Declaring Variables
|
|
127
|
+
All inputs are declared using the built-in `sensor_read()` function. The compiler infers the uncertainty, but you can also provide explicit type annotations to ensure your expectations match reality.
|
|
128
|
+
|
|
129
|
+
```calc
|
|
130
|
+
// Inferred type
|
|
131
|
+
let width = sensor_read();
|
|
132
|
+
|
|
133
|
+
// Explicitly type-checked distribution
|
|
134
|
+
let length: Measured<Normal(10.0, 1.0)> = sensor_read();
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Math & Operations
|
|
138
|
+
You can safely combine independent measurements using standard arithmetic. The compiler propagates the mean and standard deviation automatically using the Delta-method.
|
|
139
|
+
|
|
140
|
+
```calc
|
|
141
|
+
let w = sensor_read();
|
|
142
|
+
let h = sensor_read();
|
|
143
|
+
|
|
144
|
+
let perimeter = w + w + h + h;
|
|
145
|
+
let area = w * h;
|
|
146
|
+
let ratio = w / h;
|
|
147
|
+
let root = sqrt(w);
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Safe Variable Reuse
|
|
151
|
+
If you *must* multiply correlated variables, you must use mathematically safe functions provided by the language to bypass the compiler error:
|
|
152
|
+
|
|
153
|
+
```calc
|
|
154
|
+
// For perfect self-correlation (squaring a variable)
|
|
155
|
+
let w_squared = square(w);
|
|
156
|
+
|
|
157
|
+
// If you have a known covariance estimate for two distinct variables
|
|
158
|
+
let correlated_area = correlated(w, h, cov=0.5);
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Testing
|
|
164
|
+
|
|
165
|
+
The project has a highly robust, fuzzed test suite covering lexical analysis, recursive-descent parsing, bidirectional type checking, and hypothesis-driven property tests for the distribution math.
|
|
166
|
+
|
|
167
|
+
- **Fuzzing**: The parser and typechecker are subjected to thousands of randomly generated inputs and deep AST structures via `hypothesis` to ensure zero unhandled exceptions.
|
|
168
|
+
- **Performance**: The typechecker dependency-union performance is strictly validated against large generated programs.
|
|
169
|
+
|
|
170
|
+
To run the full test suite:
|
|
171
|
+
```bash
|
|
172
|
+
# Windows
|
|
173
|
+
.\test.bat
|
|
174
|
+
|
|
175
|
+
# Linux/macOS
|
|
176
|
+
uv run pytest tests/
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
To run the Monte Carlo simulation cross-validation (which proves our analytic Delta-method formulas match empirical random sampling):
|
|
180
|
+
```bash
|
|
181
|
+
uv run python scripts/monte_carlo_report.py
|
|
182
|
+
```
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Uncertain Error Catalog
|
|
2
|
+
|
|
3
|
+
This catalog documents the structured diagnostics emitted by the `uncertain` compiler and typechecker. Every error is designed to pinpoint the exact location of the issue and provide actionable help.
|
|
4
|
+
|
|
5
|
+
## `uncertain-reuse`
|
|
6
|
+
**Description:** The intellectual centerpiece of the language. This error is triggered when a variable (representing a probability distribution) appears in both operands of a product or ratio without an explicit correlation annotation. Treating reused variables as independent understates the true variance of the result.
|
|
7
|
+
|
|
8
|
+
**Example:**
|
|
9
|
+
```
|
|
10
|
+
let x = Normal(10.0, 2.0);
|
|
11
|
+
let y = x * x;
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
**Diagnostic Output:**
|
|
15
|
+
```text
|
|
16
|
+
error: uncertain reuse without correlation annotation
|
|
17
|
+
--> line 2:9
|
|
18
|
+
|
|
|
19
|
+
2 | let y = x * x;
|
|
20
|
+
| ^^^^^ `x` appears twice in this product
|
|
21
|
+
|
|
|
22
|
+
= note: treating repeated occurrences of `x` as independent understates
|
|
23
|
+
the true variance of the result.
|
|
24
|
+
= help: use `square(x)` for the correct self-product variance formula,
|
|
25
|
+
or wrap with `correlated(..., ..., cov = ...)` if you have an explicit
|
|
26
|
+
covariance estimate.
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## `undefined-var`
|
|
32
|
+
**Description:** Emitted when a variable is referenced before it has been defined using a `let` binding.
|
|
33
|
+
|
|
34
|
+
**Example:**
|
|
35
|
+
```
|
|
36
|
+
let y = x + 1.0;
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Diagnostic Output:**
|
|
40
|
+
```text
|
|
41
|
+
error: undefined variable `x`
|
|
42
|
+
--> line 1:9
|
|
43
|
+
|
|
|
44
|
+
1 | let y = x + 1.0;
|
|
45
|
+
| ^ not found in scope
|
|
46
|
+
|
|
|
47
|
+
= note: `x` must be declared with `let` before use.
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## `type-mismatch`
|
|
53
|
+
**Description:** Emitted when the user provides an explicit type annotation (e.g., `Normal(mean, stddev)`) for a `let` binding, but the typechecker infers a distribution with a different mean or standard deviation.
|
|
54
|
+
|
|
55
|
+
**Example:**
|
|
56
|
+
```
|
|
57
|
+
let a: Normal(10.0, 0.0) = 5.0 + 4.0;
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Diagnostic Output:**
|
|
61
|
+
```text
|
|
62
|
+
error: type annotation mismatch
|
|
63
|
+
--> line 1:1
|
|
64
|
+
|
|
|
65
|
+
1 | let a: Normal(10.0, 0.0) = 5.0 + 4.0;
|
|
66
|
+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ inferred type does not match annotation
|
|
67
|
+
|
|
|
68
|
+
= note: the distribution computed by the typechecker differs from the explicit type annotation.
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## `math-domain-error`
|
|
74
|
+
**Description:** Emitted during typechecking/evaluation when an invalid mathematical operation is performed on a distribution, such as dividing by a distribution with a zero mean, or attempting to take the square root of a distribution with a negative mean.
|
|
75
|
+
|
|
76
|
+
**Example:**
|
|
77
|
+
```
|
|
78
|
+
let a = Normal(-5.0, 1.0);
|
|
79
|
+
let b = sqrt(a);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Diagnostic Output:**
|
|
83
|
+
```text
|
|
84
|
+
error: math domain error
|
|
85
|
+
--> line 2:9
|
|
86
|
+
|
|
|
87
|
+
2 | let b = sqrt(a);
|
|
88
|
+
| ^^^^^^^ invalid operation
|
|
89
|
+
|
|
|
90
|
+
= note: cannot compute the square root of a distribution with a negative mean (-5.0)
|
|
91
|
+
```
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// my_experiment.calc
|
|
2
|
+
|
|
3
|
+
// Let's say we have three sensors reading some physical properties
|
|
4
|
+
let width = sensor_read();
|
|
5
|
+
let length = sensor_read();
|
|
6
|
+
let height = sensor_read();
|
|
7
|
+
|
|
8
|
+
// We can safely calculate the perimeter and area since width and length are independent
|
|
9
|
+
let perimeter = width + width + length + length;
|
|
10
|
+
let area = width * length;
|
|
11
|
+
|
|
12
|
+
// We can also calculate volume
|
|
13
|
+
let volume = area * height;
|
|
14
|
+
|
|
15
|
+
// Let's say we want the square of the height.
|
|
16
|
+
// Using `height * height` will throw a compiler error because we're reusing the same variable!
|
|
17
|
+
// Instead, we use the mathematically safe `square()` function:
|
|
18
|
+
let height_squared = square(height);
|
|
19
|
+
|
|
20
|
+
// And we can take the square root of a measurement
|
|
21
|
+
let root_width = sqrt(width);
|
|
22
|
+
|
|
23
|
+
// If we know two measurements are correlated (e.g. from the same faulty batch of sensors),
|
|
24
|
+
// we can safely multiply them by explicitly providing the covariance:
|
|
25
|
+
let correlated_area = correlated(width, length, cov=0.5);
|