Browse Source

Fixed some things and added multithreaded profiler

master
Thomas Johnson 3 years ago
parent
commit
eada768781
  1. 17
      src/clause_gen.rs
  2. 78
      src/karmarkar.rs
  3. 204
      src/main.rs
  4. 1
      src/maxsat.rs

17
src/clause_gen.rs

@ -1,6 +1,7 @@
// vim: ts=4 sw=4 et
use crate::clause::*;
use rand::distributions::{Distribution, Uniform};
/*
* Produces a unique variable name from a given usize. The variable name is
@ -21,8 +22,8 @@ fn get_var_name(n: usize) -> String {
/*
* Returns a list of integers from 0 to (n-1) in a random order.
*/
fn get_rand_list(n: usize) -> Vec<usize> {
let mut int_list: Vec<usize> = (0..n).collect();
fn get_rand_list(v: usize, n: usize) -> Vec<usize> {
let mut int_list: Vec<usize> = (0..v).collect();
let mut r_list: Vec<usize> = Vec::new();
for _ in 0..n {
let idx: usize = rand::random::<usize>() % int_list.len();
@ -34,9 +35,9 @@ fn get_rand_list(n: usize) -> Vec<usize> {
/*
* Generates a random clause from a slice of variables.
*/
pub fn gen_clause(var_list: &[Variable]) -> Clause {
pub fn gen_clause(var_list: &[Variable], size: usize) -> Clause {
let mut cl = Clause::new();
for i in get_rand_list(var_list.len()) {
for i in get_rand_list(var_list.len(), size) {
cl.push(var_list[i].get_literal(rand::random()));
}
cl
@ -46,16 +47,16 @@ pub fn gen_clause(var_list: &[Variable]) -> Clause {
* Generates a list of clauses that has [count] clauses that will have up to
* [max_vars] literals in them. No empty clauses are generated.
*/
pub fn gen_clause_list(count: usize, max_vars: usize) -> ClauseList {
pub fn gen_clause_list(count: usize, max_vars: usize, max_size: usize) -> ClauseList {
let mut cls = ClauseList::new();
let vars: Vec<Variable> = get_rand_list(max_vars)
let vars: Vec<Variable> = get_rand_list(max_vars, max_vars)
.into_iter()
.map(get_var_name)
.map(Variable::from_name)
.collect();
for _ in 0..count {
let idx = rand::random::<usize>() % (max_vars - 1) + 1;
cls.push(gen_clause(vars.split_at(idx).0));
let n = Uniform::from(1..=max_size).sample(&mut rand::thread_rng());
cls.push(gen_clause(&vars, n));
}
cls
}

78
src/karmarkar.rs

@ -489,16 +489,12 @@ impl StandardFormLP {
(vars, StandardFormLP { c, A, b })
}
pub fn from_ineqs_and_objective_with_varset(
pub fn from_constraints_and_objective(
varset: impl IntoIterator<Item = Variable>,
ineqs: impl IntoIterator<Item = LeqInequality>,
eqs: impl IntoIterator<Item = Equality>,
objective: LinearCombination,
) -> StandardFormLP {
let mut counter = NewVariableCounter::new();
let constraints = ineqs
.into_iter()
.map(|c| c.slacken(&mut counter))
.collect::<Vec<_>>();
let constraints = eqs.into_iter().collect::<Vec<_>>();
let vars = varset.into_iter().collect::<Vec<_>>();
let ncstr = constraints.len();
let nvars = vars.len();
@ -773,9 +769,9 @@ impl Karmarkar {
/// get accurate to within ε in the unprojected space, assuming the problem this instance is
/// solving is the result of projecting a lower-dimensional one. Otherwise, just try to be
/// accurate within ε in this space.
pub fn iterate_until_failed_or_precise(&mut self, unproject: bool) {
pub fn iterate_until_failed_or_precise(&mut self, unproject: bool, maxiters: usize) {
use IterationResult::*;
loop {
for _ in 0..maxiters {
match self.iterate() {
Failed() => break,
Distance(d) => {
@ -849,10 +845,14 @@ impl BMIPSolver {
objective: LinearCombination,
) -> BMIPSolver {
let constr_varset = constrained_vars.into_iter().collect::<HashSet<_>>();
let ineqs = ineqs.into_iter().collect::<Vec<_>>();
let mut counter = NewVariableCounter::new();
let constraints = ineqs
.into_iter()
.map(|x| x.slacken(&mut counter))
.collect::<Vec<_>>();
let mut varset = HashSet::new();
objective.add_vars_to(&mut varset);
ineqs.iter().for_each(|c| c.add_vars_to(&mut varset));
constraints.iter().for_each(|c| c.add_vars_to(&mut varset));
let freevars = varset
.difference(&constr_varset)
.cloned()
@ -864,9 +864,9 @@ impl BMIPSolver {
let mut projected_feasibility_problems = Vec::new();
let mut feasible_points = Vec::new();
let mut objective_offsets = Vec::new();
let problem = StandardFormLP::from_ineqs_and_objective_with_varset(
let problem = StandardFormLP::from_constraints_and_objective(
freevars.iter().cloned().chain(bvars.iter().cloned()),
ineqs,
constraints,
objective,
);
let proj_problem = Karmarkar::from_feasible_value(
@ -920,13 +920,13 @@ impl BMIPSolver {
}
fn solve_at_depth(&mut self, nassigned: usize) -> Option<Fl> {
let nvars = self.nvars();
let nvars = self.nvars() - nassigned;
self.feasible_points[nassigned].fill(1.0);
HomogenousLP::project_point_inv_inplace(
HomogenousLP::project_point_inplace(
&self.feasible_points[nassigned],
&mut self.projected_feasibility_problems[nassigned].guess,
);
self.projected_feasibility_problems[nassigned].iterate_until_failed_or_precise(true);
self.projected_feasibility_problems[nassigned].iterate_until_failed_or_precise(true, 1000);
HomogenousLP::project_point_inv_inplace(
&self.projected_feasibility_problems[nassigned].guess,
&mut self.feasible_points[nassigned],
@ -938,10 +938,10 @@ impl BMIPSolver {
HomogenousLP::project_point_inplace(
&self.projected_feasibility_problems[nassigned]
.guess
.rows(0, self.nvars()),
.rows(0, nvars),
&mut self.projected_problems[nassigned].guess,
);
self.projected_problems[nassigned].iterate_until_failed_or_precise(true);
self.projected_problems[nassigned].iterate_until_failed_or_precise(true, 1000);
HomogenousLP::project_point_inv_inplace(
&self.projected_problems[nassigned].guess,
&mut self.feasible_points[nassigned].rows_mut(0, nvars),
@ -960,12 +960,14 @@ impl BMIPSolver {
None => false,
Some((best, _)) => match self.solve_at_depth(nassigned) {
None => true,
Some(value) => value < best,
Some(value) => value < best + DEFAULT_ε,
},
}
}
fn record_maximum(&mut self, value: Fl) {
#[cfg(debug_mip)]
println!("{: ^width$}record!", "", width = self.bvars.len());
let best_solution = match &mut self.best_solution {
x @ None => {
*x = Some((value, DVector::zeros(self.bvars.len())));
@ -995,7 +997,7 @@ impl BMIPSolver {
}
fn assign(&mut self, nassigned: usize, v: Fl) {
let nvars = self.nvars();
let nvars = self.nvars() - nassigned;
self.bvars.iter().nth_back(nassigned).unwrap().assign(v);
let newb = &self.problems[nassigned].b - v * &self.problems[nassigned].A.column(nvars - 1);
self.problems[nassigned + 1].b.copy_from(&newb);
@ -1016,6 +1018,13 @@ impl BMIPSolver {
}
fn search_at_depth(&mut self, nassigned: usize) {
#[cfg(debug_mip)]
println!(
"{: ^width$}searching at depth {}",
"",
nassigned,
width = nassigned
);
if nassigned == self.bvars.len() {
self.accumulate_maximum(nassigned);
} else if !self.can_prune(nassigned) {
@ -1025,7 +1034,12 @@ impl BMIPSolver {
self.assign(nassigned, 1.0);
self.search_at_depth(nassigned + 1);
self.unassign(nassigned);
} else {
#[cfg(debug_mip)]
println!("{: ^width$} pruned!", "", width = nassigned);
}
#[cfg(debug_mip)]
println!("{: ^width$}done with {}", "", nassigned, width = nassigned);
}
pub fn assign_from_best_solution(&mut self) -> Option<Fl> {
@ -1059,7 +1073,7 @@ fn clause_to_inequality(
expr += varmap.get(&literal.get_variable()).unwrap();
}
}
expr << 1.0
Expr::from(1.0) << expr
}
/// Accepts a ClauseList and a list of weights. Entries correspond between the lists. If the weight
@ -1093,7 +1107,7 @@ where
let lvar = Variable::from_name(var.get_name().clone());
weightmap
.get(&var)
.map(|x| objective.insert_variable(lvar.clone(), *x));
.map(|x| objective.insert_variable(lvar.clone(), -*x));
varmap.insert(var.clone(), lvar);
}
let mut constraints = Vec::new();
@ -1108,11 +1122,25 @@ where
Fl: From<T>,
{
let (ineqs, objective, varmap) = clause_list_to_constraints(&mut clause_list, weights);
println!("maximize {}", objective);
println!("subject to");
for ineq in &ineqs {
println!(" {}", ineq);
}
let varset = varmap.values().cloned();
let mut solver = BMIPSolver::from_ineqs_and_objective(ineqs, varset, objective);
println!("solving");
solver.solve();
println!("done");
for (bv, lv) in varmap {
lv.get_assignment().map(|x| x > 0.5).map(|x| bv.assign(x));
lv.get_assignment()
.map(|x| x > 0.5)
.map(|x| bv.assign(x))
.or_else(|| {
println!("unassigning");
bv.unassign();
None
});
}
}
@ -1162,7 +1190,7 @@ mod test {
let mut km =
Karmarkar::from_feasible_value(proj_frp.clone(), proj_frp_feasible_point).unwrap();
km.iterate_until_failed_or_precise(true);
km.iterate_until_failed_or_precise(true, 1000);
let a0 = (km.guess.len() as Fl).recip();
print!("final x': {}", km.guess);
println!("sum of elements: {}", km.guess.sum());
@ -1191,7 +1219,7 @@ mod test {
let proj_feasible_point = HomogenousLP::project_point(&feasible_point);
let proj = HomogenousLP::project_from_sflp(sf.clone());
let mut km = Karmarkar::from_feasible_value(proj.clone(), proj_feasible_point).unwrap();
km.iterate_until_failed_or_precise(true);
km.iterate_until_failed_or_precise(true, 1000);
print!("final x': {}", km.guess);
let solution = HomogenousLP::project_point_inv(&km.guess);
println!("vars: {:?}", vars);

204
src/main.rs

@ -23,19 +23,201 @@ use crate::clause_gen::*;
use crate::maxsat::*;
use std::env;
use std::fs::File;
use std::io::{self, BufWriter, Write};
use std::mem::drop;
use std::process::exit;
use std::sync::mpsc::{channel, sync_channel};
use std::thread;
use std::time::{Duration, Instant};
fn main() {
let args : Vec<String> = std::env::args().collect();
if args.len() != 3 {
println!("BAD!");
std::process::exit(-1);
fn main() -> io::Result<()> {
let mut log = BufWriter::new(File::create("log.txt")?);
let args: Vec<String> = env::args().collect();
if args.len() != 5 {
println!("can't accept {} arguments!", args.len());
println!("Usage: {} <clause count max> <variable count max> <clause size> <samples per parameter set>", args[0]);
println!("ah ah ah ... you didn't say the magic word");
std::thread::sleep(Duration::from_millis(2000));
println!("ah ah ah ... you didn't say the magic word");
std::thread::sleep(Duration::from_millis(2000));
loop {
println!("ah ah ah ...");
std::thread::sleep(Duration::from_millis(800));
}
}
let clause_count : usize = args[1].parse().unwrap();
let var_count : usize = args[2].parse().unwrap();
let rand_clause_list = gen_clause_list(clause_count, var_count);
let max_clause_count: usize = args[1].parse().unwrap();
let max_var_count: usize = args[2].parse().unwrap();
let max_clause_size: usize = args[3].parse().unwrap();
let num_samples: usize = args[4].parse().unwrap();
println!("===== TRIAL RUN =====");
let rand_clause_list = gen_clause_list(max_clause_count, max_var_count, max_clause_size);
println!("{}", rand_clause_list);
println!("MAXSAT (By BFS): {}", solve_by_bfs(&rand_clause_list));
std::process::exit(0);
let solution = solve_by_bfs(&rand_clause_list);
println!("MAXSAT (By BFS): {}", solution);
let weights = vec![Some(1); rand_clause_list.len()];
let list = rand_clause_list.copy();
crate::karmarkar::ilp_maxsat(list, &weights);
println!(
"MAXSAT (By ILP): {}",
rand_clause_list
.iter()
.filter(|c| matches!(c.eval(), Some(true)))
.count()
);
println!("=== END TRIAL RUN ===");
let job_runner_generator = || {
|(clause_count, var_count, clause_size, which)| {
let rand_clause_list = gen_clause_list(clause_count, var_count, clause_size);
let time;
match which {
0 => {
let tick = Instant::now();
let _solution = solve_by_bfs(&rand_clause_list);
time = tick.elapsed().as_secs_f64();
}
1 => {
let weights = vec![Some(1); rand_clause_list.len()];
let list = rand_clause_list.clone();
let tick = Instant::now();
crate::karmarkar::ilp_maxsat(list, &weights);
time = tick.elapsed().as_secs_f64();
}
_ => panic!("unexpected job type"),
}
(clause_count, var_count, clause_size, which, time)
}
};
let (jtx, jrx) = sync_channel(0);
thread::spawn(move || {
for clause_count in 1..=max_clause_count {
for var_count in 1..=max_var_count {
for jobtype in 0..=1 {
for _ in 0..num_samples {
let _ = jtx.send((
clause_count,
var_count,
usize::min(max_clause_size, var_count),
jobtype,
));
}
}
}
}
});
let get_job = move || match jrx.recv() {
Ok(job) => Some(job),
_ => None,
};
let (rtx, rrx) = channel();
let mut result_map = std::collections::HashMap::new();
let result_loop = thread::spawn(move || {
while let Ok(Some(result)) = rrx.recv() {
let _: (usize, usize, usize, usize, f64) = result; // helping the type inference engine
result_map
.entry((result.0, result.1, result.2, result.3))
.and_modify(|i| *i += result.4)
.or_insert(result.4);
println!("{:?}", result);
}
for (k, v) in &result_map {
if k.3 == 0 {
let mut k = k.clone();
k.3 = 1;
let v2 = result_map.get(&k).unwrap();
writeln!(
log,
"{} {} {} {}",
k.0,
k.1,
v / num_samples as f64,
v2 / num_samples as f64
)
.unwrap();
}
}
});
let handler = move |result: Option<(usize, usize, usize, usize, f64)>| {
let _ = rtx.send(result);
};
//dispatcher(16, 10, job_runner_generator, get_job, handler);
let _ = result_loop.join();
exit(0);
}
fn dispatcher<Fw, J, R>(
nthreads: usize,
bufsize: usize,
worker_generator: impl Fn() -> Fw,
get_job: impl Fn() -> Option<J>,
handler: impl Fn(Option<R>),
) where
Fw: Fn(J) -> R + Send + 'static,
J: Send + 'static,
R: Send + 'static,
{
use std::thread::spawn;
let mut threads = Vec::new();
let (result_tx, result_rx) = channel();
for i in 0..nthreads {
let worker = worker_generator();
let (tx, rx) = channel();
let result_chan = result_tx.clone();
threads.push((
Some(tx),
spawn(move || loop {
match rx.recv() {
Ok(job) => {
let result = worker(job);
let _ = result_chan.send((i, result));
}
_ => break,
}
}),
));
}
drop(result_tx);
'outer: for _ in 0..bufsize {
for thread in threads.iter() {
match get_job() {
Some(job) => {
let _ = thread.0.as_ref().unwrap().send(job);
}
None => break 'outer,
}
}
}
loop {
let next_job = get_job();
match next_job {
Some(job) => {
let (i, result) = result_rx.recv().unwrap();
let _ = threads[i].0.as_ref().unwrap().send(job);
handler(Some(result));
}
None => break,
}
}
for thread in threads.iter_mut() {
thread.0.take();
}
loop {
match result_rx.recv() {
Ok((_, result)) => handler(Some(result)),
_ => break,
}
}
handler(None);
for thread in threads {
let _ = thread.1.join();
}
}

1
src/maxsat.rs

@ -9,6 +9,7 @@ fn bfs_step(cs: &ClauseList, vs: &Vec<Variable>, n: usize) -> usize {
.filter_map(|c| c.eval())
.map(|b| if b { 1 } else { 0 })
.sum();
#[cfg(debug_bfs)]
println!("{}\t= {}", cs, r);
r
} else {

Loading…
Cancel
Save