From 4b0541c67a30bbd2a0a3fa9ed61a2ac655ea46ea Mon Sep 17 00:00:00 2001 From: Arne Keller Date: Thu, 18 Mar 2021 10:53:39 +0100 Subject: [PATCH] WASM port + misc. changes --- .gitignore | 1 + Cargo.lock | 166 ++++++++++++++++++++++++++ Cargo.toml | 5 + base.js | 23 ++++ index.html | 49 ++++++++ src/bin/svg-wasm.rs | 243 +++++++++++++++++++++++++++++++++++++++ src/bin/svg.rs | 109 +----------------- src/lib.rs | 164 +++++++++++++++++++------- src/main.rs | 2 +- src/{ui.rs => ui/mod.rs} | 2 + src/ui/svg.rs | 142 +++++++++++++++++++++++ 11 files changed, 759 insertions(+), 147 deletions(-) create mode 100644 base.js create mode 100644 index.html create mode 100644 src/bin/svg-wasm.rs rename src/{ui.rs => ui/mod.rs} (97%) create mode 100644 src/ui/svg.rs diff --git a/.gitignore b/.gitignore index ea8c4bf..4f96631 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +/dist diff --git a/Cargo.lock b/Cargo.lock index e1e220c..d07c290 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,43 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "bumpalo" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "console_error_panic_hook" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8d976903543e0c48546a91908f21588a680a8c8f984df9a5d69feccb2b2a211" +dependencies = [ + "cfg-if 0.1.10", + "wasm-bindgen", +] + +[[package]] +name = "console_log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501a375961cef1a0d44767200e66e4a559283097e91d0730b1d75dfb2f8a1494" +dependencies = [ + "log", + "web-sys", +] + [[package]] name = "either" version = "1.6.1" @@ -15,14 +53,142 @@ dependencies = [ "either", ] +[[package]] +name = "js-sys" +version = "0.3.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc9f84f9b115ce7843d60706df1422a916680bfdfcbdb0447c5614ff9d7e4d78" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "kv" version = "0.0.1" dependencies = [ + "console_error_panic_hook", + "console_log", "itertools", + "log", "svg_fmt", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "proc-macro2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", ] [[package]] name = "svg_fmt" version = "0.4.0" + +[[package]] +name = "syn" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fd9d1e9976102a03c542daa2eff1b43f9d72306342f3f8b3ed5fb8908195d6f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "wasm-bindgen" +version = "0.2.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee1280240b7c461d6a0071313e08f34a60b0365f14260362e5a2b17d1d31aa7" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b7d8b6942b8bb3a9b0e73fc79b98095a27de6fa247615e59d096754a3bc2aa8" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ac38da8ef716661f0f36c0d8320b89028efe10c7c0afde65baffb496ce0d3b" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc053ec74d454df287b9374ee8abb36ffd5acb95ba87da3ba5b7d3fe20eb401e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d6f8ec44822dd71f5f221a5847fb34acd9060535c1211b70a05844c0f6383b1" + +[[package]] +name = "web-sys" +version = "0.3.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec600b26223b2948cedfde2a0aa6756dcf1fef616f43d7b3097aaf53a6c4d92b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] diff --git a/Cargo.toml b/Cargo.toml index d05514c..f0abb5b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,5 +7,10 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +console_error_panic_hook = "0.1.6" +console_log = "0.2.0" itertools = "0.10.0" +log = "0.4.14" svg_fmt = { path = "../rust_debug/svg_fmt" } +wasm-bindgen = "0.2.71" +web-sys = { version = "0.3.48", features = ["Window", "Document", "Location", "HtmlCollection", "HtmlElement", "CssStyleDeclaration", "HtmlTextAreaElement", "HtmlInputElement"] } diff --git a/base.js b/base.js new file mode 100644 index 0000000..765b687 --- /dev/null +++ b/base.js @@ -0,0 +1,23 @@ +function handleTextChange(event) { + const target = event.target; + const value = target.value; + if (value.length == 0) { + target.value = "-"; + } else { + target.value = value.substr(value.length - 1); + } +} +function handleTextScroll(event) { + const target = event.target; + const value = target.value; + if (event.deltaY < 0) { + target.value = "1"; // scroll up + } else { + target.value = "0"; // scroll down + } + event.preventDefault(); +} +export function attachTypeListener(element) { + element.addEventListener("input", handleTextChange); + element.addEventListener("wheel", handleTextScroll); +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..9620139 --- /dev/null +++ b/index.html @@ -0,0 +1,49 @@ + + + + + KV diagram calculator + + + + +
+
+ +
+

