nrel-shift 0.1.0a0__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.
- NREL-shift-0.1.0a0/LICENSE.txt +29 -0
- NREL-shift-0.1.0a0/NREL_shift.egg-info/PKG-INFO +45 -0
- NREL-shift-0.1.0a0/NREL_shift.egg-info/SOURCES.txt +35 -0
- NREL-shift-0.1.0a0/NREL_shift.egg-info/dependency_links.txt +1 -0
- NREL-shift-0.1.0a0/NREL_shift.egg-info/entry_points.txt +2 -0
- NREL-shift-0.1.0a0/NREL_shift.egg-info/requires.txt +18 -0
- NREL-shift-0.1.0a0/NREL_shift.egg-info/top_level.txt +1 -0
- NREL-shift-0.1.0a0/PKG-INFO +45 -0
- NREL-shift-0.1.0a0/README.md +30 -0
- NREL-shift-0.1.0a0/setup.cfg +4 -0
- NREL-shift-0.1.0a0/setup.py +30 -0
- NREL-shift-0.1.0a0/shift/__init__.py +0 -0
- NREL-shift-0.1.0a0/shift/cli/__init__.py +0 -0
- NREL-shift-0.1.0a0/shift/cli/shift.py +69 -0
- NREL-shift-0.1.0a0/shift/clustering.py +229 -0
- NREL-shift-0.1.0a0/shift/config_template.py +300 -0
- NREL-shift-0.1.0a0/shift/constants.py +188 -0
- NREL-shift-0.1.0a0/shift/enums.py +100 -0
- NREL-shift-0.1.0a0/shift/examples/__init__.py +0 -0
- NREL-shift-0.1.0a0/shift/examples/example0.py +235 -0
- NREL-shift-0.1.0a0/shift/exceptions.py +588 -0
- NREL-shift-0.1.0a0/shift/exporter/__init__.py +0 -0
- NREL-shift-0.1.0a0/shift/exporter/base.py +44 -0
- NREL-shift-0.1.0a0/shift/exporter/opendss.py +584 -0
- NREL-shift-0.1.0a0/shift/facade.py +578 -0
- NREL-shift-0.1.0a0/shift/feeder_network.py +289 -0
- NREL-shift-0.1.0a0/shift/geometry.py +365 -0
- NREL-shift-0.1.0a0/shift/graph.py +227 -0
- NREL-shift-0.1.0a0/shift/line_section.py +745 -0
- NREL-shift-0.1.0a0/shift/load.py +218 -0
- NREL-shift-0.1.0a0/shift/load_builder.py +551 -0
- NREL-shift-0.1.0a0/shift/network_plots.py +295 -0
- NREL-shift-0.1.0a0/shift/primary_network_builder.py +892 -0
- NREL-shift-0.1.0a0/shift/secondary_network_builder.py +413 -0
- NREL-shift-0.1.0a0/shift/transformer.py +228 -0
- NREL-shift-0.1.0a0/shift/transformer_builder.py +536 -0
- NREL-shift-0.1.0a0/shift/utils.py +569 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022, Alliance for Sustainable Energy, LLC
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: NREL-shift
|
|
3
|
+
Version: 0.1.0a0
|
|
4
|
+
Summary: Generate synthetic feeders using open street map data
|
|
5
|
+
Home-page: https://github.com/nrel/shift
|
|
6
|
+
Author: Kapil Duwadi
|
|
7
|
+
Author-email: kapil.duwadi@nrel.gov
|
|
8
|
+
Keywords: Synthetic feeder,Open steet data,OpenDSS
|
|
9
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.9
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE.txt
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
Simple Synthetic Distribution Feeder Generation Tool (SHIFT)
|
|
18
|
+
|
|
19
|
+
<img src="docs/images/shift.svg" width="400" style="display:block;margin:auto;">
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+

|
|
23
|
+

|
|
24
|
+

