rtc-tools 2.7.0a2__py3-none-any.whl → 2.7.0a4__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.

Potentially problematic release.


This version of rtc-tools might be problematic. Click here for more details.

@@ -1,12 +1,11 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: rtc-tools
3
- Version: 2.7.0a2
3
+ Version: 2.7.0a4
4
4
  Summary: Toolbox for control and optimization of water systems.
5
5
  Home-page: https://oss.deltares.nl/web/rtc-tools/home
6
+ Download-URL: http://github.com/deltares/rtc-tools/
6
7
  Author: Deltares
7
8
  Maintainer: Deltares
8
- License: UNKNOWN
9
- Download-URL: http://gitlab.com/deltares/rtc-tools/
10
9
  Platform: Windows
11
10
  Platform: Linux
12
11
  Platform: Mac OS-X
@@ -24,20 +23,30 @@ Classifier: Operating System :: Microsoft :: Windows
24
23
  Classifier: Operating System :: POSIX
25
24
  Classifier: Operating System :: Unix
26
25
  Classifier: Operating System :: MacOS
27
- Requires-Python: >=3.8
26
+ Requires-Python: >=3.9
28
27
  License-File: COPYING.LESSER
29
- Requires-Dist: casadi ==3.6.*,>=3.6.3
30
- Requires-Dist: numpy <1.26,>=1.16.0
31
- Requires-Dist: scipy <1.11,>=1.0.0
32
- Requires-Dist: pymoca ==0.9.*,>=0.9.1
33
- Requires-Dist: rtc-tools-channel-flow >=1.1.0
34
- Requires-Dist: defusedxml >=0.7.0
35
- Provides-Extra: all
36
- Requires-Dist: netCDF4 ; extra == 'all'
28
+ Requires-Dist: casadi!=3.6.6,==3.6.*,>=3.6.3
29
+ Requires-Dist: numpy>=1.16.0
30
+ Requires-Dist: scipy>=1.0.0
31
+ Requires-Dist: pymoca==0.9.*,>=0.9.1
32
+ Requires-Dist: rtc-tools-channel-flow>=1.2.0
33
+ Requires-Dist: defusedxml>=0.7.0
34
+ Requires-Dist: importlib_metadata>=5.0.0; python_version < "3.10"
37
35
  Provides-Extra: netcdf
38
- Requires-Dist: netCDF4 ; extra == 'netcdf'
36
+ Requires-Dist: netCDF4; extra == "netcdf"
37
+ Provides-Extra: all
38
+ Requires-Dist: netCDF4; extra == "all"
39
+ Dynamic: author
40
+ Dynamic: classifier
41
+ Dynamic: description
42
+ Dynamic: download-url
43
+ Dynamic: home-page
44
+ Dynamic: maintainer
45
+ Dynamic: platform
46
+ Dynamic: provides-extra
47
+ Dynamic: requires-dist
48
+ Dynamic: requires-python
49
+ Dynamic: summary
39
50
 
40
51
  RTC-Tools is the Deltares toolbox for control and optimization of water systems.
41
52
 
42
-
43
-
@@ -1,36 +1,36 @@
1
1
  rtctools/__init__.py,sha256=91hvS2-ryd2Pvw0COpsUzTwJwSnTZ035REiej-1hNI4,107
2
- rtctools/_version.py,sha256=khi7aLWt95iU8AN9lfjGJYvYFy3VrtunF5V_MRtL7tI,499
3
- rtctools/rtctoolsapp.py,sha256=UnkuiJhv0crEEVs8H6PYvMuc2y_q6V_xLuyKEgXj9GM,4200
2
+ rtctools/_version.py,sha256=TTAfg7Gh6Ox8Z9rQJDS7RWMBJfCxGUeEFi7vajV7vsg,499
3
+ rtctools/rtctoolsapp.py,sha256=VvoOgpu1R5vzaQsUzBjyB4uPenMv2yWukcA6N9KDEr8,4275
4
4
  rtctools/util.py,sha256=PaeKfDUA174ODZbY5fZjCTf-F-TdhW7yEuP189Ro190,9075
5
5
  rtctools/_internal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  rtctools/_internal/alias_tools.py,sha256=XuQSAhhFuVtwn0yrAObZWIKPsSF4j2axXRtEmitIFPs,5310
7
7
  rtctools/_internal/caching.py,sha256=p4gqSL7kCI7Hff-KjMEP7mhJCQSiU_lYm2MR7E18gBM,905
