~/bend-docscommunity

sbx.bend source

sbx.bend on the hub · documented module

# Simulated Binary Crossover — C# Unsga3.Operators.Crossover.SimulatedBinaryCrossover.# Defaults match C#: η = 30, pair probability = 1.0 (Deb & Agrawal).# Per-variable walk peels x1 / x2 / box intervals together (guide: the# list is the shape a flat loop walks). Same RNG order as the old# index + List.get / set_var_at loop; children are built once at the end.import Baseimport ./individual.bend as Indimport ./bounds.bend as Boundsimport ./rng.bend as Rngimport ./util.bend as Utiltype Sbx is Data:  MkSbx{    e: F32    p: F32  }def mk(+e: F32, +p: F32) -> Sbx:  MkSbx{e, p}def default() -> Sbx:  MkSbx{30.0, 1.0}def eta(op: Sbx) -> F32:  match op:    case MkSbx{e, p}:      edef probability(op: Sbx) -> F32:  match op:    case MkSbx{e, p}:      pdef child1(trip: Rng.State & Ind.Individual & Ind.Individual) -> Ind.Individual:  (rng, c1, c2) = trip  c1def child2(trip: Rng.State & Ind.Individual & Ind.Individual) -> Ind.Individual:  (rng, c1, c2) = trip  c2def near(+x1: F32, +x2: F32) -> Bool:  F32.is_lt(F32.abs(F32.sub(x1, x2)), 1.0e-14)# C#: if rand <= 1/alpha then (rand*alpha)^(1/(η+1)) else (1/(2-rand*alpha))^(1/(η+1)).def beta.pick(+rand: F32, +alpha: F32, +inv: F32, lo: Bool) -> F32:  match lo:    case True{}:      F32.pow(F32.mul(rand, alpha), inv)    case False{}:      F32.pow(F32.div(1.0, F32.sub(2.0, F32.mul(rand, alpha))), inv)def beta(+rand: F32, +alpha: F32, +inv: F32) -> F32:  beta.pick(rand, alpha, inv, F32.is_le(rand, F32.div(1.0, alpha)))def close_heads(xs: List<&2, F32>, ys: List<&2, F32>) -> Bool:  match xs ys:    case +x <> xr +y <> yr:      near(x, y)    case Nil{} ys:      False{}    case x <> xs Nil{}:      False{}# Draw the next per-var coin only when more variables remain.def advance(rng: Rng.State, rest: Nat) -> Rng.State:  match rest:    case 0n:      rng    case 1n+g:      Rng.next_double(rng)def finish(+m: Nat, trip: Rng.State & List<&2, F32> & List<&2, F32>) -> Rng.State & Ind.Individual & Ind.Individual:  (rng, xs, ys) = trip  (rng, Ind.from_variables(xs, m), Ind.from_variables(ys, m))def done(rng: Rng.State, acc1: List<&2, F32>, acc2: List<&2, F32>) -> Rng.State & List<&2, F32> & List<&2, F32>:  (rng, List.reverse(&2, F32, acc1), List.reverse(&2, F32, acc2))def skip_kind(skip: Bool, close: Bool) -> Nat:  match skip:    case True{}:      0n    case False{}:      match close:        case True{}:          0n        case False{}:          1n# Kind: 0n = copy heads (skip or close), 1n = SBX the heads.# One def so skip/cross are not mutually recursive with `loop`.def loop.go(  fuel: Nat,  kind: Nat,  xs1: List<&2, F32>,  xs2: List<&2, F32>,  ivs: List<&2, Bounds.Interval>,  +eta: F32,  rng: Rng.State,  acc1: List<&2, F32>,  acc2: List<&2, F32>) -> Rng.State & List<&2, F32> & List<&2, F32>:  match fuel kind xs1 xs2 ivs:    case 0n k a b c:      done(rng, acc1, acc2)    case 1n++f 0n +x1 <> +rest1 +x2 <> +rest2 +iv <> ivrest:      +rng2 = advance(rng, f)      loop.go(        f,        skip_kind(F32.is_gt(Rng.last_double(rng2), 0.5), close_heads(rest1, rest2)),        rest1, rest2, ivrest, eta, rng2, x1 <> acc1, x2 <> acc2      )    case 1n++f 1n+p +x1 <> +rest1 +x2 <> +rest2 +iv <> ivrest:      +lo = Bounds.lo(iv)      +hi = Bounds.hi(iv)      +gt = F32.is_gt(x1, x2)      +a = Bool.pick(F32, gt, x2, x1)      +b = Bool.pick(F32, gt, x1, x2)      +delta = F32.sub(b, a)      +rng_a = Rng.next_double(rng)      +rand = Rng.last_double(rng_a)      +inv = F32.div(1.0, F32.add(eta, 1.0))      +neg = F32.neg(F32.add(eta, 1.0))      +alpha1 = F32.sub(2.0, F32.pow(F32.add(1.0, F32.div(F32.mul(2.0, F32.sub(a, lo)), delta)), neg))      +alpha2 = F32.sub(2.0, F32.pow(F32.add(1.0, F32.div(F32.mul(2.0, F32.sub(hi, b)), delta)), neg))      +c1v = F32.clamp(F32.mul(0.5, F32.sub(F32.add(a, b), F32.mul(beta(rand, alpha1, inv), delta))), lo, hi)      +c2v = F32.clamp(F32.mul(0.5, F32.add(F32.add(a, b), F32.mul(beta(rand, alpha2, inv), delta))), lo, hi)      +rng_b = Rng.next_double(rng_a)      +sw = F32.is_gt(Rng.last_double(rng_b), 0.5)      +n1 = Bool.pick(F32, sw, c2v, c1v)      +n2 = Bool.pick(F32, sw, c1v, c2v)      +rng_c = advance(rng_b, f)      loop.go(        f,        skip_kind(F32.is_gt(Rng.last_double(rng_c), 0.5), close_heads(rest1, rest2)),        rest1, rest2, ivrest, eta, rng_c, n1 <> acc1, n2 <> acc2      )    case 1n++f k Nil{} b c:      done(rng, acc1, acc2)    case 1n++f k a <> as Nil{} c:      done(rng, acc1, acc2)    case 1n++f k a <> as b <> bs Nil{}:      done(rng, acc1, acc2)def loop(  fuel: Nat,  skip: Bool,  close: Bool,  +eta: F32,  xs1: List<&2, F32>,  xs2: List<&2, F32>,  ivs: List<&2, Bounds.Interval>,  rng: Rng.State,  acc1: List<&2, F32>,  acc2: List<&2, F32>) -> Rng.State & List<&2, F32> & List<&2, F32>:  loop.go(fuel, skip_kind(skip, close), xs1, xs2, ivs, eta, rng, acc1, acc2)def start(  +eta: F32,  +x1s: List<&2, F32>,  +x2s: List<&2, F32>,  +box: Bounds.Box,  rng: Rng.State,  +m: Nat,  n: Nat) -> Rng.State & Ind.Individual & Ind.Individual:  match n:    case 0n:      (rng, Ind.from_variables(x1s, m), Ind.from_variables(x2s, m))    case 1n+f:      +rng2 = Rng.next_double(rng)      finish(        m,        loop(          1n+f,          F32.is_gt(Rng.last_double(rng2), 0.5),          close_heads(x1s, x2s),          eta, x1s, x2s, Bounds.intervals_of(box), rng2, Nil{}, Nil{}        )      )def maybe(  +eta: F32,  +x1s: List<&2, F32>,  +x2s: List<&2, F32>,  +box: Bounds.Box,  rng: Rng.State,  +m: Nat,  +n: Nat,  skip: Bool) -> Rng.State & Ind.Individual & Ind.Individual:  match skip:    case True{}:      (rng, Ind.from_variables(x1s, m), Ind.from_variables(x2s, m))    case False{}:      start(eta, x1s, x2s, box, rng, m, n)def draw(  +eta: F32,  +prob: F32,  +x1s: List<&2, F32>,  +x2s: List<&2, F32>,  +box: Bounds.Box,  rng: Rng.State,  +m: Nat,  +n: Nat) -> Rng.State & Ind.Individual & Ind.Individual:  +rng2 = Rng.next_double(rng)  maybe(eta, x1s, x2s, box, rng2, m, n, F32.is_gt(Rng.last_double(rng2), prob))# p=0 copies parents before any NextDouble. Matches C# children when# pair probability is 0 (every draw is > 0). Bit-zero is definitional# on the 0.0 literal; F32.is_gt stays off this path.def draw_or_copy(  +eta: F32,  +prob: F32,  +x1s: List<&2, F32>,  +x2s: List<&2, F32>,  +box: Bounds.Box,  rng: Rng.State,  +m: Nat,  +n: Nat,  zero: Bool) -> Rng.State & Ind.Individual & Ind.Individual:  match zero:    case True{}:      (rng, Ind.from_variables(x1s, m), Ind.from_variables(x2s, m))    case False{}:      draw(eta, prob, x1s, x2s, box, rng, m, n)def crossover(  +op: Sbx,  +p1: Ind.Individual,  +p2: Ind.Individual,  +box: Bounds.Box,  rng: Rng.State) -> Rng.State & Ind.Individual & Ind.Individual:  +x1s = Ind.variables(p1)  +x2s = Ind.variables(p2)  +m = Bounds.n_obj(box)  +prob = probability(op)  draw_or_copy(    eta(op),    prob,    x1s,    x2s,    box,    rng,    m,    Bounds.n_var(box),    Util.f32_is_zero(prob)  )