WASM port + misc. changes

This commit is contained in:
Arne Keller 2021-03-18 10:53:39 +01:00
parent 24139814cc
commit 4b0541c67a
11 changed files with 759 additions and 147 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
/target /target
/dist

166
Cargo.lock generated
View File

@ -1,5 +1,43 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # 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]] [[package]]
name = "either" name = "either"
version = "1.6.1" version = "1.6.1"
@ -15,14 +53,142 @@ dependencies = [
"either", "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]] [[package]]
name = "kv" name = "kv"
version = "0.0.1" version = "0.0.1"
dependencies = [ dependencies = [
"console_error_panic_hook",
"console_log",
"itertools", "itertools",
"log",
"svg_fmt", "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]] [[package]]
name = "svg_fmt" name = "svg_fmt"
version = "0.4.0" 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",
]

View File

@ -7,5 +7,10 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
console_error_panic_hook = "0.1.6"
console_log = "0.2.0"
itertools = "0.10.0" itertools = "0.10.0"
log = "0.4.14"
svg_fmt = { path = "../rust_debug/svg_fmt" } 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"] }

23
base.js Normal file
View File

@ -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);
}

49
index.html Normal file
View File

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<title>KV diagram calculator</title>
<link data-trunk rel="rust" data-bin="svg-wasm"/>
<style>
textarea {
resize: none;
font-size: 32px;
line-height: 64px;
width: 64px;
height: 64px;
padding-left: 24px;
box-sizing: border-box;
overflow-y: hidden;
}
#input-container {
transform: translate(0px, 0px); /* new absolute context */
padding-bottom: 1em;
}
.solution {
display: inline-block;
}
.solution span {
display: block;
width: 100%;
text-align: center;
}
</style>
</head>
<body>
<div id="main">
<div id="settings">
<label>Number of variables: <input type="number" value="3" id="variables"></label><button id="apply-variables">Apply</button>
</div>
<p id="hints">
Change cells by scrolling (up => 1, down => 0), typing (=> 1, 0) or deleting (=> -).
</p>
<p id="examples">
Load <a href="#--111100">example 1</a>, <a href="#-1111000-1-01---">example 2</a>, <a href="#11101111111111111111101111111101">example 3</a>.
</p>
<div id="input-container"></div>
<button id="calculate">Run</button>
<div id="output-help"></div>
<div id="output-container"></div>
</div>
</body>
</html>

243
src/bin/svg-wasm.rs Normal file
View File

@ -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<dyn FnMut()>);
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::<HtmlElement>()
}
fn get_output_help() -> HtmlElement {
web!(document).get_element_by_id("output-help").unwrap().unchecked_into::<HtmlElement>()
}
fn p(text: &str) -> HtmlElement {
let p = web!(document).create_element("p").unwrap().unchecked_into::<HtmlElement>();
p.set_inner_text(text);
p
}
fn span(text: &str) -> HtmlElement {
let p = web!(document).create_element("span").unwrap().unchecked_into::<HtmlElement>();
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::<Vec<_>>();
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::<char, _>(|&x| x.into())).collect::<String>();
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::<HtmlElement>();
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::<HtmlElement>();
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::<EventTarget>();
let closure = Closure::wrap(Box::new(|| run()) as Box<dyn FnMut()>);
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::<EventTarget>();
let closure = Closure::wrap(Box::new(|| update_input_grid(&grid(get_var_number()))) as Box<dyn FnMut()>);
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::<HtmlElement>()
}
fn get_input_el(idx: usize) -> Option<HtmlElement> {
web!(document).get_element_by_id(&format!("input{}", idx)).map(|x| x.unchecked_into::<HtmlElement>())
}
fn create_input_el(idx: usize, x: usize, y: usize) -> HtmlElement {
let input = web!(document).create_element("textarea").unwrap().unchecked_into::<HtmlTextAreaElement>();
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::<HtmlTextAreaElement>();
match &*el.value() {
"0" => Zero,
"1" => One,
_ => Any
}
}
fn set_input(idx: usize, out: Output) {
let el = get_input_el(idx).unwrap().unchecked_into::<HtmlTextAreaElement>();
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;
}
}
}

View File

@ -1,32 +1,23 @@
use std::env::args; use std::env::args;
use svg_fmt::*;
use kv::*; use kv::*;
const SIZE_FACTOR: usize = 64; 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() { fn main() {
let args = args().collect::<Vec<_>>(); let args = args().collect::<Vec<_>>();
let vars = [A, B, C]; 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, 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); let groups = find_groups(&function, &vars, One);
eprintln!("all:"); eprintln!("all:");
for x in &groups { for x in &groups {
eprintln!("{}", print_implicant(x)); eprintln!("{}", print_implicant(x));
} }
eprintln!("prime:"); eprintln!("prime:");
let (prime, other) = find_prime(&function, &vars, One, &groups); let (prime, other) = find_core(&function, &vars, One, &groups);
for x in &prime { for x in &prime {
eprintln!("{}", print_implicant(x)); eprintln!("{}", print_implicant(x));
} }
@ -54,93 +45,5 @@ fn main() {
.collect() .collect()
}; };
let (grid, w, h) = grid(3); println!("{}", svg::get_svg(SIZE_FACTOR, &function, &vars, &block_masks));
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);
} }

