problems.bend

unified-nsga-iii@0.2.2.0 · 654 lines · 98 definitions

1# Named problems — C# Unsga3.Problems.
2# ZDT1 / ZDT2 / DTLZ2 stay the oracle problems. The rest of the unconstrained
3# catalog (ZDT3 / ZDT4 / ZDT6, DTLZ1 / DTLZ3 / DTLZ4 / DTLZ7, Sphere / Ackley /
4# Rosenbrock) matches the same C# Evaluate formulas. Minimize.
5# OSY / TNK / C1-DTLZ1 match ConstrainedProblems.cs (g ≤ 0, positive = violation).
6# Unconstrained kinds write an empty constraint vector (cv 0). No IGD here.
7# `alpha` is DTLZ4's exponent (C# default 100). Other kinds ignore it.
8
9import Base
10import unified-nsga-iii@0.2.2.0/individual.bend as Ind
11import unified-nsga-iii@0.2.2.0/bounds.bend as Bounds
12import unified-nsga-iii@0.2.2.0/util.bend as Util
13
14type Family is Data:
15  Zdt1{}
16  Zdt2{}
17  Zdt3{}
18  Zdt4{}
19  Zdt6{}
20  Dtlz1{}
21  Dtlz2{}
22  Dtlz3{}
23  Dtlz4{}
24  Dtlz7{}
25  Sphere{}
26  Ackley{}
27  Rosenbrock{}
28  Osy{}
29  Tnk{}
30  C1Dtlz1{}
31
32type Problem is Data:
33  MkProblem{
34    kind: Family
35    box: Bounds.Box
36    alpha: F32
37  }
38
39def box_of(p: Problem) -> Bounds.Box:
40  match p:
41    case MkProblem{kind, box, alpha}:
42      box
43
44def kind_of(p: Problem) -> Family:
45  match p:
46    case MkProblem{kind, box, alpha}:
47      kind
48
49def alpha_of(p: Problem) -> F32:
50  match p:
51    case MkProblem{kind, box, alpha}:
52      alpha
53
54def n_obj(p: Problem) -> Nat:
55  Bounds.n_obj(box_of(p))
56
57def n_var(p: Problem) -> Nat:
58  Bounds.n_var(box_of(p))
59
60def mk_prob(kind: Family, box: Bounds.Box) -> Problem:
61  MkProblem{kind, box, 0.0}
62
63# ZDT1 / ZDT2 / ZDT3: n variables (C# default 30), 2 objectives, x ∈ [0,1]^n.
64def zdt1(n: Nat) -> Problem:
65  mk_prob(Zdt1{}, Bounds.unit_n(n, 2n))
66
67def zdt2(n: Nat) -> Problem:
68  mk_prob(Zdt2{}, Bounds.unit_n(n, 2n))
69
70def zdt3(n: Nat) -> Problem:
71  mk_prob(Zdt3{}, Bounds.unit_n(n, 2n))
72
73# ZDT4: C# default n=10. x0 ∈ [0,1], x1.. ∈ [-5,5].
74def zdt4_ivs(n: Nat) -> List<&2, Bounds.Interval>:
75  match n:
76    case 0n:
77      Nil{}
78    case 1n+p:
79      Bounds.interval(0.0, 1.0) <> List.replicate(Bounds.Interval, p, Bounds.interval(F32.neg(5.0), 5.0))
80
81def zdt4(n: Nat) -> Problem:
82  mk_prob(Zdt4{}, Bounds.box(2n, zdt4_ivs(n)))
83
84# ZDT6: C# default n=10, x ∈ [0,1]^n.
85def zdt6(n: Nat) -> Problem:
86  mk_prob(Zdt6{}, Bounds.unit_n(n, 2n))
87
88# n = M + k - 1 (C# Dtlz*Problem).
89def dtlz_n(m: Nat, k: Nat) -> Nat:
90  match m:
91    case 0n:
92      k
93    case 1n+pm:
94      match k:
95        case 0n:
96          pm
97        case 1n+pk:
98          Nat.add(pm, 1n+pk)
99
100# DTLZ2: C# default M=3, k=10 ⇒ n=12, x ∈ [0,1]^n.
101def dtlz2(+m: Nat, k: Nat) -> Problem:
102  mk_prob(Dtlz2{}, Bounds.unit_n(dtlz_n(m, k), m))
103
104# DTLZ1: C# default M=3, k=5 ⇒ n=7.
105def dtlz1(+m: Nat, k: Nat) -> Problem:
106  mk_prob(Dtlz1{}, Bounds.unit_n(dtlz_n(m, k), m))
107
108# DTLZ3: C# default M=3, k=10. DTLZ2 map, DTLZ1 g.
109def dtlz3(+m: Nat, k: Nat) -> Problem:
110  mk_prob(Dtlz3{}, Bounds.unit_n(dtlz_n(m, k), m))
111
112# DTLZ4: C# default α=100, k=10.
113def dtlz4_alpha(+m: Nat, k: Nat, +alpha: F32) -> Problem:
114  MkProblem{Dtlz4{}, Bounds.unit_n(dtlz_n(m, k), m), alpha}
115
116def dtlz4(+m: Nat, k: Nat) -> Problem:
117  dtlz4_alpha(m, k, 100.0)
118
119# DTLZ7: C# default M=3, k=20 ⇒ n=22.
120def dtlz7(+m: Nat, k: Nat) -> Problem:
121  mk_prob(Dtlz7{}, Bounds.unit_n(dtlz_n(m, k), m))
122
123# Single-objective. M=1. C# defaults: Sphere n=10, Ackley n=30, Rosenbrock n=10.
124def sphere(n: Nat) -> Problem:
125  mk_prob(Sphere{}, Bounds.span_n(n, 1n, F32.neg(5.12), 5.12))
126
127def ackley(n: Nat) -> Problem:
128  mk_prob(Ackley{}, Bounds.span_n(n, 1n, F32.neg(32.768), 32.768))
129
130def rosenbrock(n: Nat) -> Problem:
131  mk_prob(Rosenbrock{}, Bounds.span_n(n, 1n, F32.neg(2.048), 2.048))
132
133# OSY: 6 variables, 2 objectives, 6 inequalities. pymoo 0.6 scaling.
134def osy() -> Problem:
135  mk_prob(
136    Osy{},
137    Bounds.box(
138      2n,
139      Bounds.interval(0.0, 10.0)
140        <> Bounds.interval(0.0, 10.0)
141        <> Bounds.interval(1.0, 5.0)
142        <> Bounds.interval(0.0, 6.0)
143        <> Bounds.interval(1.0, 5.0)
144        <> [Bounds.interval(0.0, 10.0)]
145    )
146  )
147
148# TNK: [0, π] × [1e-30, π]. Disk constraint uses pymoo's factor of two.
149def tnk() -> Problem:
150  mk_prob(
151    Tnk{},
152    Bounds.box(2n, Bounds.interval(0.0, F32.pi()) <> [Bounds.interval(1.0e-30, F32.pi())])
153  )
154
155# C1-DTLZ1: DTLZ1 plus one inequality. C# default M=3, k=5 (n = M+k-1).
156def c1_dtlz1(+m: Nat, k: Nat) -> Problem:
157  mk_prob(C1Dtlz1{}, Bounds.unit_n(dtlz_n(m, k), m))
158
159def sum_from(xs: List<&2, F32>) -> F32:
160  match xs:
161    case Nil{}:
162      0.0
163    case h <> t:
164      F32.add(h, sum_from(t))
165
166def sum_tail(xs: List<&2, F32>) -> F32:
167  match xs:
168    case Nil{}:
169      0.0
170    case h <> t:
171      sum_from(t)
172
173def list_tail(xs: List<&2, F32>) -> List<&2, F32>:
174  match xs:
175    case Nil{}:
176      Nil{}
177    case h <> t:
178      t
179
180def zdt_den(n: Nat) -> F32:
181  match n:
182    case 0n:
183      1.0
184    case 1n:
185      1.0
186    case 1n+p:
187      F32.from_nat(p)
188
189# C# Zdt1Problem.GLinear: 1 + 9 * sum(x[1..]) / (n - 1).
190def zdt_g(+x: List<&2, F32>) -> F32:
191  +n = Util.list_len(F32, x)
192  F32.add(1.0, F32.div(F32.mul(9.0, sum_tail(x)), zdt_den(n)))
193
194def zdt1_f(+x: List<&2, F32>) -> List<&2, F32>:
195  +x0 = Util.get_f32(x, 0n)
196  +g = zdt_g(x)
197  x0 <> [F32.mul(g, F32.sub(1.0, F32.sqrt(F32.div(x0, g))))]
198
199def zdt2_f(+x: List<&2, F32>) -> List<&2, F32>:
200  +x0 = Util.get_f32(x, 0n)
201  +g = zdt_g(x)
202  +r = F32.div(x0, g)
203  x0 <> [F32.mul(g, F32.sub(1.0, F32.mul(r, r)))]
204
205# C# Zdt3Problem: g * (1 - sqrt(r) - r * sin(10 π x0)), r = x0/g.
206def zdt3_f(+x: List<&2, F32>) -> List<&2, F32>:
207  +x0 = Util.get_f32(x, 0n)
208  +g = zdt_g(x)
209  +r = F32.div(x0, g)
210  +s = F32.sin(F32.mul(F32.mul(10.0, F32.pi()), x0))
211  x0 <> [F32.mul(g, F32.sub(F32.sub(1.0, F32.sqrt(r)), F32.mul(r, s)))]
212
213def zdt4_term(+xi: F32) -> F32:
214  F32.sub(F32.square(xi), F32.mul(10.0, F32.cos(F32.mul(F32.mul(4.0, F32.pi()), xi))))
215
216def sum_zdt4(xs: List<&2, F32>) -> F32:
217  match xs:
218    case Nil{}:
219      0.0
220    case h <> t:
221      F32.add(zdt4_term(h), sum_zdt4(t))
222
223def pred(n: Nat) -> Nat:
224  match n:
225    case 0n:
226      0n
227    case 1n+p:
228      p
229
230# C# Zdt4Problem: 1 + 10*(n-1) + Σ_{i≥1} (x_i² - 10 cos(4 π x_i)).
231def zdt4_g(+x: List<&2, F32>) -> F32:
232  +n = Util.list_len(F32, x)
233  F32.add(F32.add(1.0, F32.mul(10.0, F32.from_nat(pred(n)))), sum_zdt4(list_tail(x)))
234
235def zdt4_f(+x: List<&2, F32>) -> List<&2, F32>:
236  +x0 = Util.get_f32(x, 0n)
237  +g = zdt4_g(x)
238  x0 <> [F32.mul(g, F32.sub(1.0, F32.sqrt(F32.div(x0, g))))]
239
240# C# Zdt6Problem: biased density. g = 1 + 9 * (mean of x[1..])^0.25.
241def zdt6_f(+x: List<&2, F32>) -> List<&2, F32>:
242  +x0 = Util.get_f32(x, 0n)
243  +wave = F32.pow(F32.sin(F32.mul(F32.mul(6.0, F32.pi()), x0)), 6.0)
244  +f0 = F32.sub(1.0, F32.mul(F32.exp(F32.mul(F32.neg(4.0), x0)), wave))
245  +n = Util.list_len(F32, x)
246  +g = F32.add(1.0, F32.mul(9.0, F32.pow(F32.div(sum_tail(x), zdt_den(n)), 0.25)))
247  +r = F32.div(f0, g)
248  f0 <> [F32.mul(g, F32.sub(1.0, F32.mul(r, r)))]
249
250def sum_sq_half(xs: List<&2, F32>) -> F32:
251  match xs:
252    case Nil{}:
253      0.0
254    case h <> t:
255      F32.add(F32.square(F32.sub(h, 0.5)), sum_sq_half(t))
256
257def tail_from_m(+x: List<&2, F32>, m: Nat) -> List<&2, F32>:
258  List.drop(&2, F32, x, pred(m))
259
260# C# DtlzHelper.GDtlz2: sum (x[i] - 0.5)^2 for i >= M-1.
261def dtlz2_g(+x: List<&2, F32>, m: Nat) -> F32:
262  sum_sq_half(tail_from_m(x, m))
263
264def dtlz1_term(+xi: F32) -> F32:
265  +d = F32.sub(xi, 0.5)
266  F32.sub(F32.square(d), F32.cos(F32.mul(F32.mul(20.0, F32.pi()), d)))
267
268def sum_dtlz1(xs: List<&2, F32>) -> F32:
269  match xs:
270    case Nil{}:
271      0.0
272    case h <> t:
273      F32.add(dtlz1_term(h), sum_dtlz1(t))
274
275# C# DtlzHelper.GDtlz1: 100 * ((n - M + 1) + Σ (d² - cos(20 π d))), d = x-0.5.
276# On a legal k≥1 box the tail length is n-M+1, which is that coefficient.
277def dtlz1_g(+x: List<&2, F32>, m: Nat) -> F32:
278  +tail = tail_from_m(x, m)
279  F32.mul(100.0, F32.add(F32.from_nat(Util.list_len(F32, tail)), sum_dtlz1(tail)))
280
281def half_pi() -> F32:
282  F32.div(F32.pi(), 2.0)
283
284def prod_cos.go(+x: List<&2, F32>, fuel: Nat, +i: Nat, acc: F32) -> F32:
285  match fuel:
286    case 0n:
287      acc
288    case 1n+f:
289      prod_cos.go(
290        x,
291        f,
292        1n+i,
293        F32.mul(acc, F32.cos(F32.mul(Util.get_f32(x, i), half_pi())))
294      )
295
296def prod_cos(+x: List<&2, F32>, n: Nat) -> F32:
297  prod_cos.go(x, n, 0n, 1.0)
298
299def dtlz2_sin(+x: List<&2, F32>, +m: Nat, +i: Nat) -> F32:
300  F32.sin(F32.mul(Util.get_f32(x, pred(Nat.sub(m, i))), half_pi()))
301
302def dtlz2_fi.tail(+x: List<&2, F32>, +m: Nat, +g: F32, +i: Nat, +body: F32, first: Bool) -> F32:
303  match first:
304    case True{}:
305      body
306    case False{}:
307      F32.mul(body, dtlz2_sin(x, m, i))
308
309# C# Dtlz2Problem: (1+g) * prod_{j<M-i-1} cos(x[j] π/2) [* sin(x[M-i-1] π/2) if i>0].
310def dtlz2_fi(+x: List<&2, F32>, +m: Nat, +g: F32, +i: Nat) -> F32:
311  dtlz2_fi.tail(
312    x,
313    m,
314    g,
315    i,
316    F32.mul(F32.add(1.0, g), prod_cos(x, pred(Nat.sub(m, i)))),
317    Nat.is_eq(i, 0n)
318  )
319
320def dtlz2_fs.go(+x: List<&2, F32>, +m: Nat, +g: F32, fuel: Nat, +i: Nat) -> List<&2, F32>:
321  match fuel:
322    case 0n:
323      Nil{}
324    case 1n+f:
325      dtlz2_fi(x, m, g, i) <> dtlz2_fs.go(x, m, g, f, 1n+i)
326
327def dtlz2_f(+x: List<&2, F32>, +m: Nat) -> List<&2, F32>:
328  dtlz2_fs.go(x, m, dtlz2_g(x, m), m, 0n)
329
330def prod_xs.go(+x: List<&2, F32>, fuel: Nat, +i: Nat, acc: F32) -> F32:
331  match fuel:
332    case 0n:
333      acc
334    case 1n+f:
335      prod_xs.go(x, f, 1n+i, F32.mul(acc, Util.get_f32(x, i)))
336
337def prod_xs(+x: List<&2, F32>, n: Nat) -> F32:
338  prod_xs.go(x, n, 0n, 1.0)
339
340def dtlz1_scale(+body: F32, +x: List<&2, F32>, +idx: Nat, first: Bool) -> F32:
341  match first:
342    case True{}:
343      body
344    case False{}:
345      F32.mul(body, F32.sub(1.0, Util.get_f32(x, idx)))
346
347# C# Dtlz1Problem: 0.5*(1+g) * prod_{j<M-i-1} x[j] [* (1-x[M-i-1]) if i>0].
348def dtlz1_fi(+x: List<&2, F32>, +m: Nat, +g: F32, +i: Nat) -> F32:
349  +idx = pred(Nat.sub(m, i))
350  dtlz1_scale(F32.mul(F32.mul(0.5, F32.add(1.0, g)), prod_xs(x, idx)), x, idx, Nat.is_eq(i, 0n))
351
352def dtlz1_fs.go(+x: List<&2, F32>, +m: Nat, +g: F32, fuel: Nat, +i: Nat) -> List<&2, F32>:
353  match fuel:
354    case 0n:
355      Nil{}
356    case 1n+f:
357      dtlz1_fi(x, m, g, i) <> dtlz1_fs.go(x, m, g, f, 1n+i)
358
359def dtlz1_f(+x: List<&2, F32>, +m: Nat) -> List<&2, F32>:
360  dtlz1_fs.go(x, m, dtlz1_g(x, m), m, 0n)
361
362# F32 cos(π/2) is -4.371139e-8, not 0. The DTLZ2 map multiplies that
363# factor by (1+g). DTLZ3's g stays large on local fronts, so an in-bound
364# x=1 (SBX / polynomial mutation clamp to the box) gets a strictly
365# negative objective. Every feasible point has f≥0, so nothing dominates
366# that row. It stays on the ND front and pins an ASF extreme at (1+g).
367# DTLZ2's (1+g) is ~1, so the same raw trig is left alone (published
368# fronts). Clamp only this map back to the mathematical range f≥0.
369def nn_obj(+v: F32) -> F32:
370  Bool.pick(F32, F32.is_lt(v, 0.0), 0.0, v)
371
372def nn_objs(xs: List<&2, F32>) -> List<&2, F32>:
373  match xs:
374    case Nil{}:
375      Nil{}
376    case h <> t:
377      nn_obj(h) <> nn_objs(t)
378
379# C# Dtlz3Problem: DTLZ2 map with DTLZ1's g.
380def dtlz3_f(+x: List<&2, F32>, +m: Nat) -> List<&2, F32>:
381  nn_objs(dtlz2_fs.go(x, m, dtlz1_g(x, m), m, 0n))
382
383# Fuel shrinks first so the recursive call terminates. The g-tail stays raw.
384def map_prefix_pow.go(fuel: Nat, xs: List<&2, F32>, +alpha: F32) -> List<&2, F32>:
385  match fuel:
386    case 0n:
387      xs
388    case 1n+f:
389      match xs:
390        case Nil{}:
391          Nil{}
392        case h <> t:
393          F32.pow(h, alpha) <> map_prefix_pow.go(f, t, alpha)
394
395# Raise the first `fuel` decision variables to α.
396def map_prefix_pow(+xs: List<&2, F32>, fuel: Nat, +alpha: F32) -> List<&2, F32>:
397  map_prefix_pow.go(fuel, xs, alpha)
398
399# C# Dtlz4Problem: DTLZ2 with x^α inside the sine/cosine map. g is still GDtlz2.
400def dtlz4_f(+x: List<&2, F32>, +m: Nat, +alpha: F32) -> List<&2, F32>:
401  dtlz2_f(map_prefix_pow(x, pred(m), alpha), m)
402
403def pos_or.fin(+x: F32, +fallback: F32, ok: Bool) -> F32:
404  match ok:
405    case True{}:
406      x
407    case False{}:
408      fallback
409
410def pos_or(+x: F32, +fallback: F32) -> F32:
411  pos_or.fin(x, fallback, F32.is_gt(x, 0.0))
412
413# C# Dtlz7Problem g: 1 + 9 * sum(x[M-1..]) / (n - M + 1).
414def dtlz7_g(+x: List<&2, F32>, m: Nat) -> F32:
415  +tail = tail_from_m(x, m)
416  +den = pos_or(F32.from_nat(Util.list_len(F32, tail)), 1.0)
417  F32.add(1.0, F32.div(F32.mul(9.0, sum_from(tail)), den))
418
419def dtlz7_h.go(+x: List<&2, F32>, +g1: F32, fuel: Nat, +i: Nat, acc: F32) -> F32:
420  match fuel:
421    case 0n:
422      acc
423    case 1n+f:
424      +fi = Util.get_f32(x, i)
425      +term = F32.mul(F32.div(fi, g1), F32.add(1.0, F32.sin(F32.mul(F32.mul(3.0, F32.pi()), fi))))
426      dtlz7_h.go(x, g1, f, 1n+i, F32.add(acc, term))
427
428def dtlz7_prefix(+x: List<&2, F32>, fuel: Nat, +i: Nat) -> List<&2, F32>:
429  match fuel:
430    case 0n:
431      Nil{}
432    case 1n+f:
433      Util.get_f32(x, i) <> dtlz7_prefix(x, f, 1n+i)
434
435# f[i]=x[i] for i<M-1; f[M-1]=(1+g)*h; h = M - Σ f_i/(1+g) * (1+sin(3 π f_i)).
436def dtlz7_f(+x: List<&2, F32>, +m: Nat) -> List<&2, F32>:
437  match m:
438    case 0n:
439      Nil{}
440    case 1n+p:
441      +g = dtlz7_g(x, m)
442      +g1 = F32.add(1.0, g)
443      +h = F32.sub(F32.from_nat(m), dtlz7_h.go(x, g1, p, 0n, 0.0))
444      List.append(&2, F32, dtlz7_prefix(x, p, 0n), [F32.mul(g1, h)])
445
446def sum_sq(xs: List<&2, F32>) -> F32:
447  match xs:
448    case Nil{}:
449      0.0
450    case h <> t:
451      F32.add(F32.square(h), sum_sq(t))
452
453def sphere_f(+x: List<&2, F32>) -> List<&2, F32>:
454  [sum_sq(x)]
455
456type Acc2 is Data:
457  Acc2{
458    s1: F32
459    s2: F32
460  }
461
462def ackley_go(xs: List<&2, F32>, +s1: F32, +s2: F32) -> Acc2:
463  match xs:
464    case Nil{}:
465      Acc2{s1, s2}
466    case +h <> t:
467      ackley_go(
468        t,
469        F32.add(s1, F32.square(h)),
470        F32.add(s2, F32.cos(F32.mul(F32.mul(2.0, F32.pi()), h)))
471      )
472
473def ackley_den(n: Nat) -> F32:
474  match n:
475    case 0n:
476      1.0
477    case 1n+p:
478      F32.from_nat(1n+p)
479
480def ackley_from(+n: Nat, acc: Acc2) -> List<&2, F32>:
481  match acc:
482    case Acc2{s1, s2}:
483      +den = ackley_den(n)
484      [F32.add(
485        F32.add(
486          F32.mul(F32.neg(20.0), F32.exp(F32.mul(F32.neg(0.2), F32.sqrt(F32.div(s1, den))))),
487          F32.neg(F32.exp(F32.div(s2, den)))
488        ),
489        F32.add(20.0, F32.exp(1.0))
490      )]
491
492# C# AckleyProblem. n=0 stays total (denominator 1) instead of dividing by zero.
493def ackley_f(+x: List<&2, F32>) -> List<&2, F32>:
494  ackley_from(Util.list_len(F32, x), ackley_go(x, 0.0, 0.0))
495
496def rosen_step(rest: List<&2, F32>, +a: F32) -> F32:
497  match rest:
498    case Nil{}:
499      0.0
500    case +b <> t:
501      +diff = F32.sub(b, F32.square(a))
502      +one = F32.sub(1.0, a)
503      F32.add(F32.add(F32.mul(100.0, F32.square(diff)), F32.square(one)), rosen_step(t, b))
504
505def rosenbrock_f(+x: List<&2, F32>) -> List<&2, F32>:
506  match x:
507    case Nil{}:
508      [0.0]
509    case a <> rest:
510      [rosen_step(rest, a)]
511
512# Objectives plus g ≤ 0. Unconstrained kinds leave g empty.
513type Scored is Data:
514  Scored{
515    f: List<&2, F32>
516    g: List<&2, F32>
517  }
518
519def plain(+f: List<&2, F32>) -> Scored:
520  Scored{f, Nil{}}
521
522def scored_f(s: Scored) -> List<&2, F32>:
523  match s:
524    case Scored{f, g}:
525      f
526
527def scored_g(s: Scored) -> List<&2, F32>:
528  match s:
529    case Scored{f, g}:
530      g
531
532# C# OsyProblem. f0 negates the weighted squares (x5 is not in f0).
533def osy_f(+x: List<&2, F32>) -> List<&2, F32>:
534  +x0 = Util.get_f32(x, 0n)
535  +x1 = Util.get_f32(x, 1n)
536  +x2 = Util.get_f32(x, 2n)
537  +x3 = Util.get_f32(x, 3n)
538  +x4 = Util.get_f32(x, 4n)
539  +pen = F32.add(
540    F32.mul(25.0, F32.square(F32.sub(x0, 2.0))),
541    F32.add(
542      F32.square(F32.sub(x1, 2.0)),
543      F32.add(
544        F32.square(F32.sub(x2, 1.0)),
545        F32.add(F32.square(F32.sub(x3, 4.0)), F32.square(F32.sub(x4, 1.0)))
546      )
547    )
548  )
549  F32.neg(pen) <> [sum_sq(x)]
550
551# pymoo divisors, then negated so a positive value is a violation.
552def osy_g(+x: List<&2, F32>) -> List<&2, F32>:
553  +x0 = Util.get_f32(x, 0n)
554  +x1 = Util.get_f32(x, 1n)
555  +x2 = Util.get_f32(x, 2n)
556  +x3 = Util.get_f32(x, 3n)
557  +x4 = Util.get_f32(x, 4n)
558  +x5 = Util.get_f32(x, 5n)
559  F32.neg(F32.div(F32.sub(F32.add(x0, x1), 2.0), 2.0))
560    <> F32.neg(F32.div(F32.sub(F32.sub(6.0, x0), x1), 6.0))
561    <> F32.neg(F32.div(F32.add(F32.sub(2.0, x1), x0), 2.0))
562    <> F32.neg(F32.div(F32.add(F32.sub(2.0, x0), F32.mul(3.0, x1)), 2.0))
563    <> F32.neg(F32.div(F32.sub(F32.sub(4.0, F32.square(F32.sub(x2, 3.0))), x3), 4.0))
564    <> [F32.neg(F32.div(F32.sub(F32.add(F32.square(F32.sub(x4, 3.0)), x5), 4.0), 4.0))]
565
566def osy_score(+x: List<&2, F32>) -> Scored:
567  Scored{osy_f(x), osy_g(x)}
568
569# C# TnkProblem. g0 = -(x²+y²-1-0.1 cos(16 atan(x/y))). g1 = 2((x-0.5)²+(y-0.5)²)-1.
570def tnk_g(+x: List<&2, F32>) -> List<&2, F32>:
571  +x0 = Util.get_f32(x, 0n)
572  +x1 = Util.get_f32(x, 1n)
573  +circle = F32.sub(
574    F32.sub(F32.add(F32.square(x0), F32.square(x1)), 1.0),
575    F32.mul(0.1, F32.cos(F32.mul(16.0, F32.atan(F32.div(x0, x1)))))
576  )
577  +dx = F32.sub(x0, 0.5)
578  +dy = F32.sub(x1, 0.5)
579  F32.neg(circle) <> [F32.sub(F32.mul(2.0, F32.add(F32.square(dx), F32.square(dy))), 1.0)]
580
581def tnk_score(+x: List<&2, F32>) -> Scored:
582  Scored{Util.get_f32(x, 0n) <> [Util.get_f32(x, 1n)], tnk_g(x)}
583
584# Last objective / 0.6, earlier objectives / 0.5. fuel starts at |f|.
585def c1_w(fuel: Nat, fs: List<&2, F32>, +acc: F32) -> F32:
586  match fuel:
587    case 0n:
588      acc
589    case 1n:
590      match fs:
591        case Nil{}:
592          acc
593        case +h <> rest:
594          F32.add(acc, F32.div(h, 0.6))
595    case 1n+f:
596      match fs:
597        case Nil{}:
598          acc
599        case +h <> rest:
600          c1_w(f, rest, F32.add(acc, F32.div(h, 0.5)))
601
602def c1_g(+fs: List<&2, F32>) -> List<&2, F32>:
603  [F32.sub(c1_w(Util.list_len(F32, fs), fs, 0.0), 1.0)]
604
605def c1_score(+x: List<&2, F32>, +m: Nat) -> Scored:
606  +f = dtlz1_f(x, m)
607  Scored{f, c1_g(f)}
608
609def eval_kind(kind: Family, +x: List<&2, F32>, +m: Nat, +alpha: F32) -> Scored:
610  match kind:
611    case Zdt1{}:
612      plain(zdt1_f(x))
613    case Zdt2{}:
614      plain(zdt2_f(x))
615    case Zdt3{}:
616      plain(zdt3_f(x))
617    case Zdt4{}:
618      plain(zdt4_f(x))
619    case Zdt6{}:
620      plain(zdt6_f(x))
621    case Dtlz1{}:
622      plain(dtlz1_f(x, m))
623    case Dtlz2{}:
624      plain(dtlz2_f(x, m))
625    case Dtlz3{}:
626      plain(dtlz3_f(x, m))
627    case Dtlz4{}:
628      plain(dtlz4_f(x, m, alpha))
629    case Dtlz7{}:
630      plain(dtlz7_f(x, m))
631    case Sphere{}:
632      plain(sphere_f(x))
633    case Ackley{}:
634      plain(ackley_f(x))
635    case Rosenbrock{}:
636      plain(rosenbrock_f(x))
637    case Osy{}:
638      osy_score(x)
639    case Tnk{}:
640      tnk_score(x)
641    case C1Dtlz1{}:
642      c1_score(x, m)
643
644def apply_score(+ind: Ind.Individual, s: Scored) -> Ind.Individual:
645  match s:
646    case Scored{f, g}:
647      Ind.set_constraints(Ind.set_objectives(ind, f), g)
648
649def evaluate(+p: Problem, +ind: Ind.Individual) -> Ind.Individual:
650  apply_score(ind, eval_kind(kind_of(p), Ind.variables(ind), n_obj(p), alpha_of(p)))
651
652# Independent per individual. Mid-split + parallel recurse; same order as cons.
653def evaluate_all(+p: Problem, +xs: List<&2, Ind.Individual>) -> List<&2, Ind.Individual>:
654  Util.map_halves(~Ind.Individual, ~Ind.Individual, ~Problem, ~evaluate, p, xs)