Browse Source

did the whole project before deciding to use version control

master
Thomas Johnson 2 years ago
commit
ca1ec9c3f4
  1. 2
      .gitignore
  2. 8
      Cargo.toml
  3. 31
      src/anneal.rs
  4. 496
      src/circ.rs
  5. 25
      src/main.rs

2
.gitignore

@ -0,0 +1,2 @@
/target
Cargo.lock

8
Cargo.toml

@ -0,0 +1,8 @@
[package]
name = "circdes"
version = "0.1.0"
authors = ["thajohns <thajohns@clarkson.edu>"]
edition = "2018"
[dependencies]
rand = "0.7.3"

31
src/anneal.rs

@ -0,0 +1,31 @@
use rand::{distributions::Bernoulli, Rng};
pub trait Perturbable {
fn perturb<R: Rng>(&mut self, rng: &mut R);
}
pub trait Optimizable {
type Goal;
fn closer_than(&self, cmp: &Self, goal: &Self::Goal) -> bool;
}
/// `temp` should be between `0.0` and `1.0`; otherwise, expect a panic.
pub fn anneal_discrete_step<T: Perturbable + Optimizable<Goal = U> + Clone, U>(
s: &mut T,
temp: f64,
goal: &U,
rng: &mut impl Rng,
) -> bool {
let mut t = s.clone();
t.perturb(rng);
let closer = t.closer_than(&s, goal);
let p_switch = if closer { 1.0 } else { temp };
let dist = Bernoulli::new(p_switch).unwrap();
if rng.sample(&dist) {
core::mem::swap(&mut t, s);
true
} else {
false
}
}

496
src/circ.rs

