crc-framework 0.2.0__tar.gz → 0.2.4__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.
- {crc_framework-0.2.0 → crc_framework-0.2.4}/.github/workflows/release.yaml +2 -5
- {crc_framework-0.2.0 → crc_framework-0.2.4}/Cargo.lock +12 -52
- {crc_framework-0.2.0 → crc_framework-0.2.4}/Cargo.toml +2 -2
- {crc_framework-0.2.0 → crc_framework-0.2.4}/PKG-INFO +8 -3
- {crc_framework-0.2.0 → crc_framework-0.2.4}/README.md +4 -2
- {crc_framework-0.2.0 → crc_framework-0.2.4}/crates/core/Cargo.toml +1 -1
- {crc_framework-0.2.0 → crc_framework-0.2.4}/crates/core/src/distribution.rs +53 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/crates/core/src/lib.rs +2 -2
- {crc_framework-0.2.0 → crc_framework-0.2.4}/crates/core/src/reference/curve_fitting.rs +27 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/pyproject.toml +5 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/python/crc_framework/__init__.py +9 -7
- {crc_framework-0.2.0 → crc_framework-0.2.4}/python/crc_framework/_core.pyi +1 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/python/crc_framework/distributions/__init__.py +5 -3
- crc_framework-0.2.4/python/crc_framework/distributions/point_mass.py +55 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/src/lib.rs +28 -11
- {crc_framework-0.2.0 → crc_framework-0.2.4}/tests/test_sdk.py +19 -11
- {crc_framework-0.2.0 → crc_framework-0.2.4}/.gitignore +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/LICENSE +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/THIRD_PARTY_NOTICES.md +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/crates/core/src/constants.rs +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/crates/core/src/error.rs +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/crates/core/src/metrics.rs +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/crates/core/src/reference/climate/ag_params.rs +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/crates/core/src/reference/climate/energy_params.rs +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/crates/core/src/reference/climate/flood_params.rs +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/crates/core/src/reference/climate/mod.rs +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/crates/core/src/reference/climate/precipitation_thresholds.rs +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/crates/core/src/reference/climate/spei_params.rs +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/crates/core/src/reference/climate/wind_params.rs +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/crates/core/src/reference/mod.rs +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/crates/core/src/reference/spatial/continent_r4.rs +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/crates/core/src/reference/spatial/geography_r5.rs +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/crates/core/src/reference/spatial/ipcc.rs +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/crates/core/src/reference/spatial/mod.rs +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/crates/core/src/risk.rs +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/crates/core/src/spatial.rs +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/crates/core/src/transform.rs +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/python/crc_framework/constants.py +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/python/crc_framework/distributions/base.py +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/python/crc_framework/distributions/empirical.py +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/python/crc_framework/distributions/fitted.py +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/python/crc_framework/distributions/fitting.py +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/python/crc_framework/distributions/hurdle.py +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/python/crc_framework/distributions/tabulated.py +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/python/crc_framework/metrics/__init__.py +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/python/crc_framework/metrics/distribution.py +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/python/crc_framework/metrics/microscores.py +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/python/crc_framework/metrics/risk.py +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/python/crc_framework/metrics/spanning.py +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/python/crc_framework/models.py +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/python/crc_framework/py.typed +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/python/crc_framework/spatial.py +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/python/crc_framework/transforms/__init__.py +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/python/crc_framework/transforms/base.py +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/python/crc_framework/transforms/builtin.py +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/python/crc_framework/transforms/climate.py +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/tests/test_impacts.py +0 -0
- {crc_framework-0.2.0 → crc_framework-0.2.4}/tests/typing_smoke.py +0 -0
|
@@ -69,7 +69,6 @@ jobs:
|
|
|
69
69
|
3.11
|
|
70
70
|
3.12
|
|
71
71
|
3.13
|
|
72
|
-
3.13t
|
|
73
72
|
3.14
|
|
74
73
|
3.14t
|
|
75
74
|
- name: Build wheels
|
|
@@ -79,12 +78,11 @@ jobs:
|
|
|
79
78
|
with:
|
|
80
79
|
target: ${{ matrix.target }}
|
|
81
80
|
args: >-
|
|
82
|
-
--release
|
|
83
|
-
--out dist
|
|
81
|
+
--release
|
|
82
|
+
--out dist
|
|
84
83
|
--find-interpreter
|
|
85
84
|
sccache: "true"
|
|
86
85
|
container: "off"
|
|
87
|
-
rust-toolchain: nightly-2026-01-27
|
|
88
86
|
- name: Upload wheels
|
|
89
87
|
uses: actions/upload-artifact@v4
|
|
90
88
|
with:
|
|
@@ -102,7 +100,6 @@ jobs:
|
|
|
102
100
|
with:
|
|
103
101
|
command: sdist
|
|
104
102
|
args: --out dist
|
|
105
|
-
rust-toolchain: nightly-2026-01-27
|
|
106
103
|
- name: Upload sdist
|
|
107
104
|
uses: actions/upload-artifact@v4
|
|
108
105
|
with:
|
|
@@ -15,12 +15,6 @@ dependencies = [
|
|
|
15
15
|
"zerocopy",
|
|
16
16
|
]
|
|
17
17
|
|
|
18
|
-
[[package]]
|
|
19
|
-
name = "autocfg"
|
|
20
|
-
version = "1.5.1"
|
|
21
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
22
|
-
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
|
23
|
-
|
|
24
18
|
[[package]]
|
|
25
19
|
name = "cfg-if"
|
|
26
20
|
version = "1.0.4"
|
|
@@ -49,7 +43,7 @@ dependencies = [
|
|
|
49
43
|
|
|
50
44
|
[[package]]
|
|
51
45
|
name = "crc-framework-core"
|
|
52
|
-
version = "0.2.
|
|
46
|
+
version = "0.2.4"
|
|
53
47
|
dependencies = [
|
|
54
48
|
"h3o",
|
|
55
49
|
"phf",
|
|
@@ -58,7 +52,7 @@ dependencies = [
|
|
|
58
52
|
|
|
59
53
|
[[package]]
|
|
60
54
|
name = "crc-framework-rs"
|
|
61
|
-
version = "0.2.
|
|
55
|
+
version = "0.2.4"
|
|
62
56
|
dependencies = [
|
|
63
57
|
"crc-framework-core",
|
|
64
58
|
"h3o",
|
|
@@ -137,15 +131,6 @@ version = "0.5.0"
|
|
|
137
131
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
138
132
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|
139
133
|
|
|
140
|
-
[[package]]
|
|
141
|
-
name = "indoc"
|
|
142
|
-
version = "2.0.7"
|
|
143
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
144
|
-
checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
|
|
145
|
-
dependencies = [
|
|
146
|
-
"rustversion",
|
|
147
|
-
]
|
|
148
|
-
|
|
149
134
|
[[package]]
|
|
150
135
|
name = "libc"
|
|
151
136
|
version = "0.2.188"
|
|
@@ -158,15 +143,6 @@ version = "0.2.16"
|
|
|
158
143
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
159
144
|
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
|
|
160
145
|
|
|
161
|
-
[[package]]
|
|
162
|
-
name = "memoffset"
|
|
163
|
-
version = "0.9.1"
|
|
164
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
165
|
-
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
|
|
166
|
-
dependencies = [
|
|
167
|
-
"autocfg",
|
|
168
|
-
]
|
|
169
|
-
|
|
170
146
|
[[package]]
|
|
171
147
|
name = "once_cell"
|
|
172
148
|
version = "1.21.4"
|
|
@@ -242,35 +218,32 @@ dependencies = [
|
|
|
242
218
|
|
|
243
219
|
[[package]]
|
|
244
220
|
name = "pyo3"
|
|
245
|
-
version = "0.
|
|
221
|
+
version = "0.29.0"
|
|
246
222
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
247
|
-
checksum = "
|
|
223
|
+
checksum = "cd274650b21d4bfc26a0a47587962c1edb425f69287324355cd040c3ea66071c"
|
|
248
224
|
dependencies = [
|
|
249
|
-
"indoc",
|
|
250
225
|
"libc",
|
|
251
|
-
"memoffset",
|
|
252
226
|
"once_cell",
|
|
253
227
|
"portable-atomic",
|
|
254
228
|
"pyo3-build-config",
|
|
255
229
|
"pyo3-ffi",
|
|
256
230
|
"pyo3-macros",
|
|
257
|
-
"unindent",
|
|
258
231
|
]
|
|
259
232
|
|
|
260
233
|
[[package]]
|
|
261
234
|
name = "pyo3-build-config"
|
|
262
|
-
version = "0.
|
|
235
|
+
version = "0.29.0"
|
|
263
236
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
264
|
-
checksum = "
|
|
237
|
+
checksum = "c5e2a7d2f0d013342f295c048ad19237add5154a55b1c5a254c0ec93d4109078"
|
|
265
238
|
dependencies = [
|
|
266
239
|
"target-lexicon",
|
|
267
240
|
]
|
|
268
241
|
|
|
269
242
|
[[package]]
|
|
270
243
|
name = "pyo3-ffi"
|
|
271
|
-
version = "0.
|
|
244
|
+
version = "0.29.0"
|
|
272
245
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
273
|
-
checksum = "
|
|
246
|
+
checksum = "ca85c467da1bbc8d866eea5deff9cf29ea5f7785054a17da36e65bda9c05845b"
|
|
274
247
|
dependencies = [
|
|
275
248
|
"libc",
|
|
276
249
|
"pyo3-build-config",
|
|
@@ -278,9 +251,9 @@ dependencies = [
|
|
|
278
251
|
|
|
279
252
|
[[package]]
|
|
280
253
|
name = "pyo3-macros"
|
|
281
|
-
version = "0.
|
|
254
|
+
version = "0.29.0"
|
|
282
255
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
283
|
-
checksum = "
|
|
256
|
+
checksum = "9ac53762fd065daa3194dd09337a38bd793a188100fd1a9304c4ab312d901771"
|
|
284
257
|
dependencies = [
|
|
285
258
|
"proc-macro2",
|
|
286
259
|
"pyo3-macros-backend",
|
|
@@ -290,13 +263,12 @@ dependencies = [
|
|
|
290
263
|
|
|
291
264
|
[[package]]
|
|
292
265
|
name = "pyo3-macros-backend"
|
|
293
|
-
version = "0.
|
|
266
|
+
version = "0.29.0"
|
|
294
267
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
295
|
-
checksum = "
|
|
268
|
+
checksum = "4ca3a1557399783172dc5bf39cfca835157732532cba56b71d2292161e53b362"
|
|
296
269
|
dependencies = [
|
|
297
270
|
"heck",
|
|
298
271
|
"proc-macro2",
|
|
299
|
-
"pyo3-build-config",
|
|
300
272
|
"quote",
|
|
301
273
|
"syn 2.0.119",
|
|
302
274
|
]
|
|
@@ -345,12 +317,6 @@ dependencies = [
|
|
|
345
317
|
"getrandom 0.3.4",
|
|
346
318
|
]
|
|
347
319
|
|
|
348
|
-
[[package]]
|
|
349
|
-
name = "rustversion"
|
|
350
|
-
version = "1.0.23"
|
|
351
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
352
|
-
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
|
353
|
-
|
|
354
320
|
[[package]]
|
|
355
321
|
name = "serde"
|
|
356
322
|
version = "1.0.229"
|
|
@@ -429,12 +395,6 @@ version = "1.0.24"
|
|
|
429
395
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
430
396
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
|
431
397
|
|
|
432
|
-
[[package]]
|
|
433
|
-
name = "unindent"
|
|
434
|
-
version = "0.2.4"
|
|
435
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
436
|
-
checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
|
|
437
|
-
|
|
438
398
|
[[package]]
|
|
439
399
|
name = "version_check"
|
|
440
400
|
version = "0.9.5"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "crc-framework-rs"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.4"
|
|
4
4
|
edition = "2024"
|
|
5
5
|
rust-version = "1.85"
|
|
6
6
|
description = "Rust-powered Python bindings for the Climate Risk Commons Framework"
|
|
@@ -18,4 +18,4 @@ crate-type = ["cdylib"]
|
|
|
18
18
|
[dependencies]
|
|
19
19
|
crc-framework-core = { path = "crates/core" }
|
|
20
20
|
h3o = "=0.9.3"
|
|
21
|
-
pyo3 = { version = "0.
|
|
21
|
+
pyo3 = { version = "0.29", features = ["extension-module"] }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: crc-framework
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Classifier: Development Status :: 3 - Alpha
|
|
5
5
|
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
|
|
6
6
|
Classifier: Operating System :: OS Independent
|
|
@@ -20,6 +20,9 @@ Author: Riskthinking.AI
|
|
|
20
20
|
License-Expression: AGPL-3.0-or-later
|
|
21
21
|
Requires-Python: >=3.9
|
|
22
22
|
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
23
|
+
Project-URL: Homepage, https://github.com/RiskThinking/crc-framework-rs
|
|
24
|
+
Project-URL: Issues, https://github.com/RiskThinking/crc-framework-rs/issues
|
|
25
|
+
Project-URL: Repository, https://github.com/RiskThinking/crc-framework-rs
|
|
23
26
|
|
|
24
27
|
# Climate Risk Commons Framework
|
|
25
28
|
|
|
@@ -92,6 +95,7 @@ Choose a distribution according to the form of the available data:
|
|
|
92
95
|
- `FittedDistribution` for a known parametric family and its parameters.
|
|
93
96
|
- `HurdleDistribution` for a point mass followed by a truncated parametric
|
|
94
97
|
tail.
|
|
98
|
+
- `PointMassDistribution` for a value that is constant at every probability.
|
|
95
99
|
|
|
96
100
|
`TabulatedDistribution` does not extrapolate by default. Querying outside its
|
|
97
101
|
declared probability range raises an error unless `extrapolate=True` is set.
|
|
@@ -307,8 +311,9 @@ continent and intersecting ISO3 country codes.
|
|
|
307
311
|
The package exports the following primary interfaces:
|
|
308
312
|
|
|
309
313
|
- Distributions: `EmpiricalDistribution`, `TabulatedDistribution`,
|
|
310
|
-
`FittedDistribution`, `HurdleDistribution`, `
|
|
311
|
-
`fit_quantiles`, `fit_hurdle_quantiles`, `fit_all`, and
|
|
314
|
+
`FittedDistribution`, `HurdleDistribution`, `PointMassDistribution`,
|
|
315
|
+
`fit_distribution`, `fit_quantiles`, `fit_hurdle_quantiles`, `fit_all`, and
|
|
316
|
+
`quality_metrics`.
|
|
312
317
|
- Transforms: `impacts`, `ImpactRegistry`, `ImpactFunction`, `ClimateImpact`,
|
|
313
318
|
`LinearImpact`, `SigmoidImpact`, `PiecewiseLinearImpact`, `CallableImpact`,
|
|
314
319
|
and `CallableTransform`.
|
|
@@ -69,6 +69,7 @@ Choose a distribution according to the form of the available data:
|
|
|
69
69
|
- `FittedDistribution` for a known parametric family and its parameters.
|
|
70
70
|
- `HurdleDistribution` for a point mass followed by a truncated parametric
|
|
71
71
|
tail.
|
|
72
|
+
- `PointMassDistribution` for a value that is constant at every probability.
|
|
72
73
|
|
|
73
74
|
`TabulatedDistribution` does not extrapolate by default. Querying outside its
|
|
74
75
|
declared probability range raises an error unless `extrapolate=True` is set.
|
|
@@ -284,8 +285,9 @@ continent and intersecting ISO3 country codes.
|
|
|
284
285
|
The package exports the following primary interfaces:
|
|
285
286
|
|
|
286
287
|
- Distributions: `EmpiricalDistribution`, `TabulatedDistribution`,
|
|
287
|
-
`FittedDistribution`, `HurdleDistribution`, `
|
|
288
|
-
`fit_quantiles`, `fit_hurdle_quantiles`, `fit_all`, and
|
|
288
|
+
`FittedDistribution`, `HurdleDistribution`, `PointMassDistribution`,
|
|
289
|
+
`fit_distribution`, `fit_quantiles`, `fit_hurdle_quantiles`, `fit_all`, and
|
|
290
|
+
`quality_metrics`.
|
|
289
291
|
- Transforms: `impacts`, `ImpactRegistry`, `ImpactFunction`, `ClimateImpact`,
|
|
290
292
|
`LinearImpact`, `SigmoidImpact`, `PiecewiseLinearImpact`, `CallableImpact`,
|
|
291
293
|
and `CallableTransform`.
|
|
@@ -256,6 +256,47 @@ impl Distribution for EmpiricalDistribution {
|
|
|
256
256
|
}
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
+
/// A degenerate distribution whose entire probability mass is at one value.
|
|
260
|
+
#[derive(Debug, Clone, Copy)]
|
|
261
|
+
pub struct PointMassDistribution {
|
|
262
|
+
location: f64,
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
impl PointMassDistribution {
|
|
266
|
+
pub fn new(location: f64) -> Result<Self> {
|
|
267
|
+
if !location.is_finite() {
|
|
268
|
+
return Err(CrcError::InvalidInput(
|
|
269
|
+
"point-mass location must be finite".into(),
|
|
270
|
+
));
|
|
271
|
+
}
|
|
272
|
+
Ok(Self { location })
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
pub fn location(&self) -> f64 {
|
|
276
|
+
self.location
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
impl Distribution for PointMassDistribution {
|
|
281
|
+
fn pdf(&self, _x: f64) -> f64 {
|
|
282
|
+
// A Dirac mass has no finite density with respect to Lebesgue measure.
|
|
283
|
+
0.0
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
fn cdf(&self, x: f64) -> f64 {
|
|
287
|
+
if x < self.location { 0.0 } else { 1.0 }
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
fn ppf(&self, probability: f64) -> Result<f64> {
|
|
291
|
+
validate_probability(probability)?;
|
|
292
|
+
Ok(self.location)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
fn sample(&self, size: usize, _seed: Option<u64>) -> Result<Vec<f64>> {
|
|
296
|
+
Ok(vec![self.location; size])
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
259
300
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
260
301
|
pub enum DistributionFamily {
|
|
261
302
|
GenExtreme,
|
|
@@ -1129,6 +1170,18 @@ fn erf(x: f64) -> f64 {
|
|
|
1129
1170
|
mod tests {
|
|
1130
1171
|
use super::*;
|
|
1131
1172
|
|
|
1173
|
+
#[test]
|
|
1174
|
+
fn point_mass_is_exact_at_every_probability() {
|
|
1175
|
+
let distribution = PointMassDistribution::new(2.5).unwrap();
|
|
1176
|
+
assert_eq!(
|
|
1177
|
+
distribution.quantiles(&[0.0, 0.5, 1.0]).unwrap(),
|
|
1178
|
+
vec![2.5; 3]
|
|
1179
|
+
);
|
|
1180
|
+
assert_eq!(distribution.cdf(2.49), 0.0);
|
|
1181
|
+
assert_eq!(distribution.cdf(2.5), 1.0);
|
|
1182
|
+
assert_eq!(distribution.sample(3, Some(42)).unwrap(), vec![2.5; 3]);
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1132
1185
|
#[test]
|
|
1133
1186
|
fn return_periods_convert_to_upper_tail_probabilities() {
|
|
1134
1187
|
let distribution = TabulatedDistribution::from_return_periods(
|
|
@@ -10,8 +10,8 @@ pub mod transform;
|
|
|
10
10
|
pub use distribution::{
|
|
11
11
|
DiagnosticMetrics, Distribution, DistributionFamily, EmpiricalDistribution, FitResult,
|
|
12
12
|
FittedDistribution, HurdleDistribution, HurdleQuantileFitDiagnostics, HurdleQuantileFitResult,
|
|
13
|
-
Interpolation, QuantileFitDiagnostics, QuantileFitResult,
|
|
14
|
-
fit_hurdle_quantiles, fit_quantiles,
|
|
13
|
+
Interpolation, PointMassDistribution, QuantileFitDiagnostics, QuantileFitResult,
|
|
14
|
+
TabulatedDistribution, Tail, fit_hurdle_quantiles, fit_quantiles,
|
|
15
15
|
};
|
|
16
16
|
pub use error::{CrcError, Result};
|
|
17
17
|
pub use metrics::{
|
|
@@ -469,6 +469,17 @@ where
|
|
|
469
469
|
let mut values: Vec<f64> = simplex.iter().map(|point| objective(point)).collect();
|
|
470
470
|
let mut evaluations = values.len();
|
|
471
471
|
|
|
472
|
+
// Multi-start callers (e.g. GenExtreme's 5 shape starts) run this to
|
|
473
|
+
// completion for every start even when a start is clearly not
|
|
474
|
+
// converging. Nelder-Mead's improvement curve is front-loaded, so a
|
|
475
|
+
// start that hasn't beaten its best objective in STALL_LIMIT iterations
|
|
476
|
+
// is treated the same as one that exhausted max_iterations: it returns
|
|
477
|
+
// early as not-converged, which the caller already ranks below any
|
|
478
|
+
// converged candidate.
|
|
479
|
+
const STALL_LIMIT: usize = 300;
|
|
480
|
+
let mut best_seen = f64::INFINITY;
|
|
481
|
+
let mut stall_count = 0usize;
|
|
482
|
+
|
|
472
483
|
for iteration in 0..max_iterations {
|
|
473
484
|
let mut order: Vec<usize> = (0..simplex.len()).collect();
|
|
474
485
|
order.sort_by(|&left, &right| values[left].total_cmp(&values[right]));
|
|
@@ -498,6 +509,22 @@ where
|
|
|
498
509
|
};
|
|
499
510
|
}
|
|
500
511
|
|
|
512
|
+
if values[best] < best_seen - objective_tolerance {
|
|
513
|
+
best_seen = values[best];
|
|
514
|
+
stall_count = 0;
|
|
515
|
+
} else {
|
|
516
|
+
stall_count += 1;
|
|
517
|
+
if stall_count >= STALL_LIMIT {
|
|
518
|
+
return SimplexResult {
|
|
519
|
+
parameters: simplex[best].clone(),
|
|
520
|
+
objective: values[best],
|
|
521
|
+
converged: false,
|
|
522
|
+
iterations: iteration,
|
|
523
|
+
evaluations,
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
501
528
|
let mut centroid = vec![0.0; dimensions];
|
|
502
529
|
for &index in &order[..dimensions] {
|
|
503
530
|
for (dimension, value) in centroid.iter_mut().enumerate() {
|
|
@@ -28,6 +28,11 @@ classifiers = [
|
|
|
28
28
|
]
|
|
29
29
|
dynamic = ["version"]
|
|
30
30
|
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://github.com/RiskThinking/crc-framework-rs"
|
|
33
|
+
Repository = "https://github.com/RiskThinking/crc-framework-rs"
|
|
34
|
+
Issues = "https://github.com/RiskThinking/crc-framework-rs/issues"
|
|
35
|
+
|
|
31
36
|
[project.optional-dependencies]
|
|
32
37
|
test = ["mypy", "pytest"]
|
|
33
38
|
|
|
@@ -11,6 +11,7 @@ from .distributions import (
|
|
|
11
11
|
HurdleDistribution,
|
|
12
12
|
HurdleQuantileFitDiagnostics,
|
|
13
13
|
HurdleQuantileFitResult,
|
|
14
|
+
PointMassDistribution,
|
|
14
15
|
QuantileFitDiagnostics,
|
|
15
16
|
QuantileFitResult,
|
|
16
17
|
TabulatedDistribution,
|
|
@@ -44,34 +45,35 @@ from .transforms import (
|
|
|
44
45
|
)
|
|
45
46
|
|
|
46
47
|
__all__ = [
|
|
48
|
+
"HORIZONS",
|
|
49
|
+
"PATHWAYS",
|
|
50
|
+
"RISK_FACTORS",
|
|
47
51
|
"BinaryOutcome",
|
|
48
52
|
"CallableImpact",
|
|
49
53
|
"CallableTransform",
|
|
50
54
|
"ClimateImpact",
|
|
51
55
|
"Distribution",
|
|
52
56
|
"EmpiricalDistribution",
|
|
53
|
-
"FitDiagnostics",
|
|
54
57
|
"FitConstraints",
|
|
58
|
+
"FitDiagnostics",
|
|
55
59
|
"FitResult",
|
|
56
60
|
"FittedDistribution",
|
|
61
|
+
"Geography",
|
|
57
62
|
"HurdleDistribution",
|
|
58
63
|
"HurdleQuantileFitDiagnostics",
|
|
59
64
|
"HurdleQuantileFitResult",
|
|
60
|
-
"Geography",
|
|
61
|
-
"HORIZONS",
|
|
62
65
|
"ImpactFunction",
|
|
63
66
|
"ImpactRegistry",
|
|
64
67
|
"LinearImpact",
|
|
65
68
|
"Microscore",
|
|
66
69
|
"MicroscoreSuite",
|
|
67
|
-
"PATHWAYS",
|
|
68
70
|
"Pathway",
|
|
69
71
|
"PiecewiseLinearImpact",
|
|
70
|
-
"
|
|
71
|
-
"RiskFactor",
|
|
72
|
-
"RiskResult",
|
|
72
|
+
"PointMassDistribution",
|
|
73
73
|
"QuantileFitDiagnostics",
|
|
74
74
|
"QuantileFitResult",
|
|
75
|
+
"RiskFactor",
|
|
76
|
+
"RiskResult",
|
|
75
77
|
"ScenarioMetadata",
|
|
76
78
|
"SigmoidImpact",
|
|
77
79
|
"TabulatedDistribution",
|
|
@@ -100,6 +100,7 @@ class NativeRiskResult:
|
|
|
100
100
|
branch_count: int
|
|
101
101
|
|
|
102
102
|
def empirical_distribution(samples: Sequence[float]) -> NativeDistribution: ...
|
|
103
|
+
def point_mass_distribution(location: float) -> NativeDistribution: ...
|
|
103
104
|
def tabulated_distribution(
|
|
104
105
|
probabilities: Sequence[float],
|
|
105
106
|
values: Sequence[float],
|
|
@@ -16,24 +16,26 @@ from .fitting import (
|
|
|
16
16
|
quality_metrics,
|
|
17
17
|
)
|
|
18
18
|
from .hurdle import HurdleDistribution
|
|
19
|
+
from .point_mass import PointMassDistribution
|
|
19
20
|
from .tabulated import Interpolation, TabulatedDistribution, Tail
|
|
20
21
|
|
|
21
22
|
__all__ = [
|
|
22
23
|
"Distribution",
|
|
23
24
|
"DistributionFamily",
|
|
24
25
|
"EmpiricalDistribution",
|
|
25
|
-
"FitDiagnostics",
|
|
26
26
|
"FitConstraints",
|
|
27
|
+
"FitDiagnostics",
|
|
27
28
|
"FitResult",
|
|
28
29
|
"FittedDistribution",
|
|
29
30
|
"HurdleDistribution",
|
|
30
31
|
"HurdleQuantileFitDiagnostics",
|
|
31
32
|
"HurdleQuantileFitResult",
|
|
32
33
|
"Interpolation",
|
|
33
|
-
"
|
|
34
|
-
"Tail",
|
|
34
|
+
"PointMassDistribution",
|
|
35
35
|
"QuantileFitDiagnostics",
|
|
36
36
|
"QuantileFitResult",
|
|
37
|
+
"TabulatedDistribution",
|
|
38
|
+
"Tail",
|
|
37
39
|
"fit_all",
|
|
38
40
|
"fit_distribution",
|
|
39
41
|
"fit_hurdle_quantiles",
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import numpy.typing as npt
|
|
5
|
+
|
|
6
|
+
from crc_framework import _core
|
|
7
|
+
|
|
8
|
+
from .base import ArrayLike
|
|
9
|
+
from .empirical import _evaluate
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PointMassDistribution:
|
|
13
|
+
"""A distribution concentrated exactly at one finite value."""
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
location: float,
|
|
18
|
+
*,
|
|
19
|
+
_native: _core.NativeDistribution | None = None,
|
|
20
|
+
) -> None:
|
|
21
|
+
self.location = float(location)
|
|
22
|
+
self._native = _native or _core.point_mass_distribution(self.location)
|
|
23
|
+
|
|
24
|
+
def point_mass(self, x: ArrayLike) -> float | npt.NDArray[np.float64]:
|
|
25
|
+
values = np.asarray(x, dtype=np.float64)
|
|
26
|
+
masses = np.where(values == self.location, 1.0, 0.0)
|
|
27
|
+
if values.ndim == 0:
|
|
28
|
+
return float(masses)
|
|
29
|
+
return np.asarray(masses, dtype=np.float64)
|
|
30
|
+
|
|
31
|
+
def pdf(self, x: ArrayLike) -> float | npt.NDArray[np.float64]:
|
|
32
|
+
return _evaluate(self._native, "pdf", x)
|
|
33
|
+
|
|
34
|
+
def cdf(self, x: ArrayLike) -> float | npt.NDArray[np.float64]:
|
|
35
|
+
return _evaluate(self._native, "cdf", x)
|
|
36
|
+
|
|
37
|
+
def ppf(self, probability: ArrayLike) -> float | npt.NDArray[np.float64]:
|
|
38
|
+
return _evaluate(self._native, "ppf", probability)
|
|
39
|
+
|
|
40
|
+
def quantiles(self, probabilities: ArrayLike) -> npt.NDArray[np.float64]:
|
|
41
|
+
values = np.asarray(probabilities, dtype=np.float64)
|
|
42
|
+
return np.asarray(
|
|
43
|
+
self._native.quantiles(values.reshape(-1).tolist()), dtype=np.float64
|
|
44
|
+
).reshape(values.shape)
|
|
45
|
+
|
|
46
|
+
def sample(
|
|
47
|
+
self,
|
|
48
|
+
size: int,
|
|
49
|
+
*,
|
|
50
|
+
rng: np.random.Generator | None = None,
|
|
51
|
+
seed: int | None = None,
|
|
52
|
+
) -> npt.NDArray[np.float64]:
|
|
53
|
+
if rng is not None and seed is not None:
|
|
54
|
+
raise ValueError("provide either rng or seed, not both")
|
|
55
|
+
return np.full(size, self.location, dtype=np.float64)
|
|
@@ -2,8 +2,8 @@ use std::{collections::HashMap, sync::Arc};
|
|
|
2
2
|
|
|
3
3
|
use crc_framework_core::{
|
|
4
4
|
BinaryOutcome, Distribution, DistributionFamily, EmpiricalDistribution, FittedDistribution,
|
|
5
|
-
HurdleDistribution, ImpactContext, ImpactRegistry, Interpolation,
|
|
6
|
-
TabulatedDistribution, Tail, Transform, compute_risk, compute_spanning_set,
|
|
5
|
+
HurdleDistribution, ImpactContext, ImpactRegistry, Interpolation, PointMassDistribution,
|
|
6
|
+
ScenarioMetadata, TabulatedDistribution, Tail, Transform, compute_risk, compute_spanning_set,
|
|
7
7
|
fit_hurdle_quantiles as core_fit_hurdle_quantiles, fit_quantiles as core_fit_quantiles,
|
|
8
8
|
generate_microscores,
|
|
9
9
|
};
|
|
@@ -246,6 +246,18 @@ fn empirical_distribution(samples: Vec<f64>) -> PyResult<PyDistribution> {
|
|
|
246
246
|
})
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
+
#[pyfunction]
|
|
250
|
+
fn point_mass_distribution(location: f64) -> PyResult<PyDistribution> {
|
|
251
|
+
let distribution = PointMassDistribution::new(location).map_err(py_error)?;
|
|
252
|
+
Ok(PyDistribution {
|
|
253
|
+
inner: Arc::new(distribution),
|
|
254
|
+
family: None,
|
|
255
|
+
shape: None,
|
|
256
|
+
location: Some(location),
|
|
257
|
+
scale: None,
|
|
258
|
+
})
|
|
259
|
+
}
|
|
260
|
+
|
|
249
261
|
fn interpolation(value: &str) -> PyResult<Interpolation> {
|
|
250
262
|
match value {
|
|
251
263
|
"linear_probability" => Ok(Interpolation::LinearProbability),
|
|
@@ -365,6 +377,7 @@ fn fit_candidates(samples: Vec<f64>) -> PyResult<Vec<PyFitResult>> {
|
|
|
365
377
|
#[pyfunction]
|
|
366
378
|
#[pyo3(signature = (probabilities, values, family, weights=None))]
|
|
367
379
|
fn fit_quantiles(
|
|
380
|
+
py: Python<'_>,
|
|
368
381
|
probabilities: Vec<f64>,
|
|
369
382
|
values: Vec<f64>,
|
|
370
383
|
family: &str,
|
|
@@ -372,7 +385,7 @@ fn fit_quantiles(
|
|
|
372
385
|
) -> PyResult<PyQuantileFitResult> {
|
|
373
386
|
let family = DistributionFamily::from_name(family)
|
|
374
387
|
.ok_or_else(|| PyValueError::new_err(format!("unknown distribution family {family}")))?;
|
|
375
|
-
core_fit_quantiles(&probabilities, &values, weights.as_deref(), family)
|
|
388
|
+
py.detach(move || core_fit_quantiles(&probabilities, &values, weights.as_deref(), family))
|
|
376
389
|
.map(quantile_fit_result_py)
|
|
377
390
|
.map_err(py_error)
|
|
378
391
|
}
|
|
@@ -387,6 +400,7 @@ fn fit_quantiles(
|
|
|
387
400
|
weights=None
|
|
388
401
|
))]
|
|
389
402
|
fn fit_hurdle_quantiles(
|
|
403
|
+
py: Python<'_>,
|
|
390
404
|
probabilities: Vec<f64>,
|
|
391
405
|
values: Vec<f64>,
|
|
392
406
|
family: &str,
|
|
@@ -396,14 +410,16 @@ fn fit_hurdle_quantiles(
|
|
|
396
410
|
) -> PyResult<PyHurdleQuantileFitResult> {
|
|
397
411
|
let family = DistributionFamily::from_name(family)
|
|
398
412
|
.ok_or_else(|| PyValueError::new_err(format!("unknown distribution family {family}")))?;
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
413
|
+
py.detach(move || {
|
|
414
|
+
core_fit_hurdle_quantiles(
|
|
415
|
+
&probabilities,
|
|
416
|
+
&values,
|
|
417
|
+
weights.as_deref(),
|
|
418
|
+
family,
|
|
419
|
+
atom_location,
|
|
420
|
+
atom_probability,
|
|
421
|
+
)
|
|
422
|
+
})
|
|
407
423
|
.map(hurdle_quantile_fit_result_py)
|
|
408
424
|
.map_err(py_error)
|
|
409
425
|
}
|
|
@@ -869,6 +885,7 @@ fn _core(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
|
869
885
|
m.add_class::<PyRiskLevel>()?;
|
|
870
886
|
m.add_class::<PyRiskResult>()?;
|
|
871
887
|
m.add_function(wrap_pyfunction!(empirical_distribution, m)?)?;
|
|
888
|
+
m.add_function(wrap_pyfunction!(point_mass_distribution, m)?)?;
|
|
872
889
|
m.add_function(wrap_pyfunction!(tabulated_distribution, m)?)?;
|
|
873
890
|
m.add_function(wrap_pyfunction!(return_period_distribution, m)?)?;
|
|
874
891
|
m.add_function(wrap_pyfunction!(fitted_distribution, m)?)?;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import unittest
|
|
2
2
|
|
|
3
3
|
import numpy as np
|
|
4
|
-
|
|
5
4
|
from crc_framework import (
|
|
6
5
|
BinaryOutcome,
|
|
7
6
|
EmpiricalDistribution,
|
|
8
7
|
FitConstraints,
|
|
9
8
|
FittedDistribution,
|
|
10
9
|
HurdleDistribution,
|
|
10
|
+
PointMassDistribution,
|
|
11
11
|
RiskFactor,
|
|
12
12
|
ScenarioMetadata,
|
|
13
13
|
TabulatedDistribution,
|
|
@@ -27,6 +27,18 @@ from crc_framework import (
|
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
class DistributionTests(unittest.TestCase):
|
|
30
|
+
def test_point_mass_is_exact_at_every_probability(self):
|
|
31
|
+
distribution = PointMassDistribution(0.0)
|
|
32
|
+
|
|
33
|
+
np.testing.assert_array_equal(
|
|
34
|
+
distribution.quantiles([0.0, 0.5, 0.999, 1.0]),
|
|
35
|
+
np.zeros(4),
|
|
36
|
+
)
|
|
37
|
+
self.assertEqual(distribution.cdf(-0.1), 0.0)
|
|
38
|
+
self.assertEqual(distribution.cdf(0.0), 1.0)
|
|
39
|
+
self.assertEqual(distribution.point_mass(0.0), 1.0)
|
|
40
|
+
np.testing.assert_array_equal(distribution.sample(3), np.zeros(3))
|
|
41
|
+
|
|
30
42
|
def test_return_period_input_uses_canonical_probability(self):
|
|
31
43
|
distribution = TabulatedDistribution.from_return_periods(
|
|
32
44
|
[10, 100, 1000],
|
|
@@ -44,7 +56,9 @@ class DistributionTests(unittest.TestCase):
|
|
|
44
56
|
self.assertEqual(result.distribution.quantiles([0.1, 0.5, 0.9]).shape, (3,))
|
|
45
57
|
self.assertEqual(result.distribution.sample(4, seed=42).shape, (4,))
|
|
46
58
|
diagnostics = quality_metrics(source, result.distribution)
|
|
47
|
-
self.assertAlmostEqual(
|
|
59
|
+
self.assertAlmostEqual(
|
|
60
|
+
diagnostics.ks_statistic, result.diagnostics.ks_statistic
|
|
61
|
+
)
|
|
48
62
|
constrained = fit_distribution(
|
|
49
63
|
source,
|
|
50
64
|
constraints=FitConstraints(probability=0.99, maximum_value=100.0),
|
|
@@ -72,16 +86,12 @@ class DistributionTests(unittest.TestCase):
|
|
|
72
86
|
)
|
|
73
87
|
|
|
74
88
|
def test_sample_fit_rejects_probability_knots(self):
|
|
75
|
-
tabulated = TabulatedDistribution(
|
|
76
|
-
[0.1, 0.3, 0.6, 0.9], [0.0, 1.0, 2.0, 3.0]
|
|
77
|
-
)
|
|
89
|
+
tabulated = TabulatedDistribution([0.1, 0.3, 0.6, 0.9], [0.0, 1.0, 2.0, 3.0])
|
|
78
90
|
with self.assertRaisesRegex(ValueError, "fit_quantiles"):
|
|
79
91
|
fit_distribution(tabulated) # type: ignore[arg-type]
|
|
80
92
|
|
|
81
93
|
def test_hurdle_constructor_preserves_atom_and_truncated_tail(self):
|
|
82
|
-
base = FittedDistribution.from_parameters(
|
|
83
|
-
"gumbel_r", location=0.5, scale=1.2
|
|
84
|
-
)
|
|
94
|
+
base = FittedDistribution.from_parameters("gumbel_r", location=0.5, scale=1.2)
|
|
85
95
|
hurdle = HurdleDistribution(base, atom_probability=0.4)
|
|
86
96
|
self.assertEqual(hurdle.ppf(0.4), 0.0)
|
|
87
97
|
self.assertEqual(hurdle.point_mass(0.0), 0.4)
|
|
@@ -158,9 +168,7 @@ class PipelineTests(unittest.TestCase):
|
|
|
158
168
|
class SpatialAndConstantTests(unittest.TestCase):
|
|
159
169
|
def test_reference_spatial_tables_are_exposed_without_silent_defaults(self):
|
|
160
170
|
self.assertEqual(lookup_continent(594475228122316799), "Europe")
|
|
161
|
-
self.assertEqual(
|
|
162
|
-
lookup_ipcc_region(594479566039285759), "Greenland/Iceland"
|
|
163
|
-
)
|
|
171
|
+
self.assertEqual(lookup_ipcc_region(594479566039285759), "Greenland/Iceland")
|
|
164
172
|
geography = lookup_geography(600550049193132031)
|
|
165
173
|
self.assertIsNotNone(geography)
|
|
166
174
|
self.assertEqual(geography.continent, "Africa")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{crc_framework-0.2.0 → crc_framework-0.2.4}/crates/core/src/reference/climate/energy_params.rs
RENAMED
|
File without changes
|
{crc_framework-0.2.0 → crc_framework-0.2.4}/crates/core/src/reference/climate/flood_params.rs
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{crc_framework-0.2.0 → crc_framework-0.2.4}/crates/core/src/reference/climate/spei_params.rs
RENAMED
|
File without changes
|
{crc_framework-0.2.0 → crc_framework-0.2.4}/crates/core/src/reference/climate/wind_params.rs
RENAMED
|
File without changes
|
|
File without changes
|
{crc_framework-0.2.0 → crc_framework-0.2.4}/crates/core/src/reference/spatial/continent_r4.rs
RENAMED
|
File without changes
|
{crc_framework-0.2.0 → crc_framework-0.2.4}/crates/core/src/reference/spatial/geography_r5.rs
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|