Skip to content

Commit c33e14f

Browse files
committed
Merge branch 'main' into bugfix/line-spacing-disabled-transformers
2 parents 6cc7929 + a9ae117 commit c33e14f

9 files changed

Lines changed: 1880 additions & 16 deletions

.cspell.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@
3535
"kamada",
3636
"kawai",
3737
"espvl",
38-
"aggregatable"
38+
"aggregatable",
39+
"dlat",
40+
"dlon",
41+
"DBSCAN"
3942
],
4043
"overrides": [
4144
{

DISCLAIMER.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
This material was prepared as an account of work sponsored by an agency of the United States Government. Neither the United States Government nor the United States Department of Energy, nor the Contractor, nor any or their employees, nor any jurisdiction or organization that has cooperated in the development of these materials, makes any warranty, express or implied, or assumes any legal liability or responsibility for the accuracy, completeness, or usefulness or any information, apparatus, product, software, or process disclosed, or represents that its use would not infringe privately owned rights.
2+
Reference herein to any specific commercial product, process, or service by trade name, trademark, manufacturer, or otherwise does not necessarily constitute or imply its endorsement, recommendation, or favoring by the United States Government or any agency thereof, or Battelle Memorial Institute. The views and opinions of authors expressed herein do not necessarily state or reflect those of the United States Government or any agency thereof.
3+
PACIFIC NORTHWEST NATIONAL LABORATORY
4+
operated by
5+
BATTELLE
6+
for the
7+
UNITED STATES DEPARTMENT OF ENERGY
8+
under Contract DE-AC05-76RL01830

README.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
Welcome! Follow the steps below to get `grid-reducer` up and running locally.
1111
We recommend using a Python virtual environment for a clean install 🔒🐍.
1212

13+
This software is being provided as a prototype only. For your intended use, it is your responsibility to independently validate the results in accordance with your applicable software quality assurance program.
14+
1315
## 🧪 Step 1: Set Up a Python Environment
1416

1517
To avoid dependency conflicts, create and activate a virtual environment.
@@ -34,7 +36,7 @@ conda activate grid-reducer-env
3436

3537
## 🚀 Step 2: Install the Project Locally
3638

37-
Install the project in editable mode so changes to the code reflect immediately:
39+
Install the project:
3840

3941
```bash
4042
pip install grid_reducer
@@ -77,13 +79,18 @@ reducer.export(reduced_ckt, reduced_circuit_file)
7779

7880
## 📌 Notes
7981

80-
* This is the recommended way to use the project during development.
81-
* In the future, the project may support installation via:
82+
* This is the recommended way to use the project:
8283

8384
```bash
8485
pip install grid-reducer
8586
```
8687

88+
* If you want your local changes to the code to be applied immediately (without needing to reinstall your package each time), use an “editable” install by running:
89+
90+
```bash
91+
pip install -e .
92+
```
93+
8794
Stay tuned for updates! 📬
8895

8996
Need help? Feel free to open an issue or reach out to the maintainers. 💬

examples/compare_noise_methods.ipynb

Lines changed: 1748 additions & 0 deletions
Large diffs are not rendered by default.

examples/explore_graph.ipynb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
"source": [
1818
"# 📦 Section 1: Imports & Setup\n",
1919
"from pathlib import Path\n",
20-
"import geopandas as gpd\n",
2120
"import pandas as pd\n",
2221
"from grid_reducer.opendss import OpenDSS\n",
2322
"from grid_reducer.network import get_graph_from_circuit\n",

examples/explore_noise_options.ipynb

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,13 @@
1717
"source": [
1818
"# 📦 Section 1: Imports & Setup\n",
1919
"from pathlib import Path\n",
20-
"import geopandas as gpd\n",
2120
"import pandas as pd\n",
2221
"from grid_reducer.opendss import OpenDSS\n",
2322
"from grid_reducer.network import get_graph_from_circuit\n",
2423
"from grid_reducer.plot import graph_to_geo_dataframe\n",
2524
"from grid_reducer.reducer import OpenDSSModelReducer\n",
2625
"from grid_reducer.add_differential_privacy import (\n",
2726
" LowPrivacyConfig,\n",
28-
" MediumPrivacyConfig,\n",
2927
" HighPrivacyConfig,\n",
3028
")"
3129
]

examples/explore_switch_connected_buses.ipynb

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,13 @@
1717
"source": [
1818
"# 📦 Section 1: Imports & Setup\n",
1919
"from pathlib import Path\n",
20-
"import geopandas as gpd\n",
2120
"import pandas as pd\n",
2221
"from grid_reducer.opendss import OpenDSS\n",
2322
"from grid_reducer.network import get_graph_from_circuit\n",
2423
"from grid_reducer.plot import graph_to_geo_dataframe\n",
25-
"from grid_reducer.smartds import download_s3_folder\n",
2624
"from grid_reducer.reducer import OpenDSSModelReducer\n",
2725
"from grid_reducer.add_differential_privacy import (\n",
2826
" LowPrivacyConfig,\n",
29-
" MediumPrivacyConfig,\n",
3027
" HighPrivacyConfig,\n",
3128
")"
3229
]
@@ -1597,8 +1594,8 @@
15971594
"# ## Code to download other Smart DS models\n",
15981595
"# #### Download the profiles for P12U feeder first\n",
15991596
"# download_s3_folder(\n",
1600-
"# \"oedi-data-lake\", \n",
1601-
"# \"SMART-DS/v1.0/2018/SFO/P12U/profiles/\", \n",
1597+
"# \"oedi-data-lake\",\n",
1598+
"# \"SMART-DS/v1.0/2018/SFO/P12U/profiles/\",\n",
16021599
"# \"../tests/extra_data/P12U/profiles\"\n",
16031600
"# )"
16041601
]
@@ -1611,8 +1608,8 @@
16111608
"outputs": [],
16121609
"source": [
16131610
"# download_s3_folder(\n",
1614-
"# \"oedi-data-lake\", \n",
1615-
"# \"SMART-DS/v1.0/2018/SFO/P12U/scenarios/base_timeseries/opendss/p12uhs0_1247/p12uhs0_1247--p12udt1271/\", \n",
1611+
"# \"oedi-data-lake\",\n",
1612+
"# \"SMART-DS/v1.0/2018/SFO/P12U/scenarios/base_timeseries/opendss/p12uhs0_1247/p12uhs0_1247--p12udt1271/\",\n",
16161613
"# \"../tests/extra_data/P12U/scenarios/base_timeseries/opendss/p12uhs0_1247/p12uhs0_1247--p12udt1271/\"\n",
16171614
"# )"
16181615
]

pyproject.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ dependencies = [
3434
"geopandas~=1.1.0",
3535
"mapclassify~=2.9.0",
3636
"rich~=14.0.0",
37-
"tqdm~=4.67.1"
37+
"tqdm~=4.67.1",
38+
"scikit-learn~=1.7.2",
3839
]
3940

4041
[project.optional-dependencies]
@@ -43,7 +44,7 @@ dev = [
4344
"pytest~=8.4.0",
4445
"ruff~=0.11.12",
4546
"pytest-mock~=3.14.1",
46-
"pytest-cov~=6.2.1"
47+
"pytest-cov~=7.0.0"
4748
]
4849
doc = [
4950
"mkdocs-material~=9.6.14",
@@ -103,3 +104,4 @@ packages = ["src/grid_reducer"]
103104
[tool.hatch.build.targets.sdist]
104105

105106
include = ["src/grid_reducer"]
107+

src/grid_reducer/add_differential_privacy.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from math import radians, cos, sin, asin, sqrt
55

66
import numpy as np
7+
from sklearn.cluster import DBSCAN
78

89
from grid_reducer.altdss.altdss_models import Circuit
910

@@ -163,3 +164,104 @@ def get_circuit_noisy_distances(
163164
d = coord_distance(orig_bus.X, orig_bus.Y, noisy_bus.X, noisy_bus.Y, is_geo)
164165
dists.append(d)
165166
return dists
167+
168+
169+
def get_cluster_dp_circuit(circuit: Circuit, noise_config: BasePrivacyConfig, cluster_eps=0.1, min_samples=3):
170+
"""
171+
Clusters geo-coordinates, adds Laplace noise to cluster centroids,
172+
assigns noisy centroid to all buses in cluster.
173+
"""
174+
coords = [(bus.X, bus.Y) for bus in circuit.Bus if bus.X is not None and bus.Y is not None]
175+
clustering = DBSCAN(eps=cluster_eps, min_samples=min_samples).fit(coords)
176+
labels = clustering.labels_
177+
clusters = {}
178+
for idx, label in enumerate(labels):
179+
clusters.setdefault(label, []).append(coords[idx])
180+
noisy_coords = []
181+
for _idx, label in enumerate(labels):
182+
cluster_points = clusters[label]
183+
# Compute centroid for cluster
184+
centroid = np.mean(cluster_points, axis=0)
185+
noisy_centroid = apply_planar_laplace_noise(
186+
centroid[0], centroid[1], int(noise_config.geo_coordinate_noise)
187+
)
188+
noisy_coords.append(noisy_centroid)
189+
# Build new circuit with bus coordinates replaced
190+
new_buses = []
191+
i = 0
192+
for bus in circuit.Bus:
193+
new_bus = copy.deepcopy(bus)
194+
if bus.X is not None and bus.Y is not None:
195+
new_bus.X, new_bus.Y = noisy_coords[i]
196+
i += 1
197+
new_buses.append(new_bus)
198+
new_circuit = copy.deepcopy(circuit)
199+
new_circuit.Bus = new_buses
200+
return new_circuit
201+
202+
def apply_adaptive_noise(x, y, coords, base_epsilon, min_epsilon=500, max_epsilon=5000, neighbor_radius=0.01):
203+
"""
204+
Inject Laplace noise with adaptive scaling based on local density.
205+
Points in denser areas get lower noise; sparse areas get higher noise.
206+
"""
207+
# Count neighbors within 'neighbor_radius'
208+
count = sum(
209+
math.sqrt((x - x2)**2 + (y - y2)**2) < neighbor_radius
210+
for x2, y2 in coords if (x2, y2) != (x, y)
211+
)
212+
# More neighbors --> less noise; fewer neighbors --> more noise
213+
density_score = count / len(coords)
214+
# Invert: sparse areas (density_score→0) => max_epsilon; dense areas (density_score→1) => min_epsilon
215+
adaptive_epsilon = min_epsilon + (max_epsilon - min_epsilon) * (1 - density_score)
216+
return apply_planar_laplace_noise(x, y, adaptive_epsilon)
217+
218+
def get_adaptive_dp_circuit(circuit: Circuit, base_epsilon=3500, min_epsilon=500, max_epsilon=5000, neighbor_radius=0.01):
219+
coords = [(bus.X, bus.Y) for bus in circuit.Bus if bus.X is not None and bus.Y is not None]
220+
new_buses = []
221+
for bus in circuit.Bus:
222+
new_bus = copy.deepcopy(bus)
223+
if new_bus.X is not None and new_bus.Y is not None:
224+
new_bus.X, new_bus.Y = apply_adaptive_noise(
225+
new_bus.X, new_bus.Y, coords, base_epsilon, min_epsilon, max_epsilon, neighbor_radius
226+
)
227+
new_buses.append(new_bus)
228+
new_circuit = copy.deepcopy(circuit)
229+
new_circuit.Bus = new_buses
230+
return new_circuit
231+
232+
def evaluate_dp_methods_on_circuit(
233+
circuit: Circuit,
234+
noise_config: BasePrivacyConfig,
235+
cluster_eps=0.1,
236+
min_samples=3,
237+
adaptive_params=None
238+
):
239+
# Planar Laplace mechanism
240+
laplace_circuit = get_dp_circuit(circuit, noise_config)
241+
laplace_dists = get_circuit_noisy_distances(circuit, laplace_circuit)
242+
243+
# Cluster-based mechanism
244+
cluster_circuit = get_cluster_dp_circuit(circuit, noise_config, cluster_eps, min_samples)
245+
cluster_dists = get_circuit_noisy_distances(circuit, cluster_circuit)
246+
247+
# Adaptive noise mechanism
248+
if adaptive_params is None:
249+
adaptive_params = dict(base_epsilon=noise_config.geo_coordinate_noise, min_epsilon=500, max_epsilon=5000, neighbor_radius=0.01)
250+
adaptive_circuit = get_adaptive_dp_circuit(circuit, **adaptive_params)
251+
adaptive_dists = get_circuit_noisy_distances(circuit, adaptive_circuit)
252+
253+
result = {
254+
"planar_mean_loss": np.mean(laplace_dists),
255+
"planar_median_loss": np.median(laplace_dists),
256+
"cluster_mean_loss": np.mean(cluster_dists),
257+
"cluster_median_loss": np.median(cluster_dists),
258+
"adaptive_mean_loss": np.mean(adaptive_dists),
259+
"adaptive_median_loss": np.median(adaptive_dists),
260+
"planar_dists": laplace_dists,
261+
"cluster_dists": cluster_dists,
262+
"adaptive_dists": adaptive_dists,
263+
"planar_circuit": laplace_circuit,
264+
"cluster_circuit": cluster_circuit,
265+
"adaptive_circuit": adaptive_circuit,
266+
}
267+
return result

0 commit comments

Comments
 (0)