pywr 2.0.0.dev0__cp313-cp313t-win_amd64.whl
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.
- pywr/__init__.py +61 -0
- pywr/__main__.py +4 -0
- pywr/cli.py +48 -0
- pywr/py.typed +0 -0
- pywr/pywr.cp313t-win_amd64.pyd +0 -0
- pywr/pywr.pyi +241 -0
- pywr-2.0.0.dev0.dist-info/METADATA +349 -0
- pywr-2.0.0.dev0.dist-info/RECORD +10 -0
- pywr-2.0.0.dev0.dist-info/WHEEL +4 -0
- pywr-2.0.0.dev0.dist-info/licenses/LICENSE +21 -0
pywr/__init__.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from .pywr import (
|
|
5
|
+
ModelSchema,
|
|
6
|
+
MultiNetworkModelSchema,
|
|
7
|
+
Model,
|
|
8
|
+
MultiNetworkModel,
|
|
9
|
+
ModelResult,
|
|
10
|
+
ModelTimings,
|
|
11
|
+
MultiNetworkModelTimings,
|
|
12
|
+
Timestep,
|
|
13
|
+
ScenarioIndex,
|
|
14
|
+
ParameterInfo,
|
|
15
|
+
Metric,
|
|
16
|
+
ComponentConversionError,
|
|
17
|
+
ConversionError,
|
|
18
|
+
convert_model_from_v1_json_string,
|
|
19
|
+
convert_metric_from_v1_json_string,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"ModelSchema",
|
|
24
|
+
"MultiNetworkModelSchema",
|
|
25
|
+
"Model",
|
|
26
|
+
"MultiNetworkModel",
|
|
27
|
+
"ModelResult",
|
|
28
|
+
"ModelTimings",
|
|
29
|
+
"MultiNetworkModelTimings",
|
|
30
|
+
"Timestep",
|
|
31
|
+
"ScenarioIndex",
|
|
32
|
+
"ParameterInfo",
|
|
33
|
+
"Metric",
|
|
34
|
+
"ComponentConversionError",
|
|
35
|
+
"ConversionError",
|
|
36
|
+
"convert_model_from_v1_json_string",
|
|
37
|
+
"convert_metric_from_v1_json_string",
|
|
38
|
+
"run_from_path",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def run_from_path(
|
|
43
|
+
filename: Path,
|
|
44
|
+
data_path: Optional[Path] = None,
|
|
45
|
+
output_path: Optional[Path] = None,
|
|
46
|
+
solver: str = "clp",
|
|
47
|
+
):
|
|
48
|
+
"""Load and run a Pywr model from a file path.
|
|
49
|
+
|
|
50
|
+
If the `data_path` and `output_path` are not specified, they will be set to the
|
|
51
|
+
directory containing the model file.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
if data_path is None:
|
|
55
|
+
data_path = filename.parent
|
|
56
|
+
if output_path is None:
|
|
57
|
+
output_path = filename.parent
|
|
58
|
+
|
|
59
|
+
schema = ModelSchema.from_path(filename)
|
|
60
|
+
model = schema.build(data_path=data_path, output_path=output_path)
|
|
61
|
+
model.run(solver)
|
pywr/__main__.py
ADDED
pywr/cli.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from . import run_from_path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def configure_logging():
|
|
10
|
+
logger = logging.getLogger("pywr")
|
|
11
|
+
logger.setLevel(logging.INFO)
|
|
12
|
+
|
|
13
|
+
ch = logging.StreamHandler()
|
|
14
|
+
formatter = logging.Formatter(
|
|
15
|
+
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
16
|
+
)
|
|
17
|
+
ch.setFormatter(formatter)
|
|
18
|
+
logger.addHandler(ch)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@click.group()
|
|
22
|
+
def cli():
|
|
23
|
+
configure_logging()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@cli.command()
|
|
27
|
+
@click.argument("path", type=click.Path(exists=True, file_okay=True))
|
|
28
|
+
@click.option("-s", "--solver", type=click.Choice(["clp", "highs"]), default="clp")
|
|
29
|
+
@click.option(
|
|
30
|
+
"-d", "--data-path", type=click.Path(exists=True, dir_okay=True), default=None
|
|
31
|
+
)
|
|
32
|
+
@click.option(
|
|
33
|
+
"-o", "--output-path", type=click.Path(exists=True, dir_okay=True), default=None
|
|
34
|
+
)
|
|
35
|
+
def run(path: str, solver: str, data_path: Optional[str], output_path: Optional[str]):
|
|
36
|
+
data_path = Path(data_path) if data_path is not None else None
|
|
37
|
+
output_path = Path(output_path) if output_path is not None else None
|
|
38
|
+
|
|
39
|
+
run_from_path(
|
|
40
|
+
Path(path),
|
|
41
|
+
solver=solver,
|
|
42
|
+
data_path=data_path,
|
|
43
|
+
output_path=output_path,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def start_cli():
|
|
48
|
+
cli()
|
pywr/py.typed
ADDED
|
File without changes
|
|
Binary file
|
pywr/pywr.pyi
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from os import PathLike
|
|
3
|
+
from typing import Optional, List
|
|
4
|
+
import polars as pl
|
|
5
|
+
|
|
6
|
+
class ParameterInfo:
|
|
7
|
+
"""Provides data for a custom Pywr parameter.
|
|
8
|
+
|
|
9
|
+
This is a read-only object that provides information that can be used for custom parameters in Pywr. It
|
|
10
|
+
is passed as the first argument to the `calc` and `after` methods of custom parameter objects.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def timestep(self) -> "Timestep":
|
|
15
|
+
"""Returns the current time-step object."""
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def scenario_index(self) -> "ScenarioIndex":
|
|
19
|
+
"""Returns the current scenario index object."""
|
|
20
|
+
|
|
21
|
+
def get_metric(self, name: str) -> float:
|
|
22
|
+
"""Returns a metric by name.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
name: The name of the metric to retrieve.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def get_index(self, name: str) -> int:
|
|
29
|
+
"""Returns the index of a component by name.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
name: The name of the component to retrieve the index for.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
class Timestep:
|
|
36
|
+
"""Represents a single time-step in a simulation.
|
|
37
|
+
|
|
38
|
+
This is a read-only object that provides information about the current time-step.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def is_first(self) -> bool:
|
|
43
|
+
"""Returns true if this is the first time-step."""
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def days(self) -> float:
|
|
47
|
+
"""Returns the duration of the time-step in number of days including any fractional part."""
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def date(self) -> datetime:
|
|
51
|
+
"""Returns the date of the time-step."""
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def day(self) -> int:
|
|
55
|
+
"""Returns the day of the time-step."""
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def month(self) -> int:
|
|
59
|
+
"""Returns the month of the time-step."""
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def year(self) -> int:
|
|
63
|
+
"""Returns the year of the time-step."""
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def index(self) -> int:
|
|
67
|
+
"""Returns the current time-step index."""
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def day_of_year(self) -> int:
|
|
71
|
+
"""Returns the day of the year index of the timestep.
|
|
72
|
+
|
|
73
|
+
The day of the year is one-based, meaning January 1st is day 1 and December 31st is day 365 (or 366 in leap years).
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def day_of_year_index(self) -> int:
|
|
78
|
+
"""Returns the day of the year index of the timestep.
|
|
79
|
+
|
|
80
|
+
The index is zero-based and accounts for leaps days. In non-leap years, 1 i to the index for
|
|
81
|
+
days after Feb 28th.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def fractional_day_of_year(self) -> float:
|
|
86
|
+
"""Returns the fraction day of the year of the timestep.
|
|
87
|
+
|
|
88
|
+
The index is zero-based and accounts for leaps days. In non-leap years, 1 is added to the index for
|
|
89
|
+
days after Feb 28th. The fractional part is the fraction of the day that has passed since midnight
|
|
90
|
+
(calculated to the nearest second).
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def is_leap_year(self) -> bool:
|
|
95
|
+
"""Returns true if the year of the timestep is a leap year."""
|
|
96
|
+
|
|
97
|
+
class ScenarioIndex:
|
|
98
|
+
"""Represents a scenario index in a Pywr model.
|
|
99
|
+
|
|
100
|
+
This is a read-only object that provides information about the current scenario index.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def simulation_id(self) -> int:
|
|
105
|
+
"""Returns the current simulation id."""
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def simulation_indices(self) -> List[int]:
|
|
109
|
+
"""Returns indices for each scenario group for this simulation."""
|
|
110
|
+
|
|
111
|
+
class ModelSchema:
|
|
112
|
+
@classmethod
|
|
113
|
+
def from_path(cls, path: PathLike) -> "ModelSchema":
|
|
114
|
+
"""Create a new schema object from a file path.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
path: The path to the schema JSON file.
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
@classmethod
|
|
121
|
+
def from_json_string(cls, json_string: str) -> "ModelSchema":
|
|
122
|
+
"""Create a new schema object from a JSON string.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
json_string: The JSON string representing the schema.
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
def to_json_string(self) -> str:
|
|
129
|
+
"""Serialize the schema to a JSON string."""
|
|
130
|
+
|
|
131
|
+
def build(
|
|
132
|
+
self, data_path: Optional[PathLike], output_path: Optional[PathLike]
|
|
133
|
+
) -> "Model":
|
|
134
|
+
"""Build the schema in to a Pywr model."""
|
|
135
|
+
|
|
136
|
+
class MultiNetworkModelSchema:
|
|
137
|
+
@classmethod
|
|
138
|
+
def from_path(cls, path: PathLike) -> "ModelSchema":
|
|
139
|
+
"""Create a new schema object from a file path.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
path: The path to the schema JSON file.
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
@classmethod
|
|
146
|
+
def from_json_string(cls, json_string: str) -> "ModelSchema":
|
|
147
|
+
"""Create a new schema object from a JSON string.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
json_string: The JSON string representing the schema.
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
def to_json_string(self) -> str:
|
|
154
|
+
"""Serialize the schema to a JSON string."""
|
|
155
|
+
|
|
156
|
+
def build(
|
|
157
|
+
self, data_path: Optional[PathLike], output_path: Optional[PathLike]
|
|
158
|
+
) -> "Model":
|
|
159
|
+
"""Build the schema in to a Pywr model."""
|
|
160
|
+
|
|
161
|
+
class Model:
|
|
162
|
+
def run(self, solver_name: str, solver_kwargs: Optional[dict] = None):
|
|
163
|
+
"""Run the model using the specified solver.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
solver_name: The name of the solver to use.
|
|
167
|
+
solver_kwargs: Optional keyword arguments to pass to the solver.
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
class MultiNetworkModel:
|
|
171
|
+
def run(self, solver_name: str, solver_kwargs: Optional[dict] = None):
|
|
172
|
+
"""Run the model using the specified solver.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
solver_name: The name of the solver to use.
|
|
176
|
+
solver_kwargs: Optional keyword arguments to pass to the solver.
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
class ModelResult:
|
|
180
|
+
@property
|
|
181
|
+
def network_result(self) -> "NetworkResult":
|
|
182
|
+
"""Returns the network result object."""
|
|
183
|
+
|
|
184
|
+
@property
|
|
185
|
+
def timings(self) -> "ModelTimings":
|
|
186
|
+
"""Returns the model timings object."""
|
|
187
|
+
|
|
188
|
+
class MultiNetworkModelResult:
|
|
189
|
+
def network_results(self, name: str) -> "NetworkResult":
|
|
190
|
+
"""Get the network result for a specific network by name.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
name: The name of the network to retrieve the results for.
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
@property
|
|
197
|
+
def timings(self) -> "MultiNetworkModelTimings":
|
|
198
|
+
"""Returns the model timings object."""
|
|
199
|
+
|
|
200
|
+
class NetworkResult:
|
|
201
|
+
def aggregated_value(self, name: str) -> float:
|
|
202
|
+
"""Get the aggregated value of a recorder by name, if it exists and can be aggregated.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
name: The name of the output to retrieve.
|
|
206
|
+
"""
|
|
207
|
+
|
|
208
|
+
def to_dataframe(self, name: str) -> pl.DataFrame:
|
|
209
|
+
"""Get the output of a recorder by name as a polars DataFrame.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
name: The name of the output to retrieve.
|
|
213
|
+
"""
|
|
214
|
+
|
|
215
|
+
def output_names(self) -> list[str]:
|
|
216
|
+
"""Get a list of all available output names."""
|
|
217
|
+
|
|
218
|
+
class ModelTimings:
|
|
219
|
+
@property
|
|
220
|
+
def total_duration(self) -> float:
|
|
221
|
+
"""Total duration of the model run in seconds."""
|
|
222
|
+
|
|
223
|
+
@property
|
|
224
|
+
def speed(self) -> float:
|
|
225
|
+
"""Model speed in timesteps per second."""
|
|
226
|
+
|
|
227
|
+
class MultiNetworkModelTimings:
|
|
228
|
+
@property
|
|
229
|
+
def total_duration(self) -> float:
|
|
230
|
+
"""Total duration of the model run in seconds."""
|
|
231
|
+
|
|
232
|
+
@property
|
|
233
|
+
def speed(self) -> float:
|
|
234
|
+
"""Model speed in timesteps per second."""
|
|
235
|
+
|
|
236
|
+
class Metric: ...
|
|
237
|
+
class ComponentConversionError: ...
|
|
238
|
+
class ConversionError: ...
|
|
239
|
+
|
|
240
|
+
def convert_model_from_v1_json_string(data: str): ...
|
|
241
|
+
def convert_metric_from_v1_json_string(data: str): ...
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pywr
|
|
3
|
+
Version: 2.0.0.dev0
|
|
4
|
+
Requires-Dist: pandas
|
|
5
|
+
Requires-Dist: polars>=1.32
|
|
6
|
+
Requires-Dist: pyarrow
|
|
7
|
+
Requires-Dist: click
|
|
8
|
+
Requires-Dist: openpyxl ; extra == 'excel'
|
|
9
|
+
Requires-Dist: h5py ; extra == 'hdf'
|
|
10
|
+
Provides-Extra: excel
|
|
11
|
+
Provides-Extra: hdf
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Summary:
|
|
14
|
+
Author-email: James Tomlinson <tomo.bbe@gmail.com>
|
|
15
|
+
License-Expression: MIT OR Apache-2.0
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
18
|
+
|
|
19
|
+
<!-- PROJECT SHIELDS -->
|
|
20
|
+
<!--
|
|
21
|
+
*** I'm using markdown "reference style" links for readability.
|
|
22
|
+
*** Reference links are enclosed in brackets [ ] instead of parentheses ( ).
|
|
23
|
+
*** See the bottom of this document for the declaration of the reference variables
|
|
24
|
+
*** for contributors-url, forks-url, etc. This is an optional, concise syntax you may use.
|
|
25
|
+
*** https://www.markdownguide.org/basic-syntax/#reference-style-links
|
|
26
|
+
-->
|
|
27
|
+
[![Contributors][contributors-shield]][contributors-url]
|
|
28
|
+
[![Forks][forks-shield]][forks-url]
|
|
29
|
+
[![Stargazers][stars-shield]][stars-url]
|
|
30
|
+
[![Issues][issues-shield]][issues-url]
|
|
31
|
+
[![MIT License][license-shield]][license-url]
|
|
32
|
+
[![LinkedIn][linkedin-shield]][linkedin-url]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
<!-- PROJECT LOGO -->
|
|
36
|
+
<br />
|
|
37
|
+
<div align="center">
|
|
38
|
+
|
|
39
|
+
<!--
|
|
40
|
+
*** <a href="https://github.com/pywr/pywr-next">
|
|
41
|
+
*** <img src="images/logo.png" alt="Logo" width="80" height="80">
|
|
42
|
+
*** </a>
|
|
43
|
+
-->
|
|
44
|
+
|
|
45
|
+
<h3 align="center">Pywr-next</h3>
|
|
46
|
+
|
|
47
|
+
<p align="center">
|
|
48
|
+
This is repository contains the current work-in-progress for the next major revision to
|
|
49
|
+
<a href="https://github.com/pywr/pywr">Pywr.</a> It uses Rust as a backend instead of Cython. It
|
|
50
|
+
is currently not ready for use beyond development and experimentation. Comments and discussions are welcome.
|
|
51
|
+
<br />
|
|
52
|
+
<br />
|
|
53
|
+
<a href="https://pywr.github.io/pywr-next/">User Guide</a>
|
|
54
|
+
·
|
|
55
|
+
<a href="https://github.com/pywr/pywr-next/issues">Report Bug</a>
|
|
56
|
+
·
|
|
57
|
+
<a href="https://github.com/pywr/pywr-next/issues">Request Feature</a>
|
|
58
|
+
</p>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
<!-- TABLE OF CONTENTS -->
|
|
64
|
+
<details>
|
|
65
|
+
<summary>Table of Contents</summary>
|
|
66
|
+
<ol>
|
|
67
|
+
<li>
|
|
68
|
+
<a href="#about-the-project">About The Project</a>
|
|
69
|
+
<ul>
|
|
70
|
+
<li><a href="#built-with">Built With</a></li>
|
|
71
|
+
</ul>
|
|
72
|
+
</li>
|
|
73
|
+
<li>
|
|
74
|
+
<a href="#getting-started">Getting Started</a>
|
|
75
|
+
<ul>
|
|
76
|
+
<li><a href="#prerequisites">Prerequisites</a></li>
|
|
77
|
+
<li><a href="#installation">Installation</a></li>
|
|
78
|
+
</ul>
|
|
79
|
+
</li>
|
|
80
|
+
<li><a href="#usage">Usage</a></li>
|
|
81
|
+
<li><a href="#roadmap">Roadmap</a></li>
|
|
82
|
+
<li><a href="#contributing">Contributing</a></li>
|
|
83
|
+
<li><a href="#license">License</a></li>
|
|
84
|
+
<li><a href="#contact">Contact</a></li>
|
|
85
|
+
<li><a href="#acknowledgments">Acknowledgments</a></li>
|
|
86
|
+
</ol>
|
|
87
|
+
</details>
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
<!-- ABOUT THE PROJECT -->
|
|
92
|
+
|
|
93
|
+
## About The Project
|
|
94
|
+
|
|
95
|
+
Pywr-1.x is a Python library which utilises Cython for performance. Over time this has resulted in a "core"
|
|
96
|
+
set of data structures and objects that are written in Cython to gain maximum performance. Cython has the nice
|
|
97
|
+
benefit of making it easy to extend that core functionality using regular Python. However, the border between what
|
|
98
|
+
is Python and what is Cython is a bit blurred and not well designed in certain places.
|
|
99
|
+
|
|
100
|
+
One option for the future development of Pywr (e.g. Pywr-2.x) would be a more explicit separation between the compute
|
|
101
|
+
"core" and higher level functionality. Rust is a candidate for writing that core largely independent of Python, and
|
|
102
|
+
possibly offers the benefits of (1) greater performance than Cython, and (2) easier maintenance in the future.
|
|
103
|
+
|
|
104
|
+
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
105
|
+
|
|
106
|
+
### Requirements
|
|
107
|
+
|
|
108
|
+
Any major revision to Pywr will have the following feature requirements:
|
|
109
|
+
|
|
110
|
+
- Retain the "Parameter" system from Pywr-1.x - this is core functionality that makes Pywr really flexible.
|
|
111
|
+
- Extendable in Python space.
|
|
112
|
+
- An improved approach for outputting data and metrics.
|
|
113
|
+
- Better error handling.
|
|
114
|
+
- Cross-platform.
|
|
115
|
+
- Faster!
|
|
116
|
+
- Strong input file (JSON) schema.
|
|
117
|
+
|
|
118
|
+
### Built With
|
|
119
|
+
|
|
120
|
+
[![Rust][Rust]][Rust-url]
|
|
121
|
+
[![Python][Python]][Python-url]
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
<!-- GETTING STARTED -->
|
|
129
|
+
|
|
130
|
+
### Getting started
|
|
131
|
+
|
|
132
|
+
This repository contains a version of Clp using Git submodules. In order to build those submodules
|
|
133
|
+
must be initialised first.
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
git submodule init
|
|
137
|
+
git submodule update
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Rust is required for installation of the Python extension. To create a Python development installation
|
|
141
|
+
requires first compiling the Rust library and then the Python extension. The following example uses
|
|
142
|
+
a virtual environment to install the Python dependencies, compile the Pywr extension and run the Pywr Python CLI.
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
python -m venv .venv # create a new virtual environment
|
|
146
|
+
source .venv/bin/activate # activate the virtual environment (linux)
|
|
147
|
+
# .venv\Scripts\activate # activate the virtual environment (windows)
|
|
148
|
+
pip install maturin # install maturin for building the Python extension
|
|
149
|
+
maturin develop # compile the Pywr Python extension
|
|
150
|
+
python -m pywr # run the Pywr Python CLI
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
<!-- USAGE EXAMPLES -->
|
|
158
|
+
|
|
159
|
+
## Usage
|
|
160
|
+
|
|
161
|
+
### Rust CLI
|
|
162
|
+
|
|
163
|
+
A basic command line interface is included such that you can use this version of Pywr without Python.
|
|
164
|
+
This CLI is in the `pywr-cli` crate.
|
|
165
|
+
|
|
166
|
+
To see the CLI commands available run the following:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
cargo run -p pywr-cli -- --help
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
To run a Pywr v2 model use the following:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
cargo run -p pywr-cli -- run tests/models/simple1.json
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Python CLI
|
|
179
|
+
|
|
180
|
+
If the Python extension has been compiled using the above instructions a model can be run using the basic Python
|
|
181
|
+
CLI.
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
python -m pywr run tests/models/simple1.json
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Porting a Pywr v1.x model to v2.x
|
|
188
|
+
|
|
189
|
+
This version of Pywr is not backward compatible with Pywr v1.x. One of the major reasons for this version is the
|
|
190
|
+
lack of a strong schema in the Pywr v1.x JSON files. Pywr v2.x uses an updated JSON schema that is defined in this
|
|
191
|
+
repository. Therefore, v1.x JSON files must be converted to the v2.x JSON schema. This conversion can be undertaken
|
|
192
|
+
manually, but there is also a work-in-progress conversion tool. The conversion tool uses a v1.x schema defined in
|
|
193
|
+
the [pywr-schema](https://github.com/pywr/pywr-schema) project.
|
|
194
|
+
|
|
195
|
+
**Please note that conversion from Pywr v1.x to v2.x is experimental and not all features of Pywr are implemented
|
|
196
|
+
in `pywr-schema` or have been implemented in Pywr v2.x yet. Due to the changes between these versions it is very
|
|
197
|
+
likely an automatic conversion will not completely convert your model, and it _WILL_ require manual testing and
|
|
198
|
+
checking.**
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
cargo run --no-default-features -- convert /path/to/my/v1.x/model.json
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Feedback on porting models is very welcome, so please open an issue with any questions or problems.
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
<!-- _For more examples, please refer to the [Documentation](https://example.com)_ -->
|
|
208
|
+
|
|
209
|
+
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
210
|
+
|
|
211
|
+
<!-- CRATES -->
|
|
212
|
+
|
|
213
|
+
## Crates
|
|
214
|
+
|
|
215
|
+
This repository contains the following crates:
|
|
216
|
+
|
|
217
|
+
### Pywr-core
|
|
218
|
+
|
|
219
|
+
A low-level Rust library for constructing network models. This crate interfaces with linear program solvers.
|
|
220
|
+
|
|
221
|
+
Feature flags:
|
|
222
|
+
|
|
223
|
+
| Feature | Description | Default |
|
|
224
|
+
|------------|--------------------------------------------------|---------|
|
|
225
|
+
| `pyo3` | Enable the Python bindings. | True |
|
|
226
|
+
| `highs` | Enable the HiGHS LP solver. | False |
|
|
227
|
+
| `ipm-ocl` | Enable the OpenCL IPM solver (requires nightly). | False |
|
|
228
|
+
| `ipm-simd` | Enable the AVX IPM solver (requires nightly). | False |
|
|
229
|
+
| `cbc` | Enable the CBC MILP solver. | False |
|
|
230
|
+
|
|
231
|
+
### Pywr-schema
|
|
232
|
+
|
|
233
|
+
A Rust library for validating Pywr JSON files against a schema, and then building a model from the schema
|
|
234
|
+
using `pywr-core`.
|
|
235
|
+
|
|
236
|
+
Feature flags:
|
|
237
|
+
|
|
238
|
+
| Feature | Description | Default |
|
|
239
|
+
|------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|
|
|
240
|
+
| `core` | Enable building models from the schema with `pywr-core`. This feature is enabled by default, but requires a lot of dependencies. If you only require schema validation and manipulation consider building this crate with `default-features = false` | True |
|
|
241
|
+
| `pyo3` | Enable the Python bindings. | True |
|
|
242
|
+
| `highs` | Enable the HiGHS LP solver. | False |
|
|
243
|
+
| `ipm-ocl` | Enable the OpenCL IPM solver (requires nightly). | False |
|
|
244
|
+
| `ipm-simd` | Enable the AVX IPM solver (requires nightly). | False |
|
|
245
|
+
| `cbc` | Enable the CBC MILP solver. | False |
|
|
246
|
+
|
|
247
|
+
### Pywr-cli
|
|
248
|
+
|
|
249
|
+
A command line interface for running Pywr models.
|
|
250
|
+
|
|
251
|
+
### Pywr-python
|
|
252
|
+
|
|
253
|
+
A Python extension (and package) for constructing and running Pywr models.
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
<!-- ROADMAP -->
|
|
257
|
+
|
|
258
|
+
## Roadmap
|
|
259
|
+
|
|
260
|
+
- [x] Proof-of-concept - demonstrate the benefits of the RIIR approach.
|
|
261
|
+
- [ ] Redesign of outputs & metrics.
|
|
262
|
+
- [ ] Redesign of variable API for integration with external optimisation algorithms.
|
|
263
|
+
- [ ] Implement outstanding `Parameters` from Pywr v1.x
|
|
264
|
+
- [ ] Design & implement Python API using Rust extension.
|
|
265
|
+
- [ ] Release Pywr v2.x beta
|
|
266
|
+
|
|
267
|
+
See the [open issues](https://github.com/pywr/pywr-next/issues) for a full list of proposed features (and known issues).
|
|
268
|
+
|
|
269
|
+
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
<!-- CONTRIBUTING -->
|
|
274
|
+
|
|
275
|
+
## Contributing
|
|
276
|
+
|
|
277
|
+
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any
|
|
278
|
+
contributions you make are **greatly appreciated**.
|
|
279
|
+
|
|
280
|
+
If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also
|
|
281
|
+
simply open an issue with the tag "enhancement".
|
|
282
|
+
Don't forget to give the project a star! Thanks again!
|
|
283
|
+
|
|
284
|
+
1. Fork the Project
|
|
285
|
+
2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
|
|
286
|
+
3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
|
|
287
|
+
4. Push to the Branch (`git push origin feature/AmazingFeature`)
|
|
288
|
+
5. Open a Pull Request
|
|
289
|
+
|
|
290
|
+
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
<!-- LICENSE -->
|
|
294
|
+
|
|
295
|
+
## License
|
|
296
|
+
|
|
297
|
+
Distributed under the Apache 2.0 or MIT License. See `LICENSE.txt` for more information.
|
|
298
|
+
|
|
299
|
+
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
300
|
+
|
|
301
|
+
<!-- CONTACT -->
|
|
302
|
+
|
|
303
|
+
## Contact
|
|
304
|
+
|
|
305
|
+
James Tomlinson - tomo.bbe@gmail.com
|
|
306
|
+
|
|
307
|
+
Project Link: [https://github.com/pywr/pywr-next](https://github.com/pywr/pywr-next)
|
|
308
|
+
|
|
309
|
+
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
<!-- MARKDOWN LINKS & IMAGES -->
|
|
314
|
+
<!-- https://www.markdownguide.org/basic-syntax/#reference-style-links -->
|
|
315
|
+
|
|
316
|
+
[contributors-shield]: https://img.shields.io/github/contributors/pywr/pywr-next.svg?style=for-the-badge
|
|
317
|
+
|
|
318
|
+
[contributors-url]: https://github.com/pywr/pywr-next/graphs/contributors
|
|
319
|
+
|
|
320
|
+
[forks-shield]: https://img.shields.io/github/forks/pywr/pywr-next.svg?style=for-the-badge
|
|
321
|
+
|
|
322
|
+
[forks-url]: https://github.com/pywr/pywr-next/network/members
|
|
323
|
+
|
|
324
|
+
[stars-shield]: https://img.shields.io/github/stars/pywr/pywr-next.svg?style=for-the-badge
|
|
325
|
+
|
|
326
|
+
[stars-url]: https://github.com/pywr/pywr-next/stargazers
|
|
327
|
+
|
|
328
|
+
[issues-shield]: https://img.shields.io/github/issues/pywr/pywr-next.svg?style=for-the-badge
|
|
329
|
+
|
|
330
|
+
[issues-url]: https://github.com/pywr/pywr-next/issues
|
|
331
|
+
|
|
332
|
+
[license-shield]: https://img.shields.io/github/license/pywr/pywr-next.svg?style=for-the-badge
|
|
333
|
+
|
|
334
|
+
[license-url]: https://github.com/pywr/pywr-next/blob/main/LICENSE
|
|
335
|
+
|
|
336
|
+
[linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555
|
|
337
|
+
|
|
338
|
+
[linkedin-url]: https://linkedin.com/in/james-tomlinson-a465352b
|
|
339
|
+
|
|
340
|
+
[Rust]: https://img.shields.io/badge/rust-ef4a23?style=for-the-badge&logo=rust&logoColor=white
|
|
341
|
+
|
|
342
|
+
[Rust-url]: https://www.rust-lang.org/
|
|
343
|
+
|
|
344
|
+
[Python]: https://img.shields.io/badge/python-275277?style=for-the-badge&logo=python&logoColor=white
|
|
345
|
+
|
|
346
|
+
[Python-url]: https://www.python.org/
|
|
347
|
+
|
|
348
|
+
Copyright (C) 2020-2023 James Tomlinson Associates Ltd.
|
|
349
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
pywr-2.0.0.dev0.dist-info/METADATA,sha256=shZcotSIr-tYWvrcHW09pBKn09Fy4oDjzJJX5wX8RdM,13649
|
|
2
|
+
pywr-2.0.0.dev0.dist-info/WHEEL,sha256=NIX3GH-EI7FzrKEcrM4JPmD21jlBdrrjPRxjD1D-qeI,97
|
|
3
|
+
pywr-2.0.0.dev0.dist-info/licenses/LICENSE,sha256=XYeBp8D-A_-e_GzJ2d-EsvNuHlhbTrDILJfREAPjoiI,1093
|
|
4
|
+
pywr/__init__.py,sha256=fLZHVN_moM7tZULdZCERwb9ThoV1QDyLTUXlUg4FWmc,1481
|
|
5
|
+
pywr/__main__.py,sha256=xkoHzRjxKvsW91lMdPz9ahq91_ZgcL78iJO54j6wNfQ,75
|
|
6
|
+
pywr/cli.py,sha256=nfrM_DWZP3TBDeUsS6gTyJ0cUHvRP-JEtuVdXXmgXT8,1265
|
|
7
|
+
pywr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
pywr/pywr.cp313t-win_amd64.pyd,sha256=mryUdoHefTJqlIHa2BRpBl5vY4L4B79gupYWQtTICcU,79547904
|
|
9
|
+
pywr/pywr.pyi,sha256=lQOdA8ROqM6corA6YyFcfjD-F0N1KczY6zjrLfIvX2I,7430
|
|
10
|
+
pywr-2.0.0.dev0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 James Tomlinson
|
|
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.
|