pastastore 1.6.0__py3-none-any.whl → 1.7.0__py3-none-any.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.
pastastore/base.py CHANGED
@@ -56,6 +56,11 @@ class BaseConnector(ABC):
56
56
  f"{self.n_models} models"
57
57
  )
58
58
 
59
+ @property
60
+ def empty(self):
61
+ """Check if the database is empty."""
62
+ return not any([self.n_oseries > 0, self.n_stresses > 0, self.n_models > 0])
63
+
59
64
  @abstractmethod
60
65
  def _get_library(self, libname: str):
61
66
  """Get library handle.
pastastore/store.py CHANGED
@@ -4,6 +4,8 @@ import json
4
4
  import logging
5
5
  import os
6
6
  import warnings
7
+ from concurrent.futures import ProcessPoolExecutor
8
+ from functools import partial
7
9
  from typing import Dict, List, Literal, Optional, Tuple, Union
8
10
 
9
11
  import numpy as np
@@ -12,6 +14,7 @@ import pastas as ps
12
14
  from packaging.version import parse as parse_version
13
15
  from pastas.io.pas import pastas_hook
14
16
  from tqdm.auto import tqdm
17
+ from tqdm.contrib.concurrent import process_map
15
18
 
16
19
  from pastastore.base import BaseConnector
17
20
  from pastastore.connectors import DictConnector
@@ -78,6 +81,11 @@ class PastaStore:
78
81
  self.plots = Plots(self)
79
82
  self.yaml = PastastoreYAML(self)
80
83
 
84
+ @property
85
+ def empty(self) -> bool:
86
+ """Check if the PastaStore is empty."""
87
+ return self.conn.empty
88
+
81
89
  def _register_connector_methods(self):
82
90
  """Register connector methods (internal method)."""
83
91
  methods = [
@@ -1175,18 +1183,19 @@ class PastaStore:
1175
1183
 
1176
1184
  def solve_models(
1177
1185
  self,
1178
- mls: Optional[Union[ps.Model, list, str]] = None,
1186
+ modelnames: Union[List[str], str, None] = None,
1179
1187
  report: bool = False,
1180
1188
  ignore_solve_errors: bool = False,
1181
- store_result: bool = True,
1182
1189
  progressbar: bool = True,
1190
+ parallel: bool = False,
1191
+ max_workers: Optional[int] = None,
1183
1192
  **kwargs,
1184
1193
  ) -> None:
1185
1194
  """Solves the models in the store.
1186
1195
 
1187
1196
  Parameters
1188
1197
  ----------
1189
- mls : list of str, optional
1198
+ modelnames : list of str, optional
1190
1199
  list of model names, if None all models in the pastastore
1191
1200
  are solved.
1192
1201
  report : boolean, optional
@@ -1196,43 +1205,103 @@ class PastaStore:
1196
1205
  if True, errors emerging from the solve method are ignored,
1197
1206
  default is False which will raise an exception when a model
1198
1207
  cannot be optimized
1199
- store_result : bool, optional
1200
- if True save optimized models, default is True
1201
1208
  progressbar : bool, optional
1202
- show progressbar, default is True
1203
- **kwargs :
1209
+ show progressbar, default is True.
1210
+ parallel: bool, optional
1211
+ if True, solve models in parallel using ProcessPoolExecutor
1212
+ max_workers: int, optional
1213
+ maximum number of workers to use in parallel solving, default is
1214
+ None which will use the number of cores available on the machine
1215
+ **kwargs : dictionary
1204
1216
  arguments are passed to the solve method.
1217
+
1218
+ Notes
1219
+ -----
1220
+ Users should be aware that parallel solving is platform dependent
1221
+ and may not always work. The current implementation works well for Linux users.
1222
+ For Windows users, parallel solving does not work when called directly from
1223
+ Jupyter Notebooks or IPython. To use parallel solving on Windows, the following
1224
+ code should be used in a Python file::
1225
+
1226
+ from multiprocessing import freeze_support
1227
+
1228
+ if __name__ == "__main__":
1229
+ freeze_support()
1230
+ pstore.solve_models(parallel=True)
1205
1231
  """
1206
- if mls is None:
1207
- mls = self.conn.model_names
1208
- elif isinstance(mls, ps.Model):
1209
- mls = [mls.name]
1232
+ if "mls" in kwargs:
1233
+ modelnames = kwargs.pop("mls")
1234
+ logger.warning("Argument `mls` is deprecated, use `modelnames` instead.")
1210
1235
 
1211
- desc = "Solving models"
1212
- for ml_name in tqdm(mls, desc=desc) if progressbar else mls:
1213
- ml = self.conn.get_models(ml_name)
1236
+ modelnames = self.conn._parse_names(modelnames, libname="models")
1214
1237
 
1215
- m_kwargs = {}
1216
- for key, value in kwargs.items():
1217
- if isinstance(value, pd.Series):
1218
- m_kwargs[key] = value.loc[ml_name]
1219
- else:
1220
- m_kwargs[key] = value
1221
- # Convert timestamps
1222
- for tstamp in ["tmin", "tmax"]:
1223
- if tstamp in m_kwargs:
1224
- m_kwargs[tstamp] = pd.Timestamp(m_kwargs[tstamp])
1238
+ solve_model = partial(
1239
+ self._solve_model,
1240
+ report=report,
1241
+ ignore_solve_errors=ignore_solve_errors,
1242
+ **kwargs,
1243
+ )
1244
+ if self.conn.conn_type != "pas":
1245
+ parallel = False
1246
+ logger.error(
1247
+ "Parallel solving only supported for PasConnector databases."
1248
+ "Setting parallel to `False`"
1249
+ )
1225
1250
 
1226
- try:
1227
- ml.solve(report=report, **m_kwargs)
1228
- if store_result:
1229
- self.conn.add_model(ml, overwrite=True)
1230
- except Exception as e:
1231
- if ignore_solve_errors:
1232
- warning = "solve error ignored for -> {}".format(ml.name)
1233
- ps.logger.warning(warning)
1234
- else:
1235
- raise e
1251
+ if parallel and progressbar:
1252
+ process_map(solve_model, modelnames, max_workers=max_workers)
1253
+ elif parallel and not progressbar:
1254
+ with ProcessPoolExecutor(max_workers=max_workers) as executor:
1255
+ executor.map(solve_model, modelnames)
1256
+ else:
1257
+ for ml_name in (
1258
+ tqdm(modelnames, desc="Solving models") if progressbar else modelnames
1259
+ ):
1260
+ solve_model(ml_name=ml_name)
1261
+
1262
+ def _solve_model(
1263
+ self,
1264
+ ml_name: str,
1265
+ report: bool = False,
1266
+ ignore_solve_errors: bool = False,
1267
+ **kwargs,
1268
+ ) -> None:
1269
+ """Solve a model in the store (internal method).
1270
+
1271
+ ml_name : list of str, optional
1272
+ name of a model in the pastastore
1273
+ report : boolean, optional
1274
+ determines if a report is printed when the model is solved,
1275
+ default is False
1276
+ ignore_solve_errors : boolean, optional
1277
+ if True, errors emerging from the solve method are ignored,
1278
+ default is False which will raise an exception when a model
1279
+ cannot be optimized
1280
+ **kwargs : dictionary
1281
+ arguments are passed to the solve method.
1282
+ """
1283
+ ml = self.conn.get_models(ml_name)
1284
+ m_kwargs = {}
1285
+ for key, value in kwargs.items():
1286
+ if isinstance(value, pd.Series):
1287
+ m_kwargs[key] = value.loc[ml.name]
1288
+ else:
1289
+ m_kwargs[key] = value
1290
+ # Convert timestamps
1291
+ for tstamp in ["tmin", "tmax"]:
1292
+ if tstamp in m_kwargs:
1293
+ m_kwargs[tstamp] = pd.Timestamp(m_kwargs[tstamp])
1294
+
1295
+ try:
1296
+ ml.solve(report=report, **m_kwargs)
1297
+ except Exception as e:
1298
+ if ignore_solve_errors:
1299
+ warning = "Solve error ignored for '%s': %s " % (ml.name, e)
1300
+ logger.warning(warning)
1301
+ else:
1302
+ raise e
1303
+
1304
+ self.conn.add_model(ml, overwrite=True)
1236
1305
 
1237
1306
  def model_results(
1238
1307
  self,
pastastore/styling.py CHANGED
@@ -1,8 +1,8 @@
1
1
  """Module containing dataframe styling functions."""
2
2
 
3
- import matplotlib as mpl
4
3
  import matplotlib.pyplot as plt
5
4
  import numpy as np
5
+ from matplotlib.colors import rgb2hex
6
6
 
7
7
 
8
8
  def float_styler(val, norm, cmap=None):
@@ -26,12 +26,12 @@ def float_styler(val, norm, cmap=None):
26
26
  -----
27
27
  Given some dataframe
28
28
 
29
- >>> df.map(float_styler, subset=["some column"], norm=norm, cmap=cmap)
29
+ >>> df.style.map(float_styler, subset=["some column"], norm=norm, cmap=cmap)
30
30
  """
31
31
  if cmap is None:
32
32
  cmap = plt.get_cmap("RdYlBu")
33
33
  bg = cmap(norm(val))
34
- color = mpl.colors.rgb2hex(bg)
34
+ color = rgb2hex(bg)
35
35
  c = "White" if np.mean(bg[:3]) < 0.4 else "Black"
36
36
  return f"background-color: {color}; color: {c}"
37
37
 
@@ -53,15 +53,48 @@ def boolean_styler(b):
53
53
  -----
54
54
  Given some dataframe
55
55
 
56
- >>> df.map(boolean_styler, subset=["some column"])
56
+ >>> df.style.map(boolean_styler, subset=["some column"])
57
57
  """
58
58
  if b:
59
59
  return (
60
- f"background-color: {mpl.colors.rgb2hex((231/255, 255/255, 239/255))}; "
60
+ f"background-color: {rgb2hex((231/255, 255/255, 239/255))}; "
61
61
  "color: darkgreen"
62
62
  )
63
63
  else:
64
64
  return (
65
- f"background-color: {mpl.colors.rgb2hex((255/255, 238/255, 238/255))}; "
65
+ f"background-color: {rgb2hex((255/255, 238/255, 238/255))}; "
66
66
  "color: darkred"
67
67
  )
68
+
69
+
70
+ def boolean_row_styler(row, column):
71
+ """Styler function to color rows based on the value in column.
72
+
73
+ Parameters
74
+ ----------
75
+ row : pd.Series
76
+ row in dataframe
77
+ column : str
78
+ column name to get boolean value for styling
79
+
80
+ Returns
81
+ -------
82
+ str
83
+ css for styling dataframe row
84
+
85
+ Usage
86
+ -----
87
+ Given some dataframe
88
+
89
+ >>> df.style.apply(boolean_row_styler, column="boolean_column", axis=1)
90
+ """
91
+ if row[column]:
92
+ return (
93
+ f"background-color: {rgb2hex((231/255, 255/255, 239/255))}; "
94
+ "color: darkgreen",
95
+ ) * row.size
96
+ else:
97
+ return (
98
+ f"background-color: {rgb2hex((255/255, 238/255, 238/255))}; "
99
+ "color: darkred",
100
+ ) * row.size
pastastore/version.py CHANGED
@@ -9,7 +9,7 @@ PASTAS_VERSION = parse_version(ps.__version__)
9
9
  PASTAS_LEQ_022 = PASTAS_VERSION <= parse_version("0.22.0")
10
10
  PASTAS_GEQ_150 = PASTAS_VERSION >= parse_version("1.5.0")
11
11
 
12
- __version__ = "1.6.0"
12
+ __version__ = "1.7.0"
13
13
 
14
14
 
15
15
  def show_versions(optional=False) -> None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pastastore
3
- Version: 1.6.0
3
+ Version: 1.7.0
4
4
  Summary: Tools for managing Pastas time series models.
5
5
  Author: D.A. Brakenhoff
6
6
  Maintainer-email: "D.A. Brakenhoff" <d.brakenhoff@artesia-water.nl>, "R. Calje" <r.calje@artesia-water.nl>, "M.A. Vonk" <m.vonk@artesia-water.nl>
@@ -0,0 +1,15 @@
1
+ pastastore/__init__.py,sha256=l6zRpDO0j6MIrfdljCTbkF70bt-GFlPseBd4IlmaC-o,269
2
+ pastastore/base.py,sha256=LLSg8CaKQDoRV0aOWQCSXTYQkBDH9t-xkhgLxiPxQ9Y,67333
3
+ pastastore/connectors.py,sha256=YK3I_Jb2uNwzBQvN2VwZvmTRfPeUETW-4ddcFSWkHVw,16820
4
+ pastastore/datasets.py,sha256=FHVfmKqb8beEs9NONsWrCoJY37BmlvFLSEQ1VAFmE8A,6415
5
+ pastastore/plotting.py,sha256=t6gEeHVGzrwvM6q1l8V3OkklpU75O2Y4h6nKEHRWdjo,46416
6
+ pastastore/store.py,sha256=xbv1prv6QqYj8M-2c77CT0ZQejjmNSldpuqu_M4WxoU,60906
7
+ pastastore/styling.py,sha256=4xAY0FmhKrvmAGIuoMM7Uucww_X4KAxTpEoHlsxMldc,2280
8
+ pastastore/util.py,sha256=iXHoGHfK6VDbUpufNsnzdV71oBVp-koZUD4VJj6MOwo,28250
9
+ pastastore/version.py,sha256=lo0Pof9TIeRN4t2EblhGJbu04oz9H0QT_FafAlkwfDE,1205
10
+ pastastore/yaml_interface.py,sha256=MddELxWe8_aqJRMUydOCbjoU1-ZodzxFKYnAaqJ5SqA,29947
11
+ pastastore-1.7.0.dist-info/LICENSE,sha256=DtHftfUEm99KzgwLr3rQUTg8H3kAS0Z-p5WWJgLf_OY,1082
12
+ pastastore-1.7.0.dist-info/METADATA,sha256=GX4vWuy91h3-vjiJxy0oHph1_wPCqeHJ8tTFK3lBiZU,8021
13
+ pastastore-1.7.0.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
14
+ pastastore-1.7.0.dist-info/top_level.txt,sha256=QKfonr1KJZN46MFsj8eGRBw9Mg-jO-HFvgE2orVX7Sk,11
15
+ pastastore-1.7.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (72.2.0)
2
+ Generator: setuptools (75.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,15 +0,0 @@
1
- pastastore/__init__.py,sha256=l6zRpDO0j6MIrfdljCTbkF70bt-GFlPseBd4IlmaC-o,269
2
- pastastore/base.py,sha256=n7hPrkaLjR6_8S0XRHxvviqBWvULx3W_faQcoA9HZ9I,67166
3
- pastastore/connectors.py,sha256=YK3I_Jb2uNwzBQvN2VwZvmTRfPeUETW-4ddcFSWkHVw,16820
4
- pastastore/datasets.py,sha256=FHVfmKqb8beEs9NONsWrCoJY37BmlvFLSEQ1VAFmE8A,6415
5
- pastastore/plotting.py,sha256=t6gEeHVGzrwvM6q1l8V3OkklpU75O2Y4h6nKEHRWdjo,46416
6
- pastastore/store.py,sha256=istLgbTVXvNWqTkZQtEcxWEweouh-M6HWUAXybKrESw,58286
7
- pastastore/styling.py,sha256=ioaH10ELV8CFvJA-xAKFbnBklTd6FB1TZV8sqvZrEcw,1518
8
- pastastore/util.py,sha256=iXHoGHfK6VDbUpufNsnzdV71oBVp-koZUD4VJj6MOwo,28250
9
- pastastore/version.py,sha256=p4YdipfRBvajfHzz2s7TjR_IpDOit_K_Lr2e7pnKhLU,1205
10
- pastastore/yaml_interface.py,sha256=MddELxWe8_aqJRMUydOCbjoU1-ZodzxFKYnAaqJ5SqA,29947
11
- pastastore-1.6.0.dist-info/LICENSE,sha256=DtHftfUEm99KzgwLr3rQUTg8H3kAS0Z-p5WWJgLf_OY,1082
12
- pastastore-1.6.0.dist-info/METADATA,sha256=cFimKbNDGJdY-iOFcDfpo4ger907HniL0kWFeSUvTqg,8021
13
- pastastore-1.6.0.dist-info/WHEEL,sha256=HiCZjzuy6Dw0hdX5R3LCFPDmFS4BWl8H-8W39XfmgX4,91
14
- pastastore-1.6.0.dist-info/top_level.txt,sha256=QKfonr1KJZN46MFsj8eGRBw9Mg-jO-HFvgE2orVX7Sk,11
15
- pastastore-1.6.0.dist-info/RECORD,,