pyfoiltools 0.0.1__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.
- pyfoiltools/__init__.py +0 -0
- pyfoiltools/airfoil.py +207 -0
- pyfoiltools/fitting/__init__.py +7 -0
- pyfoiltools/fitting/airfoilbspline.py +557 -0
- pyfoiltools/fitting/airfoilpoly.py +1195 -0
- pyfoiltools/fitting/airfoilshape.py +948 -0
- pyfoiltools/fitting/chordspacing.py +147 -0
- pyfoiltools/naca4.py +302 -0
- pyfoiltools-0.0.1.dist-info/METADATA +22 -0
- pyfoiltools-0.0.1.dist-info/RECORD +13 -0
- pyfoiltools-0.0.1.dist-info/WHEEL +5 -0
- pyfoiltools-0.0.1.dist-info/licenses/LICENSE +21 -0
- pyfoiltools-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Any
|
|
2
|
+
|
|
3
|
+
from matplotlib.pyplot import figure
|
|
4
|
+
from numpy import arctan, block, concatenate, cos, diag, hstack, sin, zeros
|
|
5
|
+
from numpy.linalg import lstsq, norm
|
|
6
|
+
from py2md.classes import MDReport
|
|
7
|
+
from pygeom.geom2d import BSplineCurve2D, Vector2D
|
|
8
|
+
from pygeom.tools.solvers import solve_clsq
|
|
9
|
+
|
|
10
|
+
from .airfoilpoly import AirfoilPoly, AirfoilPolyFit
|
|
11
|
+
from .airfoilshape import AirfoilShape, AirfoilShapeFit
|
|
12
|
+
from .chordspacing import ChordSpacing
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from matplotlib.axes import Axes
|
|
16
|
+
from numpy.typing import NDArray
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AirfoilBSpline():
|
|
20
|
+
name: str = None
|
|
21
|
+
adef: AirfoilPoly | AirfoilShape = None
|
|
22
|
+
bspline_u: 'BSplineCurve2D' = None
|
|
23
|
+
bspline_l: 'BSplineCurve2D' = None
|
|
24
|
+
c: float = None
|
|
25
|
+
num: int = None
|
|
26
|
+
_spacing: 'ChordSpacing' = None
|
|
27
|
+
_xu: 'NDArray' = None
|
|
28
|
+
_yu: 'NDArray' = None
|
|
29
|
+
_xl: 'NDArray' = None
|
|
30
|
+
_yl: 'NDArray' = None
|
|
31
|
+
_x: 'NDArray' = None
|
|
32
|
+
_y: 'NDArray' = None
|
|
33
|
+
|
|
34
|
+
def __init__(self, name: str, adef: AirfoilPoly | AirfoilShape,
|
|
35
|
+
bspline_u: 'BSplineCurve2D', bspline_l: 'BSplineCurve2D',
|
|
36
|
+
c: float = 1.0, num: int = 101) -> None:
|
|
37
|
+
self.name = name
|
|
38
|
+
self.adef = adef
|
|
39
|
+
self.bspline_u = bspline_u
|
|
40
|
+
self.bspline_l = bspline_l
|
|
41
|
+
self.c = c
|
|
42
|
+
self.num = num
|
|
43
|
+
|
|
44
|
+
def reset(self) -> None:
|
|
45
|
+
for attr in self.__dict__.keys():
|
|
46
|
+
if attr.startswith('_'):
|
|
47
|
+
setattr(self, attr, None)
|
|
48
|
+
|
|
49
|
+
def set_chord(self, c: float) -> None:
|
|
50
|
+
self.c = c
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def spacing(self) -> ChordSpacing:
|
|
54
|
+
if self._spacing is None:
|
|
55
|
+
self._spacing = ChordSpacing.from_num(self.num)
|
|
56
|
+
return self._spacing
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def th(self) -> 'NDArray':
|
|
60
|
+
return self.spacing.th
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def xu(self) -> 'NDArray':
|
|
64
|
+
if self._xu is None:
|
|
65
|
+
pnts_u = self.bspline_u.evaluate_points_at_t(self.spacing.s)
|
|
66
|
+
self._xu, self._yu = pnts_u.to_xy()
|
|
67
|
+
return self._xu
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def yu(self) -> 'NDArray':
|
|
71
|
+
if self._yu is None:
|
|
72
|
+
pnts_u = self.bspline_u.evaluate_points_at_t(self.spacing.s)
|
|
73
|
+
self._xu, self._yu = pnts_u.to_xy()
|
|
74
|
+
return self._yu
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def xl(self) -> 'NDArray':
|
|
78
|
+
if self._xl is None:
|
|
79
|
+
pnts_l = self.bspline_l.evaluate_points_at_t(self.spacing.s)
|
|
80
|
+
self._xl, self._yl = pnts_l.to_xy()
|
|
81
|
+
return self._xl
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def yl(self) -> 'NDArray':
|
|
85
|
+
if self._yl is None:
|
|
86
|
+
pnts_l = self.bspline_l.evaluate_points_at_t(self.spacing.s)
|
|
87
|
+
self._xl, self._yl = pnts_l.to_xy()
|
|
88
|
+
return self._yl
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def x(self) -> 'NDArray':
|
|
92
|
+
if self._x is None:
|
|
93
|
+
self._x = hstack((self.xl[::-1], self.xu[1:]))
|
|
94
|
+
return self._x
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def y(self) -> 'NDArray':
|
|
98
|
+
if self._y is None:
|
|
99
|
+
self._y = hstack((self.yl[::-1], self.yu[1:]))
|
|
100
|
+
return self._y
|
|
101
|
+
|
|
102
|
+
def plot_shape(self, ax: 'Axes' = None, **kwargs: dict[str, Any]) -> 'Axes':
|
|
103
|
+
if ax is None:
|
|
104
|
+
fig = figure(figsize=(12, 8))
|
|
105
|
+
ax = fig.gca()
|
|
106
|
+
ax.grid(True)
|
|
107
|
+
ax.set_aspect('equal')
|
|
108
|
+
ax.set_xlabel(r'$x$')
|
|
109
|
+
ax.set_ylabel(r'$y$')
|
|
110
|
+
|
|
111
|
+
kwargs.setdefault('label', self.name)
|
|
112
|
+
|
|
113
|
+
ax.plot(self.x, self.y, **kwargs)
|
|
114
|
+
return ax
|
|
115
|
+
|
|
116
|
+
def __repr__(self) -> str:
|
|
117
|
+
return f'<AirfoilBSpline: {self.name:s}>'
|
|
118
|
+
|
|
119
|
+
def to_mdobj(self) -> MDReport:
|
|
120
|
+
return self.adef.to_mdobj()
|
|
121
|
+
|
|
122
|
+
def _repr_markdown_(self) -> str:
|
|
123
|
+
return self.to_mdobj()._repr_markdown_()
|
|
124
|
+
|
|
125
|
+
def __str__(self) -> str:
|
|
126
|
+
return f'AirfoilBSpline: {self.name:s}\nBSpline_u = \n{self.bspline_u}\nBSpline_l = \n{self.bspline_l}'
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class AirfoilBSplineFit():
|
|
130
|
+
afit: AirfoilPolyFit | AirfoilShapeFit = None
|
|
131
|
+
Nu: int = None
|
|
132
|
+
Nl: int = None
|
|
133
|
+
_adef: AirfoilPoly | AirfoilShape = None
|
|
134
|
+
_spcu: ChordSpacing = None
|
|
135
|
+
_THu: 'NDArray' = None
|
|
136
|
+
_Ucu: 'NDArray' = None
|
|
137
|
+
_Vcu: 'NDArray' = None
|
|
138
|
+
_Xcu: 'NDArray' = None
|
|
139
|
+
_Ycu: 'NDArray' = None
|
|
140
|
+
_Tcu: 'NDArray' = None
|
|
141
|
+
_spcl: ChordSpacing = None
|
|
142
|
+
_THl: 'NDArray' = None
|
|
143
|
+
_Ucl: 'NDArray' = None
|
|
144
|
+
_Vcl: 'NDArray' = None
|
|
145
|
+
_Xcl: 'NDArray' = None
|
|
146
|
+
_Ycl: 'NDArray' = None
|
|
147
|
+
_Tcl: 'NDArray' = None
|
|
148
|
+
_aspl: AirfoilBSpline = None
|
|
149
|
+
_tu: 'NDArray' = None
|
|
150
|
+
_tl: 'NDArray' = None
|
|
151
|
+
_carr: 'NDArray' = None
|
|
152
|
+
_darr: 'NDArray' = None
|
|
153
|
+
_dXudTu: 'NDArray' = None
|
|
154
|
+
_dYudTu: 'NDArray' = None
|
|
155
|
+
_dXldTl: 'NDArray' = None
|
|
156
|
+
_dYldTl: 'NDArray' = None
|
|
157
|
+
_lmda: 'NDArray' = None
|
|
158
|
+
|
|
159
|
+
def __init__(self, afit: AirfoilPolyFit | AirfoilShapeFit,
|
|
160
|
+
Nu: int, Nl: int) -> None:
|
|
161
|
+
self.afit = afit
|
|
162
|
+
self.Nu = Nu
|
|
163
|
+
self.Nl = Nl
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def adef(self) -> AirfoilPoly | AirfoilShape:
|
|
167
|
+
if self._adef is None:
|
|
168
|
+
if isinstance(self.afit, AirfoilPolyFit):
|
|
169
|
+
self._adef = self.afit.aply
|
|
170
|
+
elif isinstance(self.afit, AirfoilShapeFit):
|
|
171
|
+
self._adef = self.afit.ashp
|
|
172
|
+
else:
|
|
173
|
+
raise ValueError('Invalid airfoil shape type.')
|
|
174
|
+
return self._adef
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
def spcu(self) -> ChordSpacing:
|
|
178
|
+
if self._spcu is None:
|
|
179
|
+
self._spcu = ChordSpacing.from_num(self.Nu)
|
|
180
|
+
return self._spcu
|
|
181
|
+
|
|
182
|
+
@property
|
|
183
|
+
def THu(self) -> 'NDArray':
|
|
184
|
+
if self._THu is None:
|
|
185
|
+
myu = self.adef.calc_my(self.spcu, self.adef.Cn)
|
|
186
|
+
self._THu = arctan(myu)
|
|
187
|
+
return self._THu
|
|
188
|
+
|
|
189
|
+
@property
|
|
190
|
+
def Ucu(self) -> 'NDArray':
|
|
191
|
+
if self._Ucu is None:
|
|
192
|
+
self._Ucu = -sin(self.THu)/2
|
|
193
|
+
return self._Ucu
|
|
194
|
+
|
|
195
|
+
@property
|
|
196
|
+
def Vcu(self) -> 'NDArray':
|
|
197
|
+
if self._Vcu is None:
|
|
198
|
+
self._Vcu = cos(self.THu)/2
|
|
199
|
+
return self._Vcu
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def Tcu(self) -> 'NDArray':
|
|
203
|
+
if self._Tcu is None:
|
|
204
|
+
self._Tcu = self.adef.calc_tc(self.spcu, self.adef.Tn)
|
|
205
|
+
self._Tcu[0] = self._Tcu[1]
|
|
206
|
+
return self._Tcu
|
|
207
|
+
|
|
208
|
+
@property
|
|
209
|
+
def Xcu(self) -> 'NDArray':
|
|
210
|
+
if self._Xcu is None:
|
|
211
|
+
self._Xcu = self.adef.calc_xc(self.spcu)
|
|
212
|
+
return self._Xcu
|
|
213
|
+
|
|
214
|
+
@property
|
|
215
|
+
def Ycu(self) -> 'NDArray':
|
|
216
|
+
if self._Ycu is None:
|
|
217
|
+
self._Ycu = self.adef.calc_yc(self.spcu, self.adef.Cn)
|
|
218
|
+
return self._Ycu
|
|
219
|
+
|
|
220
|
+
@property
|
|
221
|
+
def spcl(self) -> ChordSpacing:
|
|
222
|
+
if self._spcl is None:
|
|
223
|
+
self._spcl = ChordSpacing.from_num(self.Nl)
|
|
224
|
+
return self._spcl
|
|
225
|
+
|
|
226
|
+
@property
|
|
227
|
+
def THl(self) -> 'NDArray':
|
|
228
|
+
if self._THl is None:
|
|
229
|
+
myl = self.adef.calc_my(self.spcl, self.adef.Cn)
|
|
230
|
+
self._THl = arctan(myl)
|
|
231
|
+
return self._THl
|
|
232
|
+
|
|
233
|
+
@property
|
|
234
|
+
def Ucl(self) -> 'NDArray':
|
|
235
|
+
if self._Ucl is None:
|
|
236
|
+
self._Ucl = sin(self.THl)/2
|
|
237
|
+
return self._Ucl
|
|
238
|
+
|
|
239
|
+
@property
|
|
240
|
+
def Vcl(self) -> 'NDArray':
|
|
241
|
+
if self._Vcl is None:
|
|
242
|
+
self._Vcl = -cos(self.THl)/2
|
|
243
|
+
return self._Vcl
|
|
244
|
+
|
|
245
|
+
@property
|
|
246
|
+
def Tcl(self) -> 'NDArray':
|
|
247
|
+
if self._Tcl is None:
|
|
248
|
+
self._Tcl = self.adef.calc_tc(self.spcl, self.adef.Tn)
|
|
249
|
+
self._Tcl[0] = self._Tcl[1]
|
|
250
|
+
return self._Tcl
|
|
251
|
+
|
|
252
|
+
@property
|
|
253
|
+
def Xcl(self) -> 'NDArray':
|
|
254
|
+
if self._Xcl is None:
|
|
255
|
+
self._Xcl = self.adef.calc_xc(self.spcl)
|
|
256
|
+
return self._Xcl
|
|
257
|
+
|
|
258
|
+
@property
|
|
259
|
+
def Ycl(self) -> 'NDArray':
|
|
260
|
+
if self._Ycl is None:
|
|
261
|
+
self._Ycl = self.adef.calc_yc(self.spcl, self.adef.Cn)
|
|
262
|
+
return self._Ycl
|
|
263
|
+
|
|
264
|
+
@property
|
|
265
|
+
def aspl(self) -> AirfoilBSpline:
|
|
266
|
+
if self._aspl is None:
|
|
267
|
+
|
|
268
|
+
ctlpnts_u = Vector2D.zeros(self.Nu + 1)
|
|
269
|
+
ctlpnts_u.x[1:] = self.Xcu + self.Ucu*self.Tcu
|
|
270
|
+
ctlpnts_u.y[1:] = self.Ycu + self.Vcu*self.Tcu
|
|
271
|
+
bspline_u = BSplineCurve2D(ctlpnts_u, degree=self.Nu)
|
|
272
|
+
|
|
273
|
+
ctlpnts_l = Vector2D.zeros(self.Nl + 1)
|
|
274
|
+
ctlpnts_l.x[1:] = self.Xcl + self.Ucl*self.Tcl
|
|
275
|
+
ctlpnts_l.y[1:] = self.Ycl + self.Vcl*self.Tcl
|
|
276
|
+
bspline_l = BSplineCurve2D(ctlpnts_l, degree=self.Nl)
|
|
277
|
+
|
|
278
|
+
self._aspl = AirfoilBSpline(self.adef.name, self.adef,
|
|
279
|
+
bspline_u, bspline_l)
|
|
280
|
+
|
|
281
|
+
return self._aspl
|
|
282
|
+
|
|
283
|
+
@property
|
|
284
|
+
def tu(self) -> 'NDArray':
|
|
285
|
+
if self._tu is None:
|
|
286
|
+
self._tu = self.afit.spcu.s.copy()
|
|
287
|
+
self._tu = bspline_lstsq_fit_t(self.aspl.bspline_u, self.afit.xu,
|
|
288
|
+
self.afit.yu, self._tu)
|
|
289
|
+
return self._tu
|
|
290
|
+
|
|
291
|
+
@property
|
|
292
|
+
def tl(self) -> 'NDArray':
|
|
293
|
+
if self._tl is None:
|
|
294
|
+
self._tl = self.afit.spcl.s.copy()
|
|
295
|
+
self._tl = bspline_lstsq_fit_t(self.aspl.bspline_l, self.afit.xl,
|
|
296
|
+
self.afit.yl, self._tl)
|
|
297
|
+
return self._tl
|
|
298
|
+
|
|
299
|
+
@property
|
|
300
|
+
def num_Tu(self) -> int:
|
|
301
|
+
return self.Tcu.size - 1
|
|
302
|
+
|
|
303
|
+
@property
|
|
304
|
+
def num_Tl(self) -> int:
|
|
305
|
+
return self.Tcl.size - 1
|
|
306
|
+
|
|
307
|
+
@property
|
|
308
|
+
def num_tu(self) -> int:
|
|
309
|
+
return self.tu.size
|
|
310
|
+
|
|
311
|
+
@property
|
|
312
|
+
def num_tl(self) -> int:
|
|
313
|
+
return self.tl.size
|
|
314
|
+
|
|
315
|
+
@property
|
|
316
|
+
def carr(self) -> 'NDArray':
|
|
317
|
+
if self._carr is None:
|
|
318
|
+
c_Tu = zeros((1, self.num_Tu))
|
|
319
|
+
c_Tu[0, 0] = 1.0
|
|
320
|
+
c_Tl = zeros((1, self.num_Tl))
|
|
321
|
+
c_Tl[0, 0] = -1.0
|
|
322
|
+
c_tu = zeros((1, self.num_tu))
|
|
323
|
+
c_tl = zeros((1, self.num_tl))
|
|
324
|
+
self._carr = hstack((c_Tu, c_Tl, c_tu, c_tl))
|
|
325
|
+
return self._carr
|
|
326
|
+
|
|
327
|
+
@property
|
|
328
|
+
def darr(self) -> 'NDArray':
|
|
329
|
+
if self._darr is None:
|
|
330
|
+
self._darr = zeros(1)
|
|
331
|
+
return self._darr
|
|
332
|
+
|
|
333
|
+
@property
|
|
334
|
+
def dXudTu(self) -> 'NDArray':
|
|
335
|
+
if self._dXudTu is None:
|
|
336
|
+
self._dXudTu = diag(self.Ucu[:-1])
|
|
337
|
+
return self._dXudTu
|
|
338
|
+
|
|
339
|
+
@property
|
|
340
|
+
def dYudTu(self) -> 'NDArray':
|
|
341
|
+
if self._dYudTu is None:
|
|
342
|
+
self._dYudTu = diag(self.Vcu[:-1])
|
|
343
|
+
return self._dYudTu
|
|
344
|
+
|
|
345
|
+
@property
|
|
346
|
+
def dXldTl(self) -> 'NDArray':
|
|
347
|
+
if self._dXldTl is None:
|
|
348
|
+
self._dXldTl = diag(self.Ucl[:-1])
|
|
349
|
+
return self._dXldTl
|
|
350
|
+
|
|
351
|
+
@property
|
|
352
|
+
def dYldTl(self) -> 'NDArray':
|
|
353
|
+
if self._dYldTl is None:
|
|
354
|
+
self._dYldTl = diag(self.Vcl[:-1])
|
|
355
|
+
return self._dYldTl
|
|
356
|
+
|
|
357
|
+
@property
|
|
358
|
+
def lmda(self) -> 'NDArray':
|
|
359
|
+
if self._lmda is None:
|
|
360
|
+
self._lmda = zeros(self.carr.shape[0])
|
|
361
|
+
return self._lmda
|
|
362
|
+
|
|
363
|
+
def plot_airfoil_bspline_fit(self, ax: 'Axes' = None) -> 'Axes':
|
|
364
|
+
|
|
365
|
+
if ax is None:
|
|
366
|
+
fig = figure(figsize=(12, 8))
|
|
367
|
+
ax = fig.gca()
|
|
368
|
+
ax.grid(True)
|
|
369
|
+
ax.set_aspect('equal')
|
|
370
|
+
ax.set_xlabel(r'$x$')
|
|
371
|
+
ax.set_ylabel(r'$y$')
|
|
372
|
+
|
|
373
|
+
ax = self.adef.plot_shape(ax=ax)
|
|
374
|
+
ax = self.afit.airfoil.plot(ax=ax)
|
|
375
|
+
|
|
376
|
+
pnts_u = self.aspl.bspline_u.evaluate_points(100)
|
|
377
|
+
pnts_l = self.aspl.bspline_l.evaluate_points(100)
|
|
378
|
+
|
|
379
|
+
ctls_u = self.aspl.bspline_u.ctlpnts
|
|
380
|
+
ctls_l = self.aspl.bspline_l.ctlpnts
|
|
381
|
+
|
|
382
|
+
ax.plot(pnts_u.x, pnts_u.y, label='BSpline Fit Upper')
|
|
383
|
+
ax.plot(pnts_l.x, pnts_l.y, label='BSpline Fit Lower')
|
|
384
|
+
ax.plot(ctls_u.x, ctls_u.y, '-o', label='Control Points Upper')
|
|
385
|
+
ax.plot(ctls_l.x, ctls_l.y, '-o', label='Control Points Lower')
|
|
386
|
+
ax.plot(self.afit.xu, self.afit.yu, 'x', label='Target Points Upper')
|
|
387
|
+
ax.plot(self.afit.xl, self.afit.yl, 'x', label='Target Points Lower')
|
|
388
|
+
_ = ax.legend()
|
|
389
|
+
return ax
|
|
390
|
+
|
|
391
|
+
def improved_fit(self, display: bool = False) -> tuple[float, float]:
|
|
392
|
+
|
|
393
|
+
xu, yu = self.aspl.bspline_u.evaluate_points_at_t(self.tu).to_xy()
|
|
394
|
+
|
|
395
|
+
Dxu = xu - self.afit.xu
|
|
396
|
+
Dyu = yu - self.afit.yu
|
|
397
|
+
|
|
398
|
+
xl, yl = self.aspl.bspline_l.evaluate_points_at_t(self.tl).to_xy()
|
|
399
|
+
|
|
400
|
+
Dxl = xl - self.afit.xl
|
|
401
|
+
Dyl = yl - self.afit.yl
|
|
402
|
+
|
|
403
|
+
f = concatenate((Dxu, Dyu, Dxl, Dyl))
|
|
404
|
+
|
|
405
|
+
Ntu = self.aspl.bspline_u.basis_functions(self.tu).transpose()
|
|
406
|
+
|
|
407
|
+
dDxudXu = Ntu[:, 1:self.Nu]
|
|
408
|
+
dDyudYu = Ntu[:, 1:self.Nu]
|
|
409
|
+
|
|
410
|
+
Ntl = self.aspl.bspline_l.basis_functions(self.tl).transpose()
|
|
411
|
+
|
|
412
|
+
dDxldXl = Ntl[:, 1:self.Nl]
|
|
413
|
+
dDyldYl = Ntl[:, 1:self.Nl]
|
|
414
|
+
|
|
415
|
+
dDxudTu = dDxudXu@self.dXudTu + dDyudYu@self.dYudTu
|
|
416
|
+
dDxudTl = zeros((xu.size, self.Nl - 1))
|
|
417
|
+
|
|
418
|
+
dDyudTu = dDyudYu@self.dYudTu + dDyudYu@self.dYudTu
|
|
419
|
+
dDyudTl = zeros((yu.size, self.Nl - 1))
|
|
420
|
+
|
|
421
|
+
dDxldTl = dDxldXl@self.dXldTl + dDyldYl@self.dYldTl
|
|
422
|
+
dDxldTu = zeros((xl.size, self.Nu - 1))
|
|
423
|
+
|
|
424
|
+
dDyldTl = dDyldYl@self.dYldTl + dDyldYl@self.dYldTl
|
|
425
|
+
dDyldTu = zeros((yl.size, self.Nu - 1))
|
|
426
|
+
|
|
427
|
+
dDxudtu, dDyudtu = self.aspl.bspline_u.evaluate_first_derivatives_at_t(self.tu).to_xy()
|
|
428
|
+
|
|
429
|
+
dDxudtu = diag(dDxudtu)
|
|
430
|
+
dDxudtl = zeros((xu.size, self.tl.size))
|
|
431
|
+
|
|
432
|
+
dDyudtu = diag(dDyudtu)
|
|
433
|
+
dDyudtl = zeros((yu.size, self.tl.size))
|
|
434
|
+
|
|
435
|
+
dDxldtl, dDyldtl = self.aspl.bspline_l.evaluate_first_derivatives_at_t(self.tl).to_xy()
|
|
436
|
+
|
|
437
|
+
dDxldtl = diag(dDxldtl)
|
|
438
|
+
dDxldtu = zeros((xl.size, self.tu.size))
|
|
439
|
+
|
|
440
|
+
dDyldtl = diag(dDyldtl)
|
|
441
|
+
dDyldtu = zeros((yl.size, self.tu.size))
|
|
442
|
+
|
|
443
|
+
dfdTt = block([[dDxudTu, dDxudTl, dDxudtu, dDxudtl],
|
|
444
|
+
[dDyudTu, dDyudTl, dDyudtu, dDyudtl],
|
|
445
|
+
[dDxldTu, dDxldTl, dDxldtu, dDxldtl],
|
|
446
|
+
[dDyldTu, dDyldTl, dDyldtu, dDyldtl]])
|
|
447
|
+
|
|
448
|
+
dvTt, dlTt = solve_clsq(dfdTt, f, self.carr, self.darr)
|
|
449
|
+
|
|
450
|
+
dTu = dvTt[:self.num_Tu]
|
|
451
|
+
dTl = dvTt[self.num_Tu:self.num_Tu + self.num_Tl]
|
|
452
|
+
dtu = dvTt[self.num_Tu + self.num_Tl:self.num_Tu + self.num_Tl + self.num_tu]
|
|
453
|
+
dtl = dvTt[self.num_Tu + self.num_Tl + self.num_tu:]
|
|
454
|
+
|
|
455
|
+
self.Tcu[:-1] -= dTu
|
|
456
|
+
self.Tcl[:-1] -= dTl
|
|
457
|
+
|
|
458
|
+
self.tu[:] -= dtu
|
|
459
|
+
self.tl[:] -= dtl
|
|
460
|
+
|
|
461
|
+
self.lmda[:] -= dlTt
|
|
462
|
+
|
|
463
|
+
self._aspl = None
|
|
464
|
+
|
|
465
|
+
normdvar = norm(dvTt)
|
|
466
|
+
|
|
467
|
+
dcon = self.darr
|
|
468
|
+
normdcon = norm(dcon)
|
|
469
|
+
|
|
470
|
+
if display:
|
|
471
|
+
print(f'normdvar = {normdvar}\n')
|
|
472
|
+
print(f'normdcon = {normdcon}\n')
|
|
473
|
+
|
|
474
|
+
return normdcon, normdvar
|
|
475
|
+
|
|
476
|
+
def improved_fit_loop(self, tol_dcon: float = 1e-12, tol_dvar: float = 1e-3,
|
|
477
|
+
max_iter: int = 50, display: bool = False) -> tuple[float, float]:
|
|
478
|
+
|
|
479
|
+
normdcon, normdvar = float('inf'), float('inf')
|
|
480
|
+
|
|
481
|
+
iter = 1
|
|
482
|
+
while normdcon > tol_dcon or normdvar > tol_dvar:
|
|
483
|
+
normdcon, normdvar = self.improved_fit()
|
|
484
|
+
if display:
|
|
485
|
+
print(f'Iteration {iter}')
|
|
486
|
+
print(f'normdcon = {normdcon:.12f}')
|
|
487
|
+
print(f'normdvar = {normdvar:.12f}')
|
|
488
|
+
print()
|
|
489
|
+
if iter >= max_iter:
|
|
490
|
+
err = f'Maximum number of iterations ({max_iter}) reached.'
|
|
491
|
+
raise RuntimeError(err)
|
|
492
|
+
iter += 1
|
|
493
|
+
|
|
494
|
+
return normdcon, normdvar
|
|
495
|
+
|
|
496
|
+
def __repr__(self) -> str:
|
|
497
|
+
return f'<AirfoilBSplineFit: {self.adef.name:s}>'
|
|
498
|
+
|
|
499
|
+
def __str__(self) -> str:
|
|
500
|
+
return f'AirfoilBSplineFit: {self.adef.name:s}'
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def bspline_lstsq_fit_t(bspline: BSplineCurve2D, x_t: 'NDArray', y_t: 'NDArray',
|
|
504
|
+
t: 'NDArray', **kwargs: dict[str, Any]) -> 'NDArray':
|
|
505
|
+
|
|
506
|
+
max_iter: int = kwargs.get('max_iter', 50)
|
|
507
|
+
display: bool = kwargs.get('display', False)
|
|
508
|
+
|
|
509
|
+
count = 0
|
|
510
|
+
|
|
511
|
+
while True:
|
|
512
|
+
|
|
513
|
+
x, y = bspline.evaluate_points_at_t(t).to_xy()
|
|
514
|
+
|
|
515
|
+
Dx = x - x_t
|
|
516
|
+
Dy = y - y_t
|
|
517
|
+
|
|
518
|
+
f = concatenate((Dx, Dy))
|
|
519
|
+
norm_f = norm(f)
|
|
520
|
+
|
|
521
|
+
if display:
|
|
522
|
+
print(f'f = {f}\n')
|
|
523
|
+
print(f'norm_f = {norm_f}')
|
|
524
|
+
|
|
525
|
+
if norm_f < 1.0e-12:
|
|
526
|
+
if display:
|
|
527
|
+
print(f'Converged with norm_f = {norm_f}\n')
|
|
528
|
+
break
|
|
529
|
+
|
|
530
|
+
dDxdt, dDydt = bspline.evaluate_first_derivatives_at_t(t).to_xy()
|
|
531
|
+
dDxdt = diag(dDxdt)
|
|
532
|
+
dDydt = diag(dDydt)
|
|
533
|
+
|
|
534
|
+
dfdt = block([[dDxdt], [dDydt]])
|
|
535
|
+
|
|
536
|
+
dvt, _, _, _ = lstsq(dfdt, f)
|
|
537
|
+
t -= dvt
|
|
538
|
+
norm_dv = norm(dvt)
|
|
539
|
+
|
|
540
|
+
if display:
|
|
541
|
+
print(f'dvtt = {dvt}\n')
|
|
542
|
+
print(f'norm_dv = {norm_dv}\n')
|
|
543
|
+
|
|
544
|
+
if norm_dv < 1.0e-12:
|
|
545
|
+
if display:
|
|
546
|
+
print(f'Converged with norm_dv = {norm_dv}\n')
|
|
547
|
+
break
|
|
548
|
+
|
|
549
|
+
count += 1
|
|
550
|
+
|
|
551
|
+
if count > max_iter:
|
|
552
|
+
print(f'Convergence failed in {max_iter} iterations.\n')
|
|
553
|
+
print(f'norm_f = {norm_f}\n')
|
|
554
|
+
print(f'norm_dv = {norm_dv}\n')
|
|
555
|
+
break
|
|
556
|
+
|
|
557
|
+
return t
|