polynomial_mutation.bend
unified-nsga-iii@0.1.2.0 · 187 lines · 0 definitions
1# Polynomial mutation — C# Unsga3.Operators.Mutation.PolynomialMutation.
2# Default η = 20. Probability per variable is an argument (C# Run uses 1/n).
3# Walks the decision vector and box intervals together (no List.get /
4# set_var_at per index). Same RNG order as the old indexed loop.
5
6import Base
7import unified-nsga-iii@0.1.2.0/individual.bend as Ind
8import unified-nsga-iii@0.1.2.0/bounds.bend as Bounds
9import unified-nsga-iii@0.1.2.0/rng.bend as Rng
10import unified-nsga-iii@0.1.2.0/util.bend as Util
11
12type Poly is Data:
13 MkPoly{
14 e: F32
15 }
16
17def mk(+e: F32) -> Poly:
18 MkPoly{e}
19
20def default() -> Poly:
21 MkPoly{20.0}
22
23def eta(op: Poly) -> F32:
24 match op:
25 case MkPoly{e}:
26 e
27
28# C# Unsga3Algorithm: mutationProbability ?? (1.0 / nVars) at the call site.
29def typical_probability(n_var: Nat) -> F32:
30 match n_var:
31 case 0n:
32 1.0
33 case 1n+p:
34 F32.div(1.0, F32.from_nat(1n+p))
35
36def result(pair: Rng.State & Ind.Individual) -> Ind.Individual:
37 (rng, ind) = pair
38 ind
39
40def deltaq.lo(+eta: F32, +mut_pow: F32, +delta1: F32, +rand: F32) -> F32:
41 +xy = F32.sub(1.0, delta1)
42 +val = F32.add(
43 F32.mul(2.0, rand),
44 F32.mul(F32.sub(1.0, F32.mul(2.0, rand)), F32.pow(xy, F32.add(eta, 1.0)))
45 )
46 F32.sub(F32.pow(val, mut_pow), 1.0)
47
48def deltaq.hi(+eta: F32, +mut_pow: F32, +delta2: F32, +rand: F32) -> F32:
49 +xy = F32.sub(1.0, delta2)
50 +val = F32.add(
51 F32.mul(2.0, F32.sub(1.0, rand)),
52 F32.mul(F32.mul(2.0, F32.sub(rand, 0.5)), F32.pow(xy, F32.add(eta, 1.0)))
53 )
54 F32.sub(1.0, F32.pow(val, mut_pow))
55
56def deltaq.pick(+eta: F32, +mut_pow: F32, +delta1: F32, +delta2: F32, +rand: F32, lo_side: Bool) -> F32:
57 match lo_side:
58 case True{}:
59 deltaq.lo(eta, mut_pow, delta1, rand)
60 case False{}:
61 deltaq.hi(eta, mut_pow, delta2, rand)
62
63def deltaq(+eta: F32, +mut_pow: F32, +delta1: F32, +delta2: F32, +rand: F32) -> F32:
64 deltaq.pick(eta, mut_pow, delta1, delta2, rand, F32.is_lt(rand, 0.5))
65
66def tiny_head(ivs: List<&2, Bounds.Interval>) -> Bool:
67 match ivs:
68 case Nil{}:
69 False{}
70 case +iv <> rest:
71 F32.is_lt(F32.sub(Bounds.hi(iv), Bounds.lo(iv)), 1.0e-14)
72
73def advance(rng: Rng.State, rest: Nat) -> Rng.State:
74 match rest:
75 case 0n:
76 rng
77 case 1n+g:
78 Rng.next_double(rng)
79
80def skip_kind(skip: Bool, tiny: Bool) -> Nat:
81 match skip:
82 case True{}:
83 0n
84 case False{}:
85 match tiny:
86 case True{}:
87 0n
88 case False{}:
89 1n
90
91def finish(+m: Nat, pair: Rng.State & List<&2, F32>) -> Rng.State & Ind.Individual:
92 (rng, xs) = pair
93 (rng, Ind.from_variables(xs, m))
94
95def done(rng: Rng.State, acc: List<&2, F32>) -> Rng.State & List<&2, F32>:
96 (rng, List.reverse(&2, F32, acc))
97
98# Kind: 0n = copy the head (skip or tiny span), 1n = mutate the head.
99def loop.go(
100 fuel: Nat,
101 kind: Nat,
102 xs: List<&2, F32>,
103 ivs: List<&2, Bounds.Interval>,
104 +eta: F32,
105 rng: Rng.State,
106 acc: List<&2, F32>,
107 +p: F32
108) -> Rng.State & List<&2, F32>:
109 match fuel kind xs ivs:
110 case 0n k a b:
111 done(rng, acc)
112 case 1n++f 0n +x <> rest +iv <> +ivrest:
113 +rng2 = advance(rng, f)
114 loop.go(
115 f,
116 skip_kind(F32.is_gt(Rng.last_double(rng2), p), tiny_head(ivrest)),
117 rest, ivrest, eta, rng2, x <> acc, p
118 )
119 case 1n++f 1n+q +x <> rest +iv <> +ivrest:
120 +lo = Bounds.lo(iv)
121 +hi = Bounds.hi(iv)
122 +dmax = F32.sub(hi, lo)
123 +rng_a = Rng.next_double(rng)
124 +rand = Rng.last_double(rng_a)
125 +mut_pow = F32.div(1.0, F32.add(eta, 1.0))
126 +dq = deltaq(eta, mut_pow, F32.div(F32.sub(x, lo), dmax), F32.div(F32.sub(hi, x), dmax), rand)
127 +y = F32.clamp(F32.add(x, F32.mul(dq, dmax)), lo, hi)
128 +rng_b = advance(rng_a, f)
129 loop.go(
130 f,
131 skip_kind(F32.is_gt(Rng.last_double(rng_b), p), tiny_head(ivrest)),
132 rest, ivrest, eta, rng_b, y <> acc, p
133 )
134 case 1n++f k Nil{} b:
135 done(rng, acc)
136 case 1n++f k a <> as Nil{}:
137 done(rng, acc)
138
139def start(
140 +eta: F32,
141 +box: Bounds.Box,
142 rng: Rng.State,
143 +ind: Ind.Individual,
144 +p: F32,
145 n: Nat
146) -> Rng.State & Ind.Individual:
147 match n:
148 case 0n:
149 (rng, ind)
150 case 1n+f:
151 +rng2 = Rng.next_double(rng)
152 +xs = Ind.variables(ind)
153 +ivs = Bounds.intervals_of(box)
154 finish(
155 Ind.n_obj(ind),
156 loop.go(
157 1n+f,
158 skip_kind(F32.is_gt(Rng.last_double(rng2), p), tiny_head(ivs)),
159 xs, ivs, eta, rng2, Nil{}, p
160 )
161 )
162
163# p=0 leaves the decision vector unchanged before any NextDouble.
164# Matches C# when per-variable probability is 0 (every draw is > 0).
165def start_or_copy(
166 +eta: F32,
167 +box: Bounds.Box,
168 rng: Rng.State,
169 ind: Ind.Individual,
170 +p: F32,
171 n: Nat,
172 zero: Bool
173) -> Rng.State & Ind.Individual:
174 match zero:
175 case True{}:
176 (rng, ind)
177 case False{}:
178 start(eta, box, rng, ind, p, n)
179
180def mutate(
181 +op: Poly,
182 +ind: Ind.Individual,
183 +box: Bounds.Box,
184 rng: Rng.State,
185 +p: F32
186) -> Rng.State & Ind.Individual:
187 start_or_copy(eta(op), box, rng, ind, p, Ind.n_var(ind), Util.f32_is_zero(p))