8
- rtctools/_internal/casadi_helpers.py,sha256=oAf5zyFkZbaMhvhgMnQkOi2A6dBOzj-VAUkYwBf-Jxk,1410
8
+ rtctools/_internal/casadi_helpers.py,sha256=q8j5h9XXXkZMUgjg6wbkcFj1mcHi5_SdEi8SrkM---M,1457
9
9
  rtctools/_internal/debug_check_helpers.py,sha256=UgQTEPw4PyR7MbYLewSSWQqTwQj7xr5yUBk820O9Kk4,1084
10
10
  rtctools/data/__init__.py,sha256=EllgSmCdrlvQZSd1VilvjPaeYJGhY9ErPiQtedmuFoA,157
11
- rtctools/data/csv.py,sha256=iYOEED3AaNxt4ews_aAkHfl9Tq9a-9vjxvYwjVR_lQE,5231
11
+ rtctools/data/csv.py,sha256=hEpoTH3nhZaAvRN4r-9-nYeAjaFiNDRoiZWg8GxM3yo,5539
12
12
  rtctools/data/netcdf.py,sha256=xpk4Ggl7gItNG6lO7p3OJPR-elK8_CiCtxUI7cX0gwk,19109
13
- rtctools/data/pi.py,sha256=Ni1hBDdhQdcWYO-NUPhKA1WJdzbSXjZc5w5xauOBcJM,45437
13
+ rtctools/data/pi.py,sha256=5w50zWgI79Vu5EMMz-MKoManXYqsjxfFgcZN6Z3eeiQ,46638
14
14
  rtctools/data/rtc.py,sha256=1yGJZGq2Z36MYLiLuZaHnxupL4mgw-Wuu54PAG05kcM,9077
15
15
  rtctools/data/storage.py,sha256=67J4BRTl0AMEzlKNZ8Xdpy_4cGtwx8Lo_tL2n0G4S9w,13206
16
16
  rtctools/data/interpolation/__init__.py,sha256=GBubCIT5mFoSTV-lOk7cpwvZekNMEe5bvqSQJ9HE34M,73
17
17
  rtctools/data/interpolation/bspline.py,sha256=qevB842XWCH3fWlWMBqKMy1mw37ust-0YtSnb9PKCEc,948
18
- rtctools/data/interpolation/bspline1d.py,sha256=hQrok4rrBcJV_HciuFjZYSwrSP8w_VufQRP6JLZhA7U,6106
18
+ rtctools/data/interpolation/bspline1d.py,sha256=HAh7m5xLBuiFKzMzuYEqZX_GmCPChKjV7ynTS6iRZOc,6166
19
19
  rtctools/data/interpolation/bspline2d.py,sha256=ScmX0fPDxbUVtj3pbUE0L7UJocqroD_6fUT-4cvdRMc,1693
20
20
  rtctools/optimization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
- rtctools/optimization/collocated_integrated_optimization_problem.py,sha256=Bv1a1O4QTAdIpe9SOA9_oEDDph-oj-9q5yfN4SFMdW8,131343
21
+ rtctools/optimization/collocated_integrated_optimization_problem.py,sha256=KFyCnSOs4EEi-lVtdu2M-YrLuo15EDufM6jLAbFB7E0,131283
22
22
  rtctools/optimization/control_tree_mixin.py,sha256=CC6TWp3kFQgMokx6213pRLx9iY17Fd0VcwG4Wpwa0Uo,8974
23
23
  rtctools/optimization/csv_lookup_table_mixin.py,sha256=h4WKuPR1BJbYuJfQ9lx4rXalB6yYK-zajINabRL7BrA,17298
24
- rtctools/optimization/csv_mixin.py,sha256=sRp5paHWfCw2bz-23Nw-HdFLS3CZTpVwaBdFo98DbvE,12252
25
- rtctools/optimization/goal_programming_mixin.py,sha256=GK25DrbAY_rMsra080pSCDZwzLQNN2Ppd-2d0_FEllg,32999
26
- rtctools/optimization/goal_programming_mixin_base.py,sha256=oh9CsEiyYTmthcfvRbX-9Z9bIo6SHv_DCiVt9kx0sjI,43781
24
+ rtctools/optimization/csv_mixin.py,sha256=_6iPVK_EJ8PxnukepzkhFtidceucsozRML_DDEycYik,12453
25
+ rtctools/optimization/goal_programming_mixin.py,sha256=r2pPzcqi-VlstPx4CdtUT0JeemduiYBV5zeH4kA9QGs,33301
26
+ rtctools/optimization/goal_programming_mixin_base.py,sha256=IvA3cfJmUW9SaDGATD7r4Rrjku9T4oI2H68imPTlq2A,43816
27
27
  rtctools/optimization/homotopy_mixin.py,sha256=Kh0kMfxB-Fo1FBGW5tPOQk24Xx_Mmw_p0YuSQotdkMU,6905
