distparser 0.3.0__tar.gz → 0.3.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: distparser
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: Parse function-call strings into frozen scipy.stats distributions
5
5
  Author-email: Juyoung Choi <njuyoung35@gmail.com>
6
6
  Maintainer-email: Juyoung Choi <njuyoung35@gmail.com>
@@ -33,19 +33,13 @@ Dynamic: license-file
33
33
 
34
34
  # distparser
35
35
 
36
- **Parse function‑call syntax strings into frozen `scipy.stats` distributions**
36
+ **Parse function‑call strings into frozen `scipy.stats` distributions —**
37
+ **with dependency resolution, arithmetic expressions, and seed management.**
37
38
 
38
39
  [![PyPI version](https://badge.fury.io/py/distparser.svg)](https://badge.fury.io/py/distparser)
39
40
  [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
40
41
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
41
42
 
42
- ## Why `distparser`?
43
-
44
- When you have configuration files or user input that specifies a probability
45
- distribution (e.g. `"norm(loc=0, scale=1)"`), you want to turn that string
46
- into a ready‑to‑sample object. `distparser` does exactly that, with a tiny
47
- API and zero surprises.
48
-
49
43
  ## Installation
50
44
 
51
45
  ```bash
@@ -54,42 +48,47 @@ pip install distparser
54
48
 
55
49
  ## Quick Start
56
50
 
57
- ```
58
- from distparser import parse_dist
51
+ ```python
52
+ import distparser as lib
59
53
 
60
- # Positional arguments (order matters)
61
- dist = parse_dist("uniform(0, 1)")
62
- sample = dist.rvs() # e.g. 0.374
54
+ # Parse a single distribution string
55
+ d = lib.parse_dist("norm(loc=0, scale=1)")
56
+ print(d.rvs(size=3)) # array([...])
57
+ ```
63
58
 
64
- # Keyword arguments (order ignored)
65
- dist = parse_dist("norm(loc=0, scale=1)")
66
- samples = dist.rvs(size=5) # array([-0.12, 1.03, ...])
59
+ ## DistGraph dependency-aware evaluation
67
60
 
68
- # Mixed (positional + keyword) – works, but not recommended
69
- dist = parse_dist("expon(scale=2, loc=1)")
70
- ```
61
+ ```python
62
+ from distparser import DistGraph
71
63
 
72
- ## Supported Distributions
64
+ config = {
65
+ "mean": 5.0,
66
+ "noise": "norm(0, 0.1)",
67
+ "measurement": "mean + noise",
68
+ }
73
69
 
74
- Built‑in registry covers the most common continuous distributions:
70
+ graph = DistGraph(config, seed=42)
71
+ result = graph.resolve_all()
72
+ print(result["measurement"]) # 5.0 + sample from N(0, 0.1)
73
+ ```
75
74
 
76
- - `uniform(loc, scale)`
77
- - `norm(loc, scale)`
78
- - `expon(loc, scale)`
79
- - … and many more (see [docs](https://distparser.readthedocs.io)).
75
+ ## Features
80
76
 
81
- You can **add your own** at runtime:
77
+ - **15 built-in distributions** `uniform`, `norm`, `expon`, `gamma`, `beta`, `lognorm`, `weibull_min`, `t`, `chi2`, `f`, `pareto`, `cauchy`, `laplace`, `logistic`, `rayleigh`
78
+ - **Distribution aliases** — `normal` → `norm`, `gaussian` → `norm`, `unif` → `uniform`
79
+ - **DistGraph** — automatic dependency resolution via topological sort
80
+ - **Arithmetic expressions** — `"60 + 30 * uniform(0, 1)"` with `sin`, `cos`, `exp`, `sqrt`, `pi` and more
81
+ - **Seed management** — global `seed()`, context manager `seed_context()`, per-instance `DistGraph(seed=...)`
82
+ - **Bounds clipping** — clamp sampled values with `min`/`max` or `lbound`/`rbound`
83
+ - **Extensible registry** — register custom distributions at runtime
82
84
 
83
- ```python
84
- from distparser import register_distribution
85
- from scipy.stats import gumbel_r
85
+ ## Docs
86
86
 
87
- register_distribution("gumbel", gumbel_r, ["loc", "scale"])
88
- ```
87
+ Full documentation at [distparser.readthedocs.io](https://distparser.readthedocs.io).
89
88
 
90
89
  ## Development
91
90
 
92
- See [AGENTS.md](AGENTS.md) for development setup and guidelines.
91
+ See [AGENTS.md](https://github.com/njuyoung35/distparser/blob/main/AGENTS.md) for setup and guidelines.
93
92
 
94
93
  ## License
95
94
 
@@ -0,0 +1,62 @@
1
+ # distparser
2
+
3
+ **Parse function‑call strings into frozen `scipy.stats` distributions —**
4
+ **with dependency resolution, arithmetic expressions, and seed management.**
5
+
6
+ [![PyPI version](https://badge.fury.io/py/distparser.svg)](https://badge.fury.io/py/distparser)
7
+ [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ pip install distparser
14
+ ```
15
+
16
+ ## Quick Start
17
+
18
+ ```python
19
+ import distparser as lib
20
+
21
+ # Parse a single distribution string
22
+ d = lib.parse_dist("norm(loc=0, scale=1)")
23
+ print(d.rvs(size=3)) # array([...])
24
+ ```
25
+
26
+ ## DistGraph — dependency-aware evaluation
27
+
28
+ ```python
29
+ from distparser import DistGraph
30
+
31
+ config = {
32
+ "mean": 5.0,
33
+ "noise": "norm(0, 0.1)",
34
+ "measurement": "mean + noise",
35
+ }
36
+
37
+ graph = DistGraph(config, seed=42)
38
+ result = graph.resolve_all()
39
+ print(result["measurement"]) # 5.0 + sample from N(0, 0.1)
40
+ ```
41
+
42
+ ## Features
43
+
44
+ - **15 built-in distributions** — `uniform`, `norm`, `expon`, `gamma`, `beta`, `lognorm`, `weibull_min`, `t`, `chi2`, `f`, `pareto`, `cauchy`, `laplace`, `logistic`, `rayleigh`
45
+ - **Distribution aliases** — `normal` → `norm`, `gaussian` → `norm`, `unif` → `uniform`
46
+ - **DistGraph** — automatic dependency resolution via topological sort
47
+ - **Arithmetic expressions** — `"60 + 30 * uniform(0, 1)"` with `sin`, `cos`, `exp`, `sqrt`, `pi` and more
48
+ - **Seed management** — global `seed()`, context manager `seed_context()`, per-instance `DistGraph(seed=...)`
49
+ - **Bounds clipping** — clamp sampled values with `min`/`max` or `lbound`/`rbound`
50
+ - **Extensible registry** — register custom distributions at runtime
51
+
52
+ ## Docs
53
+
54
+ Full documentation at [distparser.readthedocs.io](https://distparser.readthedocs.io).
55
+
56
+ ## Development
57
+
58
+ See [AGENTS.md](https://github.com/njuyoung35/distparser/blob/main/AGENTS.md) for setup and guidelines.
59
+
60
+ ## License
61
+
62
+ MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "distparser"
7
- version = "0.3.0"
7
+ version = "0.3.2"
8
8
  description = "Parse function-call strings into frozen scipy.stats distributions"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -26,6 +26,8 @@ from scipy.stats import (
26
26
 
27
27
  from distparser.mapping import normalize_dist_name
28
28
 
29
+ _BOUND_KEYS = frozenset({"min", "max", "lbound", "rbound"})
30
+
29
31
  __all__ = [
30
32
  "DistParserError",
31
33
  "ParseError",
@@ -195,6 +197,10 @@ def parse_dist(s: str) -> Any:
195
197
 
196
198
  params.update(keyword)
197
199
 
200
+ # Strip bound keys before passing to scipy
201
+ for bk in _BOUND_KEYS:
202
+ params.pop(bk, None)
203
+
198
204
  return dist_class(**params)
199
205
 
200
206
 
@@ -4,13 +4,14 @@ from __future__ import annotations
4
4
 
5
5
  import ast as _ast
6
6
  import contextlib
7
+ import re as _re
7
8
  from typing import Any, Iterator
8
9
 
9
10
  import graphlib
10
11
  import numpy as np
11
12
  from asteval import Interpreter
12
13
 
13
- from distparser.core import REGISTRY, ParseError
14
+ from distparser.core import REGISTRY, ParseError, _split_args
14
15
 
15
16
  # ---------------------------------------------------------------------------
16
17
  # Seed management
@@ -57,6 +58,9 @@ def _get_current_rng() -> np.random.Generator:
57
58
  # Distribution sampler factory
58
59
  # ---------------------------------------------------------------------------
59
60
 
61
+
62
+ _BOUND_KEYS = frozenset({"min", "max", "lbound", "rbound"})
63
+ _NAME_RE = _re.compile(r"^([a-zA-Z_][a-zA-Z0-9_]*)\s*\((.*)\)$")
60
64
  # Math constants and functions exposed in expressions
61
65
  _MATH_SYMBOLS: dict[str, Any] = {
62
66
  "sin": np.sin,
@@ -74,7 +78,7 @@ _MATH_SYMBOLS: dict[str, Any] = {
74
78
 
75
79
  def _make_dist_samplers(rng: np.random.Generator) -> dict[str, Any]:
76
80
  """Build a dict mapping each registered distribution name to a callable
77
- sampler that returns a single ``.rvs()`` sample."""
81
+ sampler that returns a single ``.rvs()`` sample, clipped to bounds."""
78
82
  samplers: dict[str, Any] = {}
79
83
  for name, entry in REGISTRY.items():
80
84
  dist_class = entry["class"]
@@ -87,12 +91,27 @@ def _make_dist_samplers(rng: np.random.Generator) -> dict[str, Any]:
87
91
  _rng: np.random.Generator = rng,
88
92
  **kwargs: Any,
89
93
  ) -> Any:
94
+ # Separate bound kwargs from distribution kwargs
95
+ bounds: dict[str, float] = {}
96
+ dist_kwargs: dict[str, Any] = {}
97
+ for k, v in kwargs.items():
98
+ if k in _BOUND_KEYS:
99
+ bounds[k] = float(v)
100
+ else:
101
+ dist_kwargs[k] = v
102
+
90
103
  params: dict[str, Any] = {}
91
104
  for i, val in enumerate(args):
92
105
  if i < len(_po):
93
106
  params[_po[i]] = val
94
- params.update(kwargs)
95
- return _dc(**params).rvs(random_state=_rng)
107
+ params.update(dist_kwargs)
108
+
109
+ value = float(_dc(**params).rvs(random_state=_rng))
110
+
111
+ # Clip to bounds
112
+ lb = bounds.get("min", bounds.get("lbound", -float("inf")))
113
+ ub = bounds.get("max", bounds.get("rbound", float("inf")))
114
+ return float(np.clip(value, lb, ub))
96
115
 
97
116
  samplers[name] = _sampler
98
117
  return samplers
@@ -124,7 +143,7 @@ class DistGraph:
124
143
  self._raw_config = config
125
144
  self._rng = np.random.default_rng(seed) if seed is not None else None
126
145
  self._resolved: dict[str, Any] = {}
127
- self._bounds: dict[str, dict[str, tuple[float, float]]] = {}
146
+ self._bounds: dict[str, dict[str, float]] = {}
128
147
 
129
148
  # Parse config into dist expressions + bounds
130
149
  self._dists: dict[str, Any] = {}
@@ -139,18 +158,36 @@ class DistGraph:
139
158
 
140
159
  def _parse_config(self) -> None:
141
160
  for key, value in self._raw_config.items():
142
- if isinstance(value, dict):
143
- if "dist" not in value:
144
- raise ParseError(
145
- f"Config key {key!r} is a dict but missing required "
146
- f"'dist' key. Use 'dist' for the expression string."
147
- )
148
- self._dists[key] = value["dist"]
149
- if "bounds" in value:
150
- self._bounds[key] = value["bounds"]
161
+ if isinstance(value, str):
162
+ self._dists[key] = value
163
+ self._bounds[key] = self._extract_bounds(value)
151
164
  else:
152
165
  self._dists[key] = value
153
166
 
167
+ # ------------------------------------------------------------------
168
+ # Bound extraction
169
+ # ------------------------------------------------------------------
170
+
171
+ @staticmethod
172
+ def _extract_bounds(expr: str) -> dict[str, float]:
173
+ """Extract bound keyword args (min/max/lbound/rbound) from an expression."""
174
+ m = _NAME_RE.match(expr.strip())
175
+ if not m:
176
+ return {}
177
+ args_str = m.group(2)
178
+ bounds: dict[str, float] = {}
179
+ for arg in _split_args(args_str):
180
+ arg = arg.strip()
181
+ if "=" in arg:
182
+ key, val_str = arg.split("=", 1)
183
+ key = key.strip()
184
+ if key in _BOUND_KEYS:
185
+ try:
186
+ bounds[key] = float(_ast.literal_eval(val_str.strip()))
187
+ except (ValueError, SyntaxError):
188
+ pass
189
+ return bounds
190
+
154
191
  # ------------------------------------------------------------------
155
192
  # Dependency resolution
156
193
  # ------------------------------------------------------------------
@@ -257,9 +294,10 @@ class DistGraph:
257
294
  def keys(self) -> Any:
258
295
  return self._dists.keys()
259
296
 
260
- def get_bounds(self, key: str) -> dict[str, tuple[float, float]] | None:
297
+ def get_bounds(self, key: str) -> dict[str, float] | None:
261
298
  """Return the bounds dict for *key*, or ``None`` if no bounds set."""
262
- return self._bounds.get(key)
299
+ b = self._bounds.get(key)
300
+ return b if b else None
263
301
 
264
302
  @property
265
303
  def order(self) -> list[str]:
@@ -0,0 +1 @@
1
+ __version__ = "0.3.2"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: distparser
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: Parse function-call strings into frozen scipy.stats distributions
5
5
  Author-email: Juyoung Choi <njuyoung35@gmail.com>
6
6
  Maintainer-email: Juyoung Choi <njuyoung35@gmail.com>
@@ -33,19 +33,13 @@ Dynamic: license-file
33
33
 
34
34
  # distparser
35
35
 
36
- **Parse function‑call syntax strings into frozen `scipy.stats` distributions**
36
+ **Parse function‑call strings into frozen `scipy.stats` distributions —**
37
+ **with dependency resolution, arithmetic expressions, and seed management.**
37
38
 
38
39
  [![PyPI version](https://badge.fury.io/py/distparser.svg)](https://badge.fury.io/py/distparser)
39
40
  [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
40
41
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
41
42
 
42
- ## Why `distparser`?
43
-
44
- When you have configuration files or user input that specifies a probability
45
- distribution (e.g. `"norm(loc=0, scale=1)"`), you want to turn that string
46
- into a ready‑to‑sample object. `distparser` does exactly that, with a tiny
47
- API and zero surprises.
48
-
49
43
  ## Installation
50
44
 
51
45
  ```bash
@@ -54,42 +48,47 @@ pip install distparser
54
48
 
55
49
  ## Quick Start
56
50
 
57
- ```
58
- from distparser import parse_dist
51
+ ```python
52
+ import distparser as lib
59
53
 
60
- # Positional arguments (order matters)
61
- dist = parse_dist("uniform(0, 1)")
62
- sample = dist.rvs() # e.g. 0.374
54
+ # Parse a single distribution string
55
+ d = lib.parse_dist("norm(loc=0, scale=1)")
56
+ print(d.rvs(size=3)) # array([...])
57
+ ```
63
58
 
64
- # Keyword arguments (order ignored)
65
- dist = parse_dist("norm(loc=0, scale=1)")
66
- samples = dist.rvs(size=5) # array([-0.12, 1.03, ...])
59
+ ## DistGraph dependency-aware evaluation
67
60
 
68
- # Mixed (positional + keyword) – works, but not recommended
69
- dist = parse_dist("expon(scale=2, loc=1)")
70
- ```
61
+ ```python
62
+ from distparser import DistGraph
71
63
 
72
- ## Supported Distributions
64
+ config = {
65
+ "mean": 5.0,
66
+ "noise": "norm(0, 0.1)",
67
+ "measurement": "mean + noise",
68
+ }
73
69
 
74
- Built‑in registry covers the most common continuous distributions:
70
+ graph = DistGraph(config, seed=42)
71
+ result = graph.resolve_all()
72
+ print(result["measurement"]) # 5.0 + sample from N(0, 0.1)
73
+ ```
75
74
 
76
- - `uniform(loc, scale)`
77
- - `norm(loc, scale)`
78
- - `expon(loc, scale)`
79
- - … and many more (see [docs](https://distparser.readthedocs.io)).
75
+ ## Features
80
76
 
81
- You can **add your own** at runtime:
77
+ - **15 built-in distributions** `uniform`, `norm`, `expon`, `gamma`, `beta`, `lognorm`, `weibull_min`, `t`, `chi2`, `f`, `pareto`, `cauchy`, `laplace`, `logistic`, `rayleigh`
78
+ - **Distribution aliases** — `normal` → `norm`, `gaussian` → `norm`, `unif` → `uniform`
79
+ - **DistGraph** — automatic dependency resolution via topological sort
80
+ - **Arithmetic expressions** — `"60 + 30 * uniform(0, 1)"` with `sin`, `cos`, `exp`, `sqrt`, `pi` and more
81
+ - **Seed management** — global `seed()`, context manager `seed_context()`, per-instance `DistGraph(seed=...)`
82
+ - **Bounds clipping** — clamp sampled values with `min`/`max` or `lbound`/`rbound`
83
+ - **Extensible registry** — register custom distributions at runtime
82
84
 
83
- ```python
84
- from distparser import register_distribution
85
- from scipy.stats import gumbel_r
85
+ ## Docs
86
86
 
87
- register_distribution("gumbel", gumbel_r, ["loc", "scale"])
88
- ```
87
+ Full documentation at [distparser.readthedocs.io](https://distparser.readthedocs.io).
89
88
 
90
89
  ## Development
91
90
 
92
- See [AGENTS.md](AGENTS.md) for development setup and guidelines.
91
+ See [AGENTS.md](https://github.com/njuyoung35/distparser/blob/main/AGENTS.md) for setup and guidelines.
93
92
 
94
93
  ## License
95
94
 
@@ -213,6 +213,17 @@ class TestParseDistAliases:
213
213
  parse_dist("not_an_alias(0, 1)")
214
214
 
215
215
 
216
+ class TestParseDistBounds:
217
+ """Bound keys are stripped before passing to scipy."""
218
+
219
+ def test_bound_keys_are_stripped(self):
220
+ d = parse_dist("norm(loc=0, scale=1, min=-2, max=2)")
221
+ assert abs(d.kwds["loc"] - 0.0) < 1e-12
222
+ assert abs(d.kwds["scale"] - 1.0) < 1e-12
223
+ assert "min" not in d.kwds
224
+ assert "max" not in d.kwds
225
+
226
+
216
227
  # ---------------------------------------------------------------------------
217
228
  # parse_dist – error cases
218
229
  # ---------------------------------------------------------------------------
@@ -158,45 +158,45 @@ class TestSeedManagement:
158
158
 
159
159
 
160
160
  class TestDistGraphBounds:
161
- def test_bounds_dict_format(self):
162
- g = DistGraph(
163
- {
164
- "wall": {
165
- "dist": "norm(loc=0.25, scale=0.025)",
166
- "bounds": {"loc": (0.0, 1.0), "scale": (0.001, 0.1)},
167
- }
168
- }
169
- )
161
+ def test_bounds_in_expression(self):
162
+ g = DistGraph({"x": "uniform(0, 1, min=0.1, max=0.9)"}, seed=42)
170
163
  result = g.resolve_all()
171
- assert abs(result["wall"] - 0.25) < 0.2
164
+ # Clipped to [0.1, 0.9]
165
+ assert 0.1 <= result["x"] <= 0.9
172
166
 
173
167
  def test_get_bounds(self):
174
- g = DistGraph({"x": {"dist": "uniform(0, 1)", "bounds": {"loc": (-1, 2)}}})
168
+ g = DistGraph({"x": "uniform(0, 1, min=0, max=2)"})
175
169
  bounds = g.get_bounds("x")
176
- assert bounds == {"loc": (-1, 2)}
170
+ assert bounds == {"min": 0.0, "max": 2.0}
171
+
172
+ def test_get_bounds_lbound_rbound(self):
173
+ g = DistGraph({"x": "norm(0, 1, lbound=-1, rbound=1)"})
174
+ bounds = g.get_bounds("x")
175
+ assert bounds == {"lbound": -1.0, "rbound": 1.0}
177
176
 
178
177
  def test_get_bounds_none(self):
179
178
  g = DistGraph({"x": "uniform(0, 1)"})
180
179
  assert g.get_bounds("x") is None
181
180
 
182
- def test_missing_dist_key_raises(self):
183
- with pytest.raises(ParseError, match="missing required 'dist'"):
184
- DistGraph({"x": {"bounds": {"loc": (0, 1)}}})
185
-
186
- def test_bounds_not_used_for_sampling(self):
187
- # bounds are metadata only — sampling should still work
188
- g = DistGraph(
189
- {
190
- "x": {
191
- "dist": "norm(loc=100, scale=0.01)",
192
- "bounds": {"loc": (0.0, 1.0)},
193
- }
194
- },
195
- seed=42,
196
- )
181
+ def test_bounds_clip_sampling(self):
182
+ # Bounds ARE applied as clipping — value should be clamped
183
+ g = DistGraph({"x": "norm(loc=100, scale=0.01, max=50)"}, seed=42)
184
+ result = g.resolve_all()
185
+ assert result["x"] <= 50.0
186
+
187
+ def test_min_alternative(self):
188
+ g = DistGraph({"x": "uniform(0, 1, min=-10, max=-5)"}, seed=42)
189
+ result = g.resolve_all()
190
+ # Clipped to [-10, -5] — tightly constrained
191
+ assert -10 <= result["x"] <= -5
192
+
193
+ def test_nonliteral_bound_ignored_in_extract(self):
194
+ # min=a is not a literal — _extract_bounds ignores it gracefully
195
+ g = DistGraph({"a": 0.5, "x": "uniform(0, 1, min=a)"})
196
+ assert g.get_bounds("x") is None # not a literal → not extracted
197
+ # But sampler still clips since 'a' is resolved as a variable
197
198
  result = g.resolve_all()
198
- # Value is near loc=100, not clipped to bounds (0, 1)
199
- assert abs(result["x"] - 100.0) < 1.0
199
+ assert result["x"] >= 0.5
200
200
 
201
201
 
202
202
  # ---------------------------------------------------------------------------
@@ -1,63 +0,0 @@
1
- # distparser
2
-
3
- **Parse function‑call syntax strings into frozen `scipy.stats` distributions**
4
-
5
- [![PyPI version](https://badge.fury.io/py/distparser.svg)](https://badge.fury.io/py/distparser)
6
- [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
7
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
-
9
- ## Why `distparser`?
10
-
11
- When you have configuration files or user input that specifies a probability
12
- distribution (e.g. `"norm(loc=0, scale=1)"`), you want to turn that string
13
- into a ready‑to‑sample object. `distparser` does exactly that, with a tiny
14
- API and zero surprises.
15
-
16
- ## Installation
17
-
18
- ```bash
19
- pip install distparser
20
- ```
21
-
22
- ## Quick Start
23
-
24
- ```
25
- from distparser import parse_dist
26
-
27
- # Positional arguments (order matters)
28
- dist = parse_dist("uniform(0, 1)")
29
- sample = dist.rvs() # e.g. 0.374
30
-
31
- # Keyword arguments (order ignored)
32
- dist = parse_dist("norm(loc=0, scale=1)")
33
- samples = dist.rvs(size=5) # array([-0.12, 1.03, ...])
34
-
35
- # Mixed (positional + keyword) – works, but not recommended
36
- dist = parse_dist("expon(scale=2, loc=1)")
37
- ```
38
-
39
- ## Supported Distributions
40
-
41
- Built‑in registry covers the most common continuous distributions:
42
-
43
- - `uniform(loc, scale)`
44
- - `norm(loc, scale)`
45
- - `expon(loc, scale)`
46
- - … and many more (see [docs](https://distparser.readthedocs.io)).
47
-
48
- You can **add your own** at runtime:
49
-
50
- ```python
51
- from distparser import register_distribution
52
- from scipy.stats import gumbel_r
53
-
54
- register_distribution("gumbel", gumbel_r, ["loc", "scale"])
55
- ```
56
-
57
- ## Development
58
-
59
- See [AGENTS.md](AGENTS.md) for development setup and guidelines.
60
-
61
- ## License
62
-
63
- MIT
@@ -1 +0,0 @@
1
- __version__ = "0.3.0"
File without changes
File without changes