NumOpt 0.0.3__tar.gz → 0.0.5__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 (32) hide show
  1. {numopt-0.0.3 → numopt-0.0.5}/NumOpt/__init__.py +1 -1
  2. numopt-0.0.5/NumOpt/airfoil/bezier.py +198 -0
  3. numopt-0.0.5/NumOpt/airfoil/bspline.py +201 -0
  4. numopt-0.0.5/NumOpt/airfoil/export_cst2nx.py +92 -0
  5. numopt-0.0.5/NumOpt/airfoil/kulfan.py +163 -0
  6. numopt-0.0.5/NumOpt/casadi_callback/surrogate.py +181 -0
  7. numopt-0.0.5/NumOpt/casadi_callback/surrogate_with_hess.py +169 -0
  8. {numopt-0.0.3 → numopt-0.0.5}/NumOpt/cprint.py +11 -1
  9. numopt-0.0.5/NumOpt/nn/bnn.py +135 -0
  10. numopt-0.0.5/NumOpt/nx/nxcst.py +99 -0
  11. {numopt-0.0.3 → numopt-0.0.5}/NumOpt/nx/tools.py +2 -3
  12. {numopt-0.0.3 → numopt-0.0.5}/NumOpt/opti.py +6 -1
  13. numopt-0.0.5/NumOpt/optimize/__init__.py +0 -0
  14. numopt-0.0.5/NumOpt/optimize/hxy_optimization.py +39 -0
  15. numopt-0.0.5/NumOpt/optimize/v1.py +231 -0
  16. numopt-0.0.5/NumOpt/preprocess/scaler.py +82 -0
  17. {numopt-0.0.3 → numopt-0.0.5}/NumOpt.egg-info/PKG-INFO +1 -1
  18. numopt-0.0.5/NumOpt.egg-info/SOURCES.txt +29 -0
  19. {numopt-0.0.3 → numopt-0.0.5}/PKG-INFO +1 -1
  20. numopt-0.0.5/test/test01.py +920 -0
  21. numopt-0.0.3/NumOpt.egg-info/SOURCES.txt +0 -16
  22. {numopt-0.0.3 → numopt-0.0.5}/LICENSE.txt +0 -0
  23. {numopt-0.0.3 → numopt-0.0.5}/NumOpt/FSI/__init__.py +0 -0
  24. {numopt-0.0.3 → numopt-0.0.5}/NumOpt/FSI/tools.py +0 -0
  25. {numopt-0.0.3 → numopt-0.0.5}/NumOpt/nx/__init__.py +0 -0
  26. {numopt-0.0.3 → numopt-0.0.5}/NumOpt.egg-info/dependency_links.txt +0 -0
  27. {numopt-0.0.3 → numopt-0.0.5}/NumOpt.egg-info/requires.txt +0 -0
  28. {numopt-0.0.3 → numopt-0.0.5}/NumOpt.egg-info/top_level.txt +0 -0
  29. {numopt-0.0.3 → numopt-0.0.5}/README.md +0 -0
  30. {numopt-0.0.3 → numopt-0.0.5}/pyproject.toml +0 -0
  31. {numopt-0.0.3 → numopt-0.0.5}/setup.cfg +0 -0
  32. {numopt-0.0.3 → numopt-0.0.5}/setup.py +0 -0
@@ -1,4 +1,4 @@
1
1
  from NumOpt.opti import *
2
2
  from NumOpt.cprint import *
3
3
 