28
28
  rtctools/optimization/initial_state_estimation_mixin.py,sha256=74QYfG-VYYTNVg-kAnCG6QoY3_sUmaID0ideF7bPkkY,3116
29
29
  rtctools/optimization/io_mixin.py,sha256=AsZQ7YOUcUbWoczmjTXaSje5MUEsPNbQyZBJ6qzSjzU,11821
30
30
  rtctools/optimization/linearization_mixin.py,sha256=mG5S7uwvwDydw-eBPyQKnLyKoy08EBjQh25vu97afhY,1049
31
31
  rtctools/optimization/linearized_order_goal_programming_mixin.py,sha256=LQ2qpYt0YGLpEoerif4FJ5wwzq16q--27bsRjcqIU5A,9087
32
32
  rtctools/optimization/min_abs_goal_programming_mixin.py,sha256=WMOv9EF8cfDJgTunzXfI_cUmBSQK26u1HJB_9EAarfM,14031
33
- rtctools/optimization/modelica_mixin.py,sha256=6rotTEge4l2oDib4tnG2ccXqG1ryEzNxbRkk_StFAHg,17769
33
+ rtctools/optimization/modelica_mixin.py,sha256=BML4cod6LeXZRaLsHmplszDNvETlRTJreQ-dm8gHF1E,18033
34
34
  rtctools/optimization/netcdf_mixin.py,sha256=-zkXh3sMYE50c3kHsrmUVGWMSFm-0cXQpGrCm0yn-Tc,7563
35
35
  rtctools/optimization/optimization_problem.py,sha256=qzpc81NaZMeoXKuayFmBF15iXYuNAk5yxmaER_Gcz_A,44131
36
36
  rtctools/optimization/pi_mixin.py,sha256=n_ZyifuNPxcRoJXKR6Ks7DgK_b7-EToYJ23_khOwpzo,11786
@@ -41,10 +41,10 @@ rtctools/simulation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSu
41
41
  rtctools/simulation/csv_mixin.py,sha256=rGDUFPsqGHmF0_dWdXeWzWzMpkPmwCNweTBVrwSh31g,6704
42
42
  rtctools/simulation/io_mixin.py,sha256=SJasNGI--OQ9Y-Z61oeeaGCxSrNddYz4AOVfJYbmf74,6209
43
43
  rtctools/simulation/pi_mixin.py,sha256=vcizArZsOn7tGlSFCsUmkjmRFA1A1kMNqUllLlkVc9Y,9831