View File

@ -1,8 +1,9 @@
use std::fmt; use std::{fmt, str::FromStr};
use itertools::Itertools; use itertools::Itertools;
mod ui; mod ui;
use log::{debug, trace, warn};
pub use ui::*; pub use ui::*;
pub type Variable = usize; pub type Variable = usize;
@ -12,6 +13,8 @@ pub type BlockRef<'a> = &'a [(Variable, Output)];
pub const A: Variable = 1 << 0; pub const A: Variable = 1 << 0;
pub const B: Variable = 1 << 1; pub const B: Variable = 1 << 1;
pub const C: Variable = 1 << 2; pub const C: Variable = 1 << 2;
pub const D: Variable = 1 << 3;
pub const E: Variable = 1 << 4;
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Output { pub enum Output {
@ -25,45 +28,86 @@ pub use Output::*;
impl Output { impl Output {
fn to_num(self) -> usize { fn to_num(self) -> usize {
match self { match self {
Zero => 0, Zero => 0,
One => 1, One => 1,
Any => todo!() Any => todo!()
} }
} }
fn invert(self) -> Self { fn invert(self) -> Self {
match self { match self {
Zero => One, Zero => One,
One => Zero, One => Zero,
Any => Any Any => Any
} }
} }
} }
impl From<usize> for Output { impl From<usize> for Output {
fn from(x: usize) -> Self { fn from(x: usize) -> Self {
match x { match x {
0 => Zero, 0 => Zero,
1 => One, 1 => One,
_ => todo!() _ => Any
} }
} }
}
impl From<char> 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<Self, Self::Err> {
Ok(match s {
"0" => Zero,
"1" => One,
_ => Any
})
}
} }
impl fmt::Display for Output { impl fmt::Display for Output {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Zero => write!(f, "0"), Zero => write!(f, "0"),
One => write!(f, "1"), One => write!(f, "1"),
Any => write!(f, "-") Any => write!(f, "-")
} }
} }
}
impl Into<char> 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<Output>; pub type FunctionSpec = Vec<Output>;
pub fn find_groups(func: &FunctionSpec, vars: &[Variable], typ: Output) -> Vec<Vec<(Variable, Output)>> { pub fn find_groups(func: &FunctionSpec, vars: &[Variable], typ: Output) -> Vec<Vec<(Variable, Output)>> {
let mut groups: Vec<Vec<(Variable, Output)>> = Vec::new(); let mut groups: Vec<Vec<(Variable, Output)>> = 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 k in 1..vars.len()+1 {
for vars in vars.iter().combinations(k) { for vars in vars.iter().combinations(k) {
for var_bits in 0..(1 << k) { for var_bits in 0..(1 << k) {
@ -82,15 +126,18 @@ pub fn find_groups(func: &FunctionSpec, vars: &[Variable], typ: Output) -> Vec<V
inv_mask ^= if ((var_bits >> i) & 1) == 1 { 0 } else { var }; inv_mask ^= if ((var_bits >> i) & 1) == 1 { 0 } else { var };
} }
let mut fits = true; let mut fits = true;
let mut found_typ = false;
for input in 0..func.len() { for input in 0..func.len() {
if check_mask(input, mask, inv_mask) { if check_mask(input, mask, inv_mask) {
if func[input] == typ.invert() { if func[input] == typ.invert() {
fits = false; fits = false;
break; break;
} else if func[input] == typ {
found_typ = true;
} }
} }
} }
if fits { if fits && found_typ {
groups.push(var_setting); groups.push(var_setting);
} }
} }
@ -99,7 +146,7 @@ pub fn find_groups(func: &FunctionSpec, vars: &[Variable], typ: Output) -> Vec<V
groups groups
} }
pub fn find_prime<'a>(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)); assert_eq!(func.len(), 2usize.pow(vars.len() as u32));
let block_masks = blocks.iter() let block_masks = blocks.iter()
@ -130,11 +177,39 @@ pub fn find_prime<'a>(func: &FunctionSpec, vars: &[Variable], typ: Output, block
(a, b) (a, b)
} }
pub fn all_solutions(mut func: FunctionSpec, vars: &[Variable], typ: Output, prime: &[BlockRef], other: &[BlockRef]) -> Vec<Vec<Block>> { /// May return some incomplete solutions
pub fn all_solutions(func: FunctionSpec, vars: &[Variable], typ: Output, core: &[BlockRef], other: &[BlockRef]) -> Vec<Vec<Block>> {
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<Vec<usize>> {
let core_masks = core.iter()
.map(block_to_mask)
.collect::<Vec<_>>();
let other_masks = other.iter()
.map(block_to_mask)
.collect::<Vec<_>>();
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<Vec<usize>> {
assert_eq!(func.len(), 2usize.pow(vars.len() as u32)); assert_eq!(func.len(), 2usize.pow(vars.len() as u32));
// first mark all inputs covered by prime blocks as Any // first mark all inputs covered by core blocks as Any
let prime_masks = prime.iter() let prime_masks = core.iter()
.map(block_to_mask) .map(block_to_mask)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
'input: for input in 0..func.len() { '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() let other_masks = other.iter()
.map(block_to_mask) .map(block_to_mask)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
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<Vec<Block>> { fn all_recursive(func: FunctionSpec, vars: &[Variable], typ: Output, other: &[BlockRef], other_masks: &[(Variable, Variable)], start: usize) -> Vec<Vec<usize>> {
let mut all = Vec::new(); let mut all = Vec::new();
for i in 0..other.len() { 'block: for i in start..other.len() {
let block = &other[i]; trace!("trying {}", i);
let (mask, inv_mask) = other_masks[i]; let (mask, inv_mask) = other_masks[i];
for input in 0..func.len() { for input in 0..func.len() {
if func[input] != typ { if func[input] != typ {
continue; continue;
} }
if check_mask(input, mask, inv_mask) { if check_mask(input, mask, inv_mask) {
trace!("success by idx {}", input);
// this block is useful // this block is useful
// mark inputs as done // mark inputs as done
let mut func = func.clone(); let mut func = func.clone();
for input in 0..func.len() { for input in 0..func.len() {
if check_mask(input, mask, inv_mask) { if check_mask(input, mask, inv_mask) {
trace!("set {} to any", input);
func[input] = Any; 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() { if extensions.is_empty() {
all.push(vec![block.to_vec()]); all.push(vec![i]);
} }
for mut extension in extensions { for mut extension in extensions {
extension.push(block.to_vec()); if !extension.is_empty() {
all.push(extension); extension.push(i);
all.push(extension);
} else {
warn!("empty extension?");
}
} }
continue 'block;
} }
} }
} }
trace!("returning");
all all
} }
@ -211,10 +294,10 @@ pub fn print_implicate(vars: &[(Variable, Output)]) -> String {
let mut s = String::new(); let mut s = String::new();
for i in 0..vars.len() { for i in 0..vars.len() {
let (var, out) = vars[i]; let (var, out) = vars[i];
s.push(print_var(var));
if out == One { if out == One {
s += "~"; s += "̅";
} }
s += print_var(var);
if i != vars.len() - 1 { if i != vars.len() - 1 {
s += " v "; s += " v ";
} }
@ -226,10 +309,10 @@ pub fn print_implicant(vars: &[(Variable, Output)]) -> String {
let mut s = String::new(); let mut s = String::new();
for i in 0..vars.len() { for i in 0..vars.len() {
let (var, out) = vars[i]; let (var, out) = vars[i];
s.push(print_var(var));
if out == Zero { if out == Zero {
s += "~"; s += "̅";
} }
s += print_var(var);
if i != vars.len() - 1 { if i != vars.len() - 1 {
s += " "; s += " ";
} }
@ -237,13 +320,8 @@ pub fn print_implicant(vars: &[(Variable, Output)]) -> String {
s s
} }
fn print_var(var: Variable) -> &'static str { fn print_var(var: Variable) -> char {
match var { ('a' as u8 + (0usize.leading_zeros() - var.leading_zeros() - 1) as u8) as char
A => "a",
B => "b",
C => "c",
_ => "?"
}
} }
#[test] #[test]
@ -255,7 +333,7 @@ fn test_3var() {
vec![(A, One), (C, Zero)], vec![(A, One), (C, Zero)],
vec![(B, Zero), (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![ assert_eq!(prime, vec![
vec![(A, Zero), (B, Zero)], vec![(A, Zero), (B, Zero)],
vec![(A, One), (C, Zero)], vec![(A, One), (C, Zero)],

View File

@ -8,7 +8,7 @@ fn main() {
println!("{}", print_implicant(x)); println!("{}", print_implicant(x));
} }
println!("prime:"); 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 { for x in &prime {
println!("{}", print_implicant(x)); println!("{}", print_implicant(x));
} }

View File

@ -1,3 +1,5 @@
pub mod svg;
/// Returns grid coordinates, width, height /// Returns grid coordinates, width, height
pub fn grid(var_count: usize) -> (Vec<(usize, usize)>, usize, usize) { pub fn grid(var_count: usize) -> (Vec<(usize, usize)>, usize, usize) {
let mut grid = vec![(0, 0), (1, 0)]; let mut grid = vec![(0, 0), (1, 0)];

142
src/ui/svg.rs Normal file
View File

@ -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
}