gridfm-trustlayer 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.
- gridfm_trustlayer-0.1.0/CHANGELOG.md +98 -0
- gridfm_trustlayer-0.1.0/CITATION.cff +34 -0
- gridfm_trustlayer-0.1.0/CODE_OF_CONDUCT.md +1 -0
- gridfm_trustlayer-0.1.0/CONTRIBUTING.md +106 -0
- gridfm_trustlayer-0.1.0/LICENSE +201 -0
- gridfm_trustlayer-0.1.0/MANIFEST.in +7 -0
- gridfm_trustlayer-0.1.0/PKG-INFO +248 -0
- gridfm_trustlayer-0.1.0/README.md +194 -0
- gridfm_trustlayer-0.1.0/SECURITY.md +23 -0
- gridfm_trustlayer-0.1.0/gridfm_trustlayer/__init__.py +58 -0
- gridfm_trustlayer-0.1.0/gridfm_trustlayer/__main__.py +9 -0
- gridfm_trustlayer-0.1.0/gridfm_trustlayer/_version.py +3 -0
- gridfm_trustlayer-0.1.0/gridfm_trustlayer/cli/__init__.py +5 -0
- gridfm_trustlayer-0.1.0/gridfm_trustlayer/cli/apply.py +540 -0
- gridfm_trustlayer-0.1.0/gridfm_trustlayer/cli/calibrate.py +787 -0
- gridfm_trustlayer-0.1.0/gridfm_trustlayer/cli/common.py +1190 -0
- gridfm_trustlayer-0.1.0/gridfm_trustlayer/cli/compute.py +591 -0
- gridfm_trustlayer-0.1.0/gridfm_trustlayer/cli/config.py +597 -0
- gridfm_trustlayer-0.1.0/gridfm_trustlayer/cli/main.py +354 -0
- gridfm_trustlayer-0.1.0/gridfm_trustlayer/cli/reports.py +844 -0
- gridfm_trustlayer-0.1.0/gridfm_trustlayer/cli/screen.py +980 -0
- gridfm_trustlayer-0.1.0/gridfm_trustlayer/cli/status.py +513 -0
- gridfm_trustlayer-0.1.0/gridfm_trustlayer/conformal/__init__.py +6 -0
- gridfm_trustlayer-0.1.0/gridfm_trustlayer/conformal/calibrator.py +847 -0
- gridfm_trustlayer-0.1.0/gridfm_trustlayer/conformal/kernel.py +1213 -0
- gridfm_trustlayer-0.1.0/gridfm_trustlayer/conformal/tuning.py +631 -0
- gridfm_trustlayer-0.1.0/gridfm_trustlayer/interactive.py +882 -0
- gridfm_trustlayer-0.1.0/gridfm_trustlayer/io/__init__.py +18 -0
- gridfm_trustlayer-0.1.0/gridfm_trustlayer/io/artifact.py +235 -0
- gridfm_trustlayer-0.1.0/gridfm_trustlayer/io/embeddings.py +191 -0
- gridfm_trustlayer-0.1.0/gridfm_trustlayer/io/ground_truth.py +670 -0
- gridfm_trustlayer-0.1.0/gridfm_trustlayer/io/loading.py +121 -0
- gridfm_trustlayer-0.1.0/gridfm_trustlayer/io/parquet.py +283 -0
- gridfm_trustlayer-0.1.0/gridfm_trustlayer/io/predictions.py +444 -0
- gridfm_trustlayer-0.1.0/gridfm_trustlayer/io/task_adapters.py +194 -0
- gridfm_trustlayer-0.1.0/gridfm_trustlayer/parallel.py +195 -0
- gridfm_trustlayer-0.1.0/gridfm_trustlayer/py.typed +0 -0
- gridfm_trustlayer-0.1.0/gridfm_trustlayer/screening.py +116 -0
- gridfm_trustlayer-0.1.0/gridfm_trustlayer.egg-info/PKG-INFO +248 -0
- gridfm_trustlayer-0.1.0/gridfm_trustlayer.egg-info/SOURCES.txt +44 -0
- gridfm_trustlayer-0.1.0/gridfm_trustlayer.egg-info/dependency_links.txt +1 -0
- gridfm_trustlayer-0.1.0/gridfm_trustlayer.egg-info/entry_points.txt +2 -0
- gridfm_trustlayer-0.1.0/gridfm_trustlayer.egg-info/requires.txt +33 -0
- gridfm_trustlayer-0.1.0/gridfm_trustlayer.egg-info/top_level.txt +1 -0
- gridfm_trustlayer-0.1.0/pyproject.toml +86 -0
- gridfm_trustlayer-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project uses
|
|
5
|
+
[semantic versioning](https://semver.org/). Before 1.0, minor versions may
|
|
6
|
+
include breaking changes; they are listed under **Changed**.
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2026-10-15
|
|
9
|
+
|
|
10
|
+
First public release.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Two conformal methods for continuous grid-state predictions:
|
|
14
|
+
- stratified split-conformal prediction (`cp_type: element`);
|
|
15
|
+
- kernel-weighted conformal prediction (`cp_type: kernel`), localized in a scenario-embedding space, with `n_eff` (default, target 600) or `knn` bandwidth selection and `euclidean` or `cosine` distance.
|
|
16
|
+
- Stratification by `n_outages`, `outage_type`, `system_load_bin` and `pred_bins`.
|
|
17
|
+
- Direct targets (`Vm`, `Va` and other continuous task outputs) and the derived `line_loading`.
|
|
18
|
+
- The config-first CLI, `gridfm_trustlayer compute | calibrate | screen | apply`, with ten example configs in `examples/config/`.
|
|
19
|
+
- A top-level Python API: `from gridfm_trustlayer import NaiveIntervalCalibrator, KernelIntervalCalibrator, ChannelSet, ...`. A worked example is in the README, under "Python API".
|
|
20
|
+
- `CITATION.cff` and a contributing guide, `CONTRIBUTING.md`.
|
|
21
|
+
- **Statuses per value and per operating scenario**, the same in `screen` and `apply`. A value is `within_limits` when its whole interval lies inside the limits, a `violation` when it lies entirely beyond a limit, and `uncertain` when it straddles one. An operating scenario takes the worst status of its values: `secure`, `uncertain` or `insecure`, overall and per variable. The point prediction is classified the same way, as a baseline, and reported next to the interval.
|
|
22
|
+
- `screen` checks both against the true solution: `scenarios.csv` (one row per scenario), `status_by_stratum.csv` (counts per status, stratum and method, with how many were truly violated), new sections in `summary.md`, and `pred`, `status` and `point_status` in the screening detail. The existing alarm tables are unchanged.
|
|
23
|
+
- `apply` gives the statuses of new scenarios when `screen_bounds` is set: `<variable>_status` and `<variable>_point_status` in its data, and a report in `outputs.status_dir` (default `status/` next to the data) with the same tables without the truth, plus scenarios by interval status against point status. On labeled data it gives exactly the statuses of `screen` (tested).
|
|
24
|
+
- The documentation now presents the workflow as fit (`compute`, always), validate (`calibrate` and `screen`, on scenarios with a known solution) and operate (`apply`, on new scenarios).
|
|
25
|
+
- **`gridfm_trustlayer apply`: bounds for new scenarios without a known AC solution** (`inputs.apply`). Same bounds as `calibrate` (tested), without the coverage analysis. It reads only what the layer needs: the predictions (with the power-flow set-points, `Vm_target` at PV buses and `Va_target` at the reference bus), the topology, loads and branch parameters of each scenario when the layer is stratified by outages or load or calibrates `line_loading`, and the embeddings for `cp_type: kernel`. Outputs are named `*_bounds.parquet`, apart from `calibrate`'s, and hold no `*_target` columns, since for new scenarios those are placeholders except at the set-points. The config builder offers the new block. `load_ground_truth(..., solved=False)` reads scenario descriptions without solved values.
|
|
26
|
+
- `gridfm_trustlayer --version`, and `python -m gridfm_trustlayer` as an alternative to the command.
|
|
27
|
+
- Each command ends with a short summary (coverage and width per variable and the weakest stratum after `calibrate`; caught, missed and false alarms after `screen`), and long runs print their progress block by block.
|
|
28
|
+
- Pull request and issue templates, and Dependabot updates for the GitHub Actions.
|
|
29
|
+
- **Readable reports.** `calibrate` and `screen` each write a `summary.md` (method, data, tables by stratum, figures and files) next to their CSVs, which renders on GitHub or in any Markdown viewer. Coverage is given with an approximate 95% interval and the coverage of the worst bus or line, and `screen` reports caught, missed and false alarms per stratum (`screening_by_stratum.csv`), not only overall and per element. The reports of `calibrate` go to `outputs.coverage_dir`, by default `coverage/` next to the calibrated predictions. `screen` takes `--no-figures` and `save_figures: false`, like `calibrate`.
|
|
30
|
+
- **A demo notebook**, `examples/demo/demo.ipynb`: the whole workflow on IEEE 14-bus N-1 to N-5 contingencies, with predictions from the released GENCO checkpoint `gridfm/genco-pf-datakit` (`case14_ieee/small/seed0`) on freshly generated datakit scenarios: the model's error by number of outages, coverage, why the bounds are stratified by number of outages (against one set of bounds), screening against the point prediction (with every overload the point prediction misses, and the misses by number of outages), new scenarios without their solution, how kernel-weighted bounds adapt to each scenario and screen with them, the trade-off `alpha` sets between AC power flows and missed overloads, and how coverage weakens for lines loaded near their rating (with `element`, `strat: pred_bins` and the kernel). The data (32 MB: 9,723 calibration, 4,848 test and 294 new scenarios) is included. It runs in under a minute, is saved with its outputs, and CI runs it and checks the results the docs quote.
|
|
31
|
+
- **Parallel processes**, `n_jobs` (config) or `--n-jobs` (every command). The cross-validated bandwidth choice (`n_eff_target: auto`) runs its fold × candidate fits in 4 processes by default, with the same result: on 20,000 IEEE-118 calibration scenarios, 37 s instead of 75 s, at 5.0 GB of peak memory instead of 2.4 GB. When `n_jobs` is set, every command also processes its blocks of scenarios in parallel; this is off by default because on IEEE-118 test sets of 5,000 and 20,000 scenarios it was slower and took more memory, as reading parquet dominates and Arrow already uses several threads. Outputs are identical either way (tested).
|
|
32
|
+
- `load_interval_artifacts` in the Python API: reads any artifact, including the mixed bus-and-line artifacts of the CLI, and returns its calibrators by family (`"volt"`, `"line"`).
|
|
33
|
+
- `classify_status` and `scenario_status` in the Python API: the screening rule of `screen` and `apply`, for any bounds.
|
|
34
|
+
- A release workflow: publishing a GitHub release builds the sdist and wheel, checks that the tag matches the package version, and attaches both to the release.
|
|
35
|
+
- **`sparse_strata`**: a bus or line with fewer than `ceil(2 / alpha) - 1` valid calibration samples in a stratum (19 at `alpha = 0.1`), or in a test stratum absent from the calibration data, gets its quantile over all calibration scenarios there (`pooled`, the default), or infinite bounds (`infinite`, which keeps the per-stratum guarantee). Pooled bounds lose the guarantee within the stratum, so they are never silent: `compute` lists the strata concerned, a `<variable>_pooled` column marks the rows, and the coverage report counts them in `n_pooled`. The common case is the voltage magnitude of generator buses, which is predicted only when the generator is out: in the N-1 stratum, only when it is the single outage. Also `compute --sparse-strata`, the config builder and the Python API (`fit(..., sparse_strata=...)`, `pooled_masks`).
|
|
36
|
+
- **Known values are reported apart.** In a power flow, `Vm` at PV buses and `Va` at the reference bus are set-points given to the model, always inside their (zero-width) bounds. They are now left out of every coverage, width and screening number, counted in `n_known` columns, and named in the reports and terminal summaries (e.g. `261,580 known values not counted`). The calibrated and applied voltage predictions mark them in `Vm_known` and `Va_known`.
|
|
37
|
+
- Cross-validated choice of the kernel bandwidth: `n_eff_target: auto` (with `cp_type: kernel`, `bandwidth: n_eff`) makes `compute` pick the target, and optionally the distance (`distances: [euclidean, cosine]`), by K-fold cross-validation on the calibration set only, over a grid or a random sample with a fixed `budget`, grouped by load profile when datakit's `load_scenario_idx` is available. The rule is coverage first, then the narrowest intervals, with uniform weights as the fallback. Off by default; options under `tuning:`. The choice is recorded in the artifact and reported in `<artifact>_kernel_tuning*.csv`. Also available from Python as `gridfm_trustlayer.conformal.tuning.tune_n_eff_target`.
|
|
38
|
+
- A config builder: `gridfm_trustlayer.interactive.config_builder()`, a Jupyter form (optional extra `interactive`; notebook in `examples/notebooks/config_builder.ipynb`). It shows only the options that apply and validates the result with the CLI's own checks. The underlying `build_config()` works without Jupyter.
|
|
39
|
+
- A documentation site, <https://gridfm.github.io/gridfm-trustlayer/>, built with MkDocs from `docs/` and published to GitHub Pages on every push to `main` (`.github/workflows/docs.yml`): getting-started guides, a user guide, references for the CLI, the configuration keys, the data schemas and the Python API, and the methodology.
|
|
40
|
+
|
|
41
|
+
### Changed (relative to the internal 0.0.1 snapshot)
|
|
42
|
+
- **Breaking: calibration and test data must be separate datasets.** The config now takes `inputs.calibration` (read by `compute`) and `inputs.test` (read by `calibrate` and `screen`), each with its own `predictions`, `pf_node`, `pf_edge` and, for `cp_type: kernel`, `embeddings`.
|
|
43
|
+
- The in-file split is removed: `calibration_fraction`, `random_seed`, `--calibration-fraction` and `--random-seed` are no longer accepted.
|
|
44
|
+
- So is the in-sample mode, in which `calibrate` evaluated on the scenarios `compute` was fitted on.
|
|
45
|
+
- A random split of one file hides near-duplicate scenarios on both sides, which inflates coverage and kernel gains. The in-sample mode measured coverage on the fitting data.
|
|
46
|
+
- The config is rejected if both blocks point at the same file, and `calibrate` refuses test predictions identical to the calibration predictions, using a fingerprint stored in the artifact.
|
|
47
|
+
- **Migration:** move the old flat `inputs` keys under `inputs.calibration` and add an `inputs.test` block for an independently generated dataset. A flat `inputs` block now fails with a message pointing here.
|
|
48
|
+
- `analysis_pf_node` / `analysis_pf_edge` and per-command `commands.<verb>.inputs` overrides are removed, since each command reads its own dataset block.
|
|
49
|
+
- **Calibration quantiles now use the split-conformal finite-sample correction.** Each tail at level `alpha / 2` takes the `ceil((n + 1)(1 - alpha / 2))`-th smallest residual for the upper bound and the `floor((n + 1) alpha / 2)`-th for the lower bound. Version 0.0.1 used the interpolated empirical quantile, which does not carry the finite-sample coverage guarantee. Bounds are therefore slightly wider (by at most one order statistic per tail). This applies to `cp_type: element` and to the stratum fallback quantiles of `cp_type: kernel`.
|
|
50
|
+
- **No more zero-width bounds from too few calibration samples.** 0.0.1 returned a zero-width interval where an element had no valid residual. A finite bound at level `alpha / 2` needs at least `ceil(2 / alpha) - 1` valid residuals, e.g. 19 at `alpha = 0.1`; a stratum with fewer now uses the element's quantile over all strata (see `sparse_strata`), and an element with fewer over all strata gets infinite bounds. Figures plot the finite widths; the CSV outputs count the infinite ones.
|
|
51
|
+
- Kernel defaults: `bandwidth: n_eff` with `n_eff_target: 600` (0.0.1: `bandwidth: knn`, `k_nn: 20`). Artifacts record their bandwidth, so existing artifacts are read unchanged.
|
|
52
|
+
- The package version is defined once, in `gridfm_trustlayer/_version.py`, and read by the build.
|
|
53
|
+
- **Large outputs are partitioned parquet datasets**, in the layout of gridfm-datakit: the calibrated predictions (`*_with_intervals.parquet`) and the screening detail are directories with one `scenario_partition=k/` folder per 200 scenarios, zstd-compressed. `pd.read_parquet(path)` reads them as one table. The screening detail is now parquet (`screening_detail_*.parquet/`) instead of CSV, with one row per scenario, element and variable holding the true value, the bounds and four violation flags; the per-row limits, duplicated value columns and 0/1 count columns are gone, since the counts follow from the flags. On a 20,000-scenario IEEE-118 test set, `screen` writes 58 MB instead of 1 GB. Inputs may be single files or partitioned directories.
|
|
54
|
+
- **`calibrate` and `screen` process the test set block by block** (about 500,000 rows at a time): each block is read with only the needed columns and scenarios, calibrated, screened and written, and the reports are built from per-entity sums, so memory no longer grows in proportion to the test set. Reports and rows are identical whatever the block size (tested). On a 20,000-scenario IEEE-118 test set, peak memory fell from 6.3 to 2.8 GB for `calibrate` and from 3.1 to 1.3 GB for `screen`, which also runs about seven times faster. The loaders read only the columns they need and no longer re-sort the data for every column, which also lowers the memory of `compute`.
|
|
55
|
+
- The calibrated voltage predictions include `Vm_target` / `Va_target` from the ground truth when the predictions file has none, so `screen` never needs to reload it.
|
|
56
|
+
- **`cp_type: kernel` is several times faster and uses less memory.** The weighted quantile now takes each element's total weight from one matrix product and scans only the tails of the sorted residuals, in two levels (blocks, then positions), instead of accumulating the weights over all of them; scenarios whose total weight cannot reach the level skip it entirely; distances, bandwidth and weights are computed once per scenario and shared by all variables; and the test embeddings are read once when they fit in memory. The quantiles are identical to the full computation (tested). On synthetic data at IEEE-118 scale, `predict_intervals` is 6-12x faster with a third of the peak memory, and the cross-validated bandwidth choice (`n_eff_target: auto`) about 5x faster.
|
|
57
|
+
- The bandwidth solve of `bandwidth: n_eff` reaches the target effective sample size to a relative 1e-6 instead of 1e-3, using safeguarded Newton steps (5-16 iterations, against 15 bisection steps for the old tolerance) and stopping each test scenario as soon as it converges. Previously all scenarios of a batch iterated until the last one converged, so a scenario's bandwidth depended on which others were processed with it, and a weighted quantile could move to a neighbouring residual depending on where the solve stopped. With the tighter tolerance, two different solvers give the same bounds for 99.996% of the entries (IEEE-118, 10,000 test scenarios). Compared with the old tolerance, about 0.5% of individual bounds change; coverage and width summaries are unchanged to four decimals.
|
|
58
|
+
- **Breaking: new names and layout for the reports**, with fewer, clearer files and rounded numbers:
|
|
59
|
+
- `calibrate`: `coverage/summary.md`, `coverage_by_stratum.csv` (was `trust_reliability_*.csv`), `coverage_by_group.csv` (was `trust_summary_*.csv`), `coverage_by_element_{voltage,line_loading}.csv` (was `trust_analysis_*.csv`), and three figures per variable in `coverage/figures/`: coverage by stratum with its interval, coverage per bus or line, width by stratum. Voltage and line-loading results share one table per level instead of one file per family.
|
|
60
|
+
- `screen`: `summary.md`, `screening_by_stratum.csv` (new), `screening_by_group.csv` (was `trust_screen_summary_*.csv`), `screening_by_element_{voltage,line_loading}.csv` (was `trust_screen_entity_*.csv`), `screening_detail_{voltage,line_loading}.parquet/` (was `trust_screen_detail_*.parquet/`), and `figures/` (was `screen_figures/`) with a confusion matrix and, with `strat`, recall and precision per stratum. The tables have one row per screened side (`lower`, `upper`, `any`) and name the counts `caught`, `missed`, `false_alarms` and `cleared` instead of `tp`, `fn`, `fp` and `tn`.
|
|
61
|
+
- Terminal summaries give widths with their unit (`Va` in degrees) and counts with thousands separators.
|
|
62
|
+
- **`compute` reads the calibration set block by block**, like the other commands, and keeps only the arrays the calibrators need. The artifact is identical (tested, and checked on IEEE-118); peak memory on 20,000 IEEE-118 calibration scenarios fell from 3.0 to 2.0 GB, and no longer grows with the number of calibration scenarios.
|
|
63
|
+
- The two screening figures were laid out twice; they now take half the time.
|
|
64
|
+
- The command-line code is split into one module per command (`gridfm_trustlayer/cli/compute.py`, `calibrate.py`, `screen.py`, `apply.py`, with shared helpers in `common.py`); `CONTRIBUTING.md` maps the code. The CI caches its pip downloads.
|
|
65
|
+
- **Outages and angle units are judged as in the calibration set.** The artifact stores which branches and generators are in service in the calibration set and whether its angles are in degrees; `calibrate`, `screen` and `apply` count outages and convert angles against it, so a scenario gets the same stratum whatever other scenarios it comes with. Artifacts without it fall back to a scan of the data at hand, as before.
|
|
66
|
+
- **Installation:** trustlayer is published on PyPI (`pip install gridfm-trustlayer`). A new `datakit` extra installs what `line_loading` needs (gridfm-datakit 1.1.0 or later); `graphkit` adds gridfm-graphkit, which needs PyTorch. The `juliapkg<0.1.26` cap is gone, since datakit 1.1.0 supports both juliapkg APIs. Minimum versions: pyarrow 16 and matplotlib 3.8.4, the first that work with numpy 2.
|
|
67
|
+
- CLI errors (a bad config, a missing file) are one line on stderr and exit code 2 instead of a traceback; `GRIDFM_TRUSTLAYER_DEBUG=1` shows the traceback.
|
|
68
|
+
- An output path is only replaced if it holds a previous trustlayer output (a parquet file or dataset with interval bounds), and never if it is an input of the command.
|
|
69
|
+
- A missing (NaN) bound now makes a value `uncertain` instead of `within_limits`.
|
|
70
|
+
- `outputs.screened_predictions`, accepted but never used, is removed.
|
|
71
|
+
- `scripts/inspect_parquet.py` is removed; read outputs with `pd.read_parquet` or pyarrow (see "Reading the results" in the docs).
|
|
72
|
+
|
|
73
|
+
### Fixed
|
|
74
|
+
- `screen` failed to find the calibrated data of a single-family run (e.g. `line_loading` only) when `outputs.calibrated_predictions` was a folder-like path: `calibrate` writes the partitioned dataset at that very path, and `screen` looked for files inside it.
|
|
75
|
+
- Config validation took a missing `bandwidth` as `knn`, while the kernel's default is `n_eff`: `n_eff_target: auto` was rejected unless `bandwidth: n_eff` was written out, and the check that `n_eff_target` is not below `n_eff_min` was skipped for configs relying on the default.
|
|
76
|
+
- The bandwidth solve of `bandwidth: n_eff` could print an overflow `RuntimeWarning` when the effective sample size is flat in the bandwidth; the step was already discarded in favour of bisection, so results are unchanged.
|
|
77
|
+
- The width box plots used an argument of matplotlib that is deprecated from 3.11 and removed in 3.13, which would have broken the figures with a future matplotlib.
|
|
78
|
+
- **`cp_type: kernel` gave zero-width bounds to test scenarios in a stratum absent from the calibration data**, e.g. N-6 when calibration stops at N-5. They now get the pooled quantile (or infinite bounds with `sparse_strata: infinite`), and `kcp_fallback` marks them. The cross-validated bandwidth choice was affected in the same way when a fold held a stratum that the other folds lacked.
|
|
79
|
+
- With `cp_type: element`, a test stratum absent from the calibration data used the unstratified quantile silently; it now follows `sparse_strata` like any sparse stratum, and is marked and counted.
|
|
80
|
+
- **Set-points no longer shrink the bounds of buses that are PV in only some scenarios.** Their zero residuals (the prediction is replaced by the set-point) entered the calibration and pulled the quantiles towards zero, so the `Vm` bounds of such buses were far too narrow when the bus was PQ. Known values are now excluded from the fit. On a 5,000-scenario IEEE-118 test set, `Vm` coverage at these buses rose from 0.001 to 0.919. Buses of a single type were not affected.
|
|
81
|
+
- **Coverage and screening no longer count the set-points**, which were always covered and inflated the reported `Vm` and `Va` coverage.
|
|
82
|
+
- The screening confusion matrix swapped false alarms and misses in its labels; it is now drawn with true violations in rows and alarms in columns, and each cell named.
|
|
83
|
+
- `screen` now finds the calibrated parquets inside a directory. Before, it failed when `outputs.calibrated_predictions` was a directory (a mixed voltage and line-loading run), reading the whole directory as one parquet. It also failed with the default output paths whenever fewer variables were screened than calibrated, e.g. screening `Vm` after calibrating `Vm` and `Va`.
|
|
84
|
+
- In a run with one target family, `calibrate` now writes to `outputs.calibrated_predictions_volt` / `_line` when set, which is where `screen` reads from.
|
|
85
|
+
- The `kernel_fallback` diagnostic of `KernelIntervalCalibrator` no longer flags every test scenario when some element has no valid calibration residual in its stratum, e.g. a branch that is out of service in every scenario. Such an element still uses the fallback bound, but it no longer counts as a kernel fallback. The bounds themselves were not affected.
|
|
86
|
+
- Box plots in the analysis figures no longer produce `NaN` percentiles (and a `RuntimeWarning`) when some widths are infinite.
|
|
87
|
+
- **A scenario alone, or in a small batch, could get the wrong stratum.** An outage only counted for an element in service somewhere in the same dataset, so `apply` on a single N-1 scenario counted N-0 and gave it the narrower N-0 bounds; and angle units were guessed per dataset, so a test set with small angles could stay in degrees. Both now follow the calibration set (see Changed).
|
|
88
|
+
- Missing true or predicted values (NaN or infinite) are left out of the fit. Before, `element` returned a NaN quantile for that element in every scenario, and `kernel` counted the value as a perfect prediction, narrowing its bounds.
|
|
89
|
+
- `--output` pointing at an input deleted it before the run failed, including datakit's raw data, which has the same partitioned layout as trustlayer outputs.
|
|
90
|
+
- `scenario_status` raised `KeyError` on missing statuses (`None`, as `apply` writes for known values and out-of-service lines), even where they were masked out.
|
|
91
|
+
- `kcp_fallback` flagged nearly every PowerFlow scenario: a generator bus has a valid `Vm` residual in few calibration scenarios, and counted even where its `Vm` is a known set-point. Only the elements with an interval in the test scenario count now; the bounds were not affected. The bandwidth tuning's `fallback_share` had the same distortion.
|
|
92
|
+
- `kcp_n_eff` could be 0 or NaN for embeddings with many dimensions, where the squared kernel weights underflow in float32; it is now taken from the bandwidth solve.
|
|
93
|
+
- With `strat: pred_bins`, the known values (which have no prediction bin) formed a second "all" row per variable in `coverage_by_stratum.csv` and `screening_by_stratum.csv`.
|
|
94
|
+
- `commands.compute.n_eff_target: auto` with a top-level `tuning` block made the other commands fail; `tuning` is now checked by `compute` only.
|
|
95
|
+
- `tuning.n_eff_range` without a whole number in it (e.g. `[1.5, 1.7]`) gave a candidate outside the range; it is now rejected.
|
|
96
|
+
- `pred_bins` errors named `system_load_bin`.
|
|
97
|
+
|
|
98
|
+
[0.1.0]: https://github.com/gridfm/gridfm-trustlayer/releases/tag/v0.1.0
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
message: "If you use this software, please cite it, and the paper below, as follows."
|
|
3
|
+
title: "gridfm-trustlayer: Conformal trustworthiness layer for GridFM foundation models"
|
|
4
|
+
type: software
|
|
5
|
+
version: 0.1.0
|
|
6
|
+
date-released: 2026-10-15
|
|
7
|
+
license: Apache-2.0
|
|
8
|
+
repository-code: "https://github.com/gridfm/gridfm-trustlayer"
|
|
9
|
+
authors:
|
|
10
|
+
- family-names: "Alcántara"
|
|
11
|
+
given-names: "Antonio"
|
|
12
|
+
affiliation: "CUNEF Universidad"
|
|
13
|
+
email: "antonio.alcantara@cunef.edu"
|
|
14
|
+
keywords:
|
|
15
|
+
- conformal prediction
|
|
16
|
+
- uncertainty quantification
|
|
17
|
+
- foundation models
|
|
18
|
+
- power systems
|
|
19
|
+
- contingency screening
|
|
20
|
+
preferred-citation:
|
|
21
|
+
type: article
|
|
22
|
+
title: "Trustworthiness Layer for Foundation Models in Power Systems: Application to N-k Contingency Screening"
|
|
23
|
+
authors:
|
|
24
|
+
- family-names: "Alcántara"
|
|
25
|
+
given-names: "Antonio"
|
|
26
|
+
affiliation: "CUNEF Universidad"
|
|
27
|
+
- family-names: "Chatzivasileiadis"
|
|
28
|
+
given-names: "Spyros"
|
|
29
|
+
year: 2026
|
|
30
|
+
url: "https://arxiv.org/abs/2602.07995"
|
|
31
|
+
identifiers:
|
|
32
|
+
- type: other
|
|
33
|
+
value: "arXiv:2602.07995"
|
|
34
|
+
description: "arXiv preprint"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
All participants agree to abide by the Code of Conduct available at https://lfprojects.org/policies/code-of-conduct/. Please contact conduct@lfenergy.org to report any violations or concerns.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Contributing to gridfm-trustlayer
|
|
2
|
+
|
|
3
|
+
Contributions are welcome: bug reports, fixes, new conformal methods or stratifications, support for new targets or tasks, documentation and examples. For anything larger than a small fix, please open an issue first so the approach can be agreed before you start.
|
|
4
|
+
|
|
5
|
+
All participants follow the [Code of Conduct](CODE_OF_CONDUCT.md). Report security vulnerabilities privately, as described in [SECURITY.md](SECURITY.md), not in public issues.
|
|
6
|
+
|
|
7
|
+
## What belongs here
|
|
8
|
+
|
|
9
|
+
`gridfm-trustlayer` is a library: the calibration methods, the loaders for GridFM data, the command-line interface, and their documentation. Code tied to one specific study or deployment (experiment scripts, paper figures, one-off pipelines) belongs in its own repository and can use trustlayer as a dependency.
|
|
10
|
+
|
|
11
|
+
The statistical core (`gridfm_trustlayer/conformal/`) works on plain NumPy arrays and must stay free of PyTorch, Lightning and GPU dependencies.
|
|
12
|
+
|
|
13
|
+
Where things are:
|
|
14
|
+
|
|
15
|
+
| Path | Contents |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| `gridfm_trustlayer/conformal/` | the two calibrators and the bandwidth tuning |
|
|
18
|
+
| `gridfm_trustlayer/io/` | loaders for predictions, ground truth and embeddings; partitioned parquet; artifacts |
|
|
19
|
+
| `gridfm_trustlayer/screening.py` | the status rule (within limits, uncertain, violation) |
|
|
20
|
+
| `gridfm_trustlayer/parallel.py` | process pools for the bandwidth tuning and, on request, blocks of scenarios |
|
|
21
|
+
| `gridfm_trustlayer/cli/main.py` | the argument parser and the dispatch to the commands |
|
|
22
|
+
| `gridfm_trustlayer/cli/{compute,calibrate,screen,apply}.py` | one module per command |
|
|
23
|
+
| `gridfm_trustlayer/cli/common.py` | helpers shared by the commands |
|
|
24
|
+
| `gridfm_trustlayer/cli/reports.py`, `status.py` | the CSV tables, figures and `summary.md` files |
|
|
25
|
+
| `gridfm_trustlayer/cli/config.py` | config loading and validation |
|
|
26
|
+
| `gridfm_trustlayer/interactive.py` | the Jupyter config builder |
|
|
27
|
+
|
|
28
|
+
## Checklist
|
|
29
|
+
|
|
30
|
+
Before opening a pull request:
|
|
31
|
+
|
|
32
|
+
### 1. Set up
|
|
33
|
+
|
|
34
|
+
- [ ] Create a branch from `main`.
|
|
35
|
+
- [ ] Install the package with the development and test tools, and the pre-commit hooks:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
python -m pip install -e ".[dev,test]"
|
|
39
|
+
pre-commit install
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 2. Code
|
|
43
|
+
|
|
44
|
+
- [ ] Clear, readable code; comments where the reason is not obvious from the code.
|
|
45
|
+
- [ ] Type hints on all function arguments and return values.
|
|
46
|
+
- [ ] Google-style docstrings on public functions and classes; they appear in the [API reference](https://gridfm.github.io/gridfm-trustlayer/reference/api/).
|
|
47
|
+
- [ ] No debug prints or temporary files left behind.
|
|
48
|
+
- [ ] New dependencies added to `pyproject.toml`, as an optional extra if only some users need them.
|
|
49
|
+
|
|
50
|
+
### 3. Statistical changes
|
|
51
|
+
|
|
52
|
+
If the change affects how bounds are computed:
|
|
53
|
+
|
|
54
|
+
- [ ] A test compares the result with a straightforward reference implementation (see `tests/conformal_reference.py`), or checks the coverage it is meant to deliver.
|
|
55
|
+
- [ ] The pull request says whether existing results change, and by how much.
|
|
56
|
+
|
|
57
|
+
### 4. Configuration
|
|
58
|
+
|
|
59
|
+
If you add or change a config key:
|
|
60
|
+
|
|
61
|
+
- [ ] Validation in `gridfm_trustlayer/cli/config.py`.
|
|
62
|
+
- [ ] The config builder (`gridfm_trustlayer/interactive.py`), so the Jupyter form offers it.
|
|
63
|
+
- [ ] The example configs in `examples/config/`, where relevant.
|
|
64
|
+
- [ ] [`docs/reference/configuration.md`](docs/reference/configuration.md).
|
|
65
|
+
|
|
66
|
+
### 5. Tests
|
|
67
|
+
|
|
68
|
+
- [ ] Unit tests for the new behaviour and its edge cases, on small synthetic data. Do not commit datasets or large files (the demo data in `examples/demo/data` is the one exception).
|
|
69
|
+
- [ ] The full suite passes:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pytest
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 6. Checks and documentation
|
|
76
|
+
|
|
77
|
+
- [ ] The pre-commit hooks pass (ruff, mypy and the tests; the same checks as the CI):
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
pre-commit run --all-files
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
- [ ] The documentation builds without warnings, and pages affected by the change are updated:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
mkdocs build --strict
|
|
87
|
+
mkdocs serve # preview at http://127.0.0.1:8000
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
- [ ] An entry in [`CHANGELOG.md`](CHANGELOG.md), under the unreleased version.
|
|
91
|
+
|
|
92
|
+
### 7. Pull request
|
|
93
|
+
|
|
94
|
+
- [ ] Every commit is signed off (Developer Certificate of Origin), which the DCO check requires:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
git commit -s -m "Describe the change"
|
|
98
|
+
git rebase --signoff main # signs off commits already on the branch
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
- [ ] Rebase onto the latest `main`, then open a pull request with a short description of the change and why it is needed.
|
|
102
|
+
- [ ] The CI checks pass. A maintainer will review the pull request.
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
By contributing, you agree that your contributions are licensed under the [Apache License 2.0](LICENSE), the license of the project.
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|