wrapping up

This commit is contained in:
Shautvast 2024-11-12 10:47:25 +01:00
parent 906818d864
commit be386182d5
5 changed files with 295 additions and 215 deletions

22
README.md Normal file
View file

@ -0,0 +1,22 @@
**find optimal optimal path for drones in a grid of assigned values**
*solution1*
* written in java
* single drone solution
* employs a floodfill algorithm
* where possible paths are always sorted in descending order of value (so far) of the points in the path
* and which uses backtracking to find alternative paths that may be of higher value
* non-recursivity ensures no issues for large grids
* the algorithm is embedded in a rest api
* it also has a html canvas frontend that draws the path on the grid
installation:
* install jdk22, and apache maven
* run `mvn spring-boot:run`
* go to http://localhost:8080
* in the console type `fly` [enter]
*solution2*
*written in rust
*single and multiple drones
*see tests and main for validity of the algorithm

247
solution2/src/algorithm.rs Normal file
View file

@ -0,0 +1,247 @@
use crate::grid::{Grid, Path, PathsResult, Point};
use rand::Rng;
use std::collections::HashSet;
use std::hash::{DefaultHasher, Hash, Hasher};
use std::time::SystemTime;
use std::{
sync::{Arc, Mutex},
thread,
};
#[allow(non_snake_case)]
pub fn find_optimal_path_for_n_drones(
grid: Grid,
ndrones: usize,
N: u16,
t: usize,
T: u128,
) -> PathsResult {
let arc = Arc::new(Mutex::new(grid));
let mut handles = vec![];
for _ in 0..ndrones {
let gridref = Arc::clone(&arc);
let mut rng = rand::thread_rng();
// start at random position
let x: u16 = rng.gen_range(0..N);
let y: u16 = rng.gen_range(0..N);
// start new thread for a single drone
let handle = thread::spawn(move || find_optimal_path(gridref, N, t, T, x, y));
handles.push(handle);
}
let mut paths = vec![];
for handle in handles {
paths.push(handle.join().unwrap());
}
let overall_score = paths.iter().map(|p| p.value).sum();
PathsResult {
paths,
overall_score,
}
}
/// finds the optimal path for a drone in a grid of points(x,y) that each has a fixed initial value
/// observing a point (hit) resets the value to 0, after wich it gradually increases with time in a fixed rate
/// N size of square grid (rows and cols)
/// t max length of a flight path
/// T max duration of the algorithm
/// x,y drone start position in the grid
#[allow(non_snake_case)]
pub fn find_optimal_path(
grid: Arc<Mutex<Grid>>,
N: u16,
t: usize,
T: u128,
x: u16,
y: u16,
) -> Path {
let mut paths_to_consider: Vec<Path> = Vec::new();
let mut taken_paths: HashSet<u64> = HashSet::new();
// starting point
let path = Path::new(Arc::clone(&grid), x, y);
// always current max
let mut max: Path = path.clone(); // sorry
// add the first path
paths_to_consider.push(path);
// keep track of time
let t0 = SystemTime::now();
let mut running = true;
// this is time spent for the path, grid keeps the global timeframe
// same as path length, so remove?
let mut discrete_elapsed = 0;
// will keep at most 8 new directions from current location
let mut new_directions = vec![];
let mut current_path;
while running && !paths_to_consider.is_empty() {
paths_to_consider.sort();
current_path = paths_to_consider.last().unwrap().clone(); // assert = Some
// evict paths that are of max len
while current_path.length() >= t {
_ = paths_to_consider.remove(paths_to_consider.len() - 1);
if current_path.value() > max.value() {
max = current_path.clone(); // sorry
}
current_path = paths_to_consider.last().unwrap().clone();
}
let head = current_path.last();
let x = head.x;
let y = head.y;
// create a list of directions to take
new_directions.clear();
{
let arc = Arc::clone(&grid);
let mut lock = arc.lock();
let grid = lock.as_mut().unwrap();
if y > 0 {
new_directions.push(Point::new(
x,
y - 1,
grid.get_value(x, y - 1, discrete_elapsed),
));
if x < N - 1 {
new_directions.push(Point::new(
x + 1,
y - 1,
grid.get_value(x + 1, y - 1, discrete_elapsed),
));
}
}
if x > 0 {
new_directions.push(Point::new(
x - 1,
y,
grid.get_value(x - 1, y, discrete_elapsed),
));
if y > 0 {
new_directions.push(Point::new(
x - 1,
y - 1,
grid.get_value(x - 1, y - 1, discrete_elapsed),
));
}
}
if x < N - 1 {
new_directions.push(Point::new(
x + 1,
y,
grid.get_value(x + 1, y, discrete_elapsed),
));
if y < N - 1 {
new_directions.push(Point::new(
x + 1,
y + 1,
grid.get_value(x + 1, y + 1, discrete_elapsed),
));
}
}
if y < N - 1 {
new_directions.push(Point::new(
x,
y + 1,
grid.get_value(x, y + 1, discrete_elapsed),
));
if x > 0 {
new_directions.push(Point::new(
x - 1,
y + 1,
grid.get_value(x - 1, y + 1, discrete_elapsed),
));
}
}
let mut points_added = false;
for point in new_directions.iter() {
if point.value > 0.0 {
let mut new_path = current_path.clone();
new_path.add(point.clone());
let mut s = DefaultHasher::new();
new_path.hash(&mut s);
let hash = s.finish();
if !taken_paths.contains(&hash) {
points_added = true;
grid.hit(point.x, point.y, discrete_elapsed);
paths_to_consider.push(new_path);
taken_paths.insert(hash);
}
}
}
if !points_added {
// dead end, evict
let ended = paths_to_consider.remove(paths_to_consider.len() - 1);
if ended.value > max.value {
max = ended;
}
}
}
// continue?
let t1 = SystemTime::now();
if let Ok(elapsed) = t1.duration_since(t0) {
if elapsed.as_millis() > T {
running = false;
}
}
discrete_elapsed += 1;
}
max
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
pub fn test_single_drone() {
let grid = Grid::new(20);
let opt = find_optimal_path(Arc::new(Mutex::new(grid.clone())), 100, 10, 1000, 9, 9);
let mut all_points: HashSet<Point> = HashSet::new();
for point in opt.points.iter() {
if all_points.contains(point) {
if all_points.contains(point) {
let iv = grid.get_initial_value(point.x, point.y);
assert!(point.value < iv);
}
}
all_points.insert(point.clone());
}
}
#[test]
pub fn test_multiple_drones() {
let grid = Grid::new(100);
let paths_result = find_optimal_path_for_n_drones(grid.clone(), 4, 100, 10, 1000);
let mut all_points: HashSet<Point> = HashSet::new();
for path in paths_result.paths.iter() {
for point in path.points.iter() {
if all_points.contains(point) {
let iv = grid.get_initial_value(point.x, point.y);
assert!(point.value < iv);
}
all_points.insert(point.clone());
}
}
}
}

