Lots of changes; search works but is slow

This commit is contained in:
2023-06-22 16:55:50 +02:00
parent 0d3a637982
commit 5e3a57acdb
3 changed files with 212 additions and 55 deletions

View File

@@ -28,7 +28,6 @@ fn parse_coords(c: TapeObject) -> Result<Point, &'static str> {
for (k,v) in c {
let v = v.as_f64().ok_or("Coord value must be f64")? as f32;
let v = (v * 32.) as i32;
match k {
"x" => pt.0 = v,
"y" => pt.1 = v,
@@ -114,13 +113,13 @@ fn main() -> anyhow::Result<()> {
// Small region
#[cfg(ignore)]
let region = Box::from_corners(
Point(11900*32, -3800*32, -11510*32),
Point(17400*32, 3800*32, -6500*32),
Point(11900., -3800., -11510.),
Point(17400., 3800., -6500.),
);
// Everything southeast of Haffner 18 LSS 27
let region = Box::from_corners(
Point(11900*32, -3800*32, -18510*32),
Point(64400*32, 3800*32, -6500*32),
Point(11900., -3800., -18510.),
Point(64400., 3800., -6500.),
);
let mut reg_name = std::io::BufWriter::new(std::fs::File::create("region.nam")?);
@@ -128,23 +127,8 @@ fn main() -> anyhow::Result<()> {
let mut name_pos = 0;
for mut sys in f.filter_map(id).filter(|sys| region.contains(sys.coords)) {
sys.name_off = name_pos as usize;
let hdr = [sys.name.len().min(255) as u8];
if hdr[0] == 255 {
eprintln!("WARNING: Long system name {}", sys.name);
}
reg_name.write(&hdr[..])?;
reg_name.write(&sys.name.as_bytes()[..hdr[0] as usize])?;
name_pos += hdr[0] as u32 + 1;
reg_data.write(&sys.coords.0 .to_be_bytes()[..])?;
reg_data.write(&sys.coords.1 .to_be_bytes()[..])?;
reg_data.write(&sys.coords.2 .to_be_bytes()[..])?;
let rest = (sys.star_flags as u32) + (name_pos << 4);
reg_data.write(&rest.to_be_bytes()[..])?;
sys.write_name(&mut reg_name)?;
sys.write_to(&mut reg_data);
}
Ok(())
}

View File

@@ -1,13 +1,20 @@
use std::{io::Cursor, cmp::{Ordering, Reverse}, collections::{VecDeque, BinaryHeap}};
use std::{io::Cursor, cmp::{Ordering, Reverse}, collections::{VecDeque, BinaryHeap, HashMap}, hash::Hash};
use edalyze::bindata::*;
use rstar::primitives::{PointWithData, GeomWithData};
#[derive(Default)]
struct PrevLink {
system: usize,
distance: f32,
uses_jumponium: bool,
}
struct IndexData {
cost: f32,
reachable: bool,
last: usize,
last: PrevLink,
sys_data: System,
}
@@ -16,7 +23,7 @@ impl From<System> for IndexData {
Self {
cost: f32::INFINITY,
reachable: false,
last: 0,
last: PrevLink::default(),
sys_data: sys,
}
}
@@ -26,11 +33,14 @@ struct SearchNode {
id: usize,
cost: f32,
heuristic: f32,
last: (usize, bool)
}
trait SearchState {
type Id;
fn cost(&self) -> f32;
fn heuristic(&self) -> f32;
fn id(&self) -> Self::Id;
}
// ordering tuned for BinaryHeap
@@ -53,11 +63,28 @@ impl PartialEq for SearchNode {
}
impl Eq for SearchNode {}
impl SearchState for SearchNode {
type Id = usize;
fn cost(&self) -> f32 {
self.cost
}
fn heuristic(&self) -> f32 {
self.heuristic
}
fn id(&self) -> Self::Id {
self.id
}
}
trait SearchQueue<T> {
fn q_next(&mut self) -> Option<T>;
fn q_empty(&self) -> bool;
fn q_push(&mut self, item: T);
fn q_clear(&mut self);
fn q_len(&self) -> usize;
}
struct SearchEnv<Q> {
@@ -68,6 +95,7 @@ struct SearchEnv<Q> {
target_system: usize,
jumponium_cost_factor: f32,
queue: Q,
visits: usize,
}
impl<Q: SearchQueue<SearchNode>> SearchEnv<Q> {
@@ -75,13 +103,13 @@ impl<Q: SearchQueue<SearchNode>> SearchEnv<Q> {
for item in self.meta.iter_mut() {
item.cost = f32::INFINITY;
item.reachable = false;
item.last = 0;
}
self.queue.q_clear();
let init_node = SearchNode{
id: self.init_system,
cost: 0,
cost: 0.,
heuristic: self.heuristic(self.init_system),
last: (self.init_system, false),
};
self.queue.q_push(init_node);
}
@@ -89,8 +117,34 @@ impl<Q: SearchQueue<SearchNode>> SearchEnv<Q> {
fn is_done(&self) -> bool {
self.queue.q_empty() || self.meta[self.target_system].reachable
}
fn search(&mut self) {
//
fn search(&mut self) -> Option<f32> {
self.reset();
eprintln!("");
while let Some(node) = self.queue.q_next() {
// check whether node should be re-expanded
{
let scoord = self.meta[node.last.0].sys_data.coords;
let n = &mut self.meta[node.id];
if n.reachable && n.cost < node.cost {
continue;
}
n.cost = node.cost;
n.reachable = true;
n.last = PrevLink {
uses_jumponium: node.last.1,
system: node.last.0,
distance: n.sys_data.coords.distance(scoord),
};
}
self.visits += 1;
if self.visits % 1000 == 0 {
eprintln!("\x1b[1A\x1b[K{}k ({} in queue)", self.visits/100, self.queue.q_len());
}
self.visit_star(node.id, node.cost);
}
let tsys = &self.meta[self.target_system];
tsys.reachable.then_some(tsys.cost)
}
fn heuristic(&self, sys_id: usize) -> f32 {
@@ -100,16 +154,32 @@ impl<Q: SearchQueue<SearchNode>> SearchEnv<Q> {
(dist - self.jdist(sys_id)) / (4. * self.base_jump)
}
fn visit_star(&self, sys_id: usize, base_cost: f32) {
fn visit_star(&mut self, sys_id: usize, base_cost: f32) {
let star_jump = self.jdist(sys_id);
let star_jump_sq = self.boosted_jump * self.boosted_jump;
let star_jump_sq = star_jump * star_jump;
let base_jump_sq = self.base_jump * self.base_jump;
let augm_jump_sq = base_jump_sq * 4.;
let max_jump_sq = augm_jump_sq.max(star_jump_sq);
//eprintln!("Max jump: {max_jump_sq}");
let cur_pos = self.meta[sys_id].sys_data.coords;
for n_star in self.rst.locate_within_distance(cur_pos, (max_jump_sq * 1024.).ceil() as i32) {
for n_star in self.rst.locate_within_distance(cur_pos, max_jump_sq) {
//eprintln!("Examining {}", self.meta[n_star.data].sys_data.name);
let dist_sq = n_star.geom().distance_sq(cur_pos);
let (cost_factor, _range, uses_jumponium) = if dist_sq > max_jump_sq {
continue;
} else if dist_sq < star_jump_sq {
(1., star_jump, false)
} else {
// must be a jumponium jump
(self.jumponium_cost_factor, 2. * self.base_jump, true)
};
// TODO: make an estimate of the fuel used
let cost = base_cost + cost_factor;
let heuristic = self.heuristic(n_star.data);
self.queue.q_push(SearchNode { id: n_star.data, cost, heuristic, last: (sys_id, uses_jumponium) });
}
}
@@ -125,17 +195,74 @@ impl<T> SearchQueue<T> for BFS<T> {
fn q_empty(&self) -> bool { self.0.is_empty() }
fn q_push(&mut self, item: T) { self.0.push_back(item); }
fn q_clear(&mut self) { self.0.clear(); }
fn q_len(&self) -> usize { self.0.len() }
}
impl<T> Default for BFS<T> {
fn default() -> Self { BFS(VecDeque::new()) }
}
struct CostCompare<T: SearchState>(T);
impl<T: SearchState> Ord for CostCompare<T> {
fn cmp(&self, other: &Self) -> Ordering {
f32::total_cmp(&self.0.cost(), &other.0.cost()).reverse()
}
}
impl<T: SearchState> PartialOrd for CostCompare<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<T: SearchState> Eq for CostCompare<T> {
}
impl<T: SearchState> PartialEq for CostCompare<T> {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == Ordering::Equal
}
}
struct BFS1<T: SearchState>{
q: BinaryHeap<CostCompare<T>>,
seen: HashMap<T::Id, f32>,
}
impl<T: SearchState> SearchQueue<T> for BFS1<T>
where T::Id: Hash+Eq {
fn q_next(&mut self) -> Option<T> { self.q.pop().map(|x| x.0) }
fn q_empty(&self) -> bool { self.q.is_empty() }
fn q_push(&mut self, item: T) {
let id = item.id();
if let Some(odist) = self.seen.get(&id) {
if *odist < item.cost() {
return;
}
}
self.seen.insert(id, item.cost());
self.q.push(CostCompare(item));
}
fn q_clear(&mut self) {
self.q.clear();
self.seen.clear();
}
fn q_len(&self) -> usize { self.q.len() }
}
impl<T: SearchState> Default for BFS1<T> {
fn default() -> Self {
BFS1{
q: BinaryHeap::new(),
seen: HashMap::new(),
}
}
}
struct DFS<T>(Vec<T>);
impl<T> SearchQueue<T> for DFS<T> {
fn q_next(&mut self) -> Option<T> { self.0.pop() }
fn q_empty(&self) -> bool { self.0.is_empty() }
fn q_push(&mut self, item: T) { self.0.push(item); }
fn q_clear(&mut self) { self.0.clear(); }
fn q_len(&self) -> usize { self.0.len() }
}
impl<T> Default for DFS<T> {
fn default() -> Self { DFS(Vec::new()) }
@@ -147,14 +274,26 @@ impl<T: Ord> SearchQueue<T> for AStar<T> {
fn q_empty(&self) -> bool { self.0.is_empty() }
fn q_push(&mut self, item: T) { self.0.push(Reverse(item)); }
fn q_clear(&mut self) { self.0.clear(); }
fn q_len(&self) -> usize { self.0.len() }
}
impl<T: Ord> Default for AStar<T> {
fn default() -> Self { AStar(BinaryHeap::new()) }
}
use structopt::StructOpt;
#[derive(StructOpt)]
struct Options {
#[structopt(short="r", default_value="10")]
jump_range: f32,
#[structopt(short="j", default_value="10")]
cost_factor: f32,
}
fn main() -> anyhow::Result<()> {
let opts = Options::from_args();
let mut reg_dat = Cursor::new(std::fs::read("region.dat")?);
let mut reg_nam = Cursor::new(std::fs::read("region.nam")?);
@@ -183,13 +322,48 @@ fn main() -> anyhow::Result<()> {
.0;
let mut env = SearchEnv {
rst,meta: systems,base_jump: 10., init_system, target_system,
queue: BFS::default()
rst,
meta: systems,
base_jump: opts.jump_range,
init_system,
target_system,
queue: BFS1::default(),
jumponium_cost_factor: opts.cost_factor,
visits: 0,
};
if let Some(cost) = env.search() {
eprintln!("Reached in {cost}");
// produce a trace...
let mut cur_id = env.target_system;
let mut stack = vec![];
let mut total_dist = 0.;
while cur_id != env.init_system {
let sys = &env.meta[cur_id];
stack.push((&sys.sys_data.name, sys.last.distance, sys.last.uses_jumponium));
total_dist += sys.last.distance;
cur_id = sys.last.system;
}
let mut jcount = 0;
for (name, dist, usej) in stack.iter().rev().copied() {
eprintln!(" {name:-40}: {} {dist:3.0}", if usej { "j" } else { "-" });
if usej {
jcount += 1;
}
}
let opt_dist = env.meta[env.init_system].sys_data.coords.distance(env.meta[env.target_system].sys_data.coords);
let eff = 100. * opt_dist / total_dist;
eprintln!("Total distance: {total_dist} ({eff:5.2}% eff)");
eprintln!("{jcount} boosts used");
} else {
eprintln!("Unreachable after {} visits", env.visits);
std::process::exit(1);
}
env.queue.q_push(SearchNode{
heuristic: 0., cost: 0., id: 0
});

View File

@@ -6,20 +6,20 @@ pub const IS_FUEL: u8 = 1;
pub const IS_WDRF: u8 = 2;
pub const IS_NEUT: u8 = 4;
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct Point(pub i32, pub i32, pub i32);
#[derive(Copy, Clone, Debug, Default, PartialEq)]
pub struct Point(pub f32, pub f32, pub f32);
#[derive(Copy, Clone, Debug)]
pub struct Box(pub Point, pub Point);
const fn isortp(a: i32, b: i32) -> (i32, i32) {
fn fsortp(a: f32, b: f32) -> (f32, f32) {
if a < b { (a,b) } else { (b,a) }
}
impl Box {
pub const fn from_corners(x: Point, y: Point) -> Box {
let (ax, bx) = isortp(x.0, y.0);
let (ay, by) = isortp(x.1, y.1);
let (az, bz) = isortp(x.2, y.2);
pub fn from_corners(x: Point, y: Point) -> Box {
let (ax, bx) = fsortp(x.0, y.0);
let (ay, by) = fsortp(x.1, y.1);
let (az, bz) = fsortp(x.2, y.2);
let pt1 = Point(ax, ay, az);
let pt2 = Point(bx, by, bz);
@@ -33,7 +33,6 @@ impl Box {
(self.0 .1 .. self.1 .1).contains(&x.1) &&
(self.0 .2 .. self.1 .2).contains(&x.2);
}
}
#[derive(Default)]
@@ -53,18 +52,18 @@ impl System {
return Ok(name_len + 1);
}
pub fn write_to(&self, writer: &mut dyn Write) -> io::Result<()> {
writer.write_i32::<BE>(self.coords.0)?;
writer.write_i32::<BE>(self.coords.1)?;
writer.write_i32::<BE>(self.coords.2)?;
writer.write_i32::<BE>((self.coords.0 * 32.) as i32)?;
writer.write_i32::<BE>((self.coords.1 * 32.) as i32)?;
writer.write_i32::<BE>((self.coords.2 * 32.) as i32)?;
let rest = self.star_flags as u32 + ((self.name_off as u32) << 4);
writer.write_u32::<BE>(rest)?;
Ok(())
}
pub fn read_from(r: &mut dyn Read) -> io::Result<Self> {
let x = r.read_i32::<BE>()?;
let y = r.read_i32::<BE>()?;
let z = r.read_i32::<BE>()?;
let x = r.read_i32::<BE>()? as f32 / 32.;
let y = r.read_i32::<BE>()? as f32 / 32.;
let z = r.read_i32::<BE>()? as f32 / 32.;
let rest = r.read_u32::<BE>()?;
let name_off = (rest >> 4) as usize;
let star_flags = (rest & 0xF) as u8;
@@ -102,7 +101,7 @@ impl System {
}
impl rstar::Point for Point {
type Scalar = i32;
type Scalar = f32;
const DIMENSIONS: usize = 3;
fn generate(mut gen: impl FnMut(usize) -> Self::Scalar) -> Self {
let x = gen(0);
@@ -134,9 +133,9 @@ impl Point {
pub fn distance_sq(&self, other: Self) -> f32 {
let Point(sx, sy, sz) = *self;
let Point(tx, ty, tz) = other;
let dx = sx as f32 - tx as f32;
let dy = sy as f32 - ty as f32;
let dz = sz as f32 - tz as f32;
let dx = sx - tx;
let dy = sy - ty;
let dz = sz - tz;
(dx*dx) + (dy*dy) + (dz*dz)
}