rtc-tools 2.7.0.dev1__tar.gz → 2.7.1__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.
Files changed (59) hide show
  1. {rtc_tools-2.7.0.dev1/src/rtc_tools.egg-info → rtc_tools-2.7.1}/PKG-INFO +6 -4
  2. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/README.md +9 -11
  3. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/pyproject.toml +3 -2
  4. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/setup.py +6 -2
  5. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1/src/rtc_tools.egg-info}/PKG-INFO +6 -4
  6. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtc_tools.egg-info/requires.txt +4 -1
  7. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/_internal/casadi_helpers.py +13 -5
  8. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/_version.py +3 -3
  9. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/data/netcdf.py +16 -15
  10. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/data/pi.py +5 -2
  11. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/data/rtc.py +3 -3
  12. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/optimization/collocated_integrated_optimization_problem.py +16 -19
  13. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/optimization/control_tree_mixin.py +9 -6
  14. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/optimization/csv_lookup_table_mixin.py +5 -3
  15. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/optimization/csv_mixin.py +3 -0
  16. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/optimization/goal_programming_mixin.py +3 -2
  17. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/optimization/goal_programming_mixin_base.py +4 -3
  18. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/optimization/modelica_mixin.py +13 -5
  19. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/optimization/optimization_problem.py +20 -2
  20. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/optimization/pi_mixin.py +3 -3
  21. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/rtctoolsapp.py +15 -13
  22. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/simulation/io_mixin.py +1 -1
  23. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/simulation/pi_mixin.py +3 -3
  24. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/simulation/simulation_problem.py +25 -12
  25. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/util.py +1 -0
  26. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/COPYING.LESSER +0 -0
  27. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/MANIFEST.in +0 -0
  28. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/setup.cfg +0 -0
  29. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtc_tools.egg-info/SOURCES.txt +0 -0
  30. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtc_tools.egg-info/dependency_links.txt +0 -0
  31. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtc_tools.egg-info/entry_points.txt +0 -0
  32. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtc_tools.egg-info/top_level.txt +0 -0
  33. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/__init__.py +0 -0
  34. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/_internal/__init__.py +0 -0
  35. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/_internal/alias_tools.py +0 -0
  36. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/_internal/caching.py +0 -0
  37. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/_internal/debug_check_helpers.py +0 -0
  38. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/data/__init__.py +0 -0
  39. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/data/csv.py +0 -0
  40. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/data/interpolation/__init__.py +0 -0
  41. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/data/interpolation/bspline.py +0 -0
  42. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/data/interpolation/bspline1d.py +0 -0
  43. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/data/interpolation/bspline2d.py +0 -0
  44. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/data/storage.py +0 -0
  45. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/optimization/__init__.py +0 -0
  46. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/optimization/homotopy_mixin.py +0 -0
  47. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/optimization/initial_state_estimation_mixin.py +0 -0
  48. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/optimization/io_mixin.py +0 -0
  49. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/optimization/linearization_mixin.py +0 -0
  50. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/optimization/linearized_order_goal_programming_mixin.py +0 -0
  51. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/optimization/min_abs_goal_programming_mixin.py +0 -0
  52. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/optimization/netcdf_mixin.py +0 -0
  53. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/optimization/planning_mixin.py +0 -0
  54. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/optimization/single_pass_goal_programming_mixin.py +0 -0
  55. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/optimization/timeseries.py +0 -0
  56. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/simulation/__init__.py +0 -0
  57. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/src/rtctools/simulation/csv_mixin.py +0 -0
  58. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/tests/test_case.py +0 -0
  59. {rtc_tools-2.7.0.dev1 → rtc_tools-2.7.1}/versioneer.py +0 -0
@@ -1,9 +1,9 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: rtc-tools
3
- Version: 2.7.0.dev1
3
+ Version: 2.7.1
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://gitlab.com/deltares/rtc-tools/
6
+ Download-URL: http://github.com/deltares/rtc-tools/
7
7
  Author: Deltares
8
8
  Maintainer: Deltares
9
9
  Platform: Windows
@@ -25,12 +25,13 @@ Classifier: Operating System :: Unix
25
25
  Classifier: Operating System :: MacOS
26
26
  Requires-Python: >=3.9
27
27
  License-File: COPYING.LESSER
28
- Requires-Dist: casadi!=3.6.6,==3.6.*,>=3.6.3
28
+ Requires-Dist: casadi!=3.6.6,<=3.7,>=3.6.3
29
29
  Requires-Dist: numpy>=1.16.0
30
30
  Requires-Dist: scipy>=1.0.0