View file

@ -5,8 +5,10 @@ use std::{
sync::{Arc, Mutex},
};
const RECOVERY_FACTOR: f32 = 0.01;
#[allow(non_snake_case)]
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct Grid {
data: Vec<Vec<u16>>,
// keep track of every point that has been hit at some time
@ -45,17 +47,24 @@ impl Grid {
}
}
pub fn get_initial_value(&self, x: u16, y: u16) -> f32 {
*(self.data.get(y as usize).unwrap().get(x as usize).unwrap()) as f32
}
pub fn get_value(&self, x: u16, y: u16, time: usize) -> f32 {
let hit = self.hits.get(&(x, y));
let initial_value = *(self.data.get(y as usize).unwrap().get(x as usize).unwrap()) as f32;
let initial_value = self.get_initial_value(x, y);
if let Some(hit_times) = hit {
for t in hit_times.iter().rev() {
if time > *t {
let elapsed_since_hit = (time - *t) as f32;
return f32::min(elapsed_since_hit * initial_value * 0.1, initial_value);
let value = f32::min(
elapsed_since_hit * initial_value * RECOVERY_FACTOR,
initial_value,
);
return value;
}
}
0.0
@ -75,7 +84,7 @@ impl Grid {
#[derive(Debug, Clone)]
pub struct Path {
points: LinkedList<Point>,
pub points: LinkedList<Point>,
pub value: f32,
}
@ -198,3 +207,9 @@ mod test {
assert_eq!(grid.get_value(0, 1, 0), 1.0);
}
}
#[derive(Debug)]
pub struct PathsResult {
pub paths: Vec<Path>,
pub overall_score: f32,
}

View file

@ -1,180 +1,2 @@
use grid::{Grid, Path, Point};
use std::collections::HashSet;
use std::hash::{DefaultHasher, Hash, Hasher};
use std::sync::{Arc, Mutex};
use std::time::SystemTime;
pub mod algorithm;
pub mod grid;
/// finds the optimal path for a drone in a grid of points(x,y) that each has a fixed initial value
/// observing a point (hit) resets the value to 0, after wich it gradually increases with time in a fixed rate
/// N size of square grid (rows and cols)
/// t max length of a flight path
/// T max duration of the algorithm
/// x,y drone start position in the grid
#[allow(non_snake_case)]
pub fn find_optimal_path(
grid: Arc<Mutex<Grid>>,
N: u16,
t: usize,
T: u128,
x: u16,
y: u16,
) -> Path {
let mut paths_to_consider: Vec<Path> = Vec::new();
let mut taken_paths: HashSet<u64> = HashSet::new();
// starting point
let path = Path::new(Arc::clone(&grid), x, y);
// always current max
let mut max: Path = path.clone(); // sorry
// add the first path
paths_to_consider.push(path);
// keep track of time
let t0 = SystemTime::now();
let mut running = true;
// this is time spent for the path, grid keeps the global timeframe
// same as path length, so remove?
let mut discrete_elapsed = 0;
// will keep at most 8 new directions from current location
let mut new_directions = vec![];
let mut current_path;
while running && !paths_to_consider.is_empty() {
paths_to_consider.sort();
current_path = paths_to_consider.last().unwrap().clone(); // assert = Some
// evict paths that are of max len
while current_path.length() >= t {
_ = paths_to_consider.remove(paths_to_consider.len() - 1);
if current_path.value() > max.value() {
max = current_path.clone(); // sorry
}
current_path = paths_to_consider.last().unwrap().clone();
}
let head = current_path.last();
let x = head.x;
let y = head.y;
// create a list of directions to take
new_directions.clear();
{
let arc = Arc::clone(&grid);
let mut lock = arc.lock();
let grid = lock.as_mut().unwrap();
if y > 0 {
new_directions.push(Point::new(
x,
y - 1,
grid.get_value(x, y - 1, discrete_elapsed),
));
if x < N - 1 {
new_directions.push(Point::new(
x + 1,
y - 1,
grid.get_value(x + 1, y - 1, discrete_elapsed),
));
}
}
if x > 0 {
new_directions.push(Point::new(
x - 1,
y,
grid.get_value(x - 1, y, discrete_elapsed),
));
if y > 0 {
new_directions.push(Point::new(
x - 1,
y - 1,
grid.get_value(x - 1, y - 1, discrete_elapsed),
));
}
}
if x < N - 1 {
new_directions.push(Point::new(
x + 1,
y,
grid.get_value(x + 1, y, discrete_elapsed),
));
if y < N - 1 {
new_directions.push(Point::new(
x + 1,
y + 1,
grid.get_value(x + 1, y + 1, discrete_elapsed),
));
}
}
if y < N - 1 {
new_directions.push(Point::new(
x,
y + 1,
grid.get_value(x, y + 1, discrete_elapsed),
));
if x > 0 {
new_directions.push(Point::new(
x - 1,
y + 1,
grid.get_value(x - 1, y + 1, discrete_elapsed),
));
}
}
let mut points_added = false;
for point in new_directions.iter() {
if point.value > 0.0 {
let mut new_path = current_path.clone();
new_path.add(point.clone());
let mut s = DefaultHasher::new();
new_path.hash(&mut s);
let hash = s.finish();
if !taken_paths.contains(&hash) {
points_added = true;
grid.hit(point.x, point.y, discrete_elapsed);
paths_to_consider.push(new_path);
taken_paths.insert(hash);
}
}
}
if !points_added {
// dead end, evict
let ended = paths_to_consider.remove(paths_to_consider.len() - 1);
if ended.value > max.value {
max = ended;
}
}
}
// continue?
let t1 = SystemTime::now();
if let Ok(elapsed) = t1.duration_since(t0) {
if elapsed.as_millis() > T {
running = false;
}
}
discrete_elapsed += 1;
}
max
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
pub fn test() {
let mut grid = Grid::new(20);
let opt = find_optimal_path(Arc::new(Mutex::new(grid)), 100, 10, 1000, 9, 9);
println!("value {:?}", opt);
}
}

View file

@ -1,36 +1,10 @@
use rand::Rng;
use solution2::{find_optimal_path, grid::Grid};
use std::{
sync::{Arc, Mutex},
thread,
};
use solution2::{algorithm::find_optimal_path_for_n_drones, grid::Grid};
/// this app calculates paths for 4 drones, concurrently, with a shared grid
fn main() {
find_optimal_path_for_n_drones(4, 100, 10, 1000);
}
#[allow(non_snake_case)]
pub fn find_optimal_path_for_n_drones(ndrones: usize, N: u16, t: usize, T: u128) {
let grid = Grid::new(100);
let arc = Arc::new(Mutex::new(grid));
let mut handles = vec![];
for _ in 0..ndrones {
let gridref = Arc::clone(&arc);
let mut rng = rand::thread_rng();
// start at random position
let x: u16 = rng.gen_range(0..100);
let y: u16 = rng.gen_range(0..100);
// start new thread for a single drone
let handle = thread::spawn(move || find_optimal_path(gridref, N, t, T, x, y));
handles.push(handle);
}
for handle in handles {
let result = handle.join().unwrap();
println!("{result:?}");
let result = find_optimal_path_for_n_drones(Grid::new(100), 4, 100, 15, 2000);
for (i, path) in result.paths.iter().enumerate() {
println!("path {}: score {}", i, path.value())
}
println!("overall: {}", result.overall_score);
}