@ -0,0 +1,496 @@
use core::cmp::Ordering;
use rand::{distributions::Bernoulli, Rng};
use crate::anneal::{Optimizable, Perturbable};
#[derive(Debug, Clone)]
pub enum TreeGate {
And {
a: Box<TreeGate>,
b: Box<TreeGate>,
invert: bool,
num_gates: usize,
},
Or {
a: Box<TreeGate>,
b: Box<TreeGate>,
invert: bool,
num_gates: usize,
},
Input {
idx: usize,
invert: bool,
},
}
impl TreeGate {
pub fn evaluate(&self, inputs: &Vec<bool>) -> bool {
match self {
Self::And { a, b, invert, .. } => invert ^ (a.evaluate(inputs) && b.evaluate(inputs)),
Self::Or { a, b, invert, .. } => invert ^ (a.evaluate(inputs) || b.evaluate(inputs)),
Self::Input { idx, invert, .. } => invert ^ inputs[*idx],
}
}
pub fn num_interior_gates(&self) -> usize {
match self {
Self::And { num_gates, .. } => *num_gates,
Self::Or { num_gates, .. } => *num_gates,
Self::Input { .. } => 0,
}
}
pub fn adjust_num_interior_gates(&mut self) {
match self {
Self::And {
num_gates, a, b, ..
} => {
*num_gates = a.num_interior_gates() + b.num_interior_gates() + 1;
}
Self::Or {
num_gates, a, b, ..
} => {
*num_gates = a.num_interior_gates() + b.num_interior_gates() + 1;
}
Self::Input { .. } => (),
}
}
pub fn fix_num_interior_gates(&mut self) {
match self {
Self::And { a, b, .. } => {
a.fix_num_interior_gates();
b.fix_num_interior_gates();
}
Self::Or { a, b, .. } => {
a.fix_num_interior_gates();
b.fix_num_interior_gates();
}
Self::Input { .. } => (),
}
self.adjust_num_interior_gates();
}
pub fn invert(&mut self) {
match self {
TreeGate::And { invert, .. }
| TreeGate::Or { invert, .. }
| TreeGate::Input { invert, .. } => {
*invert ^= true;
}
}
}
pub fn and(a: Self, b: Self) -> Self {
let nga = a.num_interior_gates();
let ngb = b.num_interior_gates();
Self::And {
a: Box::new(a),
b: Box::new(b),
invert: false,
num_gates: nga + ngb + 1,
}
}
pub fn or(a: Self, b: Self) -> Self {
let nga = a.num_interior_gates();
let ngb = b.num_interior_gates();
Self::Or {
a: Box::new(a),
b: Box::new(b),
invert: false,
num_gates: nga + ngb + 1,
}
}
pub fn input(idx: usize) -> Self {
Self::Input { idx, invert: false }
}
pub fn nand(a: Self, b: Self) -> Self {
let nga = a.num_interior_gates();
let ngb = b.num_interior_gates();
Self::And {
a: Box::new(a),
b: Box::new(b),
invert: true,
num_gates: nga + ngb + 1,
}
}
pub fn nor(a: Self, b: Self) -> Self {
let nga = a.num_interior_gates();
let ngb = b.num_interior_gates();
Self::Or {
a: Box::new(a),
b: Box::new(b),
invert: true,
num_gates: nga + ngb + 1,
}
}
pub fn ninput(idx: usize) -> Self {
Self::Input { idx, invert: true }
}
pub fn switch_type(&mut self) -> bool {
let mut temp;
macro_rules! each {
($a:ident, $b:ident, $invert:ident, $num_gates:ident, $type:ident) => {{
temp = Self::$type {
a: Box::new(Self::input(0)),
b: Box::new(Self::input(0)),
invert: false,
num_gates: *$num_gates,
};
match &mut temp {
Self::$type {
a: ta,
b: tb,
invert: tinvert,
..
} => {
core::mem::swap($a, ta);
core::mem::swap($b, tb);
core::mem::swap($invert, tinvert);
}
_ => unreachable!(),
}
core::mem::swap(self, &mut temp);
true
}};
}
match self {
Self::And {
a,
b,
num_gates,
invert,
} => each!(a, b, invert, num_gates, Or),
Self::Or {
a,
b,
num_gates,
invert,
} => each!(a, b, invert, num_gates, And),
_ => false,
}
}
/// It is assumed that a responsible caller leaves the interior gate counts in a correct state
/// on the part of the circuit it changes..
pub fn search_interior_gates<T, F: FnOnce(&mut Box<Self>) -> T>(
root: &mut Box<Self>,
idx: usize,
f: F,
) -> T {
match &mut **root {
TreeGate::And {
a, b, num_gates, ..
}
| TreeGate::Or {
a, b, num_gates, ..
} => {
let ag;
if idx == num_gates.clone() - 1 {
f(root)
} else if {
ag = a.num_interior_gates();
idx < ag
} {
let rv = TreeGate::search_interior_gates(a, idx, f);
*num_gates = a.num_interior_gates() + b.num_interior_gates() + 1;
rv
} else {
let rv = TreeGate::search_interior_gates(b, idx - ag, f);
*num_gates = a.num_interior_gates() + b.num_interior_gates() + 1;
rv
}
}
TreeGate::Input { .. } => {
panic!("attempt to search for interior gates on a circuit with only an input gate!")
}
}
}
fn multiline_string(&self, indent: usize, upper: bool, mut fill: Vec<bool>) -> String {
let mut pad = String::new();
for b in fill.iter().skip(1) {
let c = if *b { "│" } else { " " };
pad = format!("{}{}", pad, c);
}
let c = if indent == 0 {
""
} else if upper {
"┌"
} else {
"└"
};
pad = format!("{}{}", pad, c);
let mut upper_fill = fill.clone();
upper_fill.push(!upper);
fill.push(upper);
match self {
TreeGate::And { a, b, invert, .. } => format!(
"{}\n{}{}\n{}",
a.multiline_string(indent + 1, true, upper_fill),
pad,
if *invert { "x" } else { "X" },
b.multiline_string(indent + 1, false, fill)
),
TreeGate::Or { a, b, invert, .. } => format!(
"{}\n{}{}\n{}",
a.multiline_string(indent + 1, true, upper_fill),
pad,
if *invert { "o" } else { "O" },
b.multiline_string(indent + 1, false, fill)
),
TreeGate::Input { idx, invert, .. } => {
format!("{}{}Input({})", pad, if *invert { "/" } else { "" }, idx)
}
}
}
}
impl core::fmt::Display for TreeGate {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.multiline_string(0, false, Vec::new()))
}
}
#[derive(Debug, Clone)]
pub struct TreeCircuit {
pub root: Box<TreeGate>,
pub num_inputs: usize,
num_gates_to_add: usize, // Even the odds by adding as many gates as were last deleted
}
impl TreeCircuit {
pub fn new_simple(num_inputs: usize) -> Self {
if num_inputs == 0 {
panic!("one cannot create a circuit with no inputs!");
}
TreeCircuit {
root: Box::new(TreeGate::input(0)),
num_inputs,
num_gates_to_add: 0,
}
}
pub fn new(num_inputs: usize, root: TreeGate) -> Self {
if num_inputs == 0 {
panic!("one cannot create a circuit with no inputs!");
}
TreeCircuit {
root: Box::new(root),
num_inputs,
num_gates_to_add: 0,
}
}
pub fn distance_from_goal(&self, goal: &Goal) -> usize {
let mut current_input = vec![false; self.num_inputs];
let mut counter = 0;
let mut hits = 0;
loop {
if goal[counter] != self.root.evaluate(&current_input) {
hits += 1;
}
if current_input.iter().fold(true, |x, y| x & y) {
break;
}
let mut i = self.num_inputs;
loop {
i -= 1;
current_input[i] ^= true;
if current_input[i] == true {
break;
}
}
counter += 1;
}
hits
}
pub fn act_on_random_gate<T, F: FnOnce(&mut Box<TreeGate>, &mut R) -> T, R: Rng>(
&mut self,
rng: &mut R,
f: F,
) -> Option<T> {
let choice = rng.gen_range(0, self.root.num_interior_gates() + 1);
if choice == self.root.num_interior_gates() {
None
} else {
Some(TreeGate::search_interior_gates(
&mut self.root,
choice,
|c| f(c, rng),
))
}
}
}
impl Perturbable for TreeCircuit {
fn perturb<R: Rng>(&mut self, rng: &mut R) {
use rand::distributions::WeightedIndex;
let choice = WeightedIndex::new(&[1, 1, 1, 1]).unwrap();
match rng.sample(choice) {
0 => {
// Add gates
let num_to_add = usize::max(self.num_gates_to_add, 1);
for _ in 0..num_to_add {
let random_input = TreeGate::input(rng.gen_range(0, self.num_inputs));
let get_rand_gate = |rng: &mut R, new_input: TreeGate| {
let dist = WeightedIndex::new(&[1, 1]).unwrap();
let invert = rng.sample(&dist);
match rng.sample(&dist) {
0 => {
let mut gate = TreeGate::and(new_input.clone(), new_input);
if invert == 1 {
gate.invert();
}
gate
}
1 => {
let mut gate = TreeGate::or(new_input.clone(), new_input);
if invert == 1 {
gate.invert();
}
gate
}
_ => unreachable!(),
}
};
let link_rand_gate =
|rng: &mut R, new_input, other_input: &mut Box<TreeGate>| {
let mut gate = get_rand_gate(rng, new_input);
match &mut gate {
// The randomness here isn't functional; it's mostly for visual
// balance
TreeGate::And { a, b, .. } | TreeGate::Or { a, b, .. } => {
let dist = Bernoulli::new(0.5).unwrap();
if rng.sample(&dist) {
core::mem::swap(a.as_mut(), other_input.as_mut());
} else {
core::mem::swap(b.as_mut(), other_input.as_mut());
}
}
_ => unreachable!(),
}
gate
};
if let None = self.act_on_random_gate(rng, |gate, rng| {
match &mut **gate {
TreeGate::And { a, b, .. } | TreeGate::Or { a, b, .. } => {
let dist = Bernoulli::new(0.5).unwrap();
if rng.sample(&dist) {
// Insert before `a`
let mut gate = link_rand_gate(rng, random_input.clone(), a);
core::mem::swap(&mut **a, &mut gate);
a.adjust_num_interior_gates();
} else {
// Insert before `b`
let mut gate = link_rand_gate(rng, random_input.clone(), b);
core::mem::swap(&mut **b, &mut gate);
b.adjust_num_interior_gates();
}
}
_ => panic!("unexpected gate type!"),
}
gate.adjust_num_interior_gates();
}) {
let mut gate = link_rand_gate(rng, random_input, &mut self.root);
core::mem::swap(&mut *self.root, &mut gate);
self.root.adjust_num_interior_gates();
}
}
self.num_gates_to_add = 1;
}
1 => {
// Remove gates
let mut removed_gate_count = 0; // apparently can't move uninitialized variable into closure, which makes sense I guess
if let None = self.act_on_random_gate(rng, |gate, rng| {
let mut temp = TreeGate::input(0);
match &mut **gate {
TreeGate::And { a, b, .. } | TreeGate::Or { a, b, .. } => {
let dist = Bernoulli::new(0.5).unwrap();
if rng.sample(&dist) {
core::mem::swap(a.as_mut(), &mut temp);
removed_gate_count = 1 + b.num_interior_gates();
} else {
core::mem::swap(b.as_mut(), &mut temp);
removed_gate_count = 1 + a.num_interior_gates();
}
}
_ => panic!("unexpected gate type!"),
}
core::mem::swap(gate.as_mut(), &mut temp);
}) {
self.perturb(rng); // Try again
} else {
self.num_gates_to_add = removed_gate_count;
}
}
2 => {
// Change gates
if let Some(true) = self.act_on_random_gate(rng, |gate, _rng| gate.switch_type()) {
} else {
self.perturb(rng); // Try again
}
}
3 => {
// Invert gates
if let Some(()) = self.act_on_random_gate(rng, |gate, rng| match &mut **gate {
TreeGate::And { a, b, .. } | TreeGate::Or { a, b, .. } => {
let dist = Bernoulli::new(0.5).unwrap();
if rng.sample(dist) {
a.invert();
} else {
b.invert();
}
}
_ => unreachable!(),
}) {
} else {
self.root.invert();
}
}
_ => panic!("random index is out of range! wrong distribution?"),
}
}
}
type Goal = Vec<bool>;
impl Optimizable for TreeCircuit {
type Goal = Goal;
fn closer_than(&self, cmp: &Self, goal: &Self::Goal) -> bool {
let dist = self.distance_from_goal(&goal);
match dist.cmp(&cmp.distance_from_goal(&goal)) {
Ordering::Less => true,
Ordering::Equal => {
dist == 0 && self.root.num_interior_gates() < cmp.root.num_interior_gates()
}
Ordering::Greater => false,
}
}
}
#[cfg(test)]
mod test {
use super::*;
use rand::thread_rng;
#[test]
//#[ignore]
fn print_perturbations() {
let mut circ = TreeCircuit::new_simple(4);
let mut rng = thread_rng();
for _ in 0..100 {
println!("{}", circ.root);
circ.perturb(&mut rng);
}
}
}

25
src/main.rs

@ -0,0 +1,25 @@
pub mod anneal;
pub mod circ;
use anneal::anneal_discrete_step;
use circ::TreeCircuit;
fn main() {
let num_iters = 100000;
let num_inputs = 3;
let mut circ = TreeCircuit::new_simple(num_inputs);
let mut rng = rand::thread_rng();
let ib = 1.0 / (num_iters as f64);
let mut goal = Vec::new();
for i in 0usize..1 << num_inputs {
goal.push(i.count_ones() & 1 == 0);
}
for i in 0..num_iters {
if i % 137 == 0 {
println!("step: {}", i);
}
let temp = ((num_iters - i) as f64) * ib;
anneal_discrete_step(&mut circ, temp, &goal, &mut rng);
}
println!("{}: \n{}", circ.distance_from_goal(&goal), circ.root);
}
Loading…
Cancel
Save