normalization.bend
unified-nsga-iii@0.1.2.0 · 515 lines · 0 definitions
1# Normalization — C# Unsga3.Core.Normalization (v0).
2# Persistent ideal / worst, ASF extreme points (previous extremes ∪ ND,
3# or the whole population when the ND index set is empty), intercept
4# hyperplane nadir via (E−ideal)·b = 1, then C# front/pop/unit fallbacks.
5# ASF: floor 1e-3 on ideal-translated objectives; preferred axis weight 1,
6# other axes 1e-6 (divide form).
7
8import Base
9import unified-nsga-iii@0.1.2.0/individual.bend as Ind
10import unified-nsga-iii@0.1.2.0/util.bend as Util
11
12type State is Data:
13 MkState{
14 n_objectives: Nat
15 ideal: List<&2, F32>
16 worst: List<&2, F32>
17 nadir: List<&2, F32>
18 intercepts: List<&2, F32>
19 extremes: List<&2, List<&2, F32>>
20 }
21
22def new_state(n_objectives: Nat) -> State:
23 MkState{n_objectives, Nil{}, Nil{}, Nil{}, Nil{}, Nil{}}
24
25def state_n(st: State) -> Nat:
26 match st:
27 case MkState{n_objectives, ideal, worst, nadir, intercepts, extremes}:
28 n_objectives
29
30def state_ideal(st: State) -> List<&2, F32>:
31 match st:
32 case MkState{n_objectives, ideal, worst, nadir, intercepts, extremes}:
33 ideal
34
35# Column min/max are associative. Mid-split then vec_min / vec_max.
36def col_min.go(+fuel: Nat, pop: List<&2, Ind.Individual>) -> List<&2, F32>:
37 match fuel pop:
38 case 0n Nil{}:
39 Nil{}
40 case 0n ind <> xs:
41 Util.vec_min(Ind.objectives(ind), col_min.go(0n, xs))
42 case 1n+p Nil{}:
43 Nil{}
44 case 1n+p ind <> Nil{}:
45 Ind.objectives(ind)
46 case 1n+p h <> t:
47 +all = {h <> t : List<&2, Ind.Individual>}
48 +mid = Nat.div(Util.list_len(Ind.Individual, all), 2n)
49 lo hi = col_min.go(p, List.take(&2, Ind.Individual, all, mid)) col_min.go(p, List.drop(&2, Ind.Individual, all, mid))
50 Util.vec_min(lo, hi)
51
52def col_min(+pop: List<&2, Ind.Individual>) -> List<&2, F32>:
53 col_min.go(Util.list_len(Ind.Individual, pop), pop)
54
55def col_max.go(+fuel: Nat, pop: List<&2, Ind.Individual>) -> List<&2, F32>:
56 match fuel pop:
57 case 0n Nil{}:
58 Nil{}
59 case 0n ind <> xs:
60 Util.vec_max(Ind.objectives(ind), col_max.go(0n, xs))
61 case 1n+p Nil{}:
62 Nil{}
63 case 1n+p ind <> Nil{}:
64 Ind.objectives(ind)
65 case 1n+p h <> t:
66 +all = {h <> t : List<&2, Ind.Individual>}
67 +mid = Nat.div(Util.list_len(Ind.Individual, all), 2n)
68 lo hi = col_max.go(p, List.take(&2, Ind.Individual, all, mid)) col_max.go(p, List.drop(&2, Ind.Individual, all, mid))
69 Util.vec_max(lo, hi)
70
71def col_max(+pop: List<&2, Ind.Individual>) -> List<&2, F32>:
72 col_max.go(Util.list_len(Ind.Individual, pop), pop)
73
74def get_ind(+pop: List<&2, Ind.Individual>, i: Nat) -> Maybe<&2, Ind.Individual>:
75 List.get(&2, Ind.Individual, pop, i)
76
77def max_at.go(acc: List<&2, F32>, m: Maybe<&2, Ind.Individual>) -> List<&2, F32>:
78 match m:
79 case None{}:
80 acc
81 case Some{ind}:
82 Util.vec_max(acc, Ind.objectives(ind))
83
84def max_at(+pop: List<&2, Ind.Individual>, acc: List<&2, F32>, i: Nat) -> List<&2, F32>:
85 max_at.go(acc, get_ind(pop, i))
86
87def col_max_idx.go(+pop: List<&2, Ind.Individual>, +fuel: Nat, idxs: List<&2, Nat>) -> List<&2, F32>:
88 match fuel idxs:
89 case 0n Nil{}:
90 Nil{}
91 case 0n +i <> rest:
92 max_at(pop, col_max_idx.go(pop, 0n, rest), i)
93 case 1n+p Nil{}:
94 Nil{}
95 case 1n+p +i <> Nil{}:
96 max_at(pop, Nil{}, i)
97 case 1n+p h <> t:
98 +all = {h <> t : List<&2, Nat>}
99 +mid = Nat.div(Util.list_len(Nat, all), 2n)
100 lo hi = col_max_idx.go(pop, p, List.take(&2, Nat, all, mid)) col_max_idx.go(pop, p, List.drop(&2, Nat, all, mid))
101 Util.vec_max(lo, hi)
102
103def col_max_idx(+pop: List<&2, Ind.Individual>, idxs: List<&2, Nat>) -> List<&2, F32>:
104 match idxs:
105 case Nil{}:
106 col_max(pop)
107 case +i <> +rest:
108 +xs = {i <> rest : List<&2, Nat>}
109 col_max_idx.go(pop, Util.list_len(Nat, xs), xs)
110
111def merge_ideal(prev: List<&2, F32>, cur: List<&2, F32>) -> List<&2, F32>:
112 match prev:
113 case Nil{}:
114 cur
115 case x <> xs:
116 Util.vec_min(x <> xs, cur)
117
118def merge_worst(prev: List<&2, F32>, cur: List<&2, F32>) -> List<&2, F32>:
119 match prev:
120 case Nil{}:
121 cur
122 case x <> xs:
123 Util.vec_max(x <> xs, cur)
124
125def nd_or_all(nd: List<&2, Nat>, n: Nat) -> List<&2, Nat>:
126 match nd:
127 case Nil{}:
128 List.range(n)
129 case h <> t:
130 h <> t
131
132def obj_at.go(m: Maybe<&2, Ind.Individual>) -> List<&2, F32>:
133 match m:
134 case None{}:
135 Nil{}
136 case Some{ind}:
137 Ind.objectives(ind)
138
139def obj_at(+pop: List<&2, Ind.Individual>, i: Nat) -> List<&2, F32>:
140 obj_at.go(get_ind(pop, i))
141
142def gather_objs.one(+pop: List<&2, Ind.Individual>, +i: Nat) -> List<&2, F32>:
143 obj_at(pop, i)
144
145def gather_objs(+pop: List<&2, Ind.Individual>, +idxs: List<&2, Nat>) -> List<&2, List<&2, F32>>:
146 Util.map_halves(~Nat, ~List<&2, F32>, ~List<&2, Ind.Individual>, ~gather_objs.one, pop, idxs)
147
148def map_norm.one(+ideal: List<&2, F32>, +nadir: List<&2, F32>, +ind: Ind.Individual) -> List<&2, F32>:
149 Util.vec_norm_div(Ind.objectives(ind), ideal, nadir)
150
151# Independent per individual given ideal/nadir. Mid-split + parallel recurse.
152def map_norm.go(+ideal: List<&2, F32>, +nadir: List<&2, F32>, +fuel: Nat, pop: List<&2, Ind.Individual>) -> List<&2, List<&2, F32>>:
153 match fuel pop:
154 case 0n Nil{}:
155 Nil{}
156 case 0n h <> t:
157 map_norm.one(ideal, nadir, h) <> map_norm.go(ideal, nadir, 0n, t)
158 case 1n+p Nil{}:
159 Nil{}
160 case 1n+p h <> Nil{}:
161 map_norm.one(ideal, nadir, h) <> Nil{}
162 case 1n+p h <> t:
163 +all = {h <> t : List<&2, Ind.Individual>}
164 +mid = Nat.div(Util.list_len(Ind.Individual, all), 2n)
165 lo hi = map_norm.go(ideal, nadir, p, List.take(&2, Ind.Individual, all, mid)) map_norm.go(ideal, nadir, p, List.drop(&2, Ind.Individual, all, mid))
166 List.append(&2, List<&2, F32>, lo, hi)
167
168def map_norm(+ideal: List<&2, F32>, +nadir: List<&2, F32>, +pop: List<&2, Ind.Individual>) -> List<&2, List<&2, F32>>:
169 map_norm.go(ideal, nadir, Util.list_len(Ind.Individual, pop), pop)
170
171def n_obj_pop(pop: List<&2, Ind.Individual>) -> Nat:
172 match pop:
173 case Nil{}:
174 0n
175 case ind <> rest:
176 Ind.n_obj(ind)
177
178# ASF on ideal-translated objectives (pymoo __F = F − ideal).
179def asf_term(+fj: F32, +ij: F32, on_axis: Bool) -> F32:
180 +raw = F32.sub(fj, ij)
181 +v = Bool.pick(F32, F32.is_lt(raw, 1.0e-3), 0.0, raw)
182 +w = Bool.pick(F32, on_axis, 1.0, 1.0e-6)
183 F32.div(v, w)
184
185def asf.go(f: List<&2, F32>, ideal: List<&2, F32>, +axis: Nat, +j: Nat, acc: F32) -> F32:
186 match f ideal:
187 case Nil{} ys:
188 acc
189 case xs Nil{}:
190 acc
191 case x <> xs y <> ys:
192 asf.go(xs, ys, axis, 1n+j, F32.max(acc, asf_term(x, y, Nat.is_eq(j, axis))))
193
194def asf(f: List<&2, F32>, +axis: Nat, +ideal: List<&2, F32>) -> F32:
195 match f ideal:
196 case Nil{} ys:
197 0.0
198 case xs Nil{}:
199 0.0
200 case x <> xs y <> ys:
201 asf.go(xs, ys, axis, 1n, asf_term(x, y, Nat.is_eq(0n, axis)))
202
203def pick_axis.go(rest: List<&2, List<&2, F32>>, +ideal: List<&2, F32>, +axis: Nat, +best_f: List<&2, F32>, +best_asf: F32) -> List<&2, F32>:
204 match rest:
205 case Nil{}:
206 best_f
207 case +f <> fs:
208 +a = asf(f, axis, ideal)
209 pick_axis.go(
210 fs,
211 ideal,
212 axis,
213 Bool.pick(List<&2, F32>, F32.is_lt(a, best_asf), f, best_f),
214 Bool.pick(F32, F32.is_lt(a, best_asf), a, best_asf)
215 )
216
217def pick_axis(cand: List<&2, List<&2, F32>>, +ideal: List<&2, F32>, +axis: Nat) -> List<&2, F32>:
218 match cand:
219 case Nil{}:
220 Nil{}
221 case +h <> t:
222 pick_axis.go(t, ideal, axis, h, asf(h, axis, ideal))
223
224type ExtCtx is Data:
225 ExtCtx{
226 cand: List<&2, List<&2, F32>>
227 ideal: List<&2, F32>
228 }
229
230def pick_axis.one(+ctx: ExtCtx, +axis: Nat) -> List<&2, F32>:
231 match ctx:
232 case ExtCtx{cand, ideal}:
233 pick_axis(cand, ideal, axis)
234
235# One ASF extreme per axis; axes are independent.
236def pick_extremes(+cand: List<&2, List<&2, F32>>, +ideal: List<&2, F32>, m: Nat) -> List<&2, List<&2, F32>>:
237 Util.map_halves(~Nat, ~List<&2, F32>, ~ExtCtx, ~pick_axis.one, ExtCtx{cand, ideal}, List.range(m))
238
239def get_row(+mat: List<&2, List<&2, F32>>, i: Nat) -> List<&2, F32>:
240 Maybe.default(&2, List<&2, F32>, List.get(&2, List<&2, F32>, mat, i), Nil{})
241
242def mat_get(+mat: List<&2, List<&2, F32>>, i: Nat, j: Nat) -> F32:
243 Util.get_f32(get_row(mat, i), j)
244
245def swap_rows.do(+mat: List<&2, List<&2, F32>>, +i: Nat, +k: Nat) -> List<&2, List<&2, F32>>:
246 +ri = get_row(mat, i)
247 +rk = get_row(mat, k)
248 List.set(&2, List<&2, F32>, List.set(&2, List<&2, F32>, mat, i, rk), k, ri)
249
250def swap_rows.when(+mat: List<&2, List<&2, F32>>, +i: Nat, +k: Nat, same: Bool) -> List<&2, List<&2, F32>>:
251 match same:
252 case True{}:
253 mat
254 case False{}:
255 swap_rows.do(mat, i, k)
256
257def swap_rows(+mat: List<&2, List<&2, F32>>, +i: Nat, +k: Nat) -> List<&2, List<&2, F32>>:
258 swap_rows.when(mat, i, k, Nat.is_eq(i, k))
259
260def scale_vec.go(fuel: Nat, +row: List<&2, F32>, +j: Nat, +div: F32) -> List<&2, F32>:
261 match fuel:
262 case 0n:
263 row
264 case 1n+f:
265 scale_vec.go(f, Util.set_f32(row, j, F32.div(Util.get_f32(row, j), div)), 1n+j, div)
266
267def scale_row(+mat: List<&2, List<&2, F32>>, +col: Nat, +n: Nat, +div: F32) -> List<&2, List<&2, F32>>:
268 List.set(&2, List<&2, F32>, mat, col, scale_vec.go(1n+Nat.sub(n, col), get_row(mat, col), col, div))
269
270def axpy_vec.go(fuel: Nat, +row: List<&2, F32>, +piv: List<&2, F32>, +j: Nat, +factor: F32) -> List<&2, F32>:
271 match fuel:
272 case 0n:
273 row
274 case 1n+f:
275 +x = Util.get_f32(row, j)
276 +p = Util.get_f32(piv, j)
277 axpy_vec.go(f, Util.set_f32(row, j, F32.sub(x, F32.mul(factor, p))), piv, 1n+j, factor)
278
279def axpy_row(+mat: List<&2, List<&2, F32>>, +row: Nat, +col: Nat, +n: Nat, +factor: F32, +piv: List<&2, F32>) -> List<&2, List<&2, F32>>:
280 List.set(&2, List<&2, F32>, mat, row, axpy_vec.go(1n+Nat.sub(n, col), get_row(mat, row), piv, col, factor))
281
282def find_pivot.go(fuel: Nat, +mat: List<&2, List<&2, F32>>, +col: Nat, +row: Nat, +best_row: Nat, +best: F32) -> Nat:
283 match fuel:
284 case 0n:
285 best_row
286 case 1n+f:
287 +v = F32.abs(mat_get(mat, row, col))
288 find_pivot.go(
289 f,
290 mat,
291 col,
292 1n+row,
293 Bool.pick(Nat, F32.is_gt(v, best), row, best_row),
294 F32.max(v, best)
295 )
296
297def find_pivot(+mat: List<&2, List<&2, F32>>, +col: Nat, +n: Nat) -> Nat:
298 find_pivot.go(Nat.sub(n, col), mat, col, col, col, F32.abs(mat_get(mat, col, col)))
299
300def elim_one(+row: Nat, +col: Nat, +n: Nat, +piv: List<&2, F32>, +mat: List<&2, List<&2, F32>>, skip: Bool) -> List<&2, List<&2, F32>>:
301 match skip:
302 case True{}:
303 mat
304 case False{}:
305 axpy_row(mat, row, col, n, mat_get(mat, row, col), piv)
306
307def elim_rows(fuel: Nat, +row: Nat, +col: Nat, +n: Nat, +piv: List<&2, F32>, mat: List<&2, List<&2, F32>>) -> List<&2, List<&2, F32>>:
308 match fuel:
309 case 0n:
310 mat
311 case 1n+f:
312 elim_rows(f, 1n+row, col, n, piv, elim_one(row, col, n, piv, mat, Nat.is_eq(row, col)))
313
314def ge_step(+col: Nat, +n: Nat, +mat: List<&2, List<&2, F32>>, +pivot: Nat, tiny: Bool) -> Maybe<&2, List<&2, List<&2, F32>>>:
315 match tiny:
316 case True{}:
317 None{}
318 case False{}:
319 +swapped = swap_rows(mat, col, pivot)
320 +div = mat_get(swapped, col, col)
321 +scaled = scale_row(swapped, col, n, div)
322 Some{elim_rows(n, 0n, col, n, get_row(scaled, col), scaled)}
323
324def ge_col.go(fuel: Nat, +col: Nat, +n: Nat, acc: Maybe<&2, List<&2, List<&2, F32>>>) -> Maybe<&2, List<&2, List<&2, F32>>>:
325 match fuel acc:
326 case f None{}:
327 None{}
328 case 0n Some{mat}:
329 Some{mat}
330 case 1n+f Some{+mat}:
331 +pivot = find_pivot(mat, col, n)
332 ge_col.go(
333 f,
334 1n+col,
335 n,
336 ge_step(col, n, mat, pivot, F32.is_lt(F32.abs(mat_get(mat, pivot, col)), 1.0e-14))
337 )
338
339def ge_col(fuel: Nat, +col: Nat, +n: Nat, mat: List<&2, List<&2, F32>>) -> Maybe<&2, List<&2, List<&2, F32>>>:
340 ge_col.go(fuel, col, n, Some{mat})
341
342def extract_x.go(fuel: Nat, +mat: List<&2, List<&2, F32>>, +n: Nat, +i: Nat, acc: List<&2, F32>) -> List<&2, F32>:
343 match fuel:
344 case 0n:
345 acc
346 case 1n+f:
347 extract_x.go(f, mat, n, 1n+i, List.append(&2, F32, acc, [mat_get(mat, i, n)]))
348
349def extract_x(+mat: List<&2, List<&2, F32>>, +n: Nat) -> List<&2, F32>:
350 extract_x.go(n, mat, n, 0n, Nil{})
351
352def solve_linear.go(done: Maybe<&2, List<&2, List<&2, F32>>>, +n: Nat) -> Maybe<&2, List<&2, F32>>:
353 match done:
354 case None{}:
355 None{}
356 case Some{mat}:
357 Some{extract_x(mat, n)}
358
359def build_aug(+ideal: List<&2, F32>, ext: List<&2, List<&2, F32>>) -> List<&2, List<&2, F32>>:
360 match ext:
361 case Nil{}:
362 Nil{}
363 case row <> rest:
364 List.append(&2, F32, Util.vec_sub(row, ideal), [1.0]) <> build_aug(ideal, rest)
365
366def solve_linear(+ext: List<&2, List<&2, F32>>, +ideal: List<&2, F32>, +n: Nat) -> Maybe<&2, List<&2, F32>>:
367 solve_linear.go(ge_col(n, 0n, n, build_aug(ideal, ext)), n)
368
369def intercept_bad(+inv: F32) -> Bool:
370 Bool.or(
371 F32.is_ne(inv, inv),
372 Bool.or(F32.is_le(inv, 1.0e-6), F32.is_ne(F32.mul(inv, 0.0), 0.0))
373 )
374
375def inv_ok.fin(+inv: F32, bad: Bool) -> Maybe<&2, F32>:
376 match bad:
377 case True{}:
378 None{}
379 case False{}:
380 Some{inv}
381
382def inv_ok.pos(+xj: F32, tiny: Bool) -> Maybe<&2, F32>:
383 match tiny:
384 case True{}:
385 None{}
386 case False{}:
387 +inv = F32.div(1.0, xj)
388 inv_ok.fin(inv, intercept_bad(inv))
389
390# C#: |x| < 1e-12 or x < 0 fails; then intercept = 1/x must be finite and > 1e-6.
391def inv_ok(+xj: F32) -> Maybe<&2, F32>:
392 inv_ok.pos(xj, Bool.or(F32.is_lt(F32.abs(xj), 1.0e-12), F32.is_lt(xj, 0.0)))
393
394def intercepts_from_x.one(h: Maybe<&2, F32>, ys: List<&2, F32>) -> Maybe<&2, List<&2, F32>>:
395 match h:
396 case None{}:
397 None{}
398 case Some{inv}:
399 Some{inv <> ys}
400
401def intercepts_from_x.cons(+xj: F32, rest: Maybe<&2, List<&2, F32>>) -> Maybe<&2, List<&2, F32>>:
402 match rest:
403 case None{}:
404 None{}
405 case Some{ys}:
406 intercepts_from_x.one(inv_ok(xj), ys)
407
408def intercepts_from_x(x: List<&2, F32>) -> Maybe<&2, List<&2, F32>>:
409 match x:
410 case Nil{}:
411 Some{Nil{}}
412 case xj <> xs:
413 intercepts_from_x.cons(xj, intercepts_from_x(xs))
414
415def try_intercepts.go(x: Maybe<&2, List<&2, F32>>) -> Maybe<&2, List<&2, F32>>:
416 match x:
417 case None{}:
418 None{}
419 case Some{xs}:
420 intercepts_from_x(xs)
421
422def try_intercepts(+ext: List<&2, List<&2, F32>>, +ideal: List<&2, F32>) -> Maybe<&2, List<&2, F32>>:
423 try_intercepts.go(solve_linear(ext, ideal, Util.list_len(F32, ideal)))
424
425def add_ideal(ideal: List<&2, F32>, icepts: List<&2, F32>) -> List<&2, F32>:
426 match ideal icepts:
427 case Nil{} zs:
428 Nil{}
429 case xs Nil{}:
430 Nil{}
431 case x <> xs z <> zs:
432 F32.add(x, z) <> add_ideal(xs, zs)
433
434def clamp_worst(nadir: List<&2, F32>, worst: List<&2, F32>) -> List<&2, F32>:
435 match nadir worst:
436 case Nil{} ws:
437 Nil{}
438 case ns Nil{}:
439 ns
440 case +n <> ns +w <> ws:
441 Bool.pick(F32, F32.is_gt(n, w), w, n) <> clamp_worst(ns, ws)
442
443def nadir_from_maybe(m: Maybe<&2, List<&2, F32>>, +ideal: List<&2, F32>, +worst: List<&2, F32>, +front: List<&2, F32>) -> List<&2, F32>:
444 match m:
445 case None{}:
446 front
447 case Some{icepts}:
448 clamp_worst(add_ideal(ideal, icepts), worst)
449
450def fallback_pop(nadir: List<&2, F32>, ideal: List<&2, F32>, wpop: List<&2, F32>) -> List<&2, F32>:
451 match nadir ideal wpop:
452 case Nil{} ils wps:
453 Nil{}
454 case ns Nil{} wps:
455 ns
456 case ns ils Nil{}:
457 ns
458 case +n <> ns +i <> ils +w <> wps:
459 Bool.pick(F32, F32.is_le(F32.sub(n, i), 1.0e-6), w, n) <> fallback_pop(ns, ils, wps)
460
461def fallback_unit(nadir: List<&2, F32>, ideal: List<&2, F32>) -> List<&2, F32>:
462 match nadir ideal:
463 case Nil{} ils:
464 Nil{}
465 case ns Nil{}:
466 ns
467 case +n <> ns +i <> ils:
468 Bool.pick(F32, F32.is_le(F32.sub(n, i), 1.0e-6), F32.add(i, 1.0), n) <> fallback_unit(ns, ils)
469
470def spans_or_one(nadir: List<&2, F32>, ideal: List<&2, F32>) -> List<&2, F32>:
471 match nadir ideal:
472 case Nil{} ils:
473 Nil{}
474 case ns Nil{}:
475 Nil{}
476 case +n <> ns +i <> ils:
477 +span = F32.sub(n, i)
478 Bool.pick(F32, F32.is_le(span, 1.0e-12), 1.0, span) <> spans_or_one(ns, ils)
479
480def normalize_nd.body(+ideal0: List<&2, F32>, +worst0: List<&2, F32>, +ext0: List<&2, List<&2, F32>>, +pop: List<&2, Ind.Individual>, nd: List<&2, Nat>) -> State & List<&2, List<&2, F32>>:
481 +mn = col_min(pop)
482 +mx = col_max(pop)
483 +ideal = merge_ideal(ideal0, mn)
484 +worst = merge_worst(worst0, mx)
485 +nd_use = nd_or_all(nd, Util.list_len(Ind.Individual, pop))
486 +front = col_max_idx(pop, nd_use)
487 +cand = List.append(&2, List<&2, F32>, ext0, gather_objs(pop, nd_use))
488 +m = n_obj_pop(pop)
489 +ext = pick_extremes(cand, ideal, m)
490 +nadir0 = nadir_from_maybe(try_intercepts(ext, ideal), ideal, worst, front)
491 +nadir1 = fallback_pop(nadir0, ideal, mx)
492 +nadir = fallback_unit(nadir1, ideal)
493 (MkState{m, ideal, worst, nadir, spans_or_one(nadir, ideal), ext}, map_norm(ideal, nadir, pop))
494
495def normalize_nd.go(state: State, +pop: List<&2, Ind.Individual>, nd: List<&2, Nat>) -> State & List<&2, List<&2, F32>>:
496 match state:
497 case MkState{n_objectives, ideal, worst, nadir, intercepts, extremes}:
498 normalize_nd.body(ideal, worst, extremes, pop, nd)
499
500def normalize_nd(state: State, +pop: List<&2, Ind.Individual>, nd: List<&2, Nat>) -> State & List<&2, List<&2, F32>>:
501 match pop:
502 case Nil{}:
503 (state, Nil{})
504 case h <> t:
505 normalize_nd.go(state, h <> t, nd)
506
507def normalize(state: State, pop: List<&2, Ind.Individual>) -> State & List<&2, List<&2, F32>>:
508 normalize_nd(state, pop, Nil{})
509
510def normalize_vecs.snd(pair: State & List<&2, List<&2, F32>>) -> List<&2, List<&2, F32>>:
511 (state, vecs) = pair
512 vecs
513
514def normalize_vecs(+xs: List<&2, Ind.Individual>) -> List<&2, List<&2, F32>>:
515 normalize_vecs.snd(normalize(new_state(n_obj_pop(xs)), xs))