@@ -17,14 +17,14 @@ class BSplineFamily(BasisFamily):
1717 across a set of breakpoints with given order and smoothness.
1818
1919 """
20- def __init__ (self , breakpoints , degree , smoothness = None , vars = 1 ):
20+ def __init__ (self , breakpoints , degree , smoothness = None , vars = None ):
2121 """Create a B-spline basis for piecewise smooth polynomials
2222
2323 Define B-spline polynomials for a set of one or more variables.
24- B-splines are characterized by a set of intervals separated by break
25- points. On each interval we have a polynomial of a certain order
26- and the spline is continuous up to a given smoothness at interior
27- break points .
24+ B-splines are used as a basis for a set of piecewise smooth
25+ polynomials joined at breakpoints. On each interval we have a
26+ polynomial of a given order and the spline is continuous up to a
27+ given smoothness at interior breakpoints .
2828
2929 Parameters
3030 ----------
@@ -41,8 +41,11 @@ def __init__(self, breakpoints, degree, smoothness=None, vars=1):
4141 For each spline variable, the smoothness at breakpoints (number
4242 of derivatives that should match).
4343
44- vars : int or list of str, option
45- The number of spline variables or a list of spline variable names.
44+ vars : None or int, optional
45+ The number of spline variables. If specified as None (default),
46+ then the spline basis describes a single variable, with no
47+ indexing. If the number of spine variables is > 0, then the
48+ spline basis is index using the `var` keyword.
4649
4750 """
4851 # Process the breakpoints for the spline */
@@ -58,17 +61,19 @@ def __init__(self, breakpoints, degree, smoothness=None, vars=1):
5861 raise ValueError ("break points must be strictly increasing values" )
5962
6063 # Decide on the number of spline variables
61- if isinstance (vars , list ) and all ([isinstance (v , str ) for v in vars ]):
62- raise NotImplemented ("list of variable names not yet supported" )
64+ if vars is None :
65+ nvars = 1
66+ self .nvars = None # track as single variable
6367 elif not isinstance (vars , int ):
64- raise TypeError ("vars must be an integer or list of strings " )
68+ raise TypeError ("vars must be an integer" )
6569 else :
6670 nvars = vars
71+ self .nvars = nvars
6772
6873 #
6974 # Process B-spline parameters (order, smoothness)
7075 #
71- # B-splines are characterized by a set of intervals separated by
76+ # B-splines are defined on a set of intervals separated by
7277 # breakpoints. On each interval we have a polynomial of a certain
7378 # order and the spline is continuous up to a given smoothness at
7479 # breakpoints. The code in this section allows some flexibility in
@@ -99,14 +104,15 @@ def process_spline_parameters(
99104 elif all ([isinstance (v , allowed_types ) for v in values ]):
100105 # List of values => make sure it is the right size
101106 if len (values ) != length :
102- raise ValueError (f"length of '{ name } ' does not match n" )
107+ raise ValueError (f"length of '{ name } ' does not match"
108+ f" number of variables" )
103109 else :
104110 raise ValueError (f"could not parse '{ name } ' keyword" )
105111
106112 # Check to make sure the values are OK
107113 if values is not None and any ([val < minimum for val in values ]):
108114 raise ValueError (
109- f"invalid value for { name } ; must be at least { minimum } " )
115+ f"invalid value for ' { name } ' ; must be at least { minimum } " )
110116
111117 return values
112118
@@ -123,25 +129,23 @@ def process_spline_parameters(
123129 if any ([degree [i ] - smoothness [i ] < 1 for i in range (nvars )]):
124130 raise ValueError ("degree must be greater than smoothness" )
125131
126- # Store the parameters and process them in call_ntg()
127- self .nvars = nvars
132+ # Store the parameters for the spline (self.nvars already stored)
128133 self .breakpoints = breakpoints
129134 self .degree = degree
130135 self .smoothness = smoothness
131- self .nintervals = breakpoints .size - 1
132136
133137 #
134138 # Compute parameters for a SciPy BSpline object
135139 #
136- # To create a B-spline, we need to compute the knot points , keeping
137- # track of the use of repeated knot points at the initial knot and
140+ # To create a B-spline, we need to compute the knotpoints , keeping
141+ # track of the use of repeated knotpoints at the initial knot and
138142 # final knot as well as repeated knots at intermediate points
139143 # depending on the desired smoothness.
140144 #
141145
142146 # Store the coefficients for each output (useful later)
143147 self .coef_offset , self .coef_length , offset = [], [], 0
144- for i in range (self . nvars ):
148+ for i in range (nvars ):
145149 # Compute number of coefficients for the piecewise polynomial
146150 ncoefs = (self .degree [i ] + 1 ) * (len (self .breakpoints ) - 1 ) - \
147151 (self .smoothness [i ] + 1 ) * (len (self .breakpoints ) - 2 )
@@ -151,48 +155,43 @@ def process_spline_parameters(
151155 offset += ncoefs
152156 self .N = offset # save the total number of coefficients
153157
154- # Create knot points for each spline variable
158+ # Create knotpoints for each spline variable
155159 # TODO: extend to multi-dimensional breakpoints
156160 self .knotpoints = []
157- for i in range (self . nvars ):
161+ for i in range (nvars ):
158162 # Allocate space for the knotpoints
159163 self .knotpoints .append (np .empty (
160164 (self .degree [i ] + 1 ) + (len (self .breakpoints ) - 2 ) * \
161165 (self .degree [i ] - self .smoothness [i ]) + (self .degree [i ] + 1 )))
162166
163- # Initial knot points
167+ # Initial knotpoints (multiplicity = order)
164168 self .knotpoints [i ][0 :self .degree [i ] + 1 ] = self .breakpoints [0 ]
165169 offset = self .degree [i ] + 1
166170
167- # Interior knot points
171+ # Interior knotpoints (multiplicity = degree - smoothness)
168172 nknots = self .degree [i ] - self .smoothness [i ]
169173 assert nknots > 0 # just in case
170174 for j in range (1 , self .breakpoints .size - 1 ):
171175 self .knotpoints [i ][offset :offset + nknots ] = self .breakpoints [j ]
172176 offset += nknots
173177
174- # Final knot point
178+ # Final knotpoint (multiplicity = order)
175179 self .knotpoints [i ][offset :offset + self .degree [i ] + 1 ] = \
176180 self .breakpoints [- 1 ]
177181
178- def eval (self , coefs , tlist ):
179- return np .array ([
180- BSpline (self .knotpoints [i ],
181- coefs [self .coef_offset [i ]:
182- self .coef_offset [i ] + self .coef_length [i ]],
183- self .degree [i ])(tlist )
184- for i in range (self .nvars )])
185-
186182 # Compute the kth derivative of the ith basis function at time t
187- def eval_deriv (self , i , k , t , squeeze = True ):
183+ def eval_deriv (self , i , k , t , var = None ):
188184 """Evaluate the kth derivative of the ith basis function at time t."""
189- if self .nvars > 1 or not squeeze :
190- raise NotImplementedError (
191- "derivatives of multi-variable splines not yet supported" )
185+ if self .nvars is None or (self .nvars == 1 and var is None ):
186+ # Use same variable for all requests
187+ var = 0
188+ elif self .nvars > 1 and var is None :
189+ raise SystemError (
190+ "scalar variable call to multi-variable splines" )
192191
193192 # Create a coefficient vector for this spline
194- coefs = np .zeros (self .coef_length [0 ]); coefs [i ] = 1
193+ coefs = np .zeros (self .coef_length [var ]); coefs [i ] = 1
195194
196195 # Evaluate the derivative of the spline at the desired point in time
197- return BSpline (self .knotpoints [0 ], coefs ,
198- self .degree [0 ]).derivative (k )(t )
196+ return BSpline (self .knotpoints [var ], coefs ,
197+ self .degree [var ]).derivative (k )(t )
0 commit comments