Skip to content

Commit bf1354f

Browse files
Add complex alkylation optimisation test problem
* working alkylation test * ruff * ruff * ruff
1 parent 81d7e1d commit bf1354f

File tree

1 file changed

+320
-0
lines changed

1 file changed

+320
-0
lines changed
Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
# SPDX-FileCopyrightText: 2021-present M. Coleman, J. Cook, F. Franza
2+
# SPDX-FileCopyrightText: 2021-present I.A. Maione, S. McIntosh
3+
# SPDX-FileCopyrightText: 2021-present J. Morris, D. Short
4+
#
5+
# SPDX-License-Identifier: LGPL-2.1-or-later
6+
7+
"""
8+
This is a complex constrained optimisation problem from Colville
9+
Found in chapter 4 here: https://apps.dtic.mil/sti/tr/pdf/AD0679037.pdf
10+
Note that there is a typo in one of the equality constraints.
11+
See https://courses.mai.liu.se/GU/TAOP04/process-optimization.pdf
12+
"""
13+
14+
import numpy as np
15+
import pytest
16+
17+
from bluemira.optimisation import Algorithm, optimise
18+
19+
20+
class AlkylationData:
21+
c1 = 0.063
22+
c2 = 5.04
23+
c3 = 0.035
24+
c4 = 10.0
25+
c5 = 3.36
26+
d4l = 99.0 / 100.0
27+
d4u = 100.0 / 99.0
28+
d7l = 99.0 / 100.0
29+
d7u = 100.0 / 99.0
30+
d9l = 9.0 / 10.0
31+
d9u = 10.0 / 9.0
32+
d10l = 99.0 / 100.0
33+
d10u = 100.0 / 99.0
34+
dimension = 10
35+
lower_bounds = np.array([0.0, 0.0, 0.0, 0.0, 0.0, 85.0, 90.0, 3.0, 1.2, 145.0])
36+
upper_bounds = np.array([
37+
2000.0,
38+
16000.0,
39+
120.0,
40+
5000.0,
41+
2000.0,
42+
93.0,
43+
95.0,
44+
12.0,
45+
4.0,
46+
162.0,
47+
])
48+
suggested_x0 = np.array([
49+
1745.0,
50+
12000.0,
51+
110.0,
52+
3048.0,
53+
1974.0,
54+
89.2,
55+
92.8,
56+
8.0,
57+
3.6,
58+
145.0,
59+
])
60+
# Given to 1DP
61+
true_x = np.array([
62+
1698.0,
63+
15818.0,
64+
54.1,
65+
3031.0,
66+
2000.0,
67+
90.1,
68+
95.0,
69+
10.5,
70+
1.6,
71+
154.0,
72+
])
73+
# Given to 0DP
74+
true_f_x = 1769.0
75+
76+
77+
ALKYLATION_DATA = AlkylationData()
78+
79+
80+
def f_objective(x):
81+
"""
82+
This is a maximisation objective, so we flip the sign.
83+
"""
84+
return (
85+
-ALKYLATION_DATA.c1 * x[3] * x[6]
86+
+ ALKYLATION_DATA.c2 * x[0]
87+
+ ALKYLATION_DATA.c3 * x[1]
88+
+ ALKYLATION_DATA.c4 * x[2]
89+
+ ALKYLATION_DATA.c5 * x[4]
90+
)
91+
92+
93+
def df_objective(x):
94+
grad = np.zeros(ALKYLATION_DATA.dimension)
95+
grad[0] = ALKYLATION_DATA.c2
96+
grad[1] = ALKYLATION_DATA.c3
97+
grad[2] = ALKYLATION_DATA.c4
98+
grad[3] = -ALKYLATION_DATA.c1 * x[6]
99+
grad[4] = ALKYLATION_DATA.c5
100+
grad[6] = -ALKYLATION_DATA.c1 * x[3]
101+
return grad
102+
103+
104+
def f_equality1(x):
105+
return 1.22 * x[3] - x[0] - x[4]
106+
107+
108+
def df_equality1(_x):
109+
grad = np.zeros(ALKYLATION_DATA.dimension)
110+
grad[0] = -1.0
111+
grad[3] = 1.22
112+
grad[4] = -1.0
113+
return grad
114+
115+
116+
def f_equality2(x):
117+
# There is a typo where I found this orginally...
118+
return 98_000.0 * x[2] / (x[3] * x[8] + 1000.0 * x[2]) - x[5]
119+
120+
121+
def df_equality2(x):
122+
grad = np.zeros(ALKYLATION_DATA.dimension)
123+
grad[2] = (98_000.0 * x[3] * x[8]) / (x[3] * x[8] + 1000.0 * x[2]) ** 2
124+
grad[3] = -98_000.0 * x[2] * x[8] / (x[8] * x[3] + 1000.0 * x[2]) ** 2
125+
grad[5] = -1.0
126+
grad[8] = -98_000.0 * x[2] * x[3] / (x[8] * x[3] + 1000.0 * x[2]) ** 2
127+
return grad
128+
129+
130+
def f_equality3(x):
131+
return (x[1] + x[4]) / x[0] - x[7]
132+
133+
134+
def df_equality3(x):
135+
grad = np.zeros(ALKYLATION_DATA.dimension)
136+
grad[0] = -(x[1] + x[4]) / x[0] ** 2
137+
grad[1] = 1.0 / x[0]
138+
grad[4] = 1.0 / x[0]
139+
grad[7] = -1.0
140+
return grad
141+
142+
143+
def f_inequality1(x):
144+
a = 1.12 + 0.13167 * x[7] - 0.00667 * x[7] ** 2
145+
return -x[0] * a + ALKYLATION_DATA.d4l * x[3]
146+
147+
148+
def df_inequality1(x):
149+
grad = np.zeros(ALKYLATION_DATA.dimension)
150+
a = 1.12 + 0.13167 * x[7] - 0.00667 * x[7] ** 2
151+
grad[0] = -a
152+
grad[3] = ALKYLATION_DATA.d4l
153+
grad[7] = -x[0] * (0.13167 - 2.0 * 0.00667 * x[7])
154+
return grad
155+
156+
157+
def f_inequality2(x):
158+
a = 1.12 + 0.13167 * x[7] - 0.00667 * x[7] ** 2
159+
return x[0] * a - ALKYLATION_DATA.d4u * x[3]
160+
161+
162+
def df_inequality2(x):
163+
grad = np.zeros(ALKYLATION_DATA.dimension)
164+
a = 1.12 + 0.13167 * x[7] - 0.00667 * x[7] ** 2
165+
grad[0] = a
166+
grad[3] = -ALKYLATION_DATA.d4u
167+
grad[7] = x[0] * (0.13167 - 2.0 * 0.00667 * x[7])
168+
return grad
169+
170+
171+
def f_inequality3(x):
172+
return (
173+
-(86.35 + 1.098 * x[7] - 0.038 * x[7] ** 2 + 0.325 * (x[5] - 89.0))
174+
+ ALKYLATION_DATA.d7l * x[6]
175+
)
176+
177+
178+
def df_inequality3(x):
179+
grad = np.zeros(ALKYLATION_DATA.dimension)
180+
grad[5] = -0.325
181+
grad[6] = ALKYLATION_DATA.d7l
182+
grad[7] = -1.098 + 2.0 * 0.038 * x[7]
183+
return grad
184+
185+
186+
def f_inequality4(x):
187+
return (
188+
86.35 + 1.098 * x[7] - 0.038 * x[7] ** 2 + 0.325 * (x[5] - 89.0)
189+
) - ALKYLATION_DATA.d7u * x[6]
190+
191+
192+
def df_inequality4(x):
193+
grad = np.zeros(ALKYLATION_DATA.dimension)
194+
grad[5] = 0.325
195+
grad[6] = -ALKYLATION_DATA.d7u
196+
grad[7] = 1.098 - 2.0 * 0.038 * x[7]
197+
return grad
198+
199+
200+
def f_inequality5(x):
201+
return -(35.82 - 0.222 * x[9]) + ALKYLATION_DATA.d9l * x[8]
202+
203+
204+
def df_inequality5(_x):
205+
grad = np.zeros(ALKYLATION_DATA.dimension)
206+
grad[8] = ALKYLATION_DATA.d9l
207+
grad[9] = 0.222
208+
return grad
209+
210+
211+
def f_inequality6(x):
212+
return (35.82 - 0.222 * x[9]) - ALKYLATION_DATA.d9u * x[8]
213+
214+
215+
def df_inequality6(_x):
216+
grad = np.zeros(ALKYLATION_DATA.dimension)
217+
grad[8] = -ALKYLATION_DATA.d9u
218+
grad[9] = -0.222
219+
return grad
220+
221+
222+
def f_inequality7(x):
223+
return -(-133.0 + 3.0 * x[6]) + ALKYLATION_DATA.d10l * x[9]
224+
225+
226+
def df_inequality7(_x):
227+
grad = np.zeros(ALKYLATION_DATA.dimension)
228+
grad[6] = -3.0
229+
grad[9] = ALKYLATION_DATA.d10l
230+
return grad
231+
232+
233+
def f_inequality8(x):
234+
return (-133.0 + 3.0 * x[6]) - ALKYLATION_DATA.d10u * x[9]
235+
236+
237+
def df_inequality8(_x):
238+
grad = np.zeros(ALKYLATION_DATA.dimension)
239+
grad[6] = 3.0
240+
grad[9] = -ALKYLATION_DATA.d10u
241+
return grad
242+
243+
244+
def f_inequalities(x):
245+
return np.array([
246+
f_inequality1(x),
247+
f_inequality2(x),
248+
f_inequality3(x),
249+
f_inequality4(x),
250+
f_inequality5(x),
251+
f_inequality6(x),
252+
f_inequality7(x),
253+
f_inequality8(x),
254+
])
255+
256+
257+
def df_inequalities(x):
258+
return np.array([
259+
df_inequality1(x),
260+
df_inequality2(x),
261+
df_inequality3(x),
262+
df_inequality4(x),
263+
df_inequality5(x),
264+
df_inequality6(x),
265+
df_inequality7(x),
266+
df_inequality8(x),
267+
])
268+
269+
270+
@pytest.mark.parametrize(
271+
"algorithm",
272+
[
273+
Algorithm.SLSQP,
274+
Algorithm.SLSQP_SCIPY,
275+
Algorithm.COBYLA,
276+
Algorithm.COBYLA_SCIPY,
277+
Algorithm.COBYQA,
278+
],
279+
)
280+
def test_alkylation_problem(algorithm):
281+
result = optimise(
282+
f_objective,
283+
x0=ALKYLATION_DATA.suggested_x0,
284+
df_objective=df_objective,
285+
eq_constraints=[
286+
{
287+
"f_constraint": f_equality1,
288+
"df_constraint": df_equality1,
289+
"tolerance": np.array([1e-6]),
290+
},
291+
{
292+
"f_constraint": f_equality2,
293+
"df_constraint": df_equality2,
294+
"tolerance": np.array([1e-6]),
295+
},
296+
{
297+
"f_constraint": f_equality3,
298+
"df_constraint": df_equality3,
299+
"tolerance": np.array([1e-6]),
300+
},
301+
],
302+
ineq_constraints=[
303+
{
304+
"f_constraint": f_inequalities,
305+
"df_constraint": df_inequalities,
306+
"tolerance": 1e-6 * np.ones(8),
307+
}
308+
],
309+
bounds=(ALKYLATION_DATA.lower_bounds, ALKYLATION_DATA.upper_bounds),
310+
algorithm=algorithm,
311+
opt_conditions={"max_eval": 5000, "ftol_rel": 1e-9},
312+
)
313+
314+
assert np.round(abs(result.f_x)) == ALKYLATION_DATA.true_f_x
315+
np.testing.assert_allclose(
316+
np.round(result.x, decimals=1),
317+
ALKYLATION_DATA.true_x,
318+
atol=0.0,
319+
rtol=0.0033,
320+
)

0 commit comments

Comments
 (0)