|
|
25
|
+
[](https://github.com/NREL/shift/blob/main/LICENSE.txt)
|
|
26
|
+
[](https://github.com/NREL/shift/issues)
|
|
27
|
+

|
|
28
|
+

|
|
29
|
+
|
|
30
|
+
### :wave: Welcome to SHIFT repository!
|
|
31
|
+
|
|
32
|
+
[Goto full documentation](https://nrel.github.io/shift/)
|
|
33
|
+
|
|
34
|
+
Getting free distribution feeder models for your reasearch has never been easier.
|
|
35
|
+
SHIFT helps you build synthetic distribution feeders models using just the OpenStreet data e.g. buildings and road network. You can configure lots of
|
|
36
|
+
parameters and design choices when building these models and even create multiple versions of them and choose the one that fits your needs. If you are utility no worries we
|
|
37
|
+
have you covered as well. As a utility you can also integrate your data when building these models.
|
|
38
|
+
|
|
39
|
+
Having distribution models in your hand can help you perform rich analysis in understanding impact of distributed energy resources
|
|
40
|
+
such as roof top PV, energy storage (Tesla Wall, lead acid, lithium name ....), electric vehicle and more. Performing network upgrade analysis, hosting capacity
|
|
41
|
+
analysis, interconnection studies all require detailed distribution models.
|
|
42
|
+
|
|
43
|
+
So what are you waiting for. We can't wait to see how you will use this tool for your as wells your organizations's benefit.
|
|
44
|
+
|
|
45
|
+
Feel free to reach out to us or send us a comment using Github issue. We don't mind if you smash that starred icon motivating our developers and contributors.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
LICENSE.txt
|
|
2
|
+
README.md
|
|
3
|
+
setup.py
|
|
4
|
+
NREL_shift.egg-info/PKG-INFO
|
|
5
|
+
NREL_shift.egg-info/SOURCES.txt
|
|
6
|
+
NREL_shift.egg-info/dependency_links.txt
|
|
7
|
+
NREL_shift.egg-info/entry_points.txt
|
|
8
|
+
NREL_shift.egg-info/requires.txt
|
|
9
|
+
NREL_shift.egg-info/top_level.txt
|
|
10
|
+
shift/__init__.py
|
|
11
|
+
shift/clustering.py
|
|
12
|
+
shift/config_template.py
|
|
13
|
+
shift/constants.py
|
|
14
|
+
shift/enums.py
|
|
15
|
+
shift/exceptions.py
|
|
16
|
+
shift/facade.py
|
|
17
|
+
shift/feeder_network.py
|
|
18
|
+
shift/geometry.py
|
|
19
|
+
shift/graph.py
|
|
20
|
+
shift/line_section.py
|
|
21
|
+
shift/load.py
|
|
22
|
+
shift/load_builder.py
|
|
23
|
+
shift/network_plots.py
|
|
24
|
+
shift/primary_network_builder.py
|
|
25
|
+
shift/secondary_network_builder.py
|
|
26
|
+
shift/transformer.py
|
|
27
|
+
shift/transformer_builder.py
|
|
28
|
+
shift/utils.py
|
|
29
|
+
shift/cli/__init__.py
|
|
30
|
+
shift/cli/shift.py
|
|
31
|
+
shift/examples/__init__.py
|
|
32
|
+
shift/examples/example0.py
|
|
33
|
+
shift/exporter/__init__.py
|
|
34
|
+
shift/exporter/base.py
|
|
35
|
+
shift/exporter/opendss.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
shift
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: NREL-shift
|
|
3
|
+
Version: 0.1.0a0
|
|
4
|
+
Summary: Generate synthetic feeders using open street map data
|
|
5
|
+
Home-page: https://github.com/nrel/shift
|
|
6
|
+
Author: Kapil Duwadi
|
|
7
|
+
Author-email: kapil.duwadi@nrel.gov
|
|
8
|
+
Keywords: Synthetic feeder,Open steet data,OpenDSS
|
|
9
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.9
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE.txt
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
Simple Synthetic Distribution Feeder Generation Tool (SHIFT)
|
|
18
|
+
|
|
19
|
+
<img src="docs/images/shift.svg" width="400" style="display:block;margin:auto;">
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+

|
|
23
|
+

|
|
24
|
+

|
|
25
|
+
[](https://github.com/NREL/shift/blob/main/LICENSE.txt)
|
|
26
|
+
[](https://github.com/NREL/shift/issues)
|
|
27
|
+

|
|
28
|
+

|
|
29
|
+
|
|
30
|
+
### :wave: Welcome to SHIFT repository!
|
|
31
|
+
|
|
32
|
+
[Goto full documentation](https://nrel.github.io/shift/)
|
|
33
|
+
|
|
34
|
+
Getting free distribution feeder models for your reasearch has never been easier.
|
|
35
|
+
SHIFT helps you build synthetic distribution feeders models using just the OpenStreet data e.g. buildings and road network. You can configure lots of
|
|
36
|
+
parameters and design choices when building these models and even create multiple versions of them and choose the one that fits your needs. If you are utility no worries we
|
|
37
|
+
have you covered as well. As a utility you can also integrate your data when building these models.
|
|
38
|
+
|
|
39
|
+
Having distribution models in your hand can help you perform rich analysis in understanding impact of distributed energy resources
|
|
40
|
+
such as roof top PV, energy storage (Tesla Wall, lead acid, lithium name ....), electric vehicle and more. Performing network upgrade analysis, hosting capacity
|
|
41
|
+
analysis, interconnection studies all require detailed distribution models.
|
|
42
|
+
|
|
43
|
+
So what are you waiting for. We can't wait to see how you will use this tool for your as wells your organizations's benefit.
|
|
44
|
+
|
|
45
|
+
Feel free to reach out to us or send us a comment using Github issue. We don't mind if you smash that starred icon motivating our developers and contributors.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
|
|
2
|
+
Simple Synthetic Distribution Feeder Generation Tool (SHIFT)
|
|
3
|
+
|
|
4
|
+
<img src="docs/images/shift.svg" width="400" style="display:block;margin:auto;">
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
10
|
+
[](https://github.com/NREL/shift/blob/main/LICENSE.txt)
|
|
11
|
+
[](https://github.com/NREL/shift/issues)
|
|
12
|
+

|
|
13
|
+

|
|
14
|
+
|
|
15
|
+
### :wave: Welcome to SHIFT repository!
|
|
16
|
+
|
|
17
|
+
[Goto full documentation](https://nrel.github.io/shift/)
|
|
18
|
+
|
|
19
|
+
Getting free distribution feeder models for your reasearch has never been easier.
|
|
20
|
+
SHIFT helps you build synthetic distribution feeders models using just the OpenStreet data e.g. buildings and road network. You can configure lots of
|
|
21
|
+
parameters and design choices when building these models and even create multiple versions of them and choose the one that fits your needs. If you are utility no worries we
|
|
22
|
+
have you covered as well. As a utility you can also integrate your data when building these models.
|
|
23
|
+
|
|
24
|
+
Having distribution models in your hand can help you perform rich analysis in understanding impact of distributed energy resources
|
|
25
|
+
such as roof top PV, energy storage (Tesla Wall, lead acid, lithium name ....), electric vehicle and more. Performing network upgrade analysis, hosting capacity
|
|
26
|
+
analysis, interconnection studies all require detailed distribution models.
|
|
27
|
+
|
|
28
|
+
So what are you waiting for. We can't wait to see how you will use this tool for your as wells your organizations's benefit.
|
|
29
|
+
|
|
30
|
+
Feel free to reach out to us or send us a comment using Github issue. We don't mind if you smash that starred icon motivating our developers and contributors.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from setuptools import setup, find_packages
|
|
3
|
+
|
|
4
|
+
with open("README.md", "r") as fh:
|
|
5
|
+
long_description = fh.read()
|
|
6
|
+
|
|
7
|
+
with open(os.path.join(os.path.dirname(__file__), "requirements.txt")) as f:
|
|
8
|
+
requirements = f.read().splitlines()
|
|
9
|
+
|
|
10
|
+
setup(
|
|
11
|
+
name="NREL-shift",
|
|
12
|
+
long_description=long_description,
|
|
13
|
+
long_description_content_type="text/markdown",
|
|
14
|
+
version="v0.1.0-alpha",
|
|
15
|
+
description="Generate synthetic feeders using open street map data",
|
|
16
|
+
author="Kapil Duwadi",
|
|
17
|
+
author_email="kapil.duwadi@nrel.gov",
|
|
18
|
+
packages=find_packages(),
|
|
19
|
+
url="https://github.com/nrel/shift",
|
|
20
|
+
keywords="Synthetic feeder, Open steet data, OpenDSS",
|
|
21
|
+
install_requires=requirements,
|
|
22
|
+
python_requires=">=3.9",
|
|
23
|
+
package_dir={"shift": "shift"},
|
|
24
|
+
entry_points={"console_scripts": ["shift-internal=shift.cli.shift:cli"]},
|
|
25
|
+
classifiers=[
|
|
26
|
+
"License :: OSI Approved :: BSD License",
|
|
27
|
+
"Programming Language :: Python :: 3.7",
|
|
28
|
+
"Operating System :: OS Independent",
|
|
29
|
+
],
|
|
30
|
+
)
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Copyright (c) 2022, Alliance for Sustainable Energy, LLC
|
|
3
|
+
|
|
4
|
+
# All rights reserved.
|
|
5
|
+
|
|
6
|
+
# Redistribution and use in source and binary forms, with or without
|
|
7
|
+
# modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
# list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
# this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
# and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
# 3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
# contributors may be used to endorse or promote products derived from
|
|
18
|
+
# this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
30
|
+
|
|
31
|
+
"""This module handles command line interface for this package. """
|
|
32
|
+
import json
|
|
33
|
+
|
|
34
|
+
import click
|
|
35
|
+
import yaml
|
|
36
|
+
|
|
37
|
+
from shift.config_template import ConfigTemplate
|
|
38
|
+
from shift.facade import generate_feeder_from_yaml
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@click.command()
|
|
42
|
+
@click.option("-c", "--config-yaml", help="Path to config yaml file.")
|
|
43
|
+
def create_config_file(config_yaml: str) -> None:
|
|
44
|
+
"""Creates a default config yaml file. Update this yaml file and use it to
|
|
45
|
+
create the synthetic distribution feeder.
|
|
46
|
+
"""
|
|
47
|
+
config = ConfigTemplate().dict()
|
|
48
|
+
print(json.loads(json.dumps(config)))
|
|
49
|
+
with open(config_yaml, "w", encoding="utf-8") as fpointer:
|
|
50
|
+
yaml.dump(json.loads(json.dumps(config)), fpointer)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@click.command()
|
|
54
|
+
@click.option("-c", "--config-yaml", help="Path to config yaml file.")
|
|
55
|
+
def create_feeder(config_yaml: str) -> None:
|
|
56
|
+
"""Creates a synthetic distribution feeder
|
|
57
|
+
by taking the config yaml file as an input.
|
|
58
|
+
"""
|
|
59
|
+
generate_feeder_from_yaml(config_yaml)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@click.group()
|
|
63
|
+
def cli():
|
|
64
|
+
"""Entry point"""
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
cli.add_command(create_config_file)
|
|
69
|
+
cli.add_command(create_feeder)
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Copyright (c) 2022, Alliance for Sustainable Energy, LLC
|
|
3
|
+
|
|
4
|
+
# All rights reserved.
|
|
5
|
+
|
|
6
|
+
# Redistribution and use in source and binary forms, with or without
|
|
7
|
+
# modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
# list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
# this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
# and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
# 3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
# contributors may be used to endorse or promote products derived from
|
|
18
|
+
# this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
30
|
+
|
|
31
|
+
""" Contains abstract and concrete classes for implementing clustering.
|
|
32
|
+
|
|
33
|
+
This module contains classes for managing clustering of location pairs.
|
|
34
|
+
The cluster center is used as reference location to figure out
|
|
35
|
+
transformer siting. In the process mapping between
|
|
36
|
+
cluster center and locations is also created.
|
|
37
|
+
|
|
38
|
+
Examples:
|
|
39
|
+
Get the clusters from given list of location pairs and plot it.
|
|
40
|
+
|
|
41
|
+
>>> from shift.clustering import KmeansClustering
|
|
42
|
+
>>> import numpy as np
|
|
43
|
+
>>> x_array = np.array([[1,2], [3,4], [5,6]])
|
|
44
|
+
>>> cluster = KmeansClustering(2)
|
|
45
|
+
>>> cluster.get_clusters(x_array)
|
|
46
|
+
>>> cluster.plot_clusters()
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
from abc import ABC, abstractmethod
|
|
50
|
+
from typing import List, Union
|
|
51
|
+
|
|
52
|
+
from sklearn.cluster import KMeans
|
|
53
|
+
from sklearn.metrics import silhouette_score
|
|
54
|
+
import plotly.express as px
|
|
55
|
+
import pandas as pd
|
|
56
|
+
|
|
57
|
+
from shift.constants import MAX_KMEANS_LOOP, MIN_NUM_CLUSTER
|
|
58
|
+
from shift.exceptions import (
|
|
59
|
+
MaxLoopReachedForKmeans,
|
|
60
|
+
WrongInputUsed,
|
|
61
|
+
NumberOfClusterNotInRangeError,
|
|
62
|
+
EarlyMethodCallError,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class Clustering(ABC):
|
|
67
|
+
"""Abstract interface for implementing clustering subclass"""
|
|
68
|
+
|
|
69
|
+
@abstractmethod
|
|
70
|
+
def get_clusters(self, x_array: List[list]) -> dict:
|
|
71
|
+
"""Method for creating and returning clusters.
|
|
72
|
+
|
|
73
|
+
Subclass inhereting this class must implement this method and should
|
|
74
|
+
return the clusters in the following format.
|
|
75
|
+
```json
|
|
76
|
+
{"labels": [0,1,1], "centre": [(0,0), (2,2)]}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
dict: A dictionary containing cluster labels
|
|
81
|
+
and cluster centers
|
|
82
|
+
"""
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
@abstractmethod
|
|
86
|
+
def plot_clusters(self) -> None:
|
|
87
|
+
"""Method to plot clusters
|
|
88
|
+
|
|
89
|
+
Raises:
|
|
90
|
+
EarlyMethodCallError: If this method is called before calling
|
|
91
|
+
`get_clusters` method.
|
|
92
|
+
"""
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class KmeansClustering(Clustering):
|
|
97
|
+
"""Class implementing Kmeans clustering.
|
|
98
|
+
|
|
99
|
+
Attributes:
|
|
100
|
+
num_of_clusters (Union[str, int]): Number of clusters to be used
|
|
101
|
+
cluster_centers (List[Sequece]): List of cluster center
|
|
102
|
+
labels (list): Integer label for each location indicating which
|
|
103
|
+
cluster they belong to
|
|
104
|
+
xarray (List[Sequece]): List of location pairs for which
|
|
105
|
+
clustering is performed
|
|
106
|
+
optimal_clusters (int): Optimal number of clusters created if
|
|
107
|
+
`num_of_clusters` passed is `optimal`
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
def __init__(self, num_of_clusters: Union[str, int] = "optimal") -> None:
|
|
111
|
+
"""Constructor for `KmeansClustering` class.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
num_of_clusters (Union[str, int]): Number of clusters to be used
|
|
115
|
+
|
|
116
|
+
Raises:
|
|
117
|
+
NumberOfClusterNotInRangeError: if `num_of_clusters` speecified is
|
|
118
|
+
less than MIN_NUM_CLUSTER constants module.
|
|
119
|
+
"""
|
|
120
|
+
self.num_of_clusters = num_of_clusters
|
|
121
|
+
|
|
122
|
+
if isinstance(self.num_of_clusters, int):
|
|
123
|
+
if self.num_of_clusters < MIN_NUM_CLUSTER:
|
|
124
|
+
raise NumberOfClusterNotInRangeError(self.num_of_clusters)
|
|
125
|
+
|
|
126
|
+
def plot_clusters(self):
|
|
127
|
+
"""Refer to the base class for details."""
|
|
128
|
+
|
|
129
|
+
if (
|
|
130
|
+
hasattr(self, "x_array")
|
|
131
|
+
and hasattr(self, "labels")
|
|
132
|
+
and hasattr(self, "cluster_centers")
|
|
133
|
+
):
|
|
134
|
+
plot_data = {"x": [], "y": [], "label": []}
|
|
135
|
+
for d, label in zip(self.x_array, self.labels):
|
|
136
|
+
plot_data["x"].append(d[0])
|
|
137
|
+
plot_data["y"].append(d[1])
|
|
138
|
+
plot_data["label"].append(label)
|
|
139
|
+
fig = px.scatter(
|
|
140
|
+
pd.DataFrame(plot_data), x="x", y="y", color="label"
|
|
141
|
+
)
|
|
142
|
+
fig.show()
|
|
143
|
+
|
|
144
|
+
else:
|
|
145
|
+
raise EarlyMethodCallError(
|
|
146
|
+
"Call get_clusters() method first before "
|
|
147
|
+
+ "calling plot clusters method!"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
def plot_scores(self):
|
|
151
|
+
"""Method for plotting scores
|
|
152
|
+
|
|
153
|
+
Note:
|
|
154
|
+
Only use this method if `num_of_clusters` passed is `optimal`.
|
|
155
|
+
|
|
156
|
+
Raises:
|
|
157
|
+
EarlyMethodCallError: If called before calling
|
|
158
|
+
`get_clusters` method.
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
if hasattr(self, "sil_scores"):
|
|
162
|
+
plot_data = {
|
|
163
|
+
"Number of clusters": [x[0] for x in self.sil_scores],
|
|
164
|
+
"Silhouette Value": [x[1] for x in self.sil_scores],
|
|
165
|
+
}
|
|
166
|
+
fig = px.line(
|
|
167
|
+
pd.DataFrame(plot_data),
|
|
168
|
+
x="Number of clusters",
|
|
169
|
+
y="Silhouette Value",
|
|
170
|
+
)
|
|
171
|
+
fig.show()
|
|
172
|
+
|
|
173
|
+
else:
|
|
174
|
+
raise EarlyMethodCallError(
|
|
175
|
+
"Call get_clusters() method first before calling "
|
|
176
|
+
+ "plot scores method!"
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
def get_clusters(self, x_array: list) -> dict:
|
|
180
|
+
"""Refer to the base class for details.
|
|
181
|
+
|
|
182
|
+
Raises:
|
|
183
|
+
WrongInputUsed: If the number of clusters passed is invalid.
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
self.x_array = x_array
|
|
187
|
+
if isinstance(self.num_of_clusters, int):
|
|
188
|
+
kmeans = KMeans(
|
|
189
|
+
n_clusters=self.num_of_clusters, random_state=0
|
|
190
|
+
).fit(x_array)
|
|
191
|
+
|
|
192
|
+
else:
|
|
193
|
+
if self.num_of_clusters == "optimal":
|
|
194
|
+
# Let's try to find optimal number of clusters
|
|
195
|
+
self.sil_scores = []
|
|
196
|
+
|
|
197
|
+
for k in range(2, MAX_KMEANS_LOOP):
|
|
198
|
+
kmeans = KMeans(n_clusters=k).fit(x_array)
|
|
199
|
+
labels = kmeans.labels_
|
|
200
|
+
self.sil_scores.append(
|
|
201
|
+
(
|
|
202
|
+
k,
|
|
203
|
+
silhouette_score(
|
|
204
|
+
x_array, labels, metric="euclidean"
|
|
205
|
+
),
|
|
206
|
+
)
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# Break the loop if Silhouette score starts to decrease
|
|
210
|
+
if k > 2:
|
|
211
|
+
if (
|
|
212
|
+
self.sil_scores[k - 2][1]
|
|
213
|
+
< self.sil_scores[k - 3][1]
|
|
214
|
+
):
|
|
215
|
+
break
|
|
216
|
+
|
|
217
|
+
if k == MAX_KMEANS_LOOP - 1:
|
|
218
|
+
raise MaxLoopReachedForKmeans()
|
|
219
|
+
kmeans = KMeans(n_clusters=k - 1, random_state=0).fit(x_array)
|
|
220
|
+
self.optimal_clusters = k - 1
|
|
221
|
+
else:
|
|
222
|
+
raise WrongInputUsed(
|
|
223
|
+
"For now number of clusters can be either integer "
|
|
224
|
+
+ "number or 'optimal'!"
|
|
225
|
+
+ f"You provided {self.num_of_clusters}"
|
|
226
|
+
)
|
|
227
|
+
self.labels = kmeans.labels_
|
|
228
|
+
self.cluster_centers = kmeans.cluster_centers_
|
|
229
|
+
return {"labels": self.labels, "centre": self.cluster_centers}
|