+ Change cells by scrolling (up => 1, down => 0), typing (=> 1, 0) or deleting (=> -). +

+

+ Load example 1, example 2, example 3. +

+
+ +
+
+
+ + diff --git a/src/bin/svg-wasm.rs b/src/bin/svg-wasm.rs new file mode 100644 index 0000000..f1e06f4 --- /dev/null +++ b/src/bin/svg-wasm.rs @@ -0,0 +1,243 @@ +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; +use log::{Level, info}; + +use kv::*; +use web_sys::{EventTarget, HtmlElement, HtmlInputElement, HtmlTextAreaElement}; + +macro_rules! web { + ($($x:ident)*) => { + web_sys::window().unwrap()$(.$x().unwrap())* + } +} + +macro_rules! web2 { + ($($x:ident)*) => { + web_sys::window().unwrap()$(.$x())* + } +} + +const HINT_TEXT: &'static str = "Output: core blocks, prime blocks, solution 1, solution 2, ... (solutions only displayed if not equal to core or prime blocks)"; + +fn main() { + console_log::init_with_level(Level::Debug).unwrap(); + std::panic::set_hook(Box::new(console_error_panic_hook::hook)); + info!("init"); + init_settings(); + if !parse_hash() { + let grid = grid(get_var_number()); + update_input_grid(&grid); + } + let closure = Closure::wrap(Box::new(|| { parse_hash(); }) as Box); + web!().add_event_listener_with_callback("hashchange", closure.as_ref().unchecked_ref()).unwrap(); + closure.forget(); +} + +fn parse_hash() -> bool { + let hash = web!(document location hash); + if hash.len() >= 2 { + let func_spec = &hash[1..]; + let var_count = (func_spec.len() as f32).log2() as usize; + if 2usize.pow(var_count as u32) == func_spec.len() { + set_var_number(var_count); + let grid = grid(var_count); + update_input_grid(&grid); + set_input_function(func_spec); + return true; + } + } + false +} + +#[wasm_bindgen(module = "/base.js")] +extern "C" { + pub fn attachTypeListener(element: &HtmlElement); +} + +fn get_output_container() -> HtmlElement { + web!(document).get_element_by_id("output-container").unwrap().unchecked_into::() +} + +fn get_output_help() -> HtmlElement { + web!(document).get_element_by_id("output-help").unwrap().unchecked_into::() +} + +fn p(text: &str) -> HtmlElement { + let p = web!(document).create_element("p").unwrap().unchecked_into::(); + p.set_inner_text(text); + p +} + +fn span(text: &str) -> HtmlElement { + let p = web!(document).create_element("span").unwrap().unchecked_into::(); + p.set_inner_text(text); + p +} + +pub fn run() { + let document = web!(document); + + let var_count = get_var_number(); + let vars = (0..var_count).map(|x| 2usize.pow(x as u32)).collect::>(); + let function: FunctionSpec = (0..2usize.pow(var_count as u32)).map(|idx| get_input(idx)).collect(); + info!("function: {:?}", function); + let hash = Some('#').into_iter().chain(function.iter().map::(|&x| x.into())).collect::(); + web2!(location).set_hash(&hash).unwrap(); + let groups = find_groups(&function, &vars, One); + let (prime, other) = find_core(&function, &vars, One, &groups); + let solutions = real_solutions_idxed(function.clone(), &vars, One, &prime, &other); + + let output_container = get_output_container(); + if output_container.children().length() == 0 { + let output_help = get_output_help(); + output_help.append_child(&p(HINT_TEXT)).unwrap(); + output_container.append_child(&document.create_element("hr").unwrap()).unwrap(); + } + let output_sub = document.create_element("div").unwrap().unchecked_into::(); + + let (_grid, w, h) = grid(var_count); + + macro_rules! render_svg { + ($sol:expr, $mask:expr) => { + let svg = svg::get_svg(if var_count < 5 { 64 } else { 80 }, &function, &vars, $mask); + let svg_container = document.create_element("div").unwrap(); + svg_container.set_class_name("solution"); + svg_container.set_inner_html(&svg); + let svg = svg_container.children().item(0).unwrap().unchecked_into::(); + svg.style().set_property("width", &format!("{}px", w * 80)).unwrap(); + svg.style().set_property("height", &format!("{}px", h * 80)).unwrap(); + let mut text = String::new(); + for x in $sol { + if !text.is_empty() { + text += " ∨ "; + } + text += &print_implicant(x); + } + if text.is_empty() { + text += "."; + } + svg_container.append_child(&span(&text)).unwrap(); + output_sub.append_child(&svg_container).unwrap(); + }; + } + + let mut block_masks: Vec<_> = prime.iter() + .map(block_to_mask) + .collect(); + render_svg!(&prime, &block_masks); + + block_masks = prime.iter() + .map(block_to_mask) + .chain(other.iter().map(block_to_mask)) // also display non-core + .collect(); + render_svg!(prime.iter().chain(other.iter()), &block_masks); + + let mut size = usize::MAX; + for sol in &solutions { + if sol.len() > size { + break; + } + size = sol.len(); + block_masks = prime.iter() + .map(block_to_mask) + .chain((0..other.len()).map(|idx| { + if sol.contains(&idx) { + block_to_mask(&other[idx]) + } else { + (!0, 0) + } + })) + .collect(); + let blocks = prime.iter() + .chain(sol.iter().map(|&i| &other[i])); + render_svg!(blocks, &block_masks); + } + + output_container.prepend_with_node_1(&output_sub).unwrap(); + output_container.prepend_with_node_1(&document.create_element("hr").unwrap()).unwrap(); +} + +fn set_var_number(x: usize) { + get_var_number_el().set_value(&x.to_string()); +} + +fn init_settings() { + let calculate = web!(document).get_element_by_id("calculate").unwrap().unchecked_into::(); + let closure = Closure::wrap(Box::new(|| run()) as Box); + calculate.add_event_listener_with_callback("click", closure.as_ref().unchecked_ref()).unwrap(); + closure.forget(); + let apply_count = web!(document).get_element_by_id("apply-variables").unwrap().unchecked_into::(); + let closure = Closure::wrap(Box::new(|| update_input_grid(&grid(get_var_number()))) as Box); + apply_count.add_event_listener_with_callback("click", closure.as_ref().unchecked_ref()).unwrap(); + closure.forget(); +} + +fn get_var_number_el() -> HtmlInputElement { + web!(document).get_element_by_id("variables").unwrap().unchecked_into() +} + +fn get_var_number() -> usize { + get_var_number_el().value().parse().unwrap() +} + +fn get_input_container() -> HtmlElement { + web!(document).get_element_by_id("input-container").unwrap().unchecked_into::() +} + +fn get_input_el(idx: usize) -> Option { + web!(document).get_element_by_id(&format!("input{}", idx)).map(|x| x.unchecked_into::()) +} + +fn create_input_el(idx: usize, x: usize, y: usize) -> HtmlElement { + let input = web!(document).create_element("textarea").unwrap().unchecked_into::(); + input.set_attribute("id", &format!("input{}", idx)).unwrap(); + input.set_value("-"); + let style = input.style(); + style.set_property("position", "absolute").unwrap(); + style.set_property("left", &format!("{}px", x)).unwrap(); + style.set_property("top", &format!("{}px", y)).unwrap(); + get_input_container().append_child(&input).unwrap(); + attachTypeListener(&input); + input.into() +} + +fn get_input(idx: usize) -> Output { + let el = get_input_el(idx).unwrap().unchecked_into::(); + match &*el.value() { + "0" => Zero, + "1" => One, + _ => Any + } +} + +fn set_input(idx: usize, out: Output) { + let el = get_input_el(idx).unwrap().unchecked_into::(); + el.set_value(&out.to_string()); +} + +fn set_input_function(func: &str) { + for i in 0..func.len() { + set_input(i, func[i..i+1].parse().unwrap()); + } +} + +// also set in style +const GRID_SPACING: usize = 64; + +fn update_input_grid((grid, w, h): &(Vec<(usize, usize)>, usize, usize)) { + let input_container = get_input_container(); + input_container.style().set_property("width", &format!("{}px", w * 64)).unwrap(); + input_container.style().set_property("height", &format!("{}px", h * 64)).unwrap(); + for i in 0..grid.len() { + if get_input_el(i).is_none() { + create_input_el(i, grid[i].0 * GRID_SPACING, grid[i].1 * GRID_SPACING); + } + } + for i in grid.len().. { + if let Some(el) = get_input_el(i) { + el.remove(); + } else { + break; + } + } +} diff --git a/src/bin/svg.rs b/src/bin/svg.rs index acfc166..c894876 100644 --- a/src/bin/svg.rs +++ b/src/bin/svg.rs @@ -1,32 +1,23 @@ use std::env::args; -use svg_fmt::*; - use kv::*; const SIZE_FACTOR: usize = 64; -const SIZE_FONT: f32 = 20.0; -const SIZE4: usize = SIZE_FACTOR / 4; - -const COLORS: &'static [Color] = &[ - rgb(0, 0, 255), - rgb(255, 0, 0), - rgb(0, 255, 0), - rgb(255, 255, 0), -]; fn main() { let args = args().collect::>(); - let vars = [A, B, C]; - let function = vec![0, 0, 1, 1, 1, 1, 1, 0].into_iter().map(Into::into).collect(); + let vars = [A, B, C, D]; + //let function = vec![0, 0, 1, 1, 1, 1, 1, 0].into_iter().map(Into::into).collect(); + //let function = vec![0, 0, 1, 1, 1, 1, 1, 2].into_iter().map(Into::into).collect(); + let function = parse_function("-1111000-1-01---"); let groups = find_groups(&function, &vars, One); eprintln!("all:"); for x in &groups { eprintln!("{}", print_implicant(x)); } eprintln!("prime:"); - let (prime, other) = find_prime(&function, &vars, One, &groups); + let (prime, other) = find_core(&function, &vars, One, &groups); for x in &prime { eprintln!("{}", print_implicant(x)); } @@ -54,93 +45,5 @@ fn main() { .collect() }; - let (grid, w, h) = grid(3); - println!("{}", BeginSvg { w: w * SIZE_FACTOR + 3, h: h * SIZE_FACTOR + 3 }); - for (i, &(x, y)) in grid.iter().enumerate() { - println!("{}", - rectangle(1 + x * SIZE_FACTOR, 1 + y * SIZE_FACTOR, SIZE_FACTOR, SIZE_FACTOR) - .fill(white()) - .stroke(Stroke::Color(black(), 2.0)) - ); - let mut rect = rectangle(1 + x * SIZE_FACTOR, 1 + y * SIZE_FACTOR, SIZE_FACTOR, SIZE_FACTOR) - .fill(white()) - .stroke_opacity(0.7) - .inflate(-1, -1); - let mut ident = 1; - for (idx, &(mask, inv_mask)) in block_masks.iter().enumerate() { - rect = rect.inflate(-2, -2); - ident += 2; - if check_mask(i, mask, inv_mask) { - // check cells around this one - let mut up = false; - let mut down = false; - let mut left = false; - let mut right = false; - for &var in &vars { - let j = i ^ var; - if check_mask(j, mask, inv_mask) { - let (x2, y2) = grid[j]; - if (x + 1) % w == x2 && y == y2 { - right = true; - } - if x == x2 && (y + 1) % h == y2 { - down = true; - } - if x == x2 && y == (y2 + 1) % h { - up = true; - } - if x == (x2 + 1) % w && y == y2 { - left = true; - } - if !up && !down && !left && !right { - unreachable!() - } - } - } - let mut sides = rect.sides(); - for s in &mut sides { - *s = s.opacity(0.9); - } - if up { - println!("{}", sides[0].width(2.0).color(white())); - println!("{}", sides[2].width(2.0).color(COLORS[idx]).offset(0.0, -ident - 1)); - println!("{}", sides[3].width(2.0).color(COLORS[idx]).offset(0.0, -ident - 1)); - } else { - println!("{}", sides[0].width(2.0).color(COLORS[idx])); - } - if down { - println!("{}", sides[1].width(2.0).color(white())); - println!("{}", sides[2].width(2.0).color(COLORS[idx]).offset(0.0, ident + 1)); - println!("{}", sides[3].width(2.0).color(COLORS[idx]).offset(0.0, ident + 1)); - } else { - println!("{}", sides[1].width(2.0).color(COLORS[idx])); - } - if left { - println!("{}", sides[2].width(2.0).color(white())); - println!("{}", sides[0].width(2.0).color(COLORS[idx]).offset(-ident - 1, 0)); - println!("{}", sides[1].width(2.0).color(COLORS[idx]).offset(-ident - 1, 0)); - } else { - println!("{}", sides[2].width(2.0).color(COLORS[idx])); - } - if right { - println!("{}", sides[3].width(1.0).color(white())); - println!("{}", sides[0].width(2.0).color(COLORS[idx]).offset(ident + 1, 0)); - println!("{}", sides[1].width(2.0).color(COLORS[idx]).offset(ident + 1, 0)); - } else { - println!("{}", sides[3].width(2.0).color(COLORS[idx])); - } - } - } - println!("{}", - text((x+1) * SIZE_FACTOR - SIZE4, (y+1) * SIZE_FACTOR - SIZE4 / 3, i.to_string()) - .size(SIZE_FONT) - .color(black()) - ); - println!("{}", - text(x * SIZE_FACTOR + SIZE4, (y+1) * SIZE_FACTOR - SIZE4, function[i].to_string()) - .size(SIZE_FONT * 2.0) - .color(black()) - ); - } - println!("{}", EndSvg); + println!("{}", svg::get_svg(SIZE_FACTOR, &function, &vars, &block_masks)); } diff --git a/src/lib.rs b/src/lib.rs index ed50770..700b174 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,9 @@ -use std::fmt; +use std::{fmt, str::FromStr}; use itertools::Itertools; mod ui; +use log::{debug, trace, warn}; pub use ui::*; pub type Variable = usize; @@ -12,6 +13,8 @@ pub type BlockRef<'a> = &'a [(Variable, Output)]; pub const A: Variable = 1 << 0; pub const B: Variable = 1 << 1; pub const C: Variable = 1 << 2; +pub const D: Variable = 1 << 3; +pub const E: Variable = 1 << 4; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Output { @@ -25,45 +28,86 @@ pub use Output::*; impl Output { fn to_num(self) -> usize { match self { - Zero => 0, - One => 1, - Any => todo!() + Zero => 0, + One => 1, + Any => todo!() } } fn invert(self) -> Self { match self { - Zero => One, - One => Zero, - Any => Any + Zero => One, + One => Zero, + Any => Any } } } impl From for Output { - fn from(x: usize) -> Self { - match x { + fn from(x: usize) -> Self { + match x { 0 => Zero, 1 => One, - _ => todo!() + _ => Any } - } + } +} + +impl From for Output { + fn from(x: char) -> Self { + match x { + '0' => Zero, + '1' => One, + _ => Any + } + } +} + +impl FromStr for Output { + type Err = (); + + fn from_str(s: &str) -> Result { + Ok(match s { + "0" => Zero, + "1" => One, + _ => Any + }) + } } impl fmt::Display for Output { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Zero => write!(f, "0"), - One => write!(f, "1"), - Any => write!(f, "-") - } - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Zero => write!(f, "0"), + One => write!(f, "1"), + Any => write!(f, "-") + } + } +} + +impl Into for Output { + fn into(self) -> char { + match self { + Zero => '0', + One => '1', + Any => '-' + } + } +} + +pub fn parse_function(func: &str) -> FunctionSpec { + func.chars().map(|x| x.into()).collect() } pub type FunctionSpec = Vec; pub fn find_groups(func: &FunctionSpec, vars: &[Variable], typ: Output) -> Vec> { let mut groups: Vec> = Vec::new(); + // k = 0 + if func.iter().all(|&x| x != typ.invert()) { + groups.push(vec![]); + return groups; + } for k in 1..vars.len()+1 { for vars in vars.iter().combinations(k) { for var_bits in 0..(1 << k) { @@ -82,15 +126,18 @@ pub fn find_groups(func: &FunctionSpec, vars: &[Variable], typ: Output) -> Vec> i) & 1) == 1 { 0 } else { var }; } let mut fits = true; + let mut found_typ = false; for input in 0..func.len() { if check_mask(input, mask, inv_mask) { if func[input] == typ.invert() { fits = false; break; + } else if func[input] == typ { + found_typ = true; } } } - if fits { + if fits && found_typ { groups.push(var_setting); } } @@ -99,7 +146,7 @@ pub fn find_groups(func: &FunctionSpec, vars: &[Variable], typ: Output) -> Vec(func: &FunctionSpec, vars: &[Variable], typ: Output, blocks: &'a [Block]) -> (Vec<&'a [(Variable, Output)]>, Vec<&'a [(Variable, Output)]>) { +pub fn find_core<'a>(func: &FunctionSpec, vars: &[Variable], typ: Output, blocks: &'a [Block]) -> (Vec<&'a [(Variable, Output)]>, Vec<&'a [(Variable, Output)]>) { assert_eq!(func.len(), 2usize.pow(vars.len() as u32)); let block_masks = blocks.iter() @@ -130,11 +177,39 @@ pub fn find_prime<'a>(func: &FunctionSpec, vars: &[Variable], typ: Output, block (a, b) } -pub fn all_solutions(mut func: FunctionSpec, vars: &[Variable], typ: Output, prime: &[BlockRef], other: &[BlockRef]) -> Vec> { +/// May return some incomplete solutions +pub fn all_solutions(func: FunctionSpec, vars: &[Variable], typ: Output, core: &[BlockRef], other: &[BlockRef]) -> Vec> { + all_solutions_idxed(func, vars, typ, core, other) + .into_iter() + .map(|sol| sol.into_iter().map(|i| other[i].to_vec()).collect()) + .collect() +} + +pub fn real_solutions_idxed(func: FunctionSpec, vars: &[Variable], typ: Output, core: &[BlockRef], other: &[BlockRef]) -> Vec> { + let core_masks = core.iter() + .map(block_to_mask) + .collect::>(); + let other_masks = other.iter() + .map(block_to_mask) + .collect::>(); + let mut solutions = all_solutions_idxed(func.clone(), vars, typ, core, other); + solutions.sort_unstable_by_key(|x| x.len()); + solutions.into_iter() + .filter(|sol| + (0..func.len()) + .filter(|&i| func[i] == typ) + .all(|i| + core_masks.iter() + .chain(sol.iter().map(|&x| &other_masks[x])) + .any(|&(mask, inv_mask)| check_mask(i, mask, inv_mask))) + ).collect() +} + +pub fn all_solutions_idxed(mut func: FunctionSpec, vars: &[Variable], typ: Output, core: &[BlockRef], other: &[BlockRef]) -> Vec> { assert_eq!(func.len(), 2usize.pow(vars.len() as u32)); - // first mark all inputs covered by prime blocks as Any - let prime_masks = prime.iter() + // first mark all inputs covered by core blocks as Any + let prime_masks = core.iter() .map(block_to_mask) .collect::>(); 'input: for input in 0..func.len() { @@ -152,38 +227,46 @@ pub fn all_solutions(mut func: FunctionSpec, vars: &[Variable], typ: Output, pri let other_masks = other.iter() .map(block_to_mask) .collect::>(); - all_recursive(func, vars, typ, other, &other_masks) + all_recursive(func, vars, typ, other, &other_masks, 0) } -fn all_recursive(func: FunctionSpec, vars: &[Variable], typ: Output, other: &[BlockRef], other_masks: &[(Variable, Variable)]) -> Vec> { +fn all_recursive(func: FunctionSpec, vars: &[Variable], typ: Output, other: &[BlockRef], other_masks: &[(Variable, Variable)], start: usize) -> Vec> { let mut all = Vec::new(); - for i in 0..other.len() { - let block = &other[i]; + 'block: for i in start..other.len() { + trace!("trying {}", i); let (mask, inv_mask) = other_masks[i]; for input in 0..func.len() { if func[input] != typ { continue; } if check_mask(input, mask, inv_mask) { + trace!("success by idx {}", input); // this block is useful // mark inputs as done let mut func = func.clone(); for input in 0..func.len() { if check_mask(input, mask, inv_mask) { + trace!("set {} to any", input); func[input] = Any; } } - let extensions = all_recursive(func, vars, typ, other, other_masks); + let extensions = all_recursive(func, vars, typ, other, other_masks, i+1); if extensions.is_empty() { - all.push(vec![block.to_vec()]); + all.push(vec![i]); } for mut extension in extensions { - extension.push(block.to_vec()); - all.push(extension); + if !extension.is_empty() { + extension.push(i); + all.push(extension); + } else { + warn!("empty extension?"); + } } + continue 'block; } } } + trace!("returning"); all } @@ -211,10 +294,10 @@ pub fn print_implicate(vars: &[(Variable, Output)]) -> String { let mut s = String::new(); for i in 0..vars.len() { let (var, out) = vars[i]; + s.push(print_var(var)); if out == One { - s += "~"; + s += "̅"; } - s += print_var(var); if i != vars.len() - 1 { s += " v "; } @@ -226,10 +309,10 @@ pub fn print_implicant(vars: &[(Variable, Output)]) -> String { let mut s = String::new(); for i in 0..vars.len() { let (var, out) = vars[i]; + s.push(print_var(var)); if out == Zero { - s += "~"; + s += "̅"; } - s += print_var(var); if i != vars.len() - 1 { s += " "; } @@ -237,13 +320,8 @@ pub fn print_implicant(vars: &[(Variable, Output)]) -> String { s } -fn print_var(var: Variable) -> &'static str { - match var { - A => "a", - B => "b", - C => "c", - _ => "?" - } +fn print_var(var: Variable) -> char { + ('a' as u8 + (0usize.leading_zeros() - var.leading_zeros() - 1) as u8) as char } #[test] @@ -255,7 +333,7 @@ fn test_3var() { vec![(A, One), (C, Zero)], vec![(B, Zero), (C, Zero)], ]); - let prime = find_prime(&fun, &mut [A, B, C], Zero, &groups).0; + let prime = find_core(&fun, &mut [A, B, C], Zero, &groups).0; assert_eq!(prime, vec![ vec![(A, Zero), (B, Zero)], vec![(A, One), (C, Zero)], diff --git a/src/main.rs b/src/main.rs index 157a032..61c1b85 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ fn main() { println!("{}", print_implicant(x)); } println!("prime:"); - let (prime, other) = find_prime(&function, &[A, B, C], One, &groups); + let (prime, other) = find_core(&function, &[A, B, C], One, &groups); for x in &prime { println!("{}", print_implicant(x)); } diff --git a/src/ui.rs b/src/ui/mod.rs similarity index 97% rename from src/ui.rs rename to src/ui/mod.rs index a88fdc7..d346806 100644 --- a/src/ui.rs +++ b/src/ui/mod.rs @@ -1,3 +1,5 @@ +pub mod svg; + /// Returns grid coordinates, width, height pub fn grid(var_count: usize) -> (Vec<(usize, usize)>, usize, usize) { let mut grid = vec![(0, 0), (1, 0)]; diff --git a/src/ui/svg.rs b/src/ui/svg.rs new file mode 100644 index 0000000..a53b900 --- /dev/null +++ b/src/ui/svg.rs @@ -0,0 +1,142 @@ +use svg_fmt::*; + +use crate::*; + +const SIZE_FONT: f32 = 20.0; + +const COLORS: &'static [Color] = &[ + rgb(0, 170, 255), + rgb(255, 0, 0), + rgb(0, 255, 0), + rgb(255, 170, 0), + rgb(255, 0, 255), // pink + rgb(0, 128, 0), // dark green + rgb(0, 0, 128), // dark blue + rgb(128, 0, 128), // dark red +]; + +pub fn get_svg(size_factor: usize, function: &FunctionSpec, vars: &[Variable], block_masks: &[(usize, usize)]) -> String { + let size4 = size_factor / 4; + let size8 = size_factor / 8; + let delta = size_factor as f32 / 32.0; + let mut svg = String::new(); + + let (grid, w, h) = grid(vars.len()); + + let mut blocks = vec![vec![vec![]; w]; h]; + for (i, &(x, y)) in grid.iter().enumerate() { + for (idx, &(mask, inv_mask)) in block_masks.iter().enumerate() { + if check_mask(i, mask, inv_mask) { + blocks[y][x].push(idx); + } + } + } + + svg += &BeginSvg { w: w * size_factor + 3, h: h * size_factor + 3 }.to_string(); + for (i, &(x, y)) in grid.iter().enumerate() { + svg += &rectangle(1 + x * size_factor, 1 + y * size_factor, size_factor, size_factor) + .fill(white()) + .stroke(Stroke::Color(black(), 2.0)).to_string(); + let x_start = 1 + x * size_factor; + let x_end = 1 + (x+1) * size_factor; + let y_start = 1 + y * size_factor; + let y_end = 1 + (y+1) * size_factor; + let mut rect = rectangle(1 + x * size_factor, 1 + y * size_factor, size_factor, size_factor) + .fill(white()) + .stroke_opacity(0.7) + .inflate(-1, -1); + for (idx, &(mask, inv_mask)) in block_masks.iter().enumerate() { + rect = rect.inflate(-delta, -delta); + if check_mask(i, mask, inv_mask) { + // check cells around this one + let mut up = false; + let mut down = false; + let mut left = false; + let mut right = false; + for &var in vars { + let j = i ^ var; + if check_mask(j, mask, inv_mask) { + let (x2, y2) = grid[j]; + if (x + 1) % w == x2 && y == y2 { + right = true; + } + if x == x2 && (y + 1) % h == y2 { + down = true; + } + if x == x2 && y == (y2 + 1) % h { + up = true; + } + if x == (x2 + 1) % w && y == y2 { + left = true; + } + if !up && !down && !left && !right { + // happens when vars >= 5 (obviously) + } + } + } + let mut sides = rect.sides(); + for s in &mut sides { + *s = s.opacity(0.9); + } + let up = up && !(y == 0 && (0..h).all(|y| blocks[y][x].contains(&idx))); + let down = down && !(y == h-1 && (0..h).all(|y| blocks[y][x].contains(&idx))); + let left = left && !(x == 0 && (0..w).all(|x| blocks[y][x].contains(&idx))); + let right = right && !(x == w-1 && (0..w).all(|x| blocks[y][x].contains(&idx))); + if up { + svg += &sides[0].width(2.0).color(white()).to_string(); + if !left { + svg += &sides[2].width(2.0).color(COLORS[idx % COLORS.len()]).flip_to(0.0, y_start).to_string(); + } + if !right { + svg += &sides[3].width(2.0).color(COLORS[idx % COLORS.len()]).flip_to(0.0, y_start).to_string(); + } + } else { + svg += &sides[0].width(2.0).color(COLORS[idx % COLORS.len()]).to_string(); + } + if down { + svg += &sides[1].width(2.0).color(white()).to_string(); + if !left { + svg += &sides[2].width(2.0).color(COLORS[idx % COLORS.len()]).flip_to(0, y_end).to_string(); + } + if !right { + svg += &sides[3].width(2.0).color(COLORS[idx % COLORS.len()]).flip_to(0, y_end).to_string(); + } + } else { + svg += &sides[1].width(2.0).color(COLORS[idx % COLORS.len()]).to_string(); + } + if left { + svg += &sides[2].width(2.0).color(white()).to_string(); + if !up { + svg += &sides[0].width(2.0).color(COLORS[idx % COLORS.len()]).flip_to(x_start, 0).to_string(); + } + if !down { + svg += &sides[1].width(2.0).color(COLORS[idx % COLORS.len()]).flip_to(x_start, 0).to_string(); + } + } else { + svg += &sides[2].width(2.0).color(COLORS[idx % COLORS.len()]).to_string(); + } + if right { + svg += &sides[3].width(1.0).color(white()).to_string(); + if !up { + svg += &sides[0].width(2.0).color(COLORS[idx % COLORS.len()]).flip_to(x_end, 0).to_string(); + } + if !down { + svg += &sides[1].width(2.0).color(COLORS[idx % COLORS.len()]).flip_to(x_end, 0).to_string(); + } + } else { + svg += &sides[3].width(2.0).color(COLORS[idx % COLORS.len()]).to_string(); + } + } + } + svg += &text((x+1) * size_factor - size8, (y+1) * size_factor - size4 / 3, i.to_string()) + .align(Align::Right) + .size(SIZE_FONT) + .color(black()).to_string(); + svg += &text(x * size_factor + size4, (y+1) * size_factor - size4, function[i].to_string()) + .size(SIZE_FONT * 2.0) + .color(black()).to_string(); + } + svg += &EndSvg.to_string(); + + svg +} \ No newline at end of file