4
- __version__ = "0.0.3"
4
+ __version__ = "0.0.5"
@@ -0,0 +1,198 @@
1
+ from ..opti import asb, anp, cas, np
2
+ from scipy.special import comb
3
+ from ..cprint import cprint_green
4
+
5
+
6
+ class Bezier:
7
+ def __init__(self, control_points):
8
+ self.control_points = control_points
9
+ self.setup()
10
+
11
+ def setup(self):
12
+ self.order = self.control_points.shape[0] - 1
13
+ self.ndim = self.control_points.shape[1]
14
+ self._K = comb(self.order, np.arange(self.order + 1))
15
+ self._C = np.tile(self._K, (self.ndim, 1)).T * self.control_points
16
+
17
+ def __call__(self, t):
18
+ t = anp.reshape(t, (1, -1))
19
+ ti = []
20
+ t_ni = []
21
+ t_ = 1 - t
22
+ for i in range(self.order + 1):
23
+ ti.append(t**i)
24
+ t_ni.append(t_ ** (self.order - i))
25
+ ti = anp.concatenate(ti, axis=0)
26
+ t_ni = anp.concatenate(t_ni, axis=0)
27
+ t_mat = (ti * t_ni).T
28
+ pts = t_mat @ self._C
29
+ return pts
30
+
31
+
32
+ class BezierAirfoil:
33
+ def __init__(self, ctu, ctl):
34
+ self.ctu = ctu
35
+ self.ctl = ctl
36
+
37
+ self.bezier_upper = Bezier(self.ctu)
38
+ self.bezier_lower = Bezier(self.ctl)
39
+
40
+ self.__thickness = self.ctu[-1, 1] - self.ctl[-1, 1]
41
+ self.__symmetry = False
42
+
43
+ def upper_coordinates(self, t):
44
+ pts = self.bezier_upper(t)[::-1, :]
45
+ return pts
46
+
47
+ def lower_coordinates(self, t):
48
+ pts = self.bezier_lower(t)
49
+ return pts
50
+
51
+ def coordinates(self, t):
52
+ pts_u = self.upper_coordinates(t)
53
+ pts_l = self.lower_coordinates(t)
54
+
55
+ pts = anp.concatenate([pts_u[:-1, :], pts_l])
56
+ return pts
57
+
58
+ @property
59
+ def thickness(self):
60
+ return self.__thickness
61
+
62
+ @property
63
+ def symmetry(self):
64
+ return self.__symmetry
65
+
66
+ def set_thickness(self, thickness):
67
+ thickness = thickness
68
+ old_thickness = self.thickness
69
+ delta_thickness = thickness - old_thickness
70
+
71
+ t = anp.cosspace(0, 1, 100)
72
+
73
+ upper_coords = self.upper_coordinates(t)
74
+ upper_coords[:, 1] += upper_coords[:, 0] * delta_thickness / 2.0
75
+ lower_coords = self.lower_coordinates(t)
76
+ lower_coords[:, 1] -= lower_coords[:, 0] * delta_thickness / 2.0
77
+
78
+ new_af = self.fit(upper_coords, lower_coords, nctu=self.ctu.shape[0], nctl=self.ctl.shape[0], symmetry=self.symmetry)
79
+ return new_af
80
+
81
+ @staticmethod
82
+ def fit(upper_coordinates, lower_coordinates, nctu=7, nctl=7, symmetry=False):
83
+ default_options = {
84
+ "ipopt.sb": "yes",
85
+ "ipopt.max_iter": 1000,
86
+ "ipopt.max_cpu_time": 1e20,
87
+ "ipopt.mu_strategy": "adaptive",
88
+ "ipopt.fast_step_computation": "yes",
89
+ "detect_simple_bounds": False,
90
+ "expand": True,
91
+ # =======================
92
+ # if verbose
93
+ # "ipopt.print_level": 5,
94
+ # =======================
95
+ # =============================
96
+ # if no verbose
97
+ "print_time": False,
98
+ "ipopt.print_level": 0,
99
+ # =============================.
100
+ # ============================
101
+ # This is a heuristic that tends to result in more robust convergence on highly nonconvex problems.
102
+ # On convex problems and larger problems, the default setting ("adaptive") tends to result in faster convergence.
103
+ # ===========================
104
+ # "ipopt.mu_strategy": "monotone",
105
+ # ===============================
106
+ # This is another heuristic that tells the optimizer to focus on finding the feasible space initially.
107
+ # This can be good when you know your initial guess is very far from satisfying all constraints.
108
+ # "ipopt.start_with_resto": "yes",
109
+ # ===============================
110
+ }
111
+
112
+ # ------------------------ upper -------------------------------
113
+ opti = cas.Opti()
114
+ ctu = opti.variable(nctu, 2)
115
+ ctl = opti.parameter(nctl, 2)
116
+
117
+ af = BezierAirfoil(ctu=ctu, ctl=ctl)
118
+ # t = anp.cosspace(0, 1, upper_coordinates.shape[0])
119
+ t = opti.variable(upper_coordinates.shape[0])
120
+ coords = af.upper_coordinates(t)
121
+
122
+ # residual = cas.sum((coords[:, 1] - upper_coordinates[:, 1]) ** 2)
123
+ dist = coords - upper_coordinates
124
+ residual = cas.sum(cas.dot(dist, dist))
125
+
126
+ opti.subject_to(
127
+ [
128
+ # coords[:, 0] == upper_coordinates[:, 0],
129
+ ctu[0, 0] == 0.0,
130
+ ctu[0, 1] == 0.0,
131
+ ctu[1, 0] == 0.0,
132
+ ctu[-1, 0] == 1.0,
133
+ ctu[-1, 1] == upper_coordinates[0, 1],
134
+ opti.bounded(0.0, t, 1.0),
135
+ opti.bounded(0.0, ctu[:, 0], 1.0),
136
+ cas.diff(t) >= 0.0,
137
+ cas.diff(ctu[:, 0]) >= 0.0,
138
+ ]
139
+ )
140
+
141
+ opti.minimize(residual)
142
+ opti.solver("ipopt", default_options)
143
+ opti.set_initial(ctu[:, 0], np.linspace(0, 1, nctu))
144
+ opti.set_initial(ctu[:, 1], 0.05)
145
+ opti.set_initial(t, np.linspace(0, 1, upper_coordinates.shape[0]))
146
+
147
+ # cprint_green("fit upper...")
148
+ sol = opti.solve()
149
+
150
+ ctu_sol = sol.value(ctu)
151
+
152
+ # --------------------------- lower ------------------------------------------
153
+ if symmetry:
154
+ ctl_sol = np.array(ctu_sol)
155
+ ctl_sol[:, 1] = -ctl_sol[:, 1]
156
+ else:
157
+ opti = cas.Opti()
158
+ ctu = opti.parameter(nctu, 2)
159
+ ctl = opti.variable(nctl, 2)
160
+
161
+ af = BezierAirfoil(ctu=ctu, ctl=ctl)
162
+ # t = anp.cosspace(0, 1, upper_coordinates.shape[0])
163
+ t = opti.variable(lower_coordinates.shape[0])
164
+ coords = af.lower_coordinates(t)
165
+
166
+ dist = coords - lower_coordinates
167
+ # residual = cas.sum((coords[:, 1] - lower_coordinates[:, 1]) ** 2)
168
+ residual = cas.sum(cas.dot(dist, dist))
169
+
170
+ opti.subject_to(
171
+ [
172
+ # coords[:, 0] == lower_coordinates[:, 0],
173
+ ctl[0, 0] == 0.0,
174
+ ctl[0, 1] == 0.0,
175
+ ctl[1, 0] == 0.0,
176
+ ctl[-1, 0] == 1.0,
177
+ ctl[-1, 1] == lower_coordinates[-1, 1],
178
+ opti.bounded(0.0, t, 1.0),
179
+ opti.bounded(0.0, ctl[:, 0], 1.0),
180
+ cas.diff(t) >= 0.0,
181
+ cas.diff(ctl[:, 0]) >= 0.0,
182
+ ]
183
+ )
184
+
185
+ opti.minimize(residual)
186
+ opti.solver("ipopt", default_options)
187
+ opti.set_initial(ctl[:, 0], np.linspace(0, 1, nctl))
188
+ opti.set_initial(ctl[:, 1], -0.1)
189
+ opti.set_initial(t, np.linspace(0, 1, lower_coordinates.shape[0]))
190
+
191
+ # cprint_green("fit lower...")
192
+ sol = opti.solve()
193
+
194
+ ctl_sol = sol.value(ctl)
195
+
196
+ af = BezierAirfoil(ctu=ctu_sol, ctl=ctl_sol)
197
+ af.__symmetry = symmetry
198
+ return af
@@ -0,0 +1,201 @@
1
+ from ..opti import asb, anp, cas, np
2
+
3
+
4
+ class Bspline:
5
+ def __init__(self, ctrlpts, degree=3):
6
+ self.ctrlpts = ctrlpts
7
+ self.n = self.ctrlpts.shape[0] - 1
8
+ self.degree = degree
9
+ self.order = self.degree + 1
10
+ self.segments = self.n - self.degree + 1
11
+ self.knots = self.quasi_uniform_knots()
12
+
13
+ def quasi_uniform_knots(self):
14
+ middle = np.linspace(0, 1, self.segments + 1)
15
+ start = np.zeros(self.degree, dtype="f8")
16
+ end = np.ones(self.degree, dtype="f8")
17
+ knots = np.hstack([start, middle, end])
18
+ return knots
19
+
20
+ @staticmethod
21
+ def __deBoor(t, i, k, knots):
22
+ if k == 1:
23
+ return cas.if_else(cas.logic_and(t >= knots[i], t < knots[i + 1]), 1.0, 0.0)
24
+ else:
25
+ term1 = term2 = 0.0
26
+ delta_t1 = knots[i + k - 1] - knots[i]
27
+ delta_t2 = knots[i + k] - knots[i + 1]
28
+
29
+ if delta_t1 != 0.0:
30
+ term1 = (t - knots[i]) / (delta_t1) * Bspline.__deBoor(t, i, k - 1, knots)
31
+
32
+ if delta_t2 != 0.0:
33
+ term2 = (knots[i + k] - t) / (delta_t2) * Bspline.__deBoor(t, i + 1, k - 1, knots)
34
+
35
+ return term1 + term2
36
+
37
+ def N_coef(self, t, i):
38
+ return Bspline.__deBoor(t, i, self.order, self.knots)
39
+
40
+ def __call__(self, t):
41
+ def func():
42
+ tmp = 0.0
43
+ for j in range(self.n + 1):
44
+ N_coef = self.N_coef(u, j)
45
+ tmp = tmp + N_coef * self.ctrlpts[j : j + 1, :]
46
+ return tmp
47
+
48
+ nts = t.shape[0]
49
+ pts = [0.0] * nts
50
+
51
+ for i in range(nts):
52
+ u = t[i]
53
+ pts[i] = cas.if_else(u == 1.0, self.ctrlpts[-1:, :], func())
54
+ # if u == 1.0:
55
+ # pts[i] = self.ctrlpts[-1:, :]
56
+ # else:
57
+ # for j in range(self.n + 1):
58
+ # N_coef = self.N_coef(u, j)
59
+ # pts[i] = pts[i] + N_coef * self.ctrlpts[j : j + 1, :]
60
+ pts = cas.vcat(pts)
61
+ return pts
62
+
63
+
64
+ class BsplineAirfoil:
65
+ def __init__(self, ctu=None, ctl=None, degree=3):
66
+ self.ctu = ctu
67
+ self.ctl = ctl
68
+
69
+ self.bspline_upper = Bspline(ctrlpts=self.ctu, degree=degree)
70
+ self.bspline_lower = Bspline(ctrlpts=self.ctl, degree=degree)
71
+
72
+ self.__te = self.ctu[-1, 1] - self.ctl[-1, 1]
73
+ self.__symmetry = False
74
+
75
+ def upper_coordinates(self, t):
76
+ pts = self.bspline_upper(t)[::-1, :]
77
+ return pts
78
+
79
+ def lower_coordinates(self, t):
80
+ pts = self.bspline_lower(t)
81
+ return pts
82
+
83
+ def coordinates(self, t):
84
+ pts_upper = self.upper_coordinates(t)
85
+ pts_lower = self.lower_coordinates(t)
86
+
87
+ pts = cas.vcat([pts_upper[:-1, :], pts_lower])
88
+ return pts
89
+
90
+ @property
91
+ def te(self):
92
+ return self.__te
93
+
94
+ @property
95
+ def symmetry(self):
96
+ return self.__symmetry
97
+
98
+ @staticmethod
99
+ def fit(upper_coordinates, lower_coordinates, nctu=9, nctl=9, symmetry=False):
100
+ default_options = {
101
+ "ipopt.sb": "yes",
102
+ "ipopt.max_iter": 1000,
103
+ "ipopt.max_cpu_time": 1e20,
104
+ "ipopt.mu_strategy": "adaptive",
105
+ "ipopt.fast_step_computation": "yes",
106
+ "detect_simple_bounds": False,
107
+ "expand": True,
108
+ "print_time": False,
109
+ "ipopt.print_level": 0,
110
+ }
111
+
112
+ # ============================ upper ==========================
113
+ opti = cas.Opti()
114
+
115
+ ctu = opti.variable(nctu, 2)
116
+ ctu_init = np.zeros(ctu.shape)
117
+ ctu_init[1:, 0] = np.linspace(0, 1, nctu - 1)
118
+ ctu_init[1:-1, 1] = 0.5
119
+ ctu_init[-1, 1] = upper_coordinates[0, 1]
120
+
121
+ ctl = opti.variable(nctl, 2)
122
+
123
+ t = opti.variable(upper_coordinates.shape[0])
124
+ t_init = np.linspace(0, 1, upper_coordinates.shape[0], dtype="f8")
125
+
126
+ af = BsplineAirfoil(ctu=ctu, ctl=ctl)
127
+ coords = af.upper_coordinates(t)
128
+
129
+ dist = coords - upper_coordinates
130
+ residual = cas.sum(cas.dot(dist, dist))
131
+
132
+ opti.subject_to(
133
+ [
134
+ opti.bounded(0.0, t, 1.0),
135
+ opti.bounded(0.0, ctu[:, 0], 1.0),
136
+ cas.diff(t) > 0.0,
137
+ ctu[:, 0] == ctu_init[:, 0],
138
+ ctu[0, 1] == ctu_init[0, 1],
139
+ ctu[-1, 1] == ctu_init[-1, 1],
140
+ t[0] == 0.0,
141
+ t[-1] == 1.0,
142
+ ]
143
+ )
144
+
145
+ opti.minimize(residual)
146
+ opti.solver("ipopt", default_options)
147
+ opti.set_initial(ctu, ctu_init)
148
+ opti.set_initial(t, t_init)
149
+
150
+ sol = opti.solve()
151
+
152
+ ctu_sol = sol.value(ctu)
153
+
154
+ # ============================ lower ==========================
155
+ if symmetry:
156
+ ctl_sol = np.array(ctu_sol)
157
+ ctl_sol[:, 1] = -ctu_sol[:, 1]
158
+ else:
159
+ opti = cas.Opti()
160
+
161
+ ctu = opti.variable(nctu, 2)
162
+
163
+ ctl = opti.variable(nctl, 2)
164
+ ctl_init = np.zeros(ctl.shape)
165
+ ctl_init[1:, 0] = np.linspace(0, 1, nctl - 1)
166
+ ctl_init[1:-1, 1] = -0.5
167
+ ctl_init[-1, 1] = lower_coordinates[-1, 1]
168
+
169
+ t = opti.variable(lower_coordinates.shape[0])
170
+ t_init = np.linspace(0, 1, lower_coordinates.shape[0], dtype="f8")
171
+
172
+ af = BsplineAirfoil(ctu=ctu, ctl=ctl)
173
+ coords = af.lower_coordinates(t)
174
+
175
+ dist = coords - lower_coordinates
176
+ residual = cas.sum(cas.dot(dist, dist))
177
+
178
+ opti.subject_to(
179
+ [
180
+ opti.bounded(0.0, t, 1.0),
181
+ opti.bounded(0.0, ctl[:, 0], 1.0),
182
+ cas.diff(t) > 0.0,
183
+ ctl[:, 0] == ctl_init[:, 0],
184
+ ctl[0, 1] == ctl_init[0, 1],
185
+ ctl[-1, 1] == ctl_init[-1, 1],
186
+ t[0] == 0.0,
187
+ t[-1] == 1.0,
188
+ ]
189
+ )
190
+
191
+ opti.minimize(residual)
192
+ opti.solver("ipopt", default_options)
193
+ opti.set_initial(ctl, ctl_init)
194
+ opti.set_initial(t, t_init)
195
+
196
+ sol = opti.solve()
197
+
198
+ ctl_sol = sol.value(ctl)
199
+
200
+ af_fit = BsplineAirfoil(ctu=ctu_sol, ctl=ctl_sol)
201
+ return af_fit
@@ -0,0 +1,92 @@
1
+ import sympy as sp
2
+ from sympy.printing.str import StrPrinter
3
+ import aerosandbox as asb
4
+ from scipy.special import comb
5
+ import aerosandbox.numpy as anp
6
+
7
+
8
+ class CustomerPrinter(StrPrinter):
9
+ def _print_Pow(self, expr):
10
+ return f"{expr.base}^{expr.exp}"
11
+
12
+
13
+ # def comb(N, i):
14
+ # return (sp.factorial(N)) / (sp.factorial(i) * sp.factorial(N - i))
15
+
16
+
17
+ def C_func(t, N1, N2):
18
+ return t**N1 * (1 - t) ** N2
19
+
20
+
21
+ def S_func(t, A):
22
+ N = len(A) - 1
23
+ S = 0.0
24
+ for i, ai in enumerate(A):
25
+ b = comb(N, i)
26
+ si = b * t**i * (1 - t) ** (N - i)
27
+ S += si * ai
28
+ return S
29
+
30
+
31
+ def write_exp(file, N1, N2, Au, Al, Thickness,Le, yu, yl,prefix):
32
+ with open(file, "w") as fout:
33
+ fout.write(f"{prefix}N1={N1}\n")
34
+ fout.write(f"{prefix}N2={N2}\n")
35
+ for idx, a in enumerate(Au):
36
+ fout.write(f"{prefix}Au{idx}={a:.6f}\n")
37
+ for idx, a in enumerate(Al):
38
+ fout.write(f"{prefix}Al{idx}={a:.6f}\n")
39
+ fout.write(f"{prefix}Te={Thickness:.6f}\n")
40
+ fout.write(f"{prefix}Le={Le:.6f}\n")
41
+ fout.write(f"t=1.0\n")
42
+ fout.write(f"{prefix}scaler=1000.0\n")
43
+ fout.write(f"[MilliMeter]{prefix}x={prefix}scaler*t\n")
44
+ fout.write(f"[MilliMeter]{prefix}yu={prefix}scaler*({yu})\n")
45
+ fout.write(f"[MilliMeter]{prefix}yl={prefix}scaler*({yl})\n")
46
+
47
+
48
+ def cst2nx():
49
+ order = 8
50
+ prefix="af0_"
51
+ Au = sp.symbols(f"{prefix}Au:{order}")
52
+ Al = sp.symbols(f"{prefix}Al:{order}")
53
+ N1 = sp.symbols(f"{prefix}N1")
54
+ N2 = sp.symbols(f"{prefix}N2")
55
+ Te = sp.symbols(f"{prefix}Te")
56
+ Le = sp.symbols(f"{prefix}Le")
57
+ # expr=x**2
58
+
59
+ t = sp.symbols("t")
60
+
61
+ C = C_func(t,N1,N2)
62
+
63
+ Su = S_func(t, Au)+Le*t**0.5*(1-t)**(order-0.5)
64
+ yu = C * Su + t * Te / 2.0
65
+
66
+ Sl = S_func(t, Al)+Le*t**0.5*(1-t)**(order-0.5)
67
+ yl = C * Sl - t * Te / 2.0
68
+
69
+ # print(yu.subs(t,1))
70
+
71
+ # print(yu)
72
+ yu_expr = str(yu).replace("**","^")
73
+ yl_expr = str(yl).replace("**","^")
74
+
75
+ print(yu_expr)
76
+ print(yl_expr)
77
+
78
+ af = asb.Airfoil("n63415").normalize().set_TE_thickness(0.0).to_kulfan_airfoil(n_weights_per_side=order)
79
+ # af.leading_edge_weight=1.0
80
+ # print(af.lower_coordinates(anp.cosspace(0,1,100)))
81
+ write_exp(
82
+ "C:/Users/Zcaic/Desktop/cst.exp",
83
+ N1=af.N1,
84
+ N2=af.N2,
85
+ Au=af.upper_weights,
86
+ Al=af.lower_weights,
87
+ Thickness=af.TE_thickness,
88
+ Le=af.leading_edge_weight,
89
+ yu=yu_expr,
90
+ yl=yl_expr,
91
+ prefix=prefix
92
+ )
@@ -0,0 +1,163 @@
1
+ import aerosandbox as asb
2
+ import aerosandbox.numpy as anp
3
+ from ..opti import cas
4
+ import numpy as np
5
+ from scipy.special import comb
6
+
7
+
8
+ class KulfanAirfoil:
9
+ def __init__(self, Au, Al, N1=0.5, N2=1.0, Le=0.0, Te=0.0):
10
+ self.Au = Au
11
+ self.Al = Al
12
+ self.N1 = N1
13
+ self.N2 = N2
14
+ self.Le = Le
15
+ self.Te = Te
16
+
17
+ def upper_coordinates(self, x):
18
+ C = KulfanAirfoil.class_function(x, self.N1, self.N2)
19
+ S = KulfanAirfoil.shape_function(x, self.Au)
20
+ y = C * (S + self.Le * x**0.5 * (1 - x) ** (self.Au.shape[0] - 1.5)) + x * self.Te / 2.0
21
+
22
+ coords = cas.hcat([x, y])
23
+ return coords[::-1, :]
24
+
25
+ def lower_coordinates(self, x):
26
+ C = KulfanAirfoil.class_function(x, self.N1, self.N2)
27
+ S = KulfanAirfoil.shape_function(x, self.Al)
28
+ y = C * (S + self.Le * x**0.5 * (1 - x) ** (self.Al.shape[0] - 1.5)) - x * self.Te / 2.0
29
+
30
+ coords = cas.hcat([x, y])
31
+ return coords
32
+
33
+ def coordinates(self, x):
34
+ coordinates_upper = self.upper_coordinates(x)
35
+ coordinates_lower = self.lower_coordinates(x)
36
+
37
+ coordinates = cas.vcat([coordinates_upper[:-1, :], coordinates_lower])
38
+ return coordinates
39
+
40
+ @staticmethod
41
+ def class_function(x, N1, N2):
42
+ C = (x) ** N1 * (1 - x) ** N2
43
+ return C
44
+
45
+ @staticmethod
46
+ def shape_function(x, w):
47
+ N = w.shape[0] - 1 # Order of Bernstein polynomials
48
+
49
+ K = comb(N, np.arange(N + 1)) # Bernstein polynomial coefficients
50
+
51
+ B = []
52
+ for i in range(w.shape[0]):
53
+ tmp = K[i] * (x) ** i * (1 - x) ** (N - i)
54
+ B.append(tmp)
55
+ B = cas.hcat(B)
56
+ S = cas.mtimes(B, w)
57
+
58
+ return S
59
+
60
+ @staticmethod
61
+ def fit(upper_coordinates, lower_coordinates, nAu=8, nAl=8, N1=0.5, N2=1.0, symmetry=False):
62
+ if not symmetry:
63
+ opti = cas.Opti()
64
+ Al = opti.variable(nAl)
65
+ Au = opti.variable(nAu)
66
+ leading_edge_weight = opti.variable(1)
67
+ # N1 = opti.variable(1)
68
+ # N2 = opti.variable(1)
69
+ N1 = N1
70
+ N2 = N2
71
+ te = upper_coordinates[-1, 1] - lower_coordinates[-1, 1]
72
+
73
+ xu = upper_coordinates[::-1, 0]
74
+ yu = upper_coordinates[:, 1]
75
+ xl = lower_coordinates[:, 0]
76
+ yl = lower_coordinates[:, 1]
77
+
78
+ af = KulfanAirfoil(Au=Au, Al=Al, N1=N1, N2=N2, Le=leading_edge_weight, Te=te)
79
+ yu_ = af.upper_coordinates(xu)
80
+ yl_ = af.lower_coordinates(xl)
81
+
82
+ residual = cas.sum((yu_ - yu) ** 2) + cas.sum((yl_ - yl) ** 2)
83
+
84
+ default_options = {
85
+ "ipopt.sb": "yes",
86
+ "ipopt.max_iter": 1000,
87
+ "ipopt.max_cpu_time": 1e20,
88
+ "ipopt.mu_strategy": "adaptive",
89
+ "ipopt.fast_step_computation": "yes",
90
+ "detect_simple_bounds": False,
91
+ "expand": True,
92
+ "print_time": False,
93
+ "ipopt.print_level": 0,
94
+ }
95
+
96
+ opti.minimize(residual)
97
+ opti.subject_to(
98
+ [
99
+ opti.bounded(-1.0, leading_edge_weight, 1.0),
100
+ ]
101
+ )
102
+
103
+ opti.solver("ipopt", default_options)
104
+ opti.set_initial(Au, 0.05)
105
+ opti.set_initial(Al, -0.05)
106
+ opti.set_initial(leading_edge_weight, 0.0)
107
+ # opti.set_value(N1,0.5)
108
+ # opti.set_value(N2,1.0)
109
+ # opti.set_initial(N1, 0.5)
110
+ # opti.set_initial(N2, 1.0)
111
+ sol = opti.solve()
112
+
113
+ Au_sol = sol.value(Au)
114
+ Al_sol = sol.value(Al)
115
+ le_sol = sol.value(leading_edge_weight)
116
+
117
+ else:
118
+ opti = cas.Opti()
119
+ Al = opti.variable(nAl)
120
+ Au = opti.variable(nAu)
121
+ leading_edge_weight = 0.0
122
+ # N1 = opti.variable(1)
123
+ # N2 = opti.variable(1)
124
+ N1 = N1
125
+ N2 = N2
126
+ te = upper_coordinates[-1, 1] - lower_coordinates[-1, 1]
127
+
128
+ xu = upper_coordinates[::-1, 0]
129
+ yu = upper_coordinates[:, 1]
130
+ xl = lower_coordinates[:, 0]
131
+ yl = lower_coordinates[:, 1]
132
+
133
+ af = KulfanAirfoil(Au=Au, Al=Al, N1=N1, N2=N2, Le=leading_edge_weight, Te=te)
134
+ yu_ = af.upper_coordinates(xu)
135
+ yl_ = af.lower_coordinates(xl)
136
+
137
+ residual = cas.sum((yu_ - yu) ** 2)
138
+
139
+ default_options = {
140
+ "ipopt.sb": "yes",
141
+ "ipopt.max_iter": 1000,
142
+ "ipopt.max_cpu_time": 1e20,
143
+ "ipopt.mu_strategy": "adaptive",
144
+ "ipopt.fast_step_computation": "yes",
145
+ "detect_simple_bounds": False,
146
+ "expand": True,
147
+ "print_time": False,
148
+ "ipopt.print_level": 0,
149
+ }
150
+
151
+ opti.minimize(residual)
152
+
153
+ opti.solver("ipopt", default_options)
154
+ opti.set_initial(Au, 0.05)
155
+ opti.set_initial(Al, -0.05)
156
+
157
+ sol = opti.solve()
158
+
159
+ Au_sol = sol.value(Au)
160
+ Al_sol = -Au_sol
161
+ le_sol = leading_edge_weight
162
+
163
+ return KulfanAirfoil(Au=Au_sol, Al=Al_sol, N1=N1, N2=N2, Le=le_sol, Te=te)