31
31
  Requires-Dist: pymoca==0.9.*,>=0.9.1
32
32
  Requires-Dist: rtc-tools-channel-flow>=1.2.0
33
33
  Requires-Dist: defusedxml>=0.7.0
34
+ Requires-Dist: importlib_metadata>=5.0.0; python_version < "3.10"
34
35
  Provides-Extra: netcdf
35
36
  Requires-Dist: netCDF4; extra == "netcdf"
36
37
  Provides-Extra: all
@@ -40,6 +41,7 @@ Dynamic: classifier
40
41
  Dynamic: description
41
42
  Dynamic: download-url
42
43
  Dynamic: home-page
44
+ Dynamic: license-file
43
45
  Dynamic: maintainer
44
46
  Dynamic: platform
45
47
  Dynamic: provides-extra
@@ -3,9 +3,9 @@
3
3
  [![Pipeline](https://github.com/deltares/rtc-tools/actions/workflows/rtc-tools.yml/badge.svg)](
4
4
  https://github.com/deltares/rtc-tools/actions/workflows/rtc-tools.yml
5
5
  )
6
- [![Coverage](https://codecov.io/gl/deltares/rtc-tools/branch/master/graph/badge.svg)](
7
- https://codecov.io/gl/deltares/rtc-tools
8
- )
6
+ [![Coverage](
7
+ https://sonarcloud.io/api/project_badges/measure?project=Deltares_rtc-tools&metric=coverage
8
+ )](https://sonarcloud.io/summary/new_code?id=Deltares_rtc-tools)
9
9
 
10
10
  > **NOTE** The rtc-tools repository has been migrated from gitlab to here;
11
11
  see [migration from gitlab](#migration-from-gitlab).
@@ -46,12 +46,6 @@ RTC-Tools uses [CasADi](https://web.casadi.org/) as a symbolic framework for alg
46
46
  pip install rtc-tools
47
47
  ```
48
48
 
49
- When using Python 3.12 or higher, you might first need to install setuptools
50
-
51
- ```bash
52
- pip install setuptools
53
- ```
54
-
55
49
  ## Documentation
56
50
 
57
51
  Documentation and examples can be found on [readthedocs](https://rtc-tools.readthedocs.io).
@@ -71,8 +65,12 @@ and
71
65
 
72
66
 
73
67
  ## License
74
- RTC-Tools is licensed under the **[GNU Lesser General Public License v3.0](https://gitlab.com/deltares/rtc-tools/-/blob/guidelines_contributions/COPYING)**, and can be used free of charge. Deltares offers support packages for users who require assistance.
68
+ RTC-Tools is licensed under the **[GNU Lesser General Public License v3.0](COPYING)**,
69
+ and can be used free of charge. Deltares offers support packages for users who require assistance.
75
70
 
76
71
 
77
72
  ## Acknowledgment
78
- If you use RTC-Tools in your work, please acknowledge it in any resulting publications. You can do this by citing RTC-Tools and providing a link to our [website](https://oss.deltares.nl/web/rtc-tools/home) or [Gitlab repository](https://gitlab.com/deltares/rtc-tools).
73
+ If you use RTC-Tools in your work, please acknowledge it in any resulting publications.
74
+ You can do this by citing RTC-Tools and providing a link to our
75
+ [website](https://oss.deltares.nl/web/rtc-tools/home) or
76
+ [GitHub repository](https://github.com/deltares/rtc-tools).
@@ -8,15 +8,16 @@ exclude = '''
8
8
  '''
9
9
 
10
10
  [tool.ruff]
11
- ignore = [
11
+ lint.ignore = [
12
12
  "B904", # Fix someday: raising exceptions within except
13
13
  ]
14
14
  line-length = 100
15
- select = [
15
+ lint.select = [
16
16
  "B", # flake8-bugbear
17
17
  "C4", # flake8-comprehensions
18
18
  "E", # default / pycodestyle
19
19
  "F", # default / pyflakes
20
20
  "I", # isort
21
21
  "W", # pycodestyle
22
+
22
23
  ]
@@ -3,6 +3,7 @@
3
3
  RTC-Tools is the Deltares toolbox for control and optimization of water systems.
4
4
 
5
5
  """
6
+
6
7
  import sys
7
8
 
8
9
  from setuptools import find_packages, setup
@@ -38,18 +39,21 @@ setup(
38
39
  description=DOCLINES[0],
39
40
  long_description="\n".join(DOCLINES[2:]),
40
41
  url="https://oss.deltares.nl/web/rtc-tools/home",
41
- download_url="http://gitlab.com/deltares/rtc-tools/",
42
+ download_url="http://github.com/deltares/rtc-tools/",
42
43
  classifiers=[_f for _f in CLASSIFIERS.split("\n") if _f],
43
44
  platforms=["Windows", "Linux", "Mac OS-X", "Unix"],
44
45
  packages=find_packages("src"),
45
46
  package_dir={"": "src"},
46
47
  install_requires=[
47
- "casadi >= 3.6.3, == 3.6.*, !=3.6.6",
48
+ "casadi >= 3.6.3, <= 3.7, !=3.6.6",
48
49
  "numpy >= 1.16.0",
49
50
  "scipy >= 1.0.0",
50
51
  "pymoca >= 0.9.1, == 0.9.*",
51
52
  "rtc-tools-channel-flow >= 1.2.0",
52
53
  "defusedxml >= 0.7.0",
54
+ # Python 3.9's importlib.metadata does not support the "group" parameter
55
+ # to entry_points yet.
56
+ "importlib_metadata >= 5.0.0; python_version < '3.10'",
53
57
  ],
54
58
  tests_require=["pytest", "pytest-runner", "netCDF4"],
55
59
  extras_require={
@@ -1,9 +1,9 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: rtc-tools
3
- Version: 2.7.0.dev1
3
+ Version: 2.7.1
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://gitlab.com/deltares/rtc-tools/
6
+ Download-URL: http://github.com/deltares/rtc-tools/
7
7
  Author: Deltares
8
8
  Maintainer: Deltares
9
9
  Platform: Windows
@@ -25,12 +25,13 @@ Classifier: Operating System :: Unix
25
25
  Classifier: Operating System :: MacOS
26
26
  Requires-Python: >=3.9
27
27
  License-File: COPYING.LESSER
28
- Requires-Dist: casadi!=3.6.6,==3.6.*,>=3.6.3
28
+ Requires-Dist: casadi!=3.6.6,<=3.7,>=3.6.3
29
29
  Requires-Dist: numpy>=1.16.0
30
30
  Requires-Dist: scipy>=1.0.0
31
31
  Requires-Dist: pymoca==0.9.*,>=0.9.1
32
32
  Requires-Dist: rtc-tools-channel-flow>=1.2.0
33
33
  Requires-Dist: defusedxml>=0.7.0
34
+ Requires-Dist: importlib_metadata>=5.0.0; python_version < "3.10"
34
35
  Provides-Extra: netcdf
35
36
  Requires-Dist: netCDF4; extra == "netcdf"
36
37
  Provides-Extra: all
@@ -40,6 +41,7 @@ Dynamic: classifier
40
41
  Dynamic: description
41
42
  Dynamic: download-url
42
43
  Dynamic: home-page
44
+ Dynamic: license-file
43
45
  Dynamic: maintainer
44
46
  Dynamic: platform
45
47
  Dynamic: provides-extra
@@ -1,10 +1,13 @@
1
- casadi!=3.6.6,==3.6.*,>=3.6.3
1
+ casadi!=3.6.6,<=3.7,>=3.6.3
2
2
  numpy>=1.16.0
3
3
  scipy>=1.0.0
4
4
  pymoca==0.9.*,>=0.9.1
5
5
  rtc-tools-channel-flow>=1.2.0
6
6
  defusedxml>=0.7.0
7
7
 
8
+ [:python_version < "3.10"]
9
+ importlib_metadata>=5.0.0
10
+
8
11
  [all]
9
12
  netCDF4
10
13
 
@@ -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
@@ -52,4 +52,12 @@ def interpolate(ts, xs, t, equidistant, mode=0):
52
52
  mode_str = "floor"
53
53
  else:
54
54
  mode_str = "ceil"
55
+
56
+ # CasADi fails if there is just a single point. Just "extrapolate" based on
57
+ # that point, just as CasADi would do for entries in 't' outside the range
58
+ # of 'ts'.
59
+ if len(ts) == 1:
60
+ assert xs.size1() == 1
61
+ return ca.vertcat(*[xs] * len(t))
62
+
55
63
  return ca.interp1d(ts, xs, t, mode_str, equidistant)
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2025-01-29T10:45:25+0100",
11
+ "date": "2025-07-23T09:21:39+0200",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "41dd2517c9c4cd952cd3da53b551b988bc5b64ba",
15
- "version": "2.7.0.dev1"
14
+ "full-revisionid": "a3a2e2f821b5239481f43285b7dbf495a27de52e",
15
+ "version": "2.7.1"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -401,20 +401,21 @@ class ExportDataset:
401
401
  """
402
402
  assert len(set(variable_names)) == len(variable_names)
403
403
 
404
- assert (
405
- self.__time_dim is not None
406
- ), "First call write_times to ensure the time dimension has been created."
407
- assert (
408
- self.__station_dim is not None
409
- ), "First call write_station_data to ensure the station dimension has been created"
404
+ assert self.__time_dim is not None, (
405
+ "First call write_times to ensure the time dimension has been created."
406
+ )
407
+ assert self.__station_dim is not None, (
408
+ "First call write_station_data to ensure the station dimension has been created"
409
+ )
410
410
  assert (
411
411
  self.__station_id_to_index_mapping is not None
412
412
  ) # should also be created in write_station_data
413
413
 
414
414
  if ensemble_size > 1:
415
- assert (
416
- self.__ensemble_member_dim is not None
417
- ), "First call write_ensemble_data to ensure the realization dimension has been created"
415
+ assert self.__ensemble_member_dim is not None, (
416
+ "First call write_ensemble_data to ensure "
417
+ "the realization dimension has been created"
418
+ )
418
419
 
419
420
  for variable_name in variable_names:
420
421
  self.__dataset.createVariable(
@@ -446,15 +447,15 @@ class ExportDataset:
446
447
  :param values: The values that are to be written to the file
447
448
  :param ensemble_size: the number of members in the ensemble
448
449
  """
449
- assert (
450
- self.__station_id_to_index_mapping is not None
451
- ), "First call write_station_data and create_variables."
450
+ assert self.__station_id_to_index_mapping is not None, (
451
+ "First call write_station_data and create_variables."
452
+ )
452
453
 
453
454
  station_index = self.__station_id_to_index_mapping[station_id]
454
455
  if ensemble_size > 1:
455
- self.__dataset.variables[variable_name][
456
- :, station_index, ensemble_member_index
457
- ] = values
456
+ self.__dataset.variables[variable_name][:, station_index, ensemble_member_index] = (
457
+ values
458
+ )
458
459
  else:
459
460
  self.__dataset.variables[variable_name][:, station_index] = values
460
461
 
@@ -333,8 +333,11 @@ class ParameterConfig:
333
333
 
334
334
  parameters = group.findall("pi:parameter", ns)
335
335
  for parameter in parameters:
336
- yield location_id, model_id, parameter.attrib["id"], self.__parse_parameter(
337
- parameter
336
+ yield (
337
+ location_id,
338
+ model_id,
339
+ parameter.attrib["id"],
340
+ self.__parse_parameter(parameter),
338
341
  )
339
342
 
340
343
 
@@ -60,9 +60,9 @@ class DataConfig:
60
60
  logger.error(message)
61
61
  raise Exception(message)
62
62
  else:
63
- self.__location_parameter_ids[
64
- internal_id
65
- ] = self.__pi_location_parameter_id(pi_timeseries, "fews")
63
+ self.__location_parameter_ids[internal_id] = (
64
+ self.__pi_location_parameter_id(pi_timeseries, "fews")
65
+ )
66
66
  self.__variable_map[external_id] = internal_id
67
67
 
68
68
  for k in ["import", "export"]:
@@ -670,7 +670,7 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
670
670
  for ensemble_member in range(self.ensemble_size)
671
671
  ]
672
672
  if (
673
- len(values) == 1 or (np.all(values) == values[0])
673
+ len(values) == 1 or all(v == values[0] for v in values)
674
674
  ) and parameter.name() not in dynamic_parameter_names:
675
675
  constant_parameters.append(parameter)
676
676
  constant_parameter_values.append(values[0])
@@ -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
@@ -1028,8 +1028,8 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
1028
1028
  + len(self.dae_variables["constant_inputs"])
1029
1029
  ]
1030
1030
  constant_inputs_1 = accumulated_U[
1031
- 2 * len(collocated_variables)
1032
- + len(self.dae_variables["constant_inputs"]) : 2 * len(collocated_variables)
1031
+ 2 * len(collocated_variables) + len(self.dae_variables["constant_inputs"]) : 2
1032
+ * len(collocated_variables)
1033
1033
  + 2 * len(self.dae_variables["constant_inputs"])
1034
1034
  ]
1035
1035
 
@@ -1803,9 +1803,9 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
1803
1803
  # Cast delay from DM to np.array
1804
1804
  delay = delay.toarray().flatten()
1805
1805
 
1806
- assert np.all(
1807
- np.isfinite(delay)
1808
- ), "Delay duration must be resolvable to real values at transcribe()"
1806
+ assert np.all(np.isfinite(delay)), (
1807
+ "Delay duration must be resolvable to real values at transcribe()"
1808
+ )
1809
1809
 
1810
1810
  out_times = np.concatenate([history_times, collocation_times])
1811
1811
  out_values = ca.veccat(
@@ -2043,9 +2043,7 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
2043
2043
  def controls(self):
2044
2044
  return self.__controls
2045
2045
 
2046
- def _collint_get_lbx_ubx(self, count, indices):
2047
- bounds = self.bounds()
2048
-
2046
+ def _collint_get_lbx_ubx(self, bounds, count, indices):
2049
2047
  lbx = np.full(count, -np.inf, dtype=np.float64)
2050
2048
  ubx = np.full(count, np.inf, dtype=np.float64)
2051
2049
 
@@ -2210,7 +2208,7 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
2210
2208
  count = max(count, control_indices_stop)
2211
2209
 
2212
2210
  discrete = self._collint_get_discrete(count, indices)
2213
- lbx, ubx = self._collint_get_lbx_ubx(count, indices)
2211
+ lbx, ubx = self._collint_get_lbx_ubx(bounds, count, indices)
2214
2212
  x0 = self._collint_get_x0(count, indices)
2215
2213
 
2216
2214
  # Return number of control variables
@@ -2326,7 +2324,7 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
2326
2324
  offset += 1
2327
2325
 
2328
2326
  discrete = self._collint_get_discrete(count, indices)
2329
- lbx, ubx = self._collint_get_lbx_ubx(count, indices)
2327
+ lbx, ubx = self._collint_get_lbx_ubx(bounds, count, indices)
2330
2328
  x0 = self._collint_get_x0(count, indices)
2331
2329
 
2332
2330
  # Return number of state variables
@@ -2610,7 +2608,7 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
2610
2608
  else:
2611
2609
  tf = xf = ca.MX()
2612
2610
  t = ca.vertcat(t0, history_times[history_indices], times[indices], tf)
2613
- x = ca.vertcat(x0, history[history_indices], state[indices[0] : indices[-1] + 1], xf)
2611
+ x = ca.vertcat(x0, history[history_indices], state[indices], xf)
2614
2612
 
2615
2613
  return x, t
2616
2614
 
@@ -2869,8 +2867,7 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
2869
2867
 
2870
2868
  # Check coefficient matrix
2871
2869
  logger.info(
2872
- "Sanity check on objective and constraints Jacobian matrix"
2873
- "/constant coefficients values"
2870
+ "Sanity check on objective and constraints Jacobian matrix/constant coefficients values"
2874
2871
  )
2875
2872
 
2876
2873
  in_var = nlp["x"]
@@ -3113,7 +3110,7 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
3113
3110
  variable_to_all_indices = {k: set(v) for k, v in indices[0].items()}
3114
3111
  for ensemble_indices in indices[1:]:
3115
3112
  for k, v in ensemble_indices.items():
3116
- variable_to_all_indices[k] |= v
3113
+ variable_to_all_indices[k] |= set(v)
3117
3114
 
3118
3115
  if len(inds_up) > 0:
3119
3116
  exceedences = []
@@ -50,7 +50,7 @@ class ControlTreeMixin(OptimizationProblem):
50
50
  return options
51
51
 
52
52
  def discretize_control(self, variable, ensemble_member, times, offset):
53
- control_indices = np.zeros(len(times), dtype=np.int16)
53
+ control_indices = np.zeros(len(times), dtype=np.int64)
54
54
  for branch, members in self.__branches.items():
55
55
  if ensemble_member not in members:
56
56
  continue
@@ -86,6 +86,11 @@ class ControlTreeMixin(OptimizationProblem):
86
86
  logger.debug("ControlTreeMixin: Branching times:")
87
87
  logger.debug(self.__branching_times)
88
88
 
89
+ # Avoid calling constant_inputs() many times
90
+ constant_inputs = [
91
+ self.constant_inputs(ensemble_member=i) for i in range(self.ensemble_size)
92
+ ]
93
+
89
94
  # Branches start at branching times, so that the tree looks like the following:
90
95
  #
91
96
  # *-----
@@ -122,18 +127,16 @@ class ControlTreeMixin(OptimizationProblem):
122
127
  for forecast_variable in options["forecast_variables"]:
123
128
  # We assume the time stamps of the forecasts in all ensemble
124
129
  # members to be identical
125
- timeseries = self.constant_inputs(ensemble_member=0)[forecast_variable]
130
+ timeseries = constant_inputs[0][forecast_variable]
126
131
  els = np.logical_and(
127
132
  timeseries.times >= branching_time_0, timeseries.times < branching_time_1
128
133
  )
129
134
 
130
135
  # Compute distance between ensemble members
131
136
  for i, member_i in enumerate(branches[current_branch]):
132
- timeseries_i = self.constant_inputs(ensemble_member=member_i)[forecast_variable]
137
+ timeseries_i = constant_inputs[member_i][forecast_variable]
133
138
  for j, member_j in enumerate(branches[current_branch]):
134
- timeseries_j = self.constant_inputs(ensemble_member=member_j)[
135
- forecast_variable
136
- ]
139
+ timeseries_j = constant_inputs[member_j][forecast_variable]
137
140
  distances[i, j] += np.linalg.norm(
138
141
  timeseries_i.values[els] - timeseries_j.values[els]
139
142
  )
@@ -55,7 +55,7 @@ class LookupTable(LookupTableBase):
55
55
  "This lookup table was not instantiated with tck metadata. \
56
56
  Domain/Range information is unavailable."
57
57
  )
58
- if type(t) == tuple and len(t) == 2:
58
+ if isinstance(t, tuple) and len(t) == 2:
59
59
  raise NotImplementedError(
60
60
  "Domain/Range information is not yet implemented for 2D LookupTables"
61
61
  )
@@ -298,8 +298,9 @@ class CSVLookupTableMixin(OptimizationProblem):
298
298
  def check_lookup_table(lookup_table):
299
299
  if lookup_table in self.__lookup_tables:
300
300
  raise Exception(
301
- "Cannot add lookup table {},"
302
- "since there is already one with this name.".format(lookup_table)
301
+ "Cannot add lookup table {},since there is already one with this name.".format(
302
+ lookup_table
303
+ )
303
304
  )
304
305
 
305
306
  # Read CSV files
@@ -358,6 +359,7 @@ class CSVLookupTableMixin(OptimizationProblem):
358
359
  k=k,
359
360
  monotonicity=mono,
360
361
  curvature=curv,
362
+ ipopt_options={"nlp_scaling_method": "none"},
361
363
  )
362
364
  else:
363
365
  raise Exception(
@@ -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
 
@@ -351,8 +351,9 @@ class GoalProgrammingMixin(_GoalProgrammingMixinBase):
351
351
  if goal.has_target_bounds:
352
352
  # We use a violation variable formulation, with the violation
353
353
  # variables epsilon bounded between 0 and 1.
354
- m, M = np.full_like(epsilon, -np.inf, dtype=np.float64), np.full_like(
355
- epsilon, np.inf, dtype=np.float64
354
+ m, M = (
355
+ np.full_like(epsilon, -np.inf, dtype=np.float64),
356
+ np.full_like(epsilon, np.inf, dtype=np.float64),
356
357
  )
357
358
 
358
359
  # A function range does not have to be specified for critical
@@ -437,7 +437,7 @@ class _GoalConstraint:
437
437
  ):
438
438
  assert isinstance(m, (float, np.ndarray, Timeseries))
439
439
  assert isinstance(M, (float, np.ndarray, Timeseries))
440
- assert type(m) == type(M)
440
+ assert type(m) is type(M)
441
441
 
442
442
  # NumPy arrays only allowed for vector goals
443
443
  if isinstance(m, np.ndarray):
@@ -982,8 +982,9 @@ class _GoalProgrammingMixinBase(OptimizationProblem, metaclass=ABCMeta):
982
982
  if goal.has_target_bounds:
983
983
  # We use a violation variable formulation, with the violation
984
984
  # variables epsilon bounded between 0 and 1.
985
- m, M = np.full_like(epsilon, -np.inf, dtype=np.float64), np.full_like(
986
- epsilon, np.inf, dtype=np.float64
985
+ m, M = (
986
+ np.full_like(epsilon, -np.inf, dtype=np.float64),
987
+ np.full_like(epsilon, np.inf, dtype=np.float64),
987
988
  )
988
989
 
989
990
  # A function range does not have to be specified for critical
@@ -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
 
@@ -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
 
@@ -326,7 +334,7 @@ class ModelicaMixin(OptimizationProblem):
326
334
  try:
327
335
  (m, M) = bounds[sym_name]
328
336
  except KeyError:
329
- if self.__python_types.get(sym_name, float) == bool:
337
+ if self.__python_types.get(sym_name, float) is bool:
330
338
  (m, M) = (0, 1)
331
339
  else:
332
340
  (m, M) = (-np.inf, np.inf)
@@ -400,7 +408,7 @@ class ModelicaMixin(OptimizationProblem):
400
408
  return seed
401
409
 
402
410
  def variable_is_discrete(self, variable):
403
- return self.__python_types.get(variable, float) != float
411
+ return self.__python_types.get(variable, float) is not float
404
412
 
405
413
  @property
406
414
  @cached
@@ -199,12 +199,12 @@ class OptimizationProblem(DataStoreAccessor, metaclass=ABCMeta):
199
199
  log_level = logging.INFO
200
200
  logger.log(
201
201
  log_level,
202
- "Solver succeeded with status {} ({}).".format(return_status, wall_clock_time),
202
+ "Solver failed with status {} ({}).".format(return_status, wall_clock_time),
203
203
  )
204
204
  except (AttributeError, ValueError):
205
205
  logger.log(
206
206
  log_level,
207
- "Solver succeeded with status {} ({}).".format(return_status, wall_clock_time),
207
+ "Solver failed with status {} ({}).".format(return_status, wall_clock_time),
208
208
  )
209
209
 
210
210
  # Do any postprocessing
@@ -314,6 +314,24 @@ class OptimizationProblem(DataStoreAccessor, metaclass=ABCMeta):
314
314
  if log_level == logging.ERROR and not log_solver_failure_as_error:
315
315
  log_level = logging.INFO
316
316
 
317
+ if self.solver_options()["solver"].lower() == "knitro":
318
+ list_feas_flags = [
319
+ "KN_RC_OPTIMAL_OR_SATISFACTORY",
320
+ "KN_RC_ITER_LIMIT_FEAS",
321
+ "KN_RC_NEAR_OPT",
322
+ "KN_RC_FEAS_XTOL",
323
+ "KN_RC_FEAS_NO_IMPROVE",
324
+ "KN_RC_FEAS_FTOL",
325
+ "KN_RC_TIME_LIMIT_FEAS",
326
+ "KN_RC_FEVAL_LIMIT_FEAS",
327
+ "KN_RC_MIP_EXH_FEAS",
328
+ "KN_RC_MIP_TERM_FEAS",
329
+ "KN_RC_MIP_SOLVE_LIMIT_FEAS",
330
+ "KN_RC_MIP_NODE_LIMIT_FEAS",
331
+ ]
332
+ if solver_stats["return_status"] in list_feas_flags:
333
+ success = True
334
+
317
335
  return success, log_level
318
336
 
319
337
  @abstractproperty
@@ -285,8 +285,8 @@ class PIMixin(IOMixin):
285
285
  :param variable: Time series ID.
286
286
  :param unit: Unit.
287
287
  """
288
- assert hasattr(
289
- self, "_PIMixin__timeseries_import"
290
- ), "set_unit can only be called after read() in pre() has finished."
288
+ assert hasattr(self, "_PIMixin__timeseries_import"), (
289
+ "set_unit can only be called after read() in pre() has finished."
290
+ )
291
291
  self.__timeseries_import.set_unit(variable, unit, 0)
292
292
  self.__timeseries_export.set_unit(variable, unit, 0)
@@ -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(Path(importlib.resources.files(ep.module).joinpath(ep.attr)))
64
68
 
65
69
  tlds = {}
66
70
  for lf in library_folders:
@@ -100,11 +104,8 @@ def download_examples(*args):
100
104
  from zipfile import ZipFile
101
105
 
102
106
  version = rtctools.__version__
103
- rtc_full_name = "rtc-tools-{}".format(version)
104
107
  try:
105
- url = "https://gitlab.com/deltares/rtc-tools/-/archive/{}/{}.zip".format(
106
- version, rtc_full_name
107
- )
108
+ url = "https://github.com/deltares/rtc-tools/zipball/{}".format(version)
108
109
 
109
110
  opener = urllib.request.build_opener()
110
111
  urllib.request.install_opener(opener)
@@ -115,11 +116,12 @@ def download_examples(*args):
115
116
 
116
117
  with ZipFile(local_filename, "r") as z:
117
118
  target = path / "rtc-tools-examples"
118
- prefix = "{}/examples/".format(rtc_full_name)
119
+ zip_folder_name = next(x for x in z.namelist() if x.startswith("Deltares-rtc-tools-"))
120
+ prefix = "{}/examples/".format(zip_folder_name.rstrip("/"))
119
121
  members = [x for x in z.namelist() if x.startswith(prefix)]
120
122
  z.extractall(members=members)
121
123
  shutil.move(prefix, target)
122
- shutil.rmtree(rtc_full_name)
124
+ shutil.rmtree(zip_folder_name)
123
125
 
124
126
  sys.exit("Succesfully downloaded the RTC-Tools examples to '{}'".format(target.resolve()))
125
127
 
@@ -94,7 +94,7 @@ class IOMixin(SimulationProblem, metaclass=ABCMeta):
94
94
  self.__cache_loop_timeseries = {}
95
95
 
96
96
  timeseries_names = set(self.io.get_timeseries_names(0))
97
- for v in self.get_variables():
97
+ for v in self.get_input_variables():
98
98
  if v in timeseries_names:
99
99
  _, values = self.io.get_timeseries_sec(v)
100
100
  self.__cache_loop_timeseries[v] = values
@@ -248,8 +248,8 @@ class PIMixin(IOMixin):
248
248
  :param variable: Time series ID.
249
249
  :param unit: Unit.
250
250
  """
251
- assert hasattr(
252
- self, "_PIMixin__timeseries_import"
253
- ), "set_unit can only be called after read() in pre() has finished."
251
+ assert hasattr(self, "_PIMixin__timeseries_import"), (
252
+ "set_unit can only be called after read() in pre() has finished."
253
+ )
254
254
  self.__timeseries_import.set_unit(variable, unit, 0)
255
255
  self.__timeseries_export.set_unit(variable, unit, 0)
@@ -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
 
@@ -492,8 +500,9 @@ class SimulationProblem(DataStoreAccessor):
492
500
  self.set_var(var_name, numeric_start_val)
493
501
  except KeyError:
494
502
  logger.warning(
495
- "Initialize: {} not found in state vector. "
496
- "Initial value of {} not set.".format(var_name, numeric_start_val)
503
+ "Initialize: {} not found in state vector. Initial value of {} not set.".format(
504
+ var_name, numeric_start_val
505
+ )
497
506
  )
498
507
 
499
508
  # Add a residual for the difference between the state and its starting expression
@@ -517,10 +526,14 @@ class SimulationProblem(DataStoreAccessor):
517
526
  # Check that the start_value is in between the variable bounds.
518
527
  if start_is_numeric and not min_is_symbolic and not max_is_symbolic:
519
528
  if not (var.min <= start_val and start_val <= var.max):
520
- logger.warning(
521
- "Initialize: start value {} = {}".format(var_name, start_val)
522
- + " is not in between bounds {} and {}".format(var.min, var.max)
523
- + " and will be adjusted."
529
+ logger.log(
530
+ (
531
+ logging.WARNING
532
+ if source_description != "modelica file or default value"
533
+ else logging.DEBUG
534
+ ),
535
+ f"Initialize: start value {var_name} = {start_val} "
536
+ f"is not in between bounds {var.min} and {var.max} and will be adjusted.",
524
537
  )
525
538
 
526
539
  # Default start var for ders is zero
@@ -1059,9 +1072,9 @@ class SimulationProblem(DataStoreAccessor):
1059
1072
  :param dt: Timestep size of the simulation.
1060
1073
  """
1061
1074
  if self._dt_is_fixed:
1062
- assert math.isclose(
1063
- self.__dt, dt
1064
- ), "Timestep size dt is marked as constant and cannot be changed."
1075
+ assert math.isclose(self.__dt, dt), (
1076
+ "Timestep size dt is marked as constant and cannot be changed."
1077
+ )
1065
1078
  else:
1066
1079
  self.__dt = dt
1067
1080
 
@@ -1244,9 +1257,9 @@ class SimulationProblem(DataStoreAccessor):
1244
1257
  # Where imported model libraries are located.
1245
1258
  library_folders = self.modelica_library_folders.copy()
1246
1259
 
1247
- for ep in pkg_resources.iter_entry_points(group="rtctools.libraries.modelica"):
1260
+ for ep in importlib_metadata.entry_points(group="rtctools.libraries.modelica"):
1248
1261
  if ep.name == "library_folder":
1249
- library_folders.append(pkg_resources.resource_filename(ep.module_name, ep.attrs[0]))
1262
+ library_folders.append(str(importlib.resources.files(ep.module).joinpath(ep.attr)))
1250
1263
 
1251
1264
  compiler_options["library_folders"] = library_folders
1252
1265
 
@@ -110,6 +110,7 @@ def run_optimization_problem(
110
110
  "GoalProgrammingMixin",
111
111
  "PIMixin",
112
112
  "CSVMixin",
113
+ "IOMixin",
113
114
  "ModelicaMixin",
114
115
  "PlanningMixin",
115
116
  "ControlTreeMixin",
File without changes
File without changes
File without changes
File without changes