algorithm.bend

unified-nsga-iii@0.2.0.0 · 504 lines · 0 definitions

1# Unsga3Algorithm.Run — C# Unsga3.Algorithm.Unsga3Algorithm.
2# init → evaluate → loop: tournament parents → SBX (η=30, p=1.0) →
3# polynomial mutation (η=20, p=1/n) → evaluate → survival Select
4# with persistent Normalization. Defaults match C# operator defaults.
5# Mating tournament for A/B is PymooCompatible. RankNicheDistance (C#
6# ctor default) is rank → niche count → perpendicular distance → coin.
7# Survival niching in Run threads rng (C# Select(..., rng): random among
8# equal min-count niches). Empty niches
9# take closest; extras are random among near-best on that ray. Uniform
10# inNiche[Next] with this LCG collapsed oracle ZDT2.
11# Duplicate keys round each decision variable to 12 decimal places
12# (round(x*1e12)/1e12). That is not C# ToString("G12") (12 significant
13# digits). Small variables diverge; see src/g12_key.bend.
14# The seen set is the survivor population, then accepted offspring
15# (C# CreateOffspring hashes population.Members). After pop*40 pair
16# attempts, up to max(pop*20, 1) mutations must still be a new key;
17# only then are duplicates accepted.
18# Odd N: the last parent enters SBX, paired with parent 0. Even N
19# pair lookups stay the monotonic cursor (reset to pair 0 once the
20# cursor passes the last in-range start).
21
22import Base
23import unified-nsga-iii@0.2.0.0/individual.bend as Ind
24import unified-nsga-iii@0.2.0.0/non_dominated_sort.bend as NDS
25import unified-nsga-iii@0.2.0.0/normalization.bend as Norm
26import unified-nsga-iii@0.2.0.0/reference_directions.bend as Refs
27import unified-nsga-iii@0.2.0.0/survival.bend as Surv
28import unified-nsga-iii@0.2.0.0/bounds.bend as Bounds
29import unified-nsga-iii@0.2.0.0/rng.bend as Rng
30import unified-nsga-iii@0.2.0.0/sbx.bend as Sbx
31import unified-nsga-iii@0.2.0.0/polynomial_mutation.bend as Poly
32import unified-nsga-iii@0.2.0.0/problems.bend as Prob
33import unified-nsga-iii@0.2.0.0/tournament.bend as Tour
34import unified-nsga-iii@0.2.0.0/util.bend as Util
35
36type Config is Data:
37  MkConfig{
38    dirs: List<&2, List<&2, F32>>
39    pop_size: Nat
40    gens: Nat
41    seed: Nat
42    mode: Tour.Mode
43    elim_dups: Bool
44  }
45
46def mk(+dirs: List<&2, List<&2, F32>>, +pop_size: Nat, +gens: Nat, +seed: Nat, +mode: Tour.Mode, +elim_dups: Bool) -> Config:
47  MkConfig{dirs, pop_size, gens, seed, mode, elim_dups}
48
49def cfg_dirs(c: Config) -> List<&2, List<&2, F32>>:
50  match c:
51    case MkConfig{dirs, pop_size, gens, seed, mode, elim_dups}:
52      dirs
53
54def cfg_pop(c: Config) -> Nat:
55  match c:
56    case MkConfig{dirs, pop_size, gens, seed, mode, elim_dups}:
57      pop_size
58
59def cfg_gens(c: Config) -> Nat:
60  match c:
61    case MkConfig{dirs, pop_size, gens, seed, mode, elim_dups}:
62      gens
63
64def cfg_seed(c: Config) -> Nat:
65  match c:
66    case MkConfig{dirs, pop_size, gens, seed, mode, elim_dups}:
67      seed
68
69def cfg_mode(c: Config) -> Tour.Mode:
70  match c:
71    case MkConfig{dirs, pop_size, gens, seed, mode, elim_dups}:
72      mode
73
74def cfg_elim(c: Config) -> Bool:
75  match c:
76    case MkConfig{dirs, pop_size, gens, seed, mode, elim_dups}:
77      elim_dups
78
79# Smoke (not oracle): ZDT1 n=30, partitions=4 → 5 refs, pop=8, gens=3, seed=1.
80def smoke_zdt1() -> Config:
81  mk(Refs.das_dennis(2n, 4n), 8n, 3n, 1n, Tour.pymoo(), True{})
82
83# C# OracleCompare / EQUIVALENCE-style settings (not the smoke default).
84def oracle_zdt1() -> Config:
85  mk(Refs.das_dennis(2n, 12n), 52n, 100n, 1n, Tour.pymoo(), True{})
86
87def oracle_dtlz2() -> Config:
88  mk(Refs.das_dennis(3n, 12n), 92n, 150n, 1n, Tour.pymoo(), True{})
89
90def get_ind(+pop: List<&2, Ind.Individual>, i: Nat) -> Ind.Individual:
91  Maybe.default(&2, Ind.Individual, List.get(&2, Ind.Individual, pop, i), Ind.from_variables(Nil{}, 1n))
92
93def rand_xs.go(+box: Bounds.Box, fuel: Nat, +i: Nat, rng: Rng.State, acc: List<&2, F32>) -> Rng.State & List<&2, F32>:
94  match fuel:
95    case 0n:
96      (rng, List.reverse(&2, F32, acc))
97    case 1n+f:
98      +iv = Bounds.at(box, i)
99      +rng2 = Rng.next_double(rng)
100      +lo = Bounds.lo(iv)
101      +hi = Bounds.hi(iv)
102      rand_xs.go(
103        box,
104        f,
105        1n+i,
106        rng2,
107        F32.add(lo, F32.mul(F32.sub(hi, lo), Rng.last_double(rng2))) <> acc
108      )
109
110def rand_xs(+box: Bounds.Box, rng: Rng.State) -> Rng.State & List<&2, F32>:
111  rand_xs.go(box, Bounds.n_var(box), 0n, rng, Nil{})
112
113type IHold is Data:
114  IH{
115    rng: Rng.State
116    acc: List<&2, Ind.Individual>
117  }
118
119def init_next.xs(+n_obj: Nat, acc: List<&2, Ind.Individual>, pair: Rng.State & List<&2, F32>) -> IHold:
120  (rng, xs) = pair
121  IH{rng, Ind.from_variables(xs, n_obj) <> acc}
122
123def init_next(+box: Bounds.Box, +n_obj: Nat, hold: IHold) -> IHold:
124  match hold:
125    case IH{rng, acc}:
126      init_next.xs(n_obj, acc, rand_xs(box, rng))
127
128def init_loop(fuel: Nat, hold: IHold, +box: Bounds.Box, +n_obj: Nat) -> Rng.State & List<&2, Ind.Individual>:
129  match fuel hold:
130    case 0n IH{rng, acc}:
131      (rng, List.reverse(&2, Ind.Individual, acc))
132    case 1n+f h:
133      init_loop(f, init_next(box, n_obj, h), box, n_obj)
134
135def init_pop(+prob: Prob.Problem, n: Nat, rng: Rng.State) -> Rng.State & List<&2, Ind.Individual>:
136  init_loop(n, IH{rng, Nil{}}, Prob.box_of(prob), Prob.n_obj(prob))
137
138def vars_of.go(xs: List<&2, Ind.Individual>, acc: List<&2, List<&2, F32>>) -> List<&2, List<&2, F32>>:
139  match xs:
140    case Nil{}:
141      List.reverse(&2, List<&2, F32>, acc)
142    case +ind <> rest:
143      vars_of.go(rest, Util.g12_vec(Ind.variables(ind)) <> acc)
144
145def vars_of(xs: List<&2, Ind.Individual>) -> List<&2, List<&2, F32>>:
146  vars_of.go(xs, Nil{})
147
148def try_add.go(+acc: List<&2, Ind.Individual>, +child: Ind.Individual, +seen: List<&2, List<&2, F32>>, +key: List<&2, F32>, skip: Bool) -> List<&2, Ind.Individual> & List<&2, List<&2, F32>> & Bool:
149  match skip:
150    case True{}:
151      (acc, seen, False{})
152    case False{}:
153      (List.append(&2, Ind.Individual, acc, [child]), key <> seen, True{})
154
155def try_add(+acc: List<&2, Ind.Individual>, +child: Ind.Individual, +seen: List<&2, List<&2, F32>>, elim: Bool) -> List<&2, Ind.Individual> & List<&2, List<&2, F32>> & Bool:
156  +key = Util.g12_vec(Ind.variables(child))
157  try_add.go(acc, child, seen, key, Bool.and(elim, Util.has_f32s(key, seen)))
158
159def pred_or_zero(n: Nat) -> Nat:
160  match n:
161    case 0n:
162      0n
163    case 1n+p:
164      p
165
166def dec_if(n: Nat, hit: Bool) -> Nat:
167  match hit:
168    case True{}:
169      pred_or_zero(n)
170    case False{}:
171      n
172
173# in_range: pair+1 < n → use `pair` (even-N sequence, and odd N before
174# the last parent). last: pair+1 == n → keep `pair` so parent N-1
175# enters SBX. Otherwise reset to 0 (cursor already past the end).
176def wrap_pair.keep(+pair: Nat, last: Bool) -> Nat:
177  match last:
178    case True{}:
179      pair
180    case False{}:
181      0n
182
183def wrap_pair.go(+pair: Nat, in_range: Bool, last: Bool) -> Nat:
184  match in_range:
185    case True{}:
186      pair
187    case False{}:
188      wrap_pair.keep(pair, last)
189
190def wrap_pair(+pair: Nat, +n: Nat) -> Nat:
191  wrap_pair.go(pair, Nat.is_lt(1n+pair, n), Nat.is_eq(1n+pair, n))
192
193# Second parent. In range: pair+1. At the end (odd N, first parent is
194# N-1): parent 0. Even N never asks for a mate past N-1.
195def pair_mate.go(+i: Nat, ok: Bool) -> Nat:
196  match ok:
197    case True{}:
198      i
199    case False{}:
200      0n
201
202def pair_mate(+p0: Nat, +n: Nat) -> Nat:
203  pair_mate.go(1n+p0, Nat.is_lt(1n+p0, n))
204
205# C# fallback cap: max(pop * 20, 1). Unique-key stage before duplicates.
206def dup_fallback_cap(n: Nat) -> Nat:
207  Util.nat_max(Nat.mul(n, 20n), 1n)
208
209type FHold is Data:
210  FH{
211    rng: Rng.State
212    acc: List<&2, Ind.Individual>
213  }
214
215def fallback_next.m(acc: List<&2, Ind.Individual>, pair: Rng.State & Ind.Individual) -> FHold:
216  (rng, extra) = pair
217  FH{rng, List.append(&2, Ind.Individual, acc, [extra])}
218
219def fallback_next(+parents: List<&2, Ind.Individual>, +prob: Prob.Problem, +mut_p: F32, hold: FHold) -> FHold:
220  match hold:
221    case FH{rng, acc}:
222      +n = Util.list_len(Ind.Individual, parents)
223      +rng_i = Rng.next_nat(rng, n)
224      fallback_next.m(acc, Poly.mutate(Poly.default(), get_ind(parents, Rng.last_nat(rng_i)), Prob.box_of(prob), rng_i, mut_p))
225
226def fallback_loop(need: Nat, hold: FHold, +parents: List<&2, Ind.Individual>, +prob: Prob.Problem, +mut_p: F32) -> Rng.State & List<&2, Ind.Individual>:
227  match need hold:
228    case 0n FH{rng, acc}:
229      (rng, acc)
230    case 1n+k h:
231      fallback_loop(k, fallback_next(parents, prob, mut_p, h), parents, prob, mut_p)
232
233def offs_add2.hit(+need1: Nat, trip: List<&2, Ind.Individual> & List<&2, List<&2, F32>> & Bool) -> Nat & List<&2, Ind.Individual> & List<&2, List<&2, F32>>:
234  (acc2, seen2, hit2) = trip
235  (dec_if(need1, hit2), acc2, seen2)
236
237def offs_add2.rest(+need1: Nat, +elim: Bool, +c2: Ind.Individual, acc1: List<&2, Ind.Individual>, seen1: List<&2, List<&2, F32>>, done: Bool) -> Nat & List<&2, Ind.Individual> & List<&2, List<&2, F32>>:
238  match done:
239    case True{}:
240      (0n, acc1, seen1)
241    case False{}:
242      offs_add2.hit(need1, try_add(acc1, c2, seen1, elim))
243
244def offs_add2(
245  +need: Nat,
246  acc: List<&2, Ind.Individual>,
247  seen: List<&2, List<&2, F32>>,
248  +elim: Bool,
249  +c2: Ind.Individual,
250  trip: List<&2, Ind.Individual> & List<&2, List<&2, F32>> & Bool
251) -> Nat & List<&2, Ind.Individual> & List<&2, List<&2, F32>>:
252  (acc1, seen1, hit1) = trip
253  +need1 = dec_if(need, hit1)
254  offs_add2.rest(need1, elim, c2, acc1, seen1, Nat.is_eq(need1, 0n))
255
256def offs_pair.done(rng3: Rng.State, trip: Nat & List<&2, Ind.Individual> & List<&2, List<&2, F32>>) -> Rng.State & Nat & List<&2, Ind.Individual> & List<&2, List<&2, F32>>:
257  (need2, acc2, seen2) = trip
258  (rng3, need2, acc2, seen2)
259
260def offs_pair.m2(+need: Nat, +acc: List<&2, Ind.Individual>, +seen: List<&2, List<&2, F32>>, +elim: Bool, +m1: Ind.Individual, pair: Rng.State & Ind.Individual) -> Rng.State & Nat & List<&2, Ind.Individual> & List<&2, List<&2, F32>>:
261  (rng3, m2) = pair
262  offs_pair.done(rng3, offs_add2(need, acc, seen, elim, m2, try_add(acc, m1, seen, elim)))
263
264def offs_pair.m1(+need: Nat, +acc: List<&2, Ind.Individual>, +seen: List<&2, List<&2, F32>>, +prob: Prob.Problem, +mut_p: F32, +elim: Bool, +c2: Ind.Individual, pair: Rng.State & Ind.Individual) -> Rng.State & Nat & List<&2, Ind.Individual> & List<&2, List<&2, F32>>:
265  (rng2, m1) = pair
266  offs_pair.m2(need, acc, seen, elim, m1, Poly.mutate(Poly.default(), c2, Prob.box_of(prob), rng2, mut_p))
267
268def offs_pair.sbx(+need: Nat, +acc: List<&2, Ind.Individual>, +seen: List<&2, List<&2, F32>>, +prob: Prob.Problem, +mut_p: F32, +elim: Bool, trip: Rng.State & Ind.Individual & Ind.Individual) -> Rng.State & Nat & List<&2, Ind.Individual> & List<&2, List<&2, F32>>:
269  (rng1, c1, c2) = trip
270  offs_pair.m1(need, acc, seen, prob, mut_p, elim, c2, Poly.mutate(Poly.default(), c1, Prob.box_of(prob), rng1, mut_p))
271
272def offs_pair(
273  +parents: List<&2, Ind.Individual>,
274  +pair: Nat,
275  +need: Nat,
276  +acc: List<&2, Ind.Individual>,
277  +seen: List<&2, List<&2, F32>>,
278  rng: Rng.State,
279  +prob: Prob.Problem,
280  +mut_p: F32,
281  +elim: Bool
282) -> Rng.State & Nat & List<&2, Ind.Individual> & List<&2, List<&2, F32>>:
283  +n = Util.list_len(Ind.Individual, parents)
284  +p0 = wrap_pair(pair, n)
285  offs_pair.sbx(
286    need,
287    acc,
288    seen,
289    prob,
290    mut_p,
291    elim,
292    Sbx.crossover(Sbx.default(), get_ind(parents, p0), get_ind(parents, pair_mate(p0, n)), Prob.box_of(prob), rng)
293  )
294
295type OHold is Data:
296  OH{
297    need: Nat
298    pair: Nat
299    rng: Rng.State
300    acc: List<&2, Ind.Individual>
301    seen: List<&2, List<&2, F32>>
302  }
303
304def offs_next.q(+pair0: Nat, quad: Rng.State & Nat & List<&2, Ind.Individual> & List<&2, List<&2, F32>>) -> OHold:
305  (rng, need, acc, seen) = quad
306  OH{need, Nat.add(pair0, 2n), rng, acc, seen}
307
308def offs_next(+parents: List<&2, Ind.Individual>, +prob: Prob.Problem, +mut_p: F32, +elim: Bool, hold: OHold) -> OHold:
309  match hold:
310    case OH{need, +pair, rng, acc, seen}:
311      offs_next.q(pair, offs_pair(parents, pair, need, acc, seen, rng, prob, mut_p, elim))
312
313# Middle loop: mutation that must still be a new key. `need` is the
314# remaining slots. Cap is max(pop*20, 1), counted in `fuel`.
315type UHold is Data:
316  UH{
317    rng: Rng.State
318    acc: List<&2, Ind.Individual>
319    seen: List<&2, List<&2, F32>>
320    need: Nat
321  }
322
323def u_have(hold: UHold) -> Rng.State & List<&2, Ind.Individual>:
324  match hold:
325    case UH{rng, acc, seen, need}:
326      (rng, acc)
327
328def u_fallback(+parents: List<&2, Ind.Individual>, +prob: Prob.Problem, +mut_p: F32, hold: UHold) -> Rng.State & List<&2, Ind.Individual>:
329  match hold:
330    case UH{rng, acc, seen, need}:
331      fallback_loop(need, FH{rng, acc}, parents, prob, mut_p)
332
333def u_step.hold(+rng: Rng.State, +need: Nat, trip: List<&2, Ind.Individual> & List<&2, List<&2, F32>> & Bool) -> UHold:
334  (acc2, seen2, hit) = trip
335  UH{rng, acc2, seen2, dec_if(need, hit)}
336
337def u_step.child(+elim: Bool, +acc: List<&2, Ind.Individual>, +seen: List<&2, List<&2, F32>>, +need: Nat, +rng: Rng.State, +child: Ind.Individual) -> UHold:
338  u_step.hold(rng, need, try_add(acc, child, seen, elim))
339
340def u_step(+elim: Bool, +acc: List<&2, Ind.Individual>, +seen: List<&2, List<&2, F32>>, +need: Nat, pair: Rng.State & Ind.Individual) -> UHold:
341  (rng, child) = pair
342  u_step.child(elim, acc, seen, need, rng, child)
343
344def u_next(+parents: List<&2, Ind.Individual>, +prob: Prob.Problem, +mut_p: F32, +elim: Bool, hold: UHold) -> UHold:
345  match hold:
346    case UH{rng, acc, seen, need}:
347      +n = Util.list_len(Ind.Individual, parents)
348      +rng_i = Rng.next_nat(rng, n)
349      u_step(elim, acc, seen, need, Poly.mutate(Poly.default(), get_ind(parents, Rng.last_nat(rng_i)), Prob.box_of(prob), rng_i, mut_p))
350
351def u_loop(
352  fuel: Nat,
353  hold: UHold,
354  +parents: List<&2, Ind.Individual>,
355  +prob: Prob.Problem,
356  +mut_p: F32,
357  +elim: Bool
358) -> Rng.State & List<&2, Ind.Individual>:
359  match fuel hold:
360    case 0n h:
361      u_fallback(parents, prob, mut_p, h)
362    case 1n+f UH{rng, acc, seen, 0n}:
363      u_have(UH{rng, acc, seen, 0n})
364    case 1n+f UH{rng, acc, seen, 1n+k}:
365      u_loop(f, u_next(parents, prob, mut_p, elim, UH{rng, acc, seen, 1n+k}), parents, prob, mut_p, elim)
366
367def offs_done(+parents: List<&2, Ind.Individual>, +prob: Prob.Problem, +mut_p: F32, +elim: Bool, +n: Nat, hold: OHold) -> Rng.State & List<&2, Ind.Individual>:
368  match hold:
369    case OH{nrest, pair, rng, acc, seen}:
370      u_loop(dup_fallback_cap(n), UH{rng, acc, seen, nrest}, parents, prob, mut_p, elim)
371
372def offs_have(hold: OHold) -> Rng.State & List<&2, Ind.Individual>:
373  match hold:
374    case OH{need, pair, rng, acc, seen}:
375      (rng, acc)
376
377def offs_loop(
378  fuel: Nat,
379  hold: OHold,
380  +parents: List<&2, Ind.Individual>,
381  +prob: Prob.Problem,
382  +mut_p: F32,
383  +elim: Bool,
384  +n: Nat
385) -> Rng.State & List<&2, Ind.Individual>:
386  match fuel hold:
387    case 0n h:
388      offs_done(parents, prob, mut_p, elim, n, h)
389    case 1n+f OH{0n, pair, rng, acc, seen}:
390      offs_have(OH{0n, pair, rng, acc, seen})
391    case 1n+f OH{1n+k, pair, rng, acc, seen}:
392      offs_loop(f, offs_next(parents, prob, mut_p, elim, OH{1n+k, pair, rng, acc, seen}), parents, prob, mut_p, elim, n)
393
394# Hash the survivor population (not the mating sample). An individual
395# who lost every tournament is still a duplicate of P.
396def seen_start(+survivors: List<&2, Ind.Individual>, elim: Bool) -> List<&2, List<&2, F32>>:
397  match elim:
398    case True{}:
399      vars_of(survivors)
400    case False{}:
401      Nil{}
402
403def create_offspring(
404  +prob: Prob.Problem,
405  +survivors: List<&2, Ind.Individual>,
406  +parents: List<&2, Ind.Individual>,
407  +n: Nat,
408  rng: Rng.State,
409  +elim: Bool
410) -> Rng.State & List<&2, Ind.Individual>:
411  offs_loop(
412    Nat.mul(n, 40n),
413    OH{n, 0n, rng, Nil{}, seen_start(survivors, elim)},
414    parents,
415    prob,
416    Poly.typical_probability(Prob.n_var(prob)),
417    elim,
418    n
419  )
420
421def nd_front(+pop: List<&2, Ind.Individual>) -> List<&2, Ind.Individual>:
422  Surv.gather(pop, Surv.front0(NDS.sort(pop)))
423
424def emit_front(xs: List<&2, Ind.Individual>) -> IO(Unit):
425  match xs:
426    case Nil{}:
427      do IO<Unit>:
428        return Unit{}
429    case ind <> rest:
430      do IO<Unit>:
431        IO.print(Util.show_f32s(Ind.objectives(ind)))
432        emit_front(rest)
433
434type GHold is Data:
435  GH{
436    st: Norm.State
437    xs: List<&2, Ind.Individual>
438    rng: Rng.State
439  }
440
441def one_gen.prep(+rng: Rng.State, pair: Norm.State & List<&2, Ind.Individual>) -> GHold:
442  (st, xs) = pair
443  GH{st, xs, rng}
444
445def one_gen.s(+prob: Prob.Problem, +cfg: Config, trip: Norm.State & List<&2, Ind.Individual> & Rng.State) -> GHold:
446  (st, xs, rng) = trip
447  one_gen.prep(rng, Tour.prepare(xs, cfg_dirs(cfg), st))
448
449def one_gen.o(+prob: Prob.Problem, +cfg: Config, +pop: List<&2, Ind.Individual>, +state: Norm.State, trip: Rng.State & List<&2, Ind.Individual>) -> GHold:
450  (rng2, offs) = trip
451  one_gen.s(
452    prob,
453    cfg,
454    Surv.select_st(
455      List.append(&2, Ind.Individual, pop, Prob.evaluate_all(prob, offs)),
456      cfg_pop(cfg),
457      cfg_dirs(cfg),
458      state,
459      rng2
460    )
461  )
462
463def one_gen.p(+prob: Prob.Problem, +cfg: Config, +pop: List<&2, Ind.Individual>, +state: Norm.State, trip: Rng.State & List<&2, Ind.Individual>) -> GHold:
464  (rng1, parents) = trip
465  one_gen.o(prob, cfg, pop, state, create_offspring(prob, pop, parents, cfg_pop(cfg), rng1, cfg_elim(cfg)))
466
467def one_gen(+prob: Prob.Problem, +cfg: Config, hold: GHold) -> GHold:
468  match hold:
469    case GH{st, +xs, rng}:
470      one_gen.p(prob, cfg, xs, st, Tour.select_parents(cfg_mode(cfg), xs, cfg_pop(cfg), rng))
471
472def gen_pop(hold: GHold) -> List<&2, Ind.Individual>:
473  match hold:
474    case GH{st, xs, rng}:
475      xs
476
477def gen_loop(fuel: Nat, hold: GHold, +prob: Prob.Problem, +cfg: Config) -> List<&2, Ind.Individual>:
478  match fuel hold:
479    case 0n h:
480      gen_pop(h)
481    case 1n+g h:
482      gen_loop(g, one_gen(prob, cfg, h), prob, cfg)
483
484def run.after_prep(+prob: Prob.Problem, +cfg: Config, rng: Rng.State, pair: Norm.State & List<&2, Ind.Individual>) -> List<&2, Ind.Individual>:
485  (st1, pop1) = pair
486  gen_loop(cfg_gens(cfg), GH{st1, pop1, rng}, prob, cfg)
487
488def run.go(+prob: Prob.Problem, +cfg: Config, pair: Rng.State & List<&2, Ind.Individual>) -> List<&2, Ind.Individual>:
489  (rng, raw) = pair
490  run.after_prep(prob, cfg, rng, Tour.prepare(Prob.evaluate_all(prob, raw), cfg_dirs(cfg), Norm.new_state(Prob.n_obj(prob))))
491
492def run.sized(+prob: Prob.Problem, +cfg: Config, n: Nat) -> List<&2, Ind.Individual>:
493  match n:
494    case 0n:
495      Nil{}
496    case 1n+p:
497      run.go(prob, cfg, init_pop(prob, 1n+p, Rng.from_nat(cfg_seed(cfg))))
498
499def run(+prob: Prob.Problem, +cfg: Config) -> List<&2, Ind.Individual>:
500  +n = cfg_pop(cfg)
501  Util.list_take_pad(Ind.Individual, Ind.from_objectives(Nil{}), run.sized(prob, cfg, n), n)
502
503def run_zdt1_smoke() -> List<&2, Ind.Individual>:
504  run(Prob.zdt1(30n), smoke_zdt1())