rtc-tools 2.7.3__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.
- rtc_tools-2.7.3.dist-info/METADATA +53 -0
- rtc_tools-2.7.3.dist-info/RECORD +50 -0
- rtc_tools-2.7.3.dist-info/WHEEL +5 -0
- rtc_tools-2.7.3.dist-info/entry_points.txt +3 -0
- rtc_tools-2.7.3.dist-info/licenses/COPYING.LESSER +165 -0
- rtc_tools-2.7.3.dist-info/top_level.txt +1 -0
- rtctools/__init__.py +5 -0
- rtctools/_internal/__init__.py +0 -0
- rtctools/_internal/alias_tools.py +188 -0
- rtctools/_internal/caching.py +25 -0
- rtctools/_internal/casadi_helpers.py +99 -0
- rtctools/_internal/debug_check_helpers.py +41 -0
- rtctools/_version.py +21 -0
- rtctools/data/__init__.py +4 -0
- rtctools/data/csv.py +150 -0
- rtctools/data/interpolation/__init__.py +3 -0
- rtctools/data/interpolation/bspline.py +31 -0
- rtctools/data/interpolation/bspline1d.py +169 -0
- rtctools/data/interpolation/bspline2d.py +54 -0
- rtctools/data/netcdf.py +467 -0
- rtctools/data/pi.py +1236 -0
- rtctools/data/rtc.py +228 -0
- rtctools/data/storage.py +343 -0
- rtctools/optimization/__init__.py +0 -0
- rtctools/optimization/collocated_integrated_optimization_problem.py +3208 -0
- rtctools/optimization/control_tree_mixin.py +221 -0
- rtctools/optimization/csv_lookup_table_mixin.py +462 -0
- rtctools/optimization/csv_mixin.py +300 -0
- rtctools/optimization/goal_programming_mixin.py +769 -0
- rtctools/optimization/goal_programming_mixin_base.py +1094 -0
- rtctools/optimization/homotopy_mixin.py +165 -0
- rtctools/optimization/initial_state_estimation_mixin.py +89 -0
- rtctools/optimization/io_mixin.py +320 -0
- rtctools/optimization/linearization_mixin.py +33 -0
- rtctools/optimization/linearized_order_goal_programming_mixin.py +235 -0
- rtctools/optimization/min_abs_goal_programming_mixin.py +385 -0
- rtctools/optimization/modelica_mixin.py +482 -0
- rtctools/optimization/netcdf_mixin.py +177 -0
- rtctools/optimization/optimization_problem.py +1302 -0
- rtctools/optimization/pi_mixin.py +292 -0
- rtctools/optimization/planning_mixin.py +19 -0
- rtctools/optimization/single_pass_goal_programming_mixin.py +676 -0
- rtctools/optimization/timeseries.py +56 -0
- rtctools/rtctoolsapp.py +131 -0
- rtctools/simulation/__init__.py +0 -0
- rtctools/simulation/csv_mixin.py +171 -0
- rtctools/simulation/io_mixin.py +195 -0
- rtctools/simulation/pi_mixin.py +255 -0
- rtctools/simulation/simulation_problem.py +1293 -0
- rtctools/util.py +241 -0
rtctools/data/csv.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import sys
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Union
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger("rtctools")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _boolean_to_nan(data, fname):
|
|
12
|
+
"""
|
|
13
|
+
Empty columns are detected as boolean full of "False". We instead want this to be np.nan.
|
|
14
|
+
We cannot distinguish between explicitly desired boolean columns, so instead we convert all
|
|
15
|
+
boolean columns to np.nan, and raise a warning.
|
|
16
|
+
"""
|
|
17
|
+
data = data.copy()
|
|
18
|
+
|
|
19
|
+
dtypes_in = []
|
|
20
|
+
for i in range(0, len(data.dtype)):
|
|
21
|
+
dtypes_in.append(data.dtype.descr[i])
|
|
22
|
+
|
|
23
|
+
convert_to_nan = []
|
|
24
|
+
dtypes_out = []
|
|
25
|
+
for i, name in enumerate(data.dtype.names):
|
|
26
|
+
if dtypes_in[i][1][1] == "b":
|
|
27
|
+
convert_to_nan.append(name)
|
|
28
|
+
dtypes_out.append((dtypes_in[i][0], "<f8"))
|
|
29
|
+
else:
|
|
30
|
+
dtypes_out.append(dtypes_in[i])
|
|
31
|
+
|
|
32
|
+
if convert_to_nan:
|
|
33
|
+
logger.warning(
|
|
34
|
+
"Column(s) {} were detected as boolean in '{}'; converting to NaN".format(
|
|
35
|
+
", ".join(["'{}'".format(name) for name in convert_to_nan]), fname
|
|
36
|
+
)
|
|
37
|
+
)
|
|
38
|
+
data = data.astype(dtypes_out)
|
|
39
|
+
for name in convert_to_nan:
|
|
40
|
+
data[name] = np.nan
|
|
41
|
+
|
|
42
|
+
return data
|
|
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
|
+
|
|
60
|
+
def load(fname, delimiter=",", with_time=False):
|
|
61
|
+
"""
|
|
62
|
+
Check delimiter of csv and read contents to an array. Assumes no date-time conversion needed.
|
|
63
|
+
|
|
64
|
+
:param fname: Filename.
|
|
65
|
+
:param delimiter: CSV column delimiter.
|
|
66
|
+
:param with_time: Whether the first column is expected to contain time stamps.
|
|
67
|
+
|
|
68
|
+
:returns: A named numpy array with the contents of the file.
|
|
69
|
+
"""
|
|
70
|
+
c = {}
|
|
71
|
+
if with_time:
|
|
72
|
+
c.update({0: _string_to_datetime})
|
|
73
|
+
|
|
74
|
+
# Check delimiter of csv file. If semicolon, check if decimal separator is
|
|
75
|
+
# a comma.
|
|
76
|
+
if delimiter == ";":
|
|
77
|
+
with open(fname, "rb") as csvfile:
|
|
78
|
+
# Read the first line, this should be a header. Count columns by
|
|
79
|
+
# counting separator.
|
|
80
|
+
sample_csvfile = csvfile.readline()
|
|
81
|
+
n_semicolon = sample_csvfile.count(b";")
|
|
82
|
+
# We actually only need one number to evaluate if commas are used as decimal
|
|
83
|
+
# separator, but certain csv writers don't use a decimal when the value has
|
|
84
|
+
# no meaningful decimal(e.g. 12.0 becomes 12) so we read the next 1024 bytes
|
|
85
|
+
# to make sure we catch a number.
|
|
86
|
+
sample_csvfile = csvfile.read(1024)
|
|
87
|
+
# Count the commas
|
|
88
|
+
n_comma_decimal = sample_csvfile.count(b",")
|
|
89
|
+
# If commas are used as decimal separator, we need additional
|
|
90
|
+
# converters.
|
|
91
|
+
if n_comma_decimal:
|
|
92
|
+
c.update({i + len(c): _string_to_float for i in range(1 + n_semicolon - len(c))})
|
|
93
|
+
|
|
94
|
+
# Read the csv file and convert to array
|
|
95
|
+
try:
|
|
96
|
+
if len(c): # Converters exist, so use them.
|
|
97
|
+
try:
|
|
98
|
+
data = np.genfromtxt(
|
|
99
|
+
fname, delimiter=delimiter, deletechars="", dtype=None, names=True, converters=c
|
|
100
|
+
)
|
|
101
|
+
return _boolean_to_nan(data, fname)
|
|
102
|
+
except (
|
|
103
|
+
np.lib._iotools.ConverterError
|
|
104
|
+
): # value does not conform to expected date-time format
|
|
105
|
+
type, value, traceback = sys.exc_info()
|
|
106
|
+
logger.error(
|
|
107
|
+
"CSVMixin: converter of csv reader failed on {}: {}".format(fname, value)
|
|
108
|
+
)
|
|
109
|
+
raise ValueError(
|
|
110
|
+
"CSVMixin: wrong date time or value format in {}. "
|
|
111
|
+
"Should be %Y-%m-%d %H:%M:%S and numerical values everywhere.".format(fname)
|
|
112
|
+
)
|
|
113
|
+
else:
|
|
114
|
+
data = np.genfromtxt(fname, delimiter=delimiter, deletechars="", dtype=None, names=True)
|
|
115
|
+
return _boolean_to_nan(data, fname)
|
|
116
|
+
except ValueError:
|
|
117
|
+
# can occur when delimiter changes after first 1024 bytes of file,
|
|
118
|
+
# or delimiter is not , or ;
|
|
119
|
+
type, value, traceback = sys.exc_info()
|
|
120
|
+
logger.error("CSV: Value reader of csv reader failed on {}: {}".format(fname, value))
|
|
121
|
+
raise ValueError(
|
|
122
|
+
"CSV: could not read all values from {}. Used delimiter '{}'. "
|
|
123
|
+
"Please check delimiter (should be ',' or ';' throughout the file) "
|
|
124
|
+
"and if all values are numbers.".format(fname, delimiter)
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def save(fname, data, delimiter=",", with_time=False):
|
|
129
|
+
"""
|
|
130
|
+
Write the contents of an array to a csv file.
|
|
131
|
+
|
|
132
|
+
:param fname: Filename.
|
|
133
|
+
:param data: A named numpy array with the data to write.
|
|
134
|
+
:param delimiter: CSV column delimiter.
|
|
135
|
+
:param with_time: Whether to output the first column of the data as time stamps.
|
|
136
|
+
"""
|
|
137
|
+
if with_time:
|
|
138
|
+
data["time"] = [t.strftime("%Y-%m-%d %H:%M:%S") for t in data["time"]]
|
|
139
|
+
fmt = ["%s"] + (len(data.dtype.names) - 1) * ["%f"]
|
|
140
|
+
else:
|
|
141
|
+
fmt = len(data.dtype.names) * ["%f"]
|
|
142
|
+
|
|
143
|
+
np.savetxt(
|
|
144
|
+
fname,
|
|
145
|
+
data,
|
|
146
|
+
delimiter=delimiter,
|
|
147
|
+
header=delimiter.join(data.dtype.names),
|
|
148
|
+
fmt=fmt,
|
|
149
|
+
comments="",
|
|
150
|
+
)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from casadi import if_else, logic_and
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class BSpline:
|
|
5
|
+
"""
|
|
6
|
+
B-Spline base class.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
def basis(self, t, x, k, i):
|
|
10
|
+
"""
|
|
11
|
+
Evaluate the B-Spline basis function using Cox-de Boor recursion.
|
|
12
|
+
|
|
13
|
+
:param x: Point at which to evaluate.
|
|
14
|
+
:param k: Order of the basis function.
|
|
15
|
+
:param i: Knot number.
|
|
16
|
+
|
|
17
|
+
:returns: The B-Spline basis function of the given order, at the given knot, evaluated at
|
|
18
|
+
the given point.
|
|
19
|
+
"""
|
|
20
|
+
if k == 0:
|
|
21
|
+
return if_else(logic_and(t[i] <= x, x < t[i + 1]), 1.0, 0.0)
|
|
22
|
+
else:
|
|
23
|
+
if t[i] < t[i + k]:
|
|
24
|
+
a = (x - t[i]) / (t[i + k] - t[i]) * self.basis(t, x, k - 1, i)
|
|
25
|
+
else:
|
|
26
|
+
a = 0.0
|
|
27
|
+
if t[i + 1] < t[i + k + 1]:
|
|
28
|
+
b = (t[i + k + 1] - x) / (t[i + k + 1] - t[i + 1]) * self.basis(t, x, k - 1, i + 1)
|
|
29
|
+
else:
|
|
30
|
+
b = 0.0
|
|
31
|
+
return a + b
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from casadi import SX, Function, if_else, inf, jacobian, logic_and, nlpsol, sum2, vertcat
|
|
3
|
+
|
|
4
|
+
from .bspline import BSpline
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BSpline1D(BSpline):
|
|
8
|
+
"""
|
|
9
|
+
Arbitrary order, one-dimensional, non-uniform B-Spline implementation using Cox-de Boor
|
|
10
|
+
recursion.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, t, w, k=3):
|
|
14
|
+
"""
|
|
15
|
+
Create a new 1D B-Spline object.
|
|
16
|
+
|
|
17
|
+
:param t: Knot vector.
|
|
18
|
+
:param w: Weight vector.
|
|
19
|
+
:param k: Spline order.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
# Store arguments
|
|
23
|
+
self.__t = t
|
|
24
|
+
self.__w = w
|
|
25
|
+
self.__k = k
|
|
26
|
+
|
|
27
|
+
def __call__(self, x):
|
|
28
|
+
"""
|
|
29
|
+
Evaluate the B-Spline at point x.
|
|
30
|
+
|
|
31
|
+
The support of this function is the half-open interval [t[0], t[-1]).
|
|
32
|
+
|
|
33
|
+
:param x: The point at which to evaluate.
|
|
34
|
+
|
|
35
|
+
:returns: The spline evaluated at the given point.
|
|
36
|
+
"""
|
|
37
|
+
y = 0.0
|
|
38
|
+
for i in range(len(self.__t) - self.__k - 1):
|
|
39
|
+
y += if_else(
|
|
40
|
+
logic_and(x >= self.__t[i], x <= self.__t[i + self.__k + 1]),
|
|
41
|
+
self.__w[i] * self.basis(self.__t, x, self.__k, i),
|
|
42
|
+
0.0,
|
|
43
|
+
)
|
|
44
|
+
return y
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def fit(
|
|
48
|
+
cls,
|
|
49
|
+
x,
|
|
50
|
+
y,
|
|
51
|
+
k=3,
|
|
52
|
+
monotonicity=0,
|
|
53
|
+
curvature=0,
|
|
54
|
+
num_test_points=100,
|
|
55
|
+
epsilon=1e-7,
|
|
56
|
+
delta=1e-4,
|
|
57
|
+
interior_pts=None,
|
|
58
|
+
ipopt_options=None,
|
|
59
|
+
):
|
|
60
|
+
"""
|
|
61
|
+
fit() returns a tck tuple like scipy.interpolate.splrep, but adjusts
|
|
62
|
+
the weights to meet the desired constraints to the curvature of the spline curve.
|
|
63
|
+
|
|
64
|
+
:param monotonicity:
|
|
65
|
+
- is an integer, magnitude is ignored
|
|
66
|
+
- if positive, causes spline to be monotonically increasing
|
|
67
|
+
- if negative, causes spline to be monotonically decreasing
|
|
68
|
+
- if 0, leaves spline monotonicity unconstrained
|
|
69
|
+
|
|
70
|
+
:param curvature:
|
|
71
|
+
- is an integer, magnitude is ignored
|
|
72
|
+
- if positive, causes spline curvature to be positive (convex)
|
|
73
|
+
- if negative, causes spline curvature to be negative (concave)
|
|
74
|
+
- if 0, leaves spline curvature unconstrained
|
|
75
|
+
|
|
76
|
+
:param num_test_points:
|
|
77
|
+
- sets the number of points that the constraints will be applied at across
|
|
78
|
+
the range of the spline
|
|
79
|
+
|
|
80
|
+
:param epsilon:
|
|
81
|
+
- offset of monotonicity and curvature constraints from zero, ensuring strict
|
|
82
|
+
monotonicity
|
|
83
|
+
- if epsilon is set to less than the tolerance of the solver, errors will result
|
|
84
|
+
|
|
85
|
+
:param delta:
|
|
86
|
+
- amount the first and last knots are extended outside the range of the splined points
|
|
87
|
+
- ensures that the spline evaluates correctly at the first and last nodes, as
|
|
88
|
+
well as the distance delta beyond these nodes
|
|
89
|
+
|
|
90
|
+
:param interior_pts:
|
|
91
|
+
- optional list of interior knots to use
|
|
92
|
+
|
|
93
|
+
:returns: A tuple of spline knots, weights, and order.
|
|
94
|
+
"""
|
|
95
|
+
x = np.asarray(x)
|
|
96
|
+
y = np.asarray(y)
|
|
97
|
+
N = len(x)
|
|
98
|
+
|
|
99
|
+
if interior_pts is None:
|
|
100
|
+
# Generate knots: This algorithm is based on the Fitpack algorithm by p.dierckx
|
|
101
|
+
# The original code lives here: http://www.netlib.org/dierckx/
|
|
102
|
+
if k % 2 == 1:
|
|
103
|
+
interior_pts = x[k // 2 + 1 : -k // 2]
|
|
104
|
+
else:
|
|
105
|
+
interior_pts = (x[k // 2 + 1 : -k // 2] + x[k // 2 : -k // 2 - 1]) / 2
|
|
106
|
+
t = np.concatenate(
|
|
107
|
+
(np.full(k + 1, x[0] - delta), interior_pts, np.full(k + 1, x[-1] + delta))
|
|
108
|
+
)
|
|
109
|
+
num_knots = len(t)
|
|
110
|
+
|
|
111
|
+
# Casadi Variable Symbols
|
|
112
|
+
c = SX.sym("c", num_knots)
|
|
113
|
+
x_sym = SX.sym("x")
|
|
114
|
+
|
|
115
|
+
# Casadi Representation of Spline Function & Derivatives
|
|
116
|
+
expr = cls(t, c, k)(x_sym)
|
|
117
|
+
free_vars = [c, x_sym]
|
|
118
|
+
bspline = Function("bspline", free_vars, [expr])
|
|
119
|
+
J = jacobian(expr, x_sym)
|
|
120
|
+
# bspline_prime = Function('bspline_prime', free_vars, [J])
|
|
121
|
+
H = jacobian(J, x_sym)
|
|
122
|
+
bspline_prime_prime = Function("bspline_prime_prime", free_vars, [H])
|
|
123
|
+
|
|
124
|
+
# Objective Function
|
|
125
|
+
xpt = SX.sym("xpt")
|
|
126
|
+
ypt = SX.sym("ypt")
|
|
127
|
+
sq_diff = Function("sq_diff", [c, xpt, ypt], [(ypt - bspline(c, xpt)) ** 2])
|
|
128
|
+
sq_diff = sq_diff.map(N, "serial")
|
|
129
|
+
f = sum2(sq_diff(c, SX(x), SX(y)))
|
|
130
|
+
|
|
131
|
+
# Setup Curvature Constraints
|
|
132
|
+
delta_c_max = np.full(num_knots - 1, inf)
|
|
133
|
+
delta_c_min = np.full(num_knots - 1, -inf)
|
|
134
|
+
max_slope_slope = np.full(num_test_points, inf)
|
|
135
|
+
min_slope_slope = np.full(num_test_points, -inf)
|
|
136
|
+
if monotonicity != 0:
|
|
137
|
+
if monotonicity < 0:
|
|
138
|
+
delta_c_max = np.full(num_knots - 1, -epsilon)
|
|
139
|
+
else:
|
|
140
|
+
delta_c_min = np.full(num_knots - 1, epsilon)
|
|
141
|
+
if curvature != 0:
|
|
142
|
+
if curvature < 0:
|
|
143
|
+
max_slope_slope = np.full(num_test_points, -epsilon)
|
|
144
|
+
else:
|
|
145
|
+
min_slope_slope = np.full(num_test_points, epsilon)
|
|
146
|
+
monotonicity_constraints = vertcat(*[c[i + 1] - c[i] for i in range(num_knots - 1)])
|
|
147
|
+
x_linspace = np.linspace(x[0], x[-1], num_test_points)
|
|
148
|
+
curvature_constraints = vertcat(*[bspline_prime_prime(c, SX(x)) for x in x_linspace])
|
|
149
|
+
g = vertcat(monotonicity_constraints, curvature_constraints)
|
|
150
|
+
lbg = np.concatenate((delta_c_min, min_slope_slope))
|
|
151
|
+
ubg = np.concatenate((delta_c_max, max_slope_slope))
|
|
152
|
+
|
|
153
|
+
# Perform mini-optimization problem to calculate the the values of c
|
|
154
|
+
nlp = {"x": c, "f": f, "g": g}
|
|
155
|
+
my_solver = "ipopt"
|
|
156
|
+
solver = nlpsol(
|
|
157
|
+
"solver",
|
|
158
|
+
my_solver,
|
|
159
|
+
nlp,
|
|
160
|
+
{"print_time": 0, "expand": True, "ipopt": ipopt_options},
|
|
161
|
+
)
|
|
162
|
+
sol = solver(lbg=lbg, ubg=ubg)
|
|
163
|
+
stats = solver.stats()
|
|
164
|
+
return_status = stats["return_status"]
|
|
165
|
+
if return_status not in ["Solve_Succeeded", "Solved_To_Acceptable_Level", "SUCCESS"]:
|
|
166
|
+
raise Exception("Spline fitting failed with status {}".format(return_status))
|
|
167
|
+
|
|
168
|
+
# Return the new tck tuple
|
|
169
|
+
return (t, np.array(sol["x"]).ravel(), k)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from casadi import if_else, logic_and
|
|
2
|
+
|
|
3
|
+
from .bspline import BSpline
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BSpline2D(BSpline):
|
|
7
|
+
"""
|
|
8
|
+
Arbitrary order, two-dimensional, non-uniform B-Spline.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, tx, ty, w, kx=3, ky=3):
|
|
12
|
+
"""
|
|
13
|
+
Create a new 2D B-Spline object.
|
|
14
|
+
|
|
15
|
+
:param tx: Knot vector in X direction.
|
|
16
|
+
:param ty: Knot vector in Y direction.
|
|
17
|
+
:param w: Weight vector.
|
|
18
|
+
:param kx: Spline order in X direction.
|
|
19
|
+
:param ky: Spline order in Y direction.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
# Store arguments
|
|
23
|
+
self.__tx = tx
|
|
24
|
+
self.__ty = ty
|
|
25
|
+
self.__w = w
|
|
26
|
+
self.__kx = kx
|
|
27
|
+
self.__ky = ky
|
|
28
|
+
|
|
29
|
+
def __call__(self, x, y):
|
|
30
|
+
"""
|
|
31
|
+
Evaluate the B-Spline at point (x, y).
|
|
32
|
+
|
|
33
|
+
The support of this function is the half-open interval [tx[0], tx[-1]) x [ty[0], ty[-1]).
|
|
34
|
+
|
|
35
|
+
:param x: The coordinate of the point at which to evaluate.
|
|
36
|
+
:param y: The ordinate of the point at which to evaluate.
|
|
37
|
+
|
|
38
|
+
:returns: The spline evaluated at the given point.
|
|
39
|
+
"""
|
|
40
|
+
z = 0.0
|
|
41
|
+
for i in range(len(self.__tx) - self.__kx - 1):
|
|
42
|
+
bx = if_else(
|
|
43
|
+
logic_and(x >= self.__tx[i], x <= self.__tx[i + self.__kx + 1]),
|
|
44
|
+
self.basis(self.__tx, x, self.__kx, i),
|
|
45
|
+
0.0,
|
|
46
|
+
)
|
|
47
|
+
for j in range(len(self.__ty) - self.__ky - 1):
|
|
48
|
+
by = if_else(
|
|
49
|
+
logic_and(y >= self.__ty[j], y <= self.__ty[j + self.__ky + 1]),
|
|
50
|
+
self.basis(self.__ty, y, self.__ky, j),
|
|
51
|
+
0.0,
|
|
52
|
+
)
|
|
53
|
+
z += self.__w[i * (len(self.__ty) - self.__ky - 1) + j] * bx * by
|
|
54
|
+
return z
|