44
- rtctools/simulation/simulation_problem.py,sha256=TAG8J8td-23UgRz_I0Nz7yp9v_HT8ayblyYPtqbLyZo,48647
45
- rtc_tools-2.7.0a2.dist-info/COPYING.LESSER,sha256=46mU2C5kSwOnkqkw9XQAJlhBL2JAf1_uCD8lVcXyMRg,7652
46
- rtc_tools-2.7.0a2.dist-info/METADATA,sha256=OISYetGzEVCx1AWcMEcqOZpd-HT-0p6CftZd3uicWx4,1494
47
- rtc_tools-2.7.0a2.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
48
- rtc_tools-2.7.0a2.dist-info/entry_points.txt,sha256=-x622IB_l1duw2M6t6syfQ4yzOiQTp0IZxKGcYRgWgk,151
49
- rtc_tools-2.7.0a2.dist-info/top_level.txt,sha256=pnBrb58PFPd1kp1dqa-JHU7R55h3alDNJIJnF3Jf9Dw,9
50
- rtc_tools-2.7.0a2.dist-info/RECORD,,
44
+ rtctools/simulation/simulation_problem.py,sha256=TpPz8D6lWxxQXrJV3ZqCWZu33SwWSSepuqj3P6Uf84w,49804
45
+ rtc_tools-2.7.0a4.dist-info/COPYING.LESSER,sha256=46mU2C5kSwOnkqkw9XQAJlhBL2JAf1_uCD8lVcXyMRg,7652
46
+ rtc_tools-2.7.0a4.dist-info/METADATA,sha256=-Zi7t2gTm05SneRVKHsws-JRWlEtoFnOpaF7bjXrTbI,1754
47
+ rtc_tools-2.7.0a4.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
48
+ rtc_tools-2.7.0a4.dist-info/entry_points.txt,sha256=DVS8sWf3b9ph9h8srEr6zmQ7ZKGwblwgZgGPZg-jRNQ,150
49
+ rtc_tools-2.7.0a4.dist-info/top_level.txt,sha256=pnBrb58PFPd1kp1dqa-JHU7R55h3alDNJIJnF3Jf9Dw,9
50
+ rtc_tools-2.7.0a4.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.43.0)
2
+ Generator: setuptools (75.8.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,4 +1,3 @@
1
1
  [console_scripts]
2
2
  rtc-tools-copy-libraries = rtctools.rtctoolsapp:copy_libraries
3
3
  rtc-tools-download-examples = rtctools.rtctoolsapp:download_examples
4
-
@@ -5,12 +5,12 @@ import casadi as ca
5
5
  logger = logging.getLogger("rtctools")
6
6
 
7
7
 
8
- def is_affine(e, v):
8
+ def is_affine(expr, symbols):
9
9
  try:
10
- Af = ca.Function("f", [v], [ca.jacobian(e, v)]).expand()
11
- except RuntimeError as e:
12
- if "'eval_sx' not defined for" in str(e):
13
- Af = ca.Function("f", [v], [ca.jacobian(e, v)])
10
+ Af = ca.Function("f", [symbols], [ca.jacobian(expr, symbols)]).expand()
11
+ except RuntimeError as error:
12
+ if "'eval_sx' not defined for" in str(error):
13
+ Af = ca.Function("f", [symbols], [ca.jacobian(expr, symbols)])
14
14
  else:
15
15
  raise
16
16
  return Af.sparsity_jac(0, 0).nnz() == 0
rtctools/_version.py CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- # This file was generated by 'versioneer.py' (0.18) from
2
+ # This file was generated by 'versioneer.py' (0.29) from
3
3
  # revision-control system data, or from the parent directory name of an
4
4
  # unpacked source archive. Distribution tarballs contain a pre-generated copy
5
5
  # of this file.
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2024-05-14T15:15:45+0200",
11
+ "date": "2025-03-04T13:14:14+0100",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "6d06af067e4be71ea3b47593feb9be8ee7c37adc",
15
- "version": "2.7.0a2"
14
+ "full-revisionid": "240ff5dd3c6cbe9ba51eef09e6bd750cd309a1bf",
15
+ "version": "2.7.0a4"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
rtctools/data/csv.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import logging
2
2
  import sys
3
3
  from datetime import datetime
4
+ from typing import Union
4
5
 
5
6
  import numpy as np
6
7
 
@@ -41,6 +42,21 @@ def _boolean_to_nan(data, fname):
41
42
  return data
42
43
 
43
44
 
45
+ def _string_to_datetime(string: Union[str, bytes]) -> datetime:
46
+ """Convert a string to a datetime object."""
47
+ if isinstance(string, bytes):
48
+ string = string.decode("utf-8")
49
+ return datetime.strptime(string, "%Y-%m-%d %H:%M:%S")
50
+
51
+
52
+ def _string_to_float(string: Union[str, bytes]) -> float:
53
+ """Convert a string to a float."""
54
+ if isinstance(string, bytes):
55
+ string = string.decode("utf-8")
56
+ string = string.replace(",", ".")
57
+ return float(string)
58
+
59
+
44
60
  def load(fname, delimiter=",", with_time=False):
45
61
  """
46
62
  Check delimiter of csv and read contents to an array. Assumes no date-time conversion needed.
@@ -53,7 +69,7 @@ def load(fname, delimiter=",", with_time=False):
53
69
  """
54
70
  c = {}
55
71
  if with_time:
56
- c.update({0: lambda str: datetime.strptime(str.decode("utf-8"), "%Y-%m-%d %H:%M:%S")})
72
+ c.update({0: _string_to_datetime})
57
73
 
58
74
  # Check delimiter of csv file. If semicolon, check if decimal separator is
59
75
  # a comma.
@@ -73,12 +89,7 @@ def load(fname, delimiter=",", with_time=False):
73
89
  # If commas are used as decimal separator, we need additional
74
90
  # converters.
75
91
  if n_comma_decimal:
76
- c.update(
77
- {
78
- i + len(c): lambda str: float(str.decode("utf-8").replace(",", "."))
79
- for i in range(1 + n_semicolon - len(c))
80
- }
81
- )
92
+ c.update({i + len(c): _string_to_float for i in range(1 + n_semicolon - len(c))})
82
93
 
83
94
  # Read the csv file and convert to array
84
95
  try:
@@ -55,6 +55,7 @@ class BSpline1D(BSpline):
55
55
  epsilon=1e-7,
56
56
  delta=1e-4,
57
57
  interior_pts=None,
58
+ ipopt_options=None,
58
59
  ):
