RunFeemsSim 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,33 @@
1
+ # How to contribute
2
+
3
+ ## How to get started
4
+
5
+ Before anything else, please install the git hooks that run automatic scripts during each commit and merge to strip the notebooks of superfluous metadata (and avoid merge conflicts). After cloning the repository, run the following command inside it:
6
+ ```
7
+ nbdev_install_git_hooks
8
+ ```
9
+
10
+ ## Did you find a bug?
11
+
12
+ * Ensure the bug was not already reported by searching on GitHub under Issues.
13
+ * If you're unable to find an open issue addressing the problem, open a new one. Be sure to include a title and clear description, as much relevant information as possible, and a code sample or an executable test case demonstrating the expected behavior that is not occurring.
14
+ * Be sure to add the complete error messages.
15
+
16
+ #### Did you write a patch that fixes a bug?
17
+
18
+ * Open a new GitHub pull request with the patch.
19
+ * Ensure that your PR includes a test that fails without your patch, and pass with it.
20
+ * Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable.
21
+
22
+ ## PR submission guidelines
23
+
24
+ * Keep each PR focused. While it's more convenient, do not combine several unrelated fixes together. Create as many branches as needing to keep each PR focused.
25
+ * Do not mix style changes/fixes with "functional" changes. It's very difficult to review such PRs and it most likely get rejected.
26
+ * Do not add/remove vertical whitespace. Preserve the original style of the file you edit as much as you can.
27
+ * Do not turn an already submitted PR into your development playground. If after you submitted PR, you discovered that more work is needed - close the PR, do the required work and then submit a new PR. Otherwise each of your commits requires attention from maintainers of the project.
28
+ * If, however, you submitted a PR and received a request for changes, you should proceed with commits inside that PR, so that the maintainer can see the incremental fixes and won't need to review the whole PR again. In the exception case where you realize it'll take many many commits to complete the requests, then it's probably best to close the PR, do the work and then submit it again. Use common sense where you'd choose one way over another.
29
+
30
+ ## Do you want to contribute to the documentation?
31
+
32
+ * Docs are automatically created from the notebooks in the nbs folder.
33
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 SINTEF
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,5 @@
1
+ include settings.ini
2
+ include LICENSE
3
+ include CONTRIBUTING.md
4
+ include README.md
5
+ recursive-exclude * __pycache__
@@ -0,0 +1,73 @@
1
+ Metadata-Version: 2.1
2
+ Name: RunFeemsSim
3
+ Version: 0.2.2
4
+ Summary: A library for running feems simulation
5
+ Home-page: https://SintefOceanEnergySystem@dev.azure.com/SintefOceanEnergySystem/FEEMSService/_git/RunFEEMSSim
6
+ Author: Kevin Koosup Yum
7
+ Author-email: kevinkoosup.yum@sintef.no
8
+ License: Apache Software License 2.0
9
+ Keywords: FEEMS,machinery system,fuel,emissions
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Natural Language :: English
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: License :: OSI Approved :: Apache Software License
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: pip
19
+ Requires-Dist: packaging
20
+ Requires-Dist: pandas
21
+ Requires-Dist: numpy
22
+ Requires-Dist: MachSysS
23
+ Provides-Extra: dev
24
+
25
+ # RunFeemsSim Package
26
+
27
+ > The RunFeemsSim package is a Python package for running FEEMS simulations. It provides a simple
28
+ > interface for running FEEMS simulations and for visualizing the results. It also provides a basic
29
+ > pms model to apply for an electric power system that has a functionality of load dependent
30
+ > start-stop of gensets.
31
+
32
+ ## Installation of the package
33
+ The package is distributed by the Azure Artifacts package manager. To install the package,
34
+ you need to install artifacts-keyring package and should have a valid Azure DevOps account.
35
+ ```sh
36
+ pip install artifacts-keyring
37
+ ```
38
+ Then, you need to add the package source to your pip configuration. You can do this by copying
39
+ the pip configuration file (pip.conf for macOS and Linux or pip.ini for Windows) to your
40
+ virtual environment directory or base python directory. The file should contain the following lines:
41
+ ```sh
42
+ [global]
43
+ extra-index-url=https://pkgs.dev.azure.com/SintefOceanEnergySystem/_packaging/SintefOceanEnergySystem/pypi/simple/
44
+ ```
45
+ If you already have a pip configuration file, you can add the above lines to the file. Finally,
46
+ you can install the package by running the following command:
47
+ ```sh
48
+ pip install RunFeemsSim
49
+ ```
50
+
51
+ ## Installation of the development environment
52
+ For the development, one should create a virtual environment and install the package using the
53
+ requirements.txt file. First, create a virtual environment and activate it.
54
+ ```sh
55
+ python -m venv venv
56
+ source venv/bin/activate
57
+ ```
58
+ Then, you need to add the package source to your pip configuration. You can do this by copying
59
+ the pip configuration file (pip.conf for macOS and Linux or pip.ini for Windows) to your venv
60
+ directory or base python directory. The file should contain the following lines:
61
+ ```sh
62
+ [global]
63
+ extra-index-url=https://pkgs.dev.azure.com/SintefOceanEnergySystem/_packaging/SintefOceanEnergySystem/pypi/simple/
64
+ ```
65
+
66
+ Then, install the package using the requirements.txt file.
67
+ ```sh
68
+ pip install -r requirements.txt
69
+ ```
70
+
71
+ ## Usage
72
+
73
+
@@ -0,0 +1,49 @@
1
+ # RunFeemsSim Package
2
+
3
+ > The RunFeemsSim package is a Python package for running FEEMS simulations. It provides a simple
4
+ > interface for running FEEMS simulations and for visualizing the results. It also provides a basic
5
+ > pms model to apply for an electric power system that has a functionality of load dependent
6
+ > start-stop of gensets.
7
+
8
+ ## Installation of the package
9
+ The package is distributed by the Azure Artifacts package manager. To install the package,
10
+ you need to install artifacts-keyring package and should have a valid Azure DevOps account.
11
+ ```sh
12
+ pip install artifacts-keyring
13
+ ```
14
+ Then, you need to add the package source to your pip configuration. You can do this by copying
15
+ the pip configuration file (pip.conf for macOS and Linux or pip.ini for Windows) to your
16
+ virtual environment directory or base python directory. The file should contain the following lines:
17
+ ```sh
18
+ [global]
19
+ extra-index-url=https://pkgs.dev.azure.com/SintefOceanEnergySystem/_packaging/SintefOceanEnergySystem/pypi/simple/
20
+ ```
21
+ If you already have a pip configuration file, you can add the above lines to the file. Finally,
22
+ you can install the package by running the following command:
23
+ ```sh
24
+ pip install RunFeemsSim
25
+ ```
26
+
27
+ ## Installation of the development environment
28
+ For the development, one should create a virtual environment and install the package using the
29
+ requirements.txt file. First, create a virtual environment and activate it.
30
+ ```sh
31
+ python -m venv venv
32
+ source venv/bin/activate
33
+ ```
34
+ Then, you need to add the package source to your pip configuration. You can do this by copying
35
+ the pip configuration file (pip.conf for macOS and Linux or pip.ini for Windows) to your venv
36
+ directory or base python directory. The file should contain the following lines:
37
+ ```sh
38
+ [global]
39
+ extra-index-url=https://pkgs.dev.azure.com/SintefOceanEnergySystem/_packaging/SintefOceanEnergySystem/pypi/simple/
40
+ ```
41
+
42
+ Then, install the package using the requirements.txt file.
43
+ ```sh
44
+ pip install -r requirements.txt
45
+ ```
46
+
47
+ ## Usage
48
+
49
+
@@ -0,0 +1 @@
1
+ __version__ = "0.2.2"
@@ -0,0 +1,117 @@
1
+ # Autogenerated by nbdev
2
+
3
+ d = {
4
+ "settings": {
5
+ "branch": "master",
6
+ "doc_baseurl": "/RunFeemsSim/",
7
+ "doc_host": "https://kevinkoosup.yum@sintef.no.github.io",
8
+ "git_url": "https://SintefOceanEnergySystem@dev.azure.com/SintefOceanEnergySystem/FEEMSService/_git/RunFEEMSSim",
9
+ "lib_path": "RunFeemsSim",
10
+ },
11
+ "syms": {
12
+ "RunFeemsSim.machinery_calculation": {
13
+ "RunFeemsSim.machinery_calculation.MachineryCalculation": (
14
+ "machinery_calculation.html#machinerycalculation",
15
+ "RunFeemsSim/machinery_calculation.py",
16
+ ),
17
+ "RunFeemsSim.machinery_calculation.MachineryCalculation.__init__": (
18
+ "machinery_calculation.html#machinerycalculation.__init__",
19
+ "RunFeemsSim/machinery_calculation.py",
20
+ ),
21
+ "RunFeemsSim.machinery_calculation.MachineryCalculation._run_simulation": (
22
+ "machinery_calculation.html#machinerycalculation._run_simulation",
23
+ "RunFeemsSim/machinery_calculation.py",
24
+ ),
25
+ "RunFeemsSim.machinery_calculation.MachineryCalculation._set_equal_load_sharing_on_power_sources": (
26
+ "machinery_calculation.html#machinerycalculation._set_equal_load_sharing_on_power_sources",
27
+ "RunFeemsSim/machinery_calculation.py",
28
+ ),
29
+ "RunFeemsSim.machinery_calculation.MachineryCalculation._set_input_load_from_gymir_result": (
30
+ "machinery_calculation.html#machinerycalculation._set_input_load_from_gymir_result",
31
+ "RunFeemsSim/machinery_calculation.py",
32
+ ),
33
+ "RunFeemsSim.machinery_calculation.MachineryCalculation._set_input_load_time_interval_from_propulsion_power_time_series": (
34
+ "machinery_calculation.html#machinerycalculation._set_input_load_time_interval_from_propulsion_power_time_series",
35
+ "RunFeemsSim/machinery_calculation.py",
36
+ ),
37
+ "RunFeemsSim.machinery_calculation.MachineryCalculation._set_status_for_mechanical_system": (
38
+ "machinery_calculation.html#machinerycalculation._set_status_for_mechanical_system",
39
+ "RunFeemsSim/machinery_calculation.py",
40
+ ),
41
+ "RunFeemsSim.machinery_calculation.MachineryCalculation.calculate_machinery_system_output_from_gymir_result": (
42
+ "machinery_calculation.html#machinerycalculation.calculate_machinery_system_output_from_gymir_result",
43
+ "RunFeemsSim/machinery_calculation.py",
44
+ ),
45
+ "RunFeemsSim.machinery_calculation.MachineryCalculation.calculate_machinery_system_output_from_propulsion_power_time_series": (
46
+ "machinery_calculation.html#machinerycalculation.calculate_machinery_system_output_from_propulsion_power_time_series",
47
+ "RunFeemsSim/machinery_calculation.py",
48
+ ),
49
+ "RunFeemsSim.machinery_calculation.MachineryCalculation.calculate_machinery_system_output_from_statistics": (
50
+ "machinery_calculation.html#machinerycalculation.calculate_machinery_system_output_from_statistics",
51
+ "RunFeemsSim/machinery_calculation.py",
52
+ ),
53
+ "RunFeemsSim.machinery_calculation.MachineryCalculation.calculate_machinery_system_output_from_time_series_result": (
54
+ "machinery_calculation.html#machinerycalculation.calculate_machinery_system_output_from_time_series_result",
55
+ "RunFeemsSim/machinery_calculation.py",
56
+ ),
57
+ "RunFeemsSim.machinery_calculation.MachineryCalculation.electric_system": (
58
+ "machinery_calculation.html#machinerycalculation.electric_system",
59
+ "RunFeemsSim/machinery_calculation.py",
60
+ ),
61
+ "RunFeemsSim.machinery_calculation.MachineryCalculation.system_is_not_electric": (
62
+ "machinery_calculation.html#machinerycalculation.system_is_not_electric",
63
+ "RunFeemsSim/machinery_calculation.py",
64
+ ),
65
+ "RunFeemsSim.machinery_calculation.convert_gymir_result_to_propulsion_power_series": (
66
+ "machinery_calculation.html#convert_gymir_result_to_propulsion_power_series",
67
+ "RunFeemsSim/machinery_calculation.py",
68
+ ),
69
+ },
70
+ "RunFeemsSim.pms_basic": {
71
+ "RunFeemsSim.pms_basic.PmsLoadTable": (
72
+ "pms_basic.html#pmsloadtable",
73
+ "RunFeemsSim/pms_basic.py",
74
+ ),
75
+ "RunFeemsSim.pms_basic.PmsLoadTable.__post_init__": (
76
+ "pms_basic.html#pmsloadtable.__post_init__",
77
+ "RunFeemsSim/pms_basic.py",
78
+ ),
79
+ "RunFeemsSim.pms_basic.PmsLoadTable.on_pattern": (
80
+ "pms_basic.html#pmsloadtable.on_pattern",
81
+ "RunFeemsSim/pms_basic.py",
82
+ ),
83
+ "RunFeemsSim.pms_basic.PmsLoadTable.sorted_load_table": (
84
+ "pms_basic.html#pmsloadtable.sorted_load_table",
85
+ "RunFeemsSim/pms_basic.py",
86
+ ),
87
+ "RunFeemsSim.pms_basic.PmsLoadTableSimulationInterface": (
88
+ "pms_basic.html#pmsloadtablesimulationinterface",
89
+ "RunFeemsSim/pms_basic.py",
90
+ ),
91
+ "RunFeemsSim.pms_basic.PmsLoadTableSimulationInterface.__init__": (
92
+ "pms_basic.html#pmsloadtablesimulationinterface.__init__",
93
+ "RunFeemsSim/pms_basic.py",
94
+ ),
95
+ "RunFeemsSim.pms_basic.PmsLoadTableSimulationInterface.set_status": (
96
+ "pms_basic.html#pmsloadtablesimulationinterface.set_status",
97
+ "RunFeemsSim/pms_basic.py",
98
+ ),
99
+ "RunFeemsSim.pms_basic.get_min_load_table_dict_from_feems_system": (
100
+ "pms_basic.html#get_min_load_table_dict_from_feems_system",
101
+ "RunFeemsSim/pms_basic.py",
102
+ ),
103
+ "RunFeemsSim.pms_basic.get_min_load_table_dict_from_proto_system": (
104
+ "pms_basic.html#get_min_load_table_dict_from_proto_system",
105
+ "RunFeemsSim/pms_basic.py",
106
+ ),
107
+ "RunFeemsSim.pms_basic.get_rated_power_from_power_source": (
108
+ "pms_basic.html#get_rated_power_from_power_source",
109
+ "RunFeemsSim/pms_basic.py",
110
+ ),
111
+ "RunFeemsSim.pms_basic.min_load_table_dict": (
112
+ "pms_basic.html#min_load_table_dict",
113
+ "RunFeemsSim/pms_basic.py",
114
+ ),
115
+ },
116
+ },
117
+ }
@@ -0,0 +1,26 @@
1
+ # AUTOGENERATED BY NBDEV! DO NOT EDIT!
2
+
3
+ __all__ = ["index", "modules", "custom_doc_links", "git_url"]
4
+
5
+ index = {
6
+ "convert_gymir_result_to_propulsion_power_series": "00_machinery_calculation.ipynb",
7
+ "MachineryCalculation": "00_machinery_calculation.ipynb",
8
+ "PmsLoadTable": "01_pms_basic.ipynb",
9
+ "get_rated_power_from_power_source": "01_pms_basic.ipynb",
10
+ "get_min_load_table_dict_from_proto_system": "01_pms_basic.ipynb",
11
+ "get_min_load_table_dict_from_feems_system": "01_pms_basic.ipynb",
12
+ "min_load_table_dict": "01_pms_basic.ipynb",
13
+ "PmsLoadTableSimulationInterface": "01_pms_basic.ipynb",
14
+ "OnPattern": "01_pms_basic.ipynb",
15
+ "Load2OnPattern": "01_pms_basic.ipynb",
16
+ }
17
+
18
+ modules = ["machinery_calculation.py", "pms_basic.py"]
19
+
20
+ doc_url = "https://kevinkoosup.yum@sintef.no.github.io/RunFeemsSim/"
21
+
22
+ git_url = "https://"
23
+
24
+
25
+ def custom_doc_links(name):
26
+ return None
@@ -0,0 +1,341 @@
1
+ # AUTOGENERATED! DO NOT EDIT! File to edit: ../00_machinery_calculation.ipynb.
2
+
3
+ # %% auto 0
4
+ __all__ = [
5
+ "Numeric",
6
+ "convert_gymir_result_to_propulsion_power_series",
7
+ "MachineryCalculation",
8
+ ]
9
+
10
+ # %% ../00_machinery_calculation.ipynb 3
11
+ from typing import List, Union, Type, TypeVar
12
+
13
+ import MachSysS.gymir_result_pb2 as proto_gymir
14
+ from MachSysS.convert_proto_timeseries import convert_proto_timeseries_to_pd_dataframe
15
+ import numpy as np
16
+ import pandas as pd
17
+ from feems.components_model.utility import IntegrationMethod
18
+ from feems.system_model import (
19
+ ElectricPowerSystem,
20
+ HybridPropulsionSystem,
21
+ MechanicalPropulsionSystemWithElectricPowerSystem,
22
+ FEEMSResultForMachinerySystem,
23
+ )
24
+ from feems.types_for_feems import FEEMSResult, TypePower
25
+ from feems.simulation_interface import SimulationInterface
26
+ from feems.fuel import FuelSpecifiedBy
27
+
28
+ from RunFeemsSim.pms_basic import (
29
+ PmsLoadTable,
30
+ get_min_load_table_dict_from_feems_system,
31
+ PmsLoadTableSimulationInterface,
32
+ )
33
+
34
+ Numeric = TypeVar("Numeric", int, float, np.ndarray)
35
+
36
+
37
+ def convert_gymir_result_to_propulsion_power_series(
38
+ gymir_result: proto_gymir.GymirResult,
39
+ ) -> pd.Series:
40
+ time = map(lambda each: each.epoch_s, gymir_result.result)
41
+ power = map(lambda each: each.power_kw, gymir_result.result)
42
+ return pd.Series(index=time, data=power)
43
+
44
+
45
+ class MachineryCalculation:
46
+ def __init__(
47
+ self,
48
+ feems_system: Union[
49
+ ElectricPowerSystem,
50
+ MechanicalPropulsionSystemWithElectricPowerSystem,
51
+ HybridPropulsionSystem,
52
+ ],
53
+ pms: SimulationInterface = None,
54
+ maximum_allowed_power_source_load_percentage: float = 80,
55
+ ):
56
+ self.system_feems = feems_system
57
+ if pms is None:
58
+ load_table = PmsLoadTable(
59
+ min_load2on_pattern=get_min_load_table_dict_from_feems_system(
60
+ system=feems_system,
61
+ maximum_allowed_genset_load_percentage=maximum_allowed_power_source_load_percentage,
62
+ )
63
+ )
64
+ self.pms = PmsLoadTableSimulationInterface(
65
+ n_bus_ties=1, pms_load_table=load_table
66
+ )
67
+ else:
68
+ self.pms = pms
69
+ self._set_equal_load_sharing_on_power_sources(n_datapoints=1)
70
+
71
+ @property
72
+ def electric_system(self) -> ElectricPowerSystem:
73
+ if self.system_is_not_electric:
74
+ return self.system_feems.electric_system
75
+ else:
76
+ return self.system_feems
77
+
78
+ @property
79
+ def system_is_not_electric(self) -> bool:
80
+ return hasattr(self.system_feems, "electric_system")
81
+
82
+ def _set_input_load_from_gymir_result(
83
+ self,
84
+ *,
85
+ gymir_result: proto_gymir.GymirResult,
86
+ ) -> None:
87
+ propulsion_power_timeseries = convert_gymir_result_to_propulsion_power_series(
88
+ gymir_result
89
+ )
90
+ self._set_input_load_time_interval_from_propulsion_power_time_series(
91
+ propulsion_power_time_series=propulsion_power_timeseries,
92
+ auxiliary_load_kw=gymir_result.auxiliary_load_kw,
93
+ )
94
+
95
+ def _set_input_load_time_interval_from_propulsion_power_time_series(
96
+ self,
97
+ *,
98
+ propulsion_power_time_series: pd.Series,
99
+ auxiliary_load_kw: Numeric,
100
+ time_is_given_as_interval: bool = False,
101
+ ) -> None:
102
+ if time_is_given_as_interval:
103
+ propulsion_power = propulsion_power_time_series.values
104
+ time_interval_s = propulsion_power_time_series.index.to_numpy()
105
+ else:
106
+ propulsion_power = propulsion_power_time_series.values[:-1]
107
+ time_interval_s = np.diff(propulsion_power_time_series.index.to_numpy())
108
+
109
+ number_points = len(propulsion_power)
110
+ auxiliary_load_kw = np.atleast_1d(auxiliary_load_kw)
111
+ if len(auxiliary_load_kw) > 1:
112
+ auxiliary_load_kw = auxiliary_load_kw[:number_points]
113
+ else:
114
+ auxiliary_load_kw = np.repeat(auxiliary_load_kw, number_points)
115
+ # set power load
116
+ number_of_propulsors = len(self.electric_system.propulsion_drives)
117
+ if self.system_is_not_electric:
118
+ number_of_propulsors = (
119
+ self.system_feems.mechanical_system.no_mechanical_loads
120
+ )
121
+ number_of_other_loads = len(self.electric_system.other_load)
122
+ if number_of_other_loads == 0:
123
+ assert np.all(
124
+ np.atleast_1d(auxiliary_load_kw) == 0
125
+ ), "Auxiliary load is not zero while other loads are not defined in the system."
126
+ for propulsor in self.electric_system.propulsion_drives:
127
+ propulsor.set_power_input_from_output(
128
+ propulsion_power / number_of_propulsors
129
+ )
130
+ if self.system_is_not_electric:
131
+ for propulsor in self.system_feems.mechanical_system.mechanical_loads:
132
+ propulsor.set_power_input_from_output(
133
+ propulsion_power / number_of_propulsors
134
+ )
135
+ for other_load in self.electric_system.other_load:
136
+ other_load.power_input = auxiliary_load_kw / number_of_other_loads
137
+ self.electric_system.set_time_interval(
138
+ time_interval_s=time_interval_s,
139
+ integration_method=IntegrationMethod.sum_with_time,
140
+ )
141
+ if self.system_is_not_electric:
142
+ self.system_feems.mechanical_system.set_time_interval(
143
+ time_interval_s=time_interval_s,
144
+ integration_method=IntegrationMethod.sum_with_time,
145
+ )
146
+
147
+ def _set_equal_load_sharing_on_power_sources(self, n_datapoints: int) -> None:
148
+ for power_source in self.electric_system.power_sources:
149
+ power_source.load_sharing_mode = np.zeros(shape=[n_datapoints])
150
+ if self.system_is_not_electric:
151
+ for power_source in self.system_feems.mechanical_system.main_engines:
152
+ power_source.load_sharing_mode = np.zeros(shape=[n_datapoints])
153
+
154
+ def _set_status_for_mechanical_system(self) -> None:
155
+ """Set the status of main engines for the mechanical system, turning all the gensets on.
156
+ The main engines that are not used for propulsion after the power balance calculation
157
+ will be turned off.
158
+ """
159
+ if np.isscalar(self.system_feems.mechanical_system.time_interval_s):
160
+ n_data_points = 1
161
+ else:
162
+ n_data_points = len(self.system_feems.mechanical_system.time_interval_s)
163
+ for main_engine in self.system_feems.mechanical_system.main_engines:
164
+ main_engine.status = np.ones(n_data_points).astype(bool)
165
+
166
+ def _run_simulation(
167
+ self,
168
+ fuel_specified_by: FuelSpecifiedBy = FuelSpecifiedBy.IMO,
169
+ ignore_power_balance: bool = False,
170
+ ) -> Union[FEEMSResult, FEEMSResultForMachinerySystem]:
171
+ """Run the simulation and return the result.
172
+
173
+ Args:
174
+ fuel_specified_by(FuelSpecifiedBy): The fuel specified by IMO/EU/USER. Default is IMO.
175
+ ignore_power_balance(bool): If True, the power balance calculation will be ignored.
176
+
177
+ Returns:
178
+ The result of the simulation. FEEMSResult or FEEMSResultForMachinery system.
179
+ """
180
+ if ignore_power_balance:
181
+ if self.system_is_not_electric:
182
+ return self.system_feems.get_fuel_energy_consumption_running_time(
183
+ time_interval_s=self.system_feems.mechanical_system.time_interval_s,
184
+ integration_method=IntegrationMethod.sum_with_time,
185
+ fuel_specified_by=fuel_specified_by,
186
+ )
187
+ else:
188
+ return self.system_feems.get_fuel_energy_consumption_running_time(
189
+ fuel_specified_by=fuel_specified_by
190
+ )
191
+
192
+ power_kw_per_switchboard = (
193
+ self.electric_system.get_sum_consumption_kw_sources_switchboard()
194
+ )
195
+ self.pms.set_status(
196
+ power_kw_per_switchboard=power_kw_per_switchboard,
197
+ electric_power_system=self.electric_system,
198
+ time_interval_s=self.electric_system.time_interval_s,
199
+ power_source_priority=None,
200
+ )
201
+ if self.system_is_not_electric:
202
+ self._set_status_for_mechanical_system()
203
+ self.system_feems.do_power_balance_calculation()
204
+ return self.system_feems.get_fuel_energy_consumption_running_time(
205
+ time_interval_s=self.system_feems.mechanical_system.time_interval_s,
206
+ integration_method=IntegrationMethod.sum_with_time,
207
+ fuel_specified_by=fuel_specified_by,
208
+ )
209
+ else:
210
+ self.system_feems.do_power_balance_calculation()
211
+ return self.system_feems.get_fuel_energy_consumption_running_time(
212
+ fuel_specified_by=fuel_specified_by
213
+ )
214
+
215
+ def calculate_machinery_system_output_from_gymir_result(
216
+ self,
217
+ *,
218
+ gymir_result: proto_gymir.GymirResult,
219
+ fuel_specified_by: FuelSpecifiedBy = FuelSpecifiedBy.IMO,
220
+ ignore_power_balance: bool = False,
221
+ ) -> Union[FEEMSResult, FEEMSResultForMachinerySystem]:
222
+ """
223
+ Calculate the machinery system output from a Gymir result.
224
+
225
+ Args:
226
+ gymir_result(GymirResult): Gymir result given as protobuf message.
227
+ fuel_specified_by(FuelSpecifiedBy): The fuel specified by IMO/EU/USER. Default is IMO.
228
+ ignore_power_balance(bool): If True, the power balance calculation will be ignored.
229
+
230
+ Returns:
231
+ The result of the calculation. FEEMSResult or FEEMSResultForMachinerySystem.
232
+ """
233
+ self._set_input_load_from_gymir_result(gymir_result=gymir_result)
234
+ return self._run_simulation(
235
+ fuel_specified_by=fuel_specified_by,
236
+ ignore_power_balance=ignore_power_balance,
237
+ )
238
+
239
+ def calculate_machinery_system_output_from_propulsion_power_time_series(
240
+ self,
241
+ *,
242
+ propulsion_power: pd.Series,
243
+ auxiliary_power_kw: Numeric,
244
+ fuel_specified_by: FuelSpecifiedBy = FuelSpecifiedBy.IMO,
245
+ ignore_power_balance: bool = False,
246
+ ) -> Union[FEEMSResult, FEEMSResultForMachinerySystem]:
247
+ """
248
+ Calculate the machinery system output from a time series of the propulsion power and
249
+ auxiliary power.
250
+
251
+ Args:
252
+ propulsion_power(pd.Series): The propulsion power time series.
253
+ auxiliary_power_kw(Numeric): The auxiliary power in kW. It can be a single value or
254
+ a numpy array with the same length as the propulsion power.
255
+ fuel_specified_by(FuelSpecifiedBy): The fuel specified by IMO/EU/USER. Default is IMO.
256
+ ignore_power_balance(bool): If True, the power balance calculation will be ignored.
257
+
258
+ Returns:
259
+ The result of the calculation. FEEMSResult or FEEMSResultForMachinerySystem.
260
+ """
261
+ if not np.isscalar(auxiliary_power_kw):
262
+ assert (
263
+ len(propulsion_power) == len(auxiliary_power_kw)
264
+ or len(auxiliary_power_kw) == 1
265
+ ), "The length of the auxiliary power must be 1 or the same as the propulsion power"
266
+ self._set_input_load_time_interval_from_propulsion_power_time_series(
267
+ propulsion_power_time_series=propulsion_power,
268
+ auxiliary_load_kw=auxiliary_power_kw,
269
+ )
270
+ return self._run_simulation(
271
+ fuel_specified_by=fuel_specified_by,
272
+ ignore_power_balance=ignore_power_balance,
273
+ )
274
+
275
+ def calculate_machinery_system_output_from_time_series_result(
276
+ self,
277
+ *,
278
+ time_series: proto_gymir.TimeSeriesResult,
279
+ fuel_specified_by: FuelSpecifiedBy = FuelSpecifiedBy.IMO,
280
+ ignore_power_balance: bool = False,
281
+ ) -> Union[FEEMSResult, FEEMSResultForMachinerySystem]:
282
+ """
283
+ Calculate the machinery system output from statistics of the propulsion power.
284
+ Args:
285
+ time_series(TimeSeriesResult): Time series result given as protobuf message.
286
+ fuel_specified_by(FuelSpecifiedBy): The fuel specified by IMO/EU/USER. Default is IMO.
287
+ ignore_power_balance(bool): If True, the power balance calculation will be ignored.
288
+
289
+ Returns:
290
+ The result of the simulation. FEEMSResult or FEEMSResultForMachinery system.
291
+ """
292
+ df = convert_proto_timeseries_to_pd_dataframe(time_series)
293
+ self._set_input_load_time_interval_from_propulsion_power_time_series(
294
+ propulsion_power_time_series=df["propulsion_power_kw"],
295
+ auxiliary_load_kw=df["auxiliary_power_kw"].values,
296
+ )
297
+ return self._run_simulation(
298
+ fuel_specified_by=fuel_specified_by,
299
+ ignore_power_balance=ignore_power_balance,
300
+ )
301
+
302
+ def calculate_machinery_system_output_from_statistics(
303
+ self,
304
+ *,
305
+ propulsion_power: np.ndarray,
306
+ frequency: np.ndarray,
307
+ auxiliary_power_kw: Numeric,
308
+ fuel_specified_by: FuelSpecifiedBy = FuelSpecifiedBy.IMO,
309
+ ignore_power_balance: bool = False,
310
+ ) -> Union[FEEMSResult, FEEMSResultForMachinerySystem]:
311
+ """
312
+ Calculate the machinery system output from statistics of the propulsion power.
313
+
314
+ Args:
315
+ propulsion_power(np.ndarray): The propulsion power for each mode in kW.
316
+ frequency(np.ndarray): The frequency of each mode in seconds. If the frequency is
317
+ given as normalized value, the output should be interpreted as per second value.
318
+ auxiliary_power_kw(Numeric): The auxiliary power for each mode in kW. It is also
319
+ possible to give a single value for all modes.
320
+ fuel_specified_by(FuelSpecifiedBy): The fuel specified by IMO/EU/USER. Default is IMO.
321
+ ignore_power_balance(bool): If True, the power balance calculation will be ignored.
322
+
323
+ Returns:
324
+ The result of the simulation. FEEMSResult or FEEMSResultForMachinery system.
325
+ """
326
+ if not np.isscalar(auxiliary_power_kw):
327
+ assert (
328
+ len(propulsion_power) == len(auxiliary_power_kw)
329
+ or len(auxiliary_power_kw) == 1
330
+ ), "The length of the auxiliary power must be 1 or the same as the propulsion power"
331
+ self._set_input_load_time_interval_from_propulsion_power_time_series(
332
+ propulsion_power_time_series=pd.Series(
333
+ data=propulsion_power, index=frequency
334
+ ),
335
+ auxiliary_load_kw=auxiliary_power_kw,
336
+ time_is_given_as_interval=True,
337
+ )
338
+ return self._run_simulation(
339
+ fuel_specified_by=fuel_specified_by,
340
+ ignore_power_balance=ignore_power_balance,
341
+ )
@@ -0,0 +1,205 @@
1
+ # AUTOGENERATED! DO NOT EDIT! File to edit: ../01_pms_basic.ipynb.
2
+
3
+ # %% auto 0
4
+ __all__ = [
5
+ "OnPattern",
6
+ "Load2OnPattern",
7
+ "PmsLoadTable",
8
+ "get_rated_power_from_power_source",
9
+ "get_min_load_table_dict_from_proto_system",
10
+ "get_min_load_table_dict_from_feems_system",
11
+ "min_load_table_dict",
12
+ "PmsLoadTableSimulationInterface",
13
+ ]
14
+
15
+ # %% ../01_pms_basic.ipynb 3
16
+ import itertools
17
+ from dataclasses import dataclass
18
+ from itertools import chain
19
+ from typing import Dict, List, Tuple, Union
20
+
21
+ import numpy as np
22
+
23
+ from feems.components_model import SwbId
24
+ from feems.simulation_interface import SimulationInterface, EnergySourceType
25
+ from feems.system_model import (
26
+ ElectricPowerSystem,
27
+ MechanicalPropulsionSystemWithElectricPowerSystem,
28
+ HybridPropulsionSystem,
29
+ )
30
+ from feems.types_for_feems import Power_kW, TypeComponent
31
+
32
+ import MachSysS.system_structure_pb2 as proto_system
33
+ import MachSysS.convert_to_feems as feems_converter
34
+
35
+
36
+ OnPattern = Tuple[
37
+ bool, ...
38
+ ] # Tuple of on-status for each producer in a power management system.
39
+ Load2OnPattern = Dict[
40
+ Power_kW, OnPattern
41
+ ] # Mapping from load power into PMS on-status.
42
+
43
+
44
+ @dataclass
45
+ class PmsLoadTable:
46
+ """Table of producer OnPattern for each minimum load of a power management system."""
47
+
48
+ min_load2on_pattern: Load2OnPattern
49
+
50
+ def __post_init__(self) -> None:
51
+ # Sort belonging loads and patterns to obtain increasing loads with keys and values in separate tuples.
52
+ self._bins, self._i2on = zip(*sorted(self.min_load2on_pattern.items()))
53
+
54
+ def sorted_load_table(self) -> Tuple[Tuple[Power_kW, ...], Tuple[OnPattern, ...]]:
55
+ """Return the sorted load table with loads and patterns separated."""
56
+ return self._bins, self._i2on
57
+
58
+ def on_pattern(
59
+ self, load: Union[List[float], List[Power_kW], np.ndarray]
60
+ ) -> List[OnPattern]:
61
+ """Return one OnPattern for each input value in load vector."""
62
+ return [self._i2on[i] for i in np.digitize(load, self._bins[1:])] # type: ignore[no-untyped-call]
63
+
64
+
65
+ def get_rated_power_from_power_source(subsystem: proto_system.Subsystem) -> float:
66
+ if subsystem.rated_power_kw > 0:
67
+ return subsystem.rated_power_kw
68
+ else:
69
+ component = feems_converter.collect_electric_components_from_sub_system(
70
+ subsystem
71
+ )[0].get("proto_component")
72
+ return component.rated_power_kw
73
+
74
+
75
+ def get_min_load_table_dict_from_proto_system(
76
+ system: proto_system.MachinerySystem,
77
+ ) -> Load2OnPattern:
78
+ """Return a minimum load table dict generated from a protubuf message"""
79
+ gensets_rated_power_kw: List[Power_kW] = []
80
+ # Loop inspired by system_configuration_graphic.converter.converter.FeemsConverter.model()
81
+ for switchboard in system.electric_system.switchboards:
82
+ groups_rated_power = [
83
+ get_rated_power_from_power_source(subsystem)
84
+ for subsystem in switchboard.subsystems
85
+ if subsystem.power_type == proto_system.Subsystem.PowerType.POWER_SOURCE
86
+ ]
87
+ gensets_rated_power_kw.extend(groups_rated_power)
88
+
89
+ return min_load_table_dict(
90
+ gensets_rated_power_kw, system.maximum_allowed_genset_load_percentage / 100
91
+ )
92
+
93
+
94
+ def get_min_load_table_dict_from_feems_system(
95
+ system: Union[
96
+ ElectricPowerSystem,
97
+ MechanicalPropulsionSystemWithElectricPowerSystem,
98
+ HybridPropulsionSystem,
99
+ ],
100
+ maximum_allowed_genset_load_percentage,
101
+ component_types: List[TypeComponent] = None,
102
+ ) -> Load2OnPattern:
103
+ """Return a minimum load table dict generated from a feems model"""
104
+ electric_system = (
105
+ system if not hasattr(system, "electric_system") else system.electric_system
106
+ )
107
+ power_sources = [component for component in electric_system.power_sources]
108
+ if component_types is not None:
109
+ power_sources = [
110
+ component
111
+ for component in power_sources
112
+ if component.type in component_types
113
+ ]
114
+ rated_power_all = [power_source.rated_power for power_source in power_sources]
115
+ return min_load_table_dict(
116
+ rated_power_all, maximum_allowed_genset_load_percentage / 100
117
+ )
118
+
119
+
120
+ def min_load_table_dict(
121
+ rated_power_kw: List[Power_kW], max_load_factor: float
122
+ ) -> Load2OnPattern:
123
+ """Return a minimum load table dict generated from a list of genset ratings."""
124
+ patterns = list(itertools.product([False, True], repeat=len(rated_power_kw)))
125
+ assert len(patterns) == 2 ** len(rated_power_kw)
126
+ loads = [
127
+ Power_kW(
128
+ max_load_factor * sum(pwr for pwr, on in zip(rated_power_kw, pat) if on)
129
+ )
130
+ for pat in patterns
131
+ ]
132
+ assert len(loads) == len(patterns)
133
+ # Sort belonging loads and patterns to obtain increasing loads.
134
+ loads, patterns = zip(*sorted(zip(loads, patterns))) # type: ignore[assignment]
135
+ assert loads[0] == Power_kW(0)
136
+ assert patterns[0] == (False,) * len(rated_power_kw)
137
+ assert patterns[-1] == (True,) * len(rated_power_kw)
138
+ # Use all maximum loads except the highest, as minimum loads to activate the on patterns for the next higher
139
+ # load, i.e. ignoring the all-off-pattern.
140
+ return dict(zip(loads[:-1], patterns[1:]))
141
+
142
+
143
+ class PmsLoadTableSimulationInterface(SimulationInterface):
144
+ def __init__(
145
+ self,
146
+ *,
147
+ n_bus_ties: int,
148
+ pms_load_table: PmsLoadTable,
149
+ ):
150
+ _, on_patterns = pms_load_table.sorted_load_table()
151
+ self._n_power_sources = len(on_patterns[0])
152
+ self._n_bus_ties = n_bus_ties
153
+ self._pms_load_table = pms_load_table
154
+ assert all(
155
+ self._n_power_sources == len(v)
156
+ for v in pms_load_table.min_load2on_pattern.values()
157
+ ), f"All PMS on_pattern lengths must match the genset count = {self._n_power_sources}"
158
+
159
+ def set_status(
160
+ self,
161
+ *,
162
+ power_kw_per_switchboard: Dict[SwbId, np.ndarray],
163
+ electric_power_system: ElectricPowerSystem,
164
+ time_interval_s: np.ndarray,
165
+ power_source_priority: EnergySourceType = EnergySourceType.LNG_DIESEL,
166
+ ) -> None:
167
+ # Check that all switchboards have the same number of datapoints. If not, check that
168
+ # the switchboard with different number points has only one datapoint with value 0
169
+ # where there is no load.
170
+ n_datapoints = set(len(v) for v in power_kw_per_switchboard.values())
171
+ n_datapoint_max = max(n_datapoints)
172
+ if len(n_datapoints) > 1:
173
+ for swb_id, power_kw in power_kw_per_switchboard.items():
174
+ if (
175
+ len(power_kw) < n_datapoint_max
176
+ and len(power_kw) != 1
177
+ and power_kw[0] != 0
178
+ ):
179
+ raise ValueError(
180
+ f"Load vector for switchboard {swb_id} has length {len(power_kw)} "
181
+ f"but should have length {n_datapoint_max} or 1 with 0 value."
182
+ )
183
+ n_datapoints = n_datapoint_max
184
+ total_power_kw = sum(power_kw_per_switchboard.values(), np.zeros(n_datapoints))
185
+ off_vector = np.zeros(n_datapoints)
186
+ on_vector = np.ones(n_datapoints)
187
+ equal_load_sharing_vector = np.zeros(n_datapoints)
188
+ number_power_sources = len(electric_power_system.power_sources)
189
+ on_pattern_per_datapoint = np.array(
190
+ self._pms_load_table.on_pattern(total_power_kw)
191
+ )
192
+ assert on_pattern_per_datapoint.shape == (n_datapoints, self._n_power_sources)
193
+ if self._n_bus_ties > 0:
194
+ electric_power_system.set_bus_tie_status_all(
195
+ np.ones([n_datapoints, self._n_bus_ties])
196
+ )
197
+ assert (
198
+ self._n_power_sources == number_power_sources
199
+ ), f"The electric_power_system.power_sources count is different from {self._n_power_sources}"
200
+ for i, source in enumerate(electric_power_system.power_sources):
201
+ source.status = on_pattern_per_datapoint[:, i]
202
+ source.load_sharing_mode = equal_load_sharing_vector
203
+ for component in chain(electric_power_system.energy_storage):
204
+ component.status = off_vector
205
+ component.load_sharing_mode = equal_load_sharing_vector
@@ -0,0 +1,73 @@
1
+ Metadata-Version: 2.1
2
+ Name: RunFeemsSim
3
+ Version: 0.2.2
4
+ Summary: A library for running feems simulation
5
+ Home-page: https://SintefOceanEnergySystem@dev.azure.com/SintefOceanEnergySystem/FEEMSService/_git/RunFEEMSSim
6
+ Author: Kevin Koosup Yum
7
+ Author-email: kevinkoosup.yum@sintef.no
8
+ License: Apache Software License 2.0
9
+ Keywords: FEEMS,machinery system,fuel,emissions
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Natural Language :: English
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: License :: OSI Approved :: Apache Software License
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: pip
19
+ Requires-Dist: packaging
20
+ Requires-Dist: pandas
21
+ Requires-Dist: numpy
22
+ Requires-Dist: MachSysS
23
+ Provides-Extra: dev
24
+
25
+ # RunFeemsSim Package
26
+
27
+ > The RunFeemsSim package is a Python package for running FEEMS simulations. It provides a simple
28
+ > interface for running FEEMS simulations and for visualizing the results. It also provides a basic
29
+ > pms model to apply for an electric power system that has a functionality of load dependent
30
+ > start-stop of gensets.
31
+
32
+ ## Installation of the package
33
+ The package is distributed by the Azure Artifacts package manager. To install the package,
34
+ you need to install artifacts-keyring package and should have a valid Azure DevOps account.
35
+ ```sh
36
+ pip install artifacts-keyring
37
+ ```
38
+ Then, you need to add the package source to your pip configuration. You can do this by copying
39
+ the pip configuration file (pip.conf for macOS and Linux or pip.ini for Windows) to your
40
+ virtual environment directory or base python directory. The file should contain the following lines:
41
+ ```sh
42
+ [global]
43
+ extra-index-url=https://pkgs.dev.azure.com/SintefOceanEnergySystem/_packaging/SintefOceanEnergySystem/pypi/simple/
44
+ ```
45
+ If you already have a pip configuration file, you can add the above lines to the file. Finally,
46
+ you can install the package by running the following command:
47
+ ```sh
48
+ pip install RunFeemsSim
49
+ ```
50
+
51
+ ## Installation of the development environment
52
+ For the development, one should create a virtual environment and install the package using the
53
+ requirements.txt file. First, create a virtual environment and activate it.
54
+ ```sh
55
+ python -m venv venv
56
+ source venv/bin/activate
57
+ ```
58
+ Then, you need to add the package source to your pip configuration. You can do this by copying
59
+ the pip configuration file (pip.conf for macOS and Linux or pip.ini for Windows) to your venv
60
+ directory or base python directory. The file should contain the following lines:
61
+ ```sh
62
+ [global]
63
+ extra-index-url=https://pkgs.dev.azure.com/SintefOceanEnergySystem/_packaging/SintefOceanEnergySystem/pypi/simple/
64
+ ```
65
+
66
+ Then, install the package using the requirements.txt file.
67
+ ```sh
68
+ pip install -r requirements.txt
69
+ ```
70
+
71
+ ## Usage
72
+
73
+
@@ -0,0 +1,17 @@
1
+ CONTRIBUTING.md
2
+ LICENSE
3
+ MANIFEST.in
4
+ README.md
5
+ settings.ini
6
+ setup.py
7
+ RunFeemsSim/__init__.py
8
+ RunFeemsSim/_modidx.py
9
+ RunFeemsSim/_nbdev.py
10
+ RunFeemsSim/machinery_calculation.py
11
+ RunFeemsSim/pms_basic.py
12
+ RunFeemsSim.egg-info/PKG-INFO
13
+ RunFeemsSim.egg-info/SOURCES.txt
14
+ RunFeemsSim.egg-info/dependency_links.txt
15
+ RunFeemsSim.egg-info/not-zip-safe
16
+ RunFeemsSim.egg-info/requires.txt
17
+ RunFeemsSim.egg-info/top_level.txt
@@ -0,0 +1,7 @@
1
+ pip
2
+ packaging
3
+ pandas
4
+ numpy
5
+ MachSysS
6
+
7
+ [dev]
@@ -0,0 +1 @@
1
+ RunFeemsSim
@@ -0,0 +1,27 @@
1
+ [DEFAULT]
2
+ host = Azure
3
+ lib_name = RunFeemsSim
4
+ user = kevinkoosup.yum@sintef.no
5
+ description = A library for running feems simulation
6
+ keywords = FEEMS, machinery system, fuel, emissions
7
+ author = Kevin Koosup Yum
8
+ author_email = kevinkoosup.yum@sintef.no
9
+ copyright = SINTEF
10
+ branch = master
11
+ version = 0.2.2
12
+ min_python = 3.10
13
+ audience = Developers
14
+ language = English
15
+ custom_sidebar = False
16
+ license = apache2
17
+ status = 2
18
+ requirements = pandas numpy MachSysS
19
+ nbs_path = .
20
+ doc_path = docs
21
+ recursive = False
22
+ doc_baseurl = /RunFeemsSim/
23
+ git_url = https://SintefOceanEnergySystem@dev.azure.com/SintefOceanEnergySystem/FEEMSService/_git/RunFEEMSSim
24
+ lib_path = RunFeemsSim
25
+ title = RunFeemsSim
26
+ doc_host = https://kevinkoosup.yum@sintef.no.github.io
27
+
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,113 @@
1
+ from pkg_resources import parse_version
2
+ from configparser import ConfigParser
3
+ import setuptools, re, sys
4
+
5
+ assert parse_version(setuptools.__version__) >= parse_version("36.2")
6
+
7
+ # note: all settings are in settings.ini; edit there, not here
8
+ config = ConfigParser(delimiters=["="])
9
+ config.read("settings.ini")
10
+ cfg = config["DEFAULT"]
11
+
12
+ cfg_keys = "version description keywords author author_email".split()
13
+ expected = (
14
+ cfg_keys
15
+ + "lib_name user branch license status min_python audience language".split()
16
+ )
17
+ for o in expected:
18
+ assert o in cfg, "missing expected setting: {}".format(o)
19
+ setup_cfg = {o: cfg[o] for o in cfg_keys}
20
+
21
+ if len(sys.argv) > 1 and sys.argv[1] == "version":
22
+ print(setup_cfg["version"])
23
+ exit()
24
+
25
+ licenses = {
26
+ "apache2": (
27
+ "Apache Software License 2.0",
28
+ "OSI Approved :: Apache Software License",
29
+ ),
30
+ "mit": ("MIT License", "OSI Approved :: MIT License"),
31
+ "gpl2": (
32
+ "GNU General Public License v2",
33
+ "OSI Approved :: GNU General Public License v2 (GPLv2)",
34
+ ),
35
+ "gpl3": (
36
+ "GNU General Public License v3",
37
+ "OSI Approved :: GNU General Public License v3 (GPLv3)",
38
+ ),
39
+ "bsd3": ("BSD License", "OSI Approved :: BSD License"),
40
+ }
41
+ statuses = [
42
+ "1 - Planning",
43
+ "2 - Pre-Alpha",
44
+ "3 - Alpha",
45
+ "4 - Beta",
46
+ "5 - Production/Stable",
47
+ "6 - Mature",
48
+ "7 - Inactive",
49
+ ]
50
+ py_versions = "2.0 2.1 2.2 2.3 2.4 2.5 2.6 2.7 3.0 3.1 3.2 3.3 3.4 3.5 3.6 3.7 3.8 3.9 3.10".split()
51
+
52
+ lic = licenses.get(cfg["license"].lower(), (cfg["license"], None))
53
+ min_python = cfg["min_python"]
54
+
55
+ requirements = ["pip", "packaging"]
56
+ if cfg.get("requirements"):
57
+ requirements += cfg.get("requirements", "").split()
58
+ if cfg.get("pip_requirements"):
59
+ requirements += cfg.get("pip_requirements", "").split()
60
+ dev_requirements = (cfg.get("dev_requirements") or "").split()
61
+
62
+ long_description = open("README.md").read()
63
+ # ![png](docs/images/output_13_0.png)
64
+ for ext in ["png", "svg"]:
65
+ long_description = re.sub(
66
+ r"!\[" + ext + "\]\((.*)\)",
67
+ "!["
68
+ + ext
69
+ + "]("
70
+ + "https://raw.githubusercontent.com/{}/{}".format(cfg["user"], cfg["lib_name"])
71
+ + "/"
72
+ + cfg["branch"]
73
+ + "/\\1)",
74
+ long_description,
75
+ )
76
+ long_description = re.sub(
77
+ r"src=\"(.*)\." + ext + '"',
78
+ 'src="https://raw.githubusercontent.com/{}/{}'.format(
79
+ cfg["user"], cfg["lib_name"]
80
+ )
81
+ + "/"
82
+ + cfg["branch"]
83
+ + "/\\1."
84
+ + ext
85
+ + '"',
86
+ long_description,
87
+ )
88
+
89
+ setuptools.setup(
90
+ name=cfg["lib_name"],
91
+ license=lic[0],
92
+ classifiers=[
93
+ "Development Status :: " + statuses[int(cfg["status"])],
94
+ "Intended Audience :: " + cfg["audience"].title(),
95
+ "Natural Language :: " + cfg["language"].title(),
96
+ ]
97
+ + [
98
+ "Programming Language :: Python :: " + o
99
+ for o in py_versions[py_versions.index(min_python) :]
100
+ ]
101
+ + (["License :: " + lic[1]] if lic[1] else []),
102
+ url=cfg["git_url"],
103
+ packages=setuptools.find_packages(),
104
+ include_package_data=True,
105
+ install_requires=requirements,
106
+ extras_require={"dev": dev_requirements},
107
+ python_requires=">=" + cfg["min_python"],
108
+ long_description=long_description,
109
+ long_description_content_type="text/markdown",
110
+ zip_safe=False,
111
+ entry_points={"console_scripts": cfg.get("console_scripts", "").split()},
112
+ **setup_cfg
113
+ )