59
60
  """
60
61
  fit() returns a tck tuple like scipy.interpolate.splrep, but adjusts
@@ -153,7 +154,10 @@ class BSpline1D(BSpline):
153
154
  nlp = {"x": c, "f": f, "g": g}
154
155
  my_solver = "ipopt"
155
156
  solver = nlpsol(
156
- "solver", my_solver, nlp, {"print_time": 0, "expand": True, "ipopt": {"print_level": 0}}
157
+ "solver",
158
+ my_solver,
159
+ nlp,
160
+ {"print_time": 0, "expand": True, "ipopt": ipopt_options},
157
161
  )
158
162
  sol = solver(lbg=lbg, ubg=ubg)
159
163
  stats = solver.stats()
rtctools/data/pi.py CHANGED
@@ -369,8 +369,6 @@ class Timeseries:
369
369
  self.__folder = folder
370
370
  self.__basename = basename
371
371
 
372
- self.__path_xml = os.path.join(self.__folder, basename + ".xml")
373
-
374
372
  self.__internal_dtype = np.float64
375
373
  self.__pi_dtype = np.float32
376
374
 
@@ -378,7 +376,7 @@ class Timeseries:
378
376
  if self.make_new_file:
379
377
  self.__reset_xml_tree()
380
378
  else:
381
- self.__tree = DefusedElementTree.parse(self.__path_xml)
379
+ self.__tree = DefusedElementTree.parse(self.path)
382
380
  self.__xml_root = self.__tree.getroot()
383
381
 
384
382
  self.__values = [{}]
@@ -801,13 +799,20 @@ class Timeseries:
801
799
  # Add series to xml
802
800
  self.__xml_root.append(series)
803
801
 
804
- def write(self):
802
+ def write(self, output_folder=None, output_filename=None) -> None:
805
803
  """
806
804
  Writes the time series data to disk.
805
+
806
+ :param output_folder: The folder in which the output file is located.
807
+ If None, the original folder is used.
808
+ :param output_filename: The name of the output file without extension.
809
+ If None, the original filename is used.
807
810
  """
811
+ xml_path = self.output_path(output_folder, output_filename)
812
+ binary_path = self.output_binary_path(output_folder, output_filename)
808
813
 
809
814
  if self.__binary:
810
- f = io.open(self.binary_path, "wb")
815
+ f = io.open(binary_path, "wb")
811
816
 
812
817
  if self.make_new_file:
813
818
  # Force reinitialization in case write() is called more than once
@@ -876,29 +881,26 @@ class Timeseries:
876
881
  events = series.findall("pi:event", ns)
877
882
 
878
883
  t = self.__start_datetime
879
- for i in range(min(len(events), len(values))):
884
+ for i, value in enumerate(values):
880
885
  if self.dt is None:
881
886
  t = self.times[i]
882
- # Set the date/time, so that any date/time steps that
883
- # are wrong in the placeholder file are corrected.
884
- events[i].set("date", t.strftime("%Y-%m-%d"))
885
- events[i].set("time", t.strftime("%H:%M:%S"))
886
887
 
887
- # Set the value
888
- events[i].set("value", str(values[i]))
889
- if self.dt:
890
- t += self.dt
891
- for i in range(len(events), len(values)):
892
- if self.dt is None:
893
- t = self.times[i]
894
- event = ET.Element("pi:event")
888
+ if i < len(events):
889
+ event = events[i]
890
+ else:
891
+ event = ET.Element("pi:event")
892
+ series.append(event)
893
+
894
+ # Always set the date/time, so that any date/time steps
895
+ # that are wrong in the placeholder file are corrected.
895
896
  event.set("date", t.strftime("%Y-%m-%d"))
896
897
  event.set("time", t.strftime("%H:%M:%S"))
898
+
897
899
  if nans[i]:
898
900
  event.set("value", miss_val)
899
901
  else:
900
- event.set("value", str(values[i]))
901
- series.append(event)
902
+ event.set("value", str(value))
903
+
902
904
  if self.dt:
903
905
  t += self.dt
904
906
 
@@ -911,7 +913,7 @@ class Timeseries:
911
913
  f.close()
912
914
 
913
915
  self.format_xml_data()
914
- self.__tree.write(self.__path_xml)
916
+ self.__tree.write(xml_path)
915
917
 
916
918
  def format_xml_data(self):
917
919
  """
@@ -1170,16 +1172,45 @@ class Timeseries:
1170
1172
  self.__end_datetime = end_datetime
1171
1173
 
1172
1174
  @property
1173
- def path(self):
1174
- return self.__path_xml
1175
+ def path(self) -> str:
1176
+ """
1177
+ The path to the original xml file.
1178
+ """
1179
+ return os.path.join(self.__folder, self.__basename + ".xml")
1175
1180
 
1176
1181
  @property
1177
- def binary_path(self):
1182
+ def binary_path(self) -> str:
1178
1183
  """
1179
- The path for the binary data .bin file.
1184
+ The path to the original binary data .bin file.
1180
1185
  """
1181
1186
  return os.path.join(self.__folder, self.__basename + ".bin")
1182
1187
 
1188
+ def _output_path_without_extension(self, output_folder=None, output_filename=None) -> str:
1189
+ """
1190
+ Get the output path without file extension.
1191
+ """
1192
+ if output_folder is None:
1193
+ output_folder = self.__folder
1194
+ if output_filename is None:
1195
+ output_filename = self.__basename
1196
+ return os.path.join(output_folder, output_filename)
1197
+
1198
+ def output_path(self, output_folder=None, output_filename=None) -> str:
1199
+ """
1200
+ Get the path to the output xml file.
1201
+
1202
+ The optional arguments are the same as in :py:method:`write`.
1203
+ """
1204
+ return self._output_path_without_extension(output_folder, output_filename) + ".xml"
1205
+
1206
+ def output_binary_path(self, output_folder=None, output_filename=None) -> str:
1207
+ """
1208
+ Get the path to the output binary file.
1209
+
1210
+ The optional arguments are the same as in :py:method:`write`.
1211
+ """
1212
+ return self._output_path_without_extension(output_folder, output_filename) + ".bin"
1213
+
1183
1214
  def items(self, ensemble_member=0):
1184
1215
  """
1185
1216
  Returns an iterator over all timeseries IDs and value arrays for the given
@@ -898,11 +898,11 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
898
898
  function_options,
899
899
  )
900
900
 
901
+ # Expand the residual function if possible.
901
902
  try:
902
903
  dae_residual_function_integrated = dae_residual_function_integrated.expand()
903
904
  except RuntimeError as e:
904
- # We only expect to fail if the DAE was an external function
905
- if "'eval_sx' not defined for External" in str(e):
905
+ if "'eval_sx' not defined for" in str(e):
906
906
  pass
907
907
  else:
908
908
  raise
@@ -933,13 +933,13 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
933
933
  [dae_residual_collocated],
934
934
  function_options,
935
935
  )
936
+ # Expand the residual function if possible.
936
937
  try:
937
938
  self.__dae_residual_function_collocated = (
938
939
  self.__dae_residual_function_collocated.expand()
939
940
  )
940
941
  except RuntimeError as e:
941
- # We only expect to fail if the DAE was an external function
942
- if "'eval_sx' not defined for External" in str(e):
942
+ if "'eval_sx' not defined for" in str(e):
943
943
  pass
944
944
  else:
945
945
  raise
@@ -98,6 +98,9 @@ class CSVMixin(IOMixin):
98
98
  names=True,
99
99
  encoding=None,
100
100
  )
101
+ if len(self.__ensemble.shape) == 0:
102
+ # If there is only one ensemble member, the array is 0-dimensional.
103
+ self.__ensemble = np.expand_dims(self.__ensemble, 0)
101
104
 
102
105
  logger.debug("CSVMixin: Read ensemble description")
103
106
 
@@ -667,6 +667,7 @@ class GoalProgrammingMixin(_GoalProgrammingMixinBase):
667
667
  logger.info("Starting goal programming")
668
668
 
669
669
  success = False
670
+ self.skip_priority = False
670
671
 
671
672
  self.__constraint_store = [OrderedDict() for ensemble_member in range(self.ensemble_size)]
672
673
  self.__path_constraint_store = [
@@ -691,6 +692,13 @@ class GoalProgrammingMixin(_GoalProgrammingMixinBase):
691
692
  # Call the pre priority hook
692
693
  self.priority_started(priority)
693
694
 
695
+ if self.skip_priority:
696
+ logger.info(
697
+ "priority {} was removed in priority_started. No optimization problem "
698
+ "is solved at this priority.".format(priority)
699
+ )
700
+ continue
701
+
694
702
  (
695
703
  self.__subproblem_epsilons,
696
704
  self.__subproblem_objectives,
@@ -1081,6 +1081,7 @@ class _GoalProgrammingMixinBase(OptimizationProblem, metaclass=ABCMeta):
1081
1081
 
1082
1082
  :param priority: The priority level that was started.
1083
1083
  """
1084
+ self.skip_priority = False
1084
1085
  pass
1085
1086
 
1086
1087
  def priority_completed(self, priority: int) -> None:
@@ -1,10 +1,18 @@
1
+ import importlib.resources
1
2
  import itertools
2
3
  import logging
4
+ import sys
3
5
  from typing import Dict, Union
4
6
 
7
+ # Python 3.9's importlib.metadata does not support the "group" parameter to
8
+ # entry_points yet.
9
+ if sys.version_info < (3, 10):
10
+ import importlib_metadata
11
+ else:
12
+ from importlib import metadata as importlib_metadata
13
+
5
14
  import casadi as ca
6
15
  import numpy as np
7
- import pkg_resources
8
16
  import pymoca
9
17
  import pymoca.backends.casadi.api
10
18
 
@@ -54,8 +62,8 @@ class ModelicaMixin(OptimizationProblem):
54
62
  self.__pymoca_model = pymoca.backends.casadi.api.transfer_model(
55
63
  kwargs["model_folder"], model_name, compiler_options
56
64
  )
57
- except RuntimeError as error:
58
- if compiler_options.get("cache", False):
65
+ except (RuntimeError, ModuleNotFoundError) as error:
66
+ if not compiler_options.get("cache", False):
59
67
  raise error
60
68
  compiler_options["cache"] = False
61
69
  logger.warning(f"Loading model {model_name} using a cache file failed: {error}.")
@@ -174,9 +182,9 @@ class ModelicaMixin(OptimizationProblem):
174
182
  # Where imported model libraries are located.
175
183
  library_folders = self.modelica_library_folders.copy()
176
184
 
177
- for ep in pkg_resources.iter_entry_points(group="rtctools.libraries.modelica"):
185
+ for ep in importlib_metadata.entry_points(group="rtctools.libraries.modelica"):
178
186
  if ep.name == "library_folder":
179
- library_folders.append(pkg_resources.resource_filename(ep.module_name, ep.attrs[0]))
187
+ library_folders.append(str(importlib.resources.files(ep.module).joinpath(ep.attr)))
180
188
 
181
189
  compiler_options["library_folders"] = library_folders
182
190
 
rtctools/rtctoolsapp.py CHANGED
@@ -1,9 +1,17 @@
1
+ import importlib.resources
1
2
  import logging
2
3
  import os
3
4
  import shutil
4
5
  import sys
5
6
  from pathlib import Path
6
7
 
8
+ # Python 3.9's importlib.metadata does not support the "group" parameter to
9
+ # entry_points yet.
10
+ if sys.version_info < (3, 10):
11
+ import importlib_metadata
12
+ else:
13
+ from importlib import metadata as importlib_metadata
14
+
7
15
  import rtctools
8
16
 
9
17
  logging.basicConfig(format="%(asctime)s %(levelname)s %(message)s")
@@ -23,9 +31,6 @@ def copy_libraries(*args):
23
31
  if not os.path.exists(path):
24
32
  sys.exit("Folder '{}' does not exist".format(path))
25
33
 
26
- # pkg_resources can be quite a slow import, so we do it here
27
- import pkg_resources
28
-
29
34
  def _copytree(src, dst, symlinks=False, ignore=None):
30
35
  if not os.path.exists(dst):
31
36
  os.makedirs(dst)
@@ -56,11 +61,10 @@ def copy_libraries(*args):
56
61
  dst = Path(path)
57
62
 
58
63
  library_folders = []
59
- for ep in pkg_resources.iter_entry_points(group="rtctools.libraries.modelica"):
64
+
65
+ for ep in importlib_metadata.entry_points(group="rtctools.libraries.modelica"):
60
66
  if ep.name == "library_folder":
61
- library_folders.append(
62
- Path(pkg_resources.resource_filename(ep.module_name, ep.attrs[0]))
63
- )
67
+ library_folders.append(str(importlib.resources.files(ep.module).joinpath(ep.attr)))
64
68
 
65
69
  tlds = {}
66
70
  for lf in library_folders:
@@ -102,9 +106,7 @@ def download_examples(*args):
102
106
  version = rtctools.__version__
103
107
  rtc_full_name = "rtc-tools-{}".format(version)
104
108
  try:
105
- url = "https://gitlab.com/deltares/rtc-tools/-/archive/{}/{}.zip".format(
106
- version, rtc_full_name
107
- )
109
+ url = "https://github.com/deltares/rtc-tools/zipball/{}".format(version)
108
110
 
109
111
  opener = urllib.request.build_opener()
110
112
  urllib.request.install_opener(opener)
@@ -1,13 +1,21 @@
1
1
  import copy
2
+ import importlib.resources
2
3
  import itertools
3
4
  import logging
4
5
  import math
6
+ import sys
5
7
  from collections import OrderedDict
6
8
  from typing import List, Union
7
9
 
10
+ # Python 3.9's importlib.metadata does not support the "group" parameter to
11
+ # entry_points yet.
12
+ if sys.version_info < (3, 10):
13
+ import importlib_metadata
14
+ else:
15
+ from importlib import metadata as importlib_metadata
16
+
8
17
  import casadi as ca
9
18
  import numpy as np
10
- import pkg_resources
11
19
  import pymoca
12
20
  import pymoca.backends.casadi.api
13
21
 
@@ -838,6 +846,8 @@ class SimulationProblem(DataStoreAccessor):
838
846
  self.set_var("time", self.get_current_time() + dt)
839
847
 
840
848
  # take a step
849
+ if np.isnan(self.__state_vector).any():
850
+ logger.error("Found a nan in the state vector (before making the step)")
841
851
  guess = self.__state_vector[: self.__n_states]
842
852
  if len(self.__mx["parameters"]) > 0:
843
853
  next_state = self.__do_step(
@@ -845,6 +855,23 @@ class SimulationProblem(DataStoreAccessor):
845
855
  )
846
856
  else:
847
857
  next_state = self.__do_step(guess, dt, self.__state_vector)
858
+
859
+ try:
860
+ if np.isnan(next_state).any():
861
+ index_to_name = {index[0]: name for name, index in self.__indices.items()}
862
+ named_next_state = {
863
+ index_to_name[i]: float(next_state[i]) for i in range(0, next_state.shape[0])
864
+ }
865
+ variables_with_nan = [
866
+ name for name, value in named_next_state.items() if np.isnan(value)
867
+ ]
868
+ if variables_with_nan:
869
+ logger.error(
870
+ f"Found nan(s) in the next_state vector for:\n\t {variables_with_nan}"
871
+ )
872
+ except (KeyError, IndexError, TypeError):
873
+ logger.warning("Something went wrong while checking for nans in the next_state vector")
874
+
848
875
  # Check convergence of rootfinder
849
876
  rootfinder_stats = self.__do_step.stats()
850
877
 
@@ -1225,9 +1252,9 @@ class SimulationProblem(DataStoreAccessor):
1225
1252
  # Where imported model libraries are located.
1226
1253
  library_folders = self.modelica_library_folders.copy()
1227
1254
 
1228
- for ep in pkg_resources.iter_entry_points(group="rtctools.libraries.modelica"):
1255
+ for ep in importlib_metadata.entry_points(group="rtctools.libraries.modelica"):
1229
1256
  if ep.name == "library_folder":
1230
- library_folders.append(pkg_resources.resource_filename(ep.module_name, ep.attrs[0]))
1257
+ library_folders.append(str(importlib.resources.files(ep.module).joinpath(ep.attr)))
1231
1258
 
1232
1259
  compiler_options["library_folders"] = library_folders
1233
1260