Skip to content

Commit

Permalink
refactor distinct sub-command into externally usable function, `disti…
Browse files Browse the repository at this point in the history
…nct_colors()`
  • Loading branch information
rivy committed Sep 30, 2019
1 parent 2c53d05 commit 8f46d22
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 54 deletions.
47 changes: 10 additions & 37 deletions src/cli/commands/distinct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,7 @@ use std::io::{self, Write};
use crate::commands::prelude::*;

use pastel::ansi::Stream;
use pastel::distinct::{
self, DistanceMetric, IterationStatistics, OptimizationMode, OptimizationTarget,
SimulatedAnnealing, SimulationParameters,
};
use pastel::random::{self, RandomizationStrategy};
use pastel::distinct::{self, DistanceMetric, IterationStatistics};
use pastel::{Fraction, HSLA};

pub struct DistinctCommand;
Expand Down Expand Up @@ -157,58 +153,35 @@ impl GenericCommand for DistinctCommand {
_ => unreachable!("Unknown distance metric"),
};

let mut colors = match matches.values_of("color") {
let fixed_colors = match matches.values_of("color") {
None => vec![],
Some(positionals) => {
ColorArgIterator::FromPositionalArguments(config, positionals, PrintSpectrum::Yes)
.collect::<Result<Vec<_>>>()?
}
};

let fixed_colors = colors.len();
if fixed_colors > count {
let num_fixed_colors = fixed_colors.len();
if num_fixed_colors > count {
return Err(PastelError::DistinctColorFixedColorsCannotBeMoreThanCount);
}

for _ in fixed_colors..count {
colors.push(random::strategies::UniformRGB.generate());
}

let mut annealing = SimulatedAnnealing::new(
&colors,
SimulationParameters {
initial_temperature: 3.0,
cooling_rate: 0.95,
num_iterations: 100_000,
opt_target: OptimizationTarget::Mean,
opt_mode: OptimizationMode::Global,
distance_metric,
fixed_colors,
},
);

let mut callback: Box<dyn FnMut(&IterationStatistics)> = if verbose_output {
let callback: Box<dyn FnMut(&IterationStatistics)> = if verbose_output {
Box::new(|stats: &IterationStatistics| {
let stderr = io::stderr();
let brush_stderr = Brush::from_environment(Stream::Stderr);
print_iteration(&mut stderr.lock(), &brush_stderr, stats).ok();
})
} else {
Box::new(|_: &IterationStatistics| {})
};

annealing.run(callback.as_mut());

annealing.parameters.initial_temperature = 0.5;
annealing.parameters.cooling_rate = 0.98;
annealing.parameters.num_iterations = 200_000;
annealing.parameters.opt_target = OptimizationTarget::Min;
annealing.parameters.opt_mode = OptimizationMode::Local;

let result = annealing.run(callback.as_mut());
let (mut colors, distance_result) =
distinct::distinct_colors(count, distance_metric, fixed_colors, callback);

if matches.is_present("print-minimal-distance") {
writeln!(out.handle, "{:.3}", result.min_closest_distance)?;
writeln!(out.handle, "{:.3}", distance_result.min_closest_distance)?;
} else {
let mut colors = annealing.get_colors();
distinct::rearrange_sequence(&mut colors, distance_metric);

if verbose_output {
Expand Down
81 changes: 64 additions & 17 deletions src/distinct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ pub struct DistanceResult {
pub distance_metric: DistanceMetric,

/// The number of colors that are fixed and cannot be changed. The actual colors are the first
/// `fixed_colors` elements in the `colors` array.
pub fixed_colors: usize,
/// `num_fixed_colors` elements in the `colors` array.
pub num_fixed_colors: usize,
}

pub struct IterationStatistics<'a> {
Expand Down Expand Up @@ -61,7 +61,7 @@ pub struct SimulationParameters {
pub opt_target: OptimizationTarget,
pub opt_mode: OptimizationMode,
pub distance_metric: DistanceMetric,
pub fixed_colors: usize,
pub num_fixed_colors: usize,
}

pub struct SimulatedAnnealing<R: Rng> {
Expand Down Expand Up @@ -130,23 +130,23 @@ impl<R: Rng> SimulatedAnnealing<R> {
let mut result = DistanceResult::new(
&self.colors,
self.parameters.distance_metric,
self.parameters.fixed_colors,
self.parameters.num_fixed_colors,
);

if self.parameters.fixed_colors == self.colors.len() {
if self.parameters.num_fixed_colors == self.colors.len() {
return result;
}

for iter in 0..self.parameters.num_iterations {
let random_index = if self.parameters.opt_target == OptimizationTarget::Mean {
self.rng
.gen_range(self.parameters.fixed_colors, self.colors.len())
.gen_range(self.parameters.num_fixed_colors, self.colors.len())
} else {
// first check if any of the colors cannot change, if that's the case just return
// the other color. Note that the closest_pair cannot contain only fixed colors.
if result.closest_pair.0 < self.parameters.fixed_colors {
if result.closest_pair.0 < self.parameters.num_fixed_colors {
result.closest_pair.1
} else if result.closest_pair.1 < self.parameters.fixed_colors {
} else if result.closest_pair.1 < self.parameters.num_fixed_colors {
result.closest_pair.0
} else {
if self.rng.gen() {
Expand All @@ -158,7 +158,7 @@ impl<R: Rng> SimulatedAnnealing<R> {
};

debug_assert!(
random_index >= self.parameters.fixed_colors,
random_index >= self.parameters.num_fixed_colors,
"cannot change fixed color"
);

Expand Down Expand Up @@ -243,15 +243,61 @@ pub fn rearrange_sequence(colors: &mut Vec<Color>, metric: DistanceMetric) {
}
}

pub fn distinct_colors(
count: usize,
distance_metric: DistanceMetric,
fixed_colors: Vec<Color>,
mut callback: Box<dyn FnMut(&IterationStatistics)>,
) -> (Vec<Color>, DistanceResult) {
assert!(count > 1);
assert!(fixed_colors.len() <= count);

let num_fixed_colors = fixed_colors.len();
let mut colors = fixed_colors;

for _ in num_fixed_colors..count {
colors.push(random::strategies::UniformRGB.generate());
}

let mut annealing = SimulatedAnnealing::new(
&colors,
SimulationParameters {
initial_temperature: 3.0,
cooling_rate: 0.95,
num_iterations: 100_000,
opt_target: OptimizationTarget::Mean,
opt_mode: OptimizationMode::Global,
distance_metric,
num_fixed_colors,
},
);

annealing.run(callback.as_mut());

annealing.parameters.initial_temperature = 0.5;
annealing.parameters.cooling_rate = 0.98;
annealing.parameters.num_iterations = 200_000;
annealing.parameters.opt_target = OptimizationTarget::Min;
annealing.parameters.opt_mode = OptimizationMode::Local;

let result = annealing.run(callback.as_mut());

(annealing.get_colors(), result)
}

impl DistanceResult {
fn new(colors: &[(Color, Lab)], distance_metric: DistanceMetric, fixed_colors: usize) -> Self {
fn new(
colors: &[(Color, Lab)],
distance_metric: DistanceMetric,
num_fixed_colors: usize,
) -> Self {
let mut result = DistanceResult {
closest_distances: vec![(scalar::MAX, std::usize::MAX); colors.len()],
closest_pair: (std::usize::MAX, std::usize::MAX),
mean_closest_distance: 0.0,
min_closest_distance: scalar::MAX,
distance_metric,
fixed_colors,
num_fixed_colors,
};

for i in 0..colors.len() {
Expand Down Expand Up @@ -309,16 +355,16 @@ impl DistanceResult {
let mut closest_pair_set = false;

for (i, (dist, closest_i)) in self.closest_distances.iter().enumerate() {
if i < self.fixed_colors && *closest_i < self.fixed_colors {
if i < self.num_fixed_colors && *closest_i < self.num_fixed_colors {
continue;
}

self.mean_closest_distance += *dist;

// the closest pair must ignore pairs of fixed_colors because we cannot change them. On
// the closest pair must ignore pairs of fixed colors because we cannot change them. On
// the other hand we can consider pairs with at least one non fixed color because we
// can change that.
if (i >= self.fixed_colors || *closest_i >= self.fixed_colors)
if (i >= self.num_fixed_colors || *closest_i >= self.num_fixed_colors)
&& (*dist < self.min_closest_distance || !closest_pair_set)
{
self.closest_pair = (i, *closest_i);
Expand All @@ -328,7 +374,8 @@ impl DistanceResult {
self.min_closest_distance = self.min_closest_distance.min(*dist);
}

self.mean_closest_distance /= (self.closest_distances.len() - self.fixed_colors) as Scalar;
self.mean_closest_distance /=
(self.closest_distances.len() - self.num_fixed_colors) as Scalar;
}

fn distance(&self, a: &(Color, Lab), b: &(Color, Lab)) -> Scalar {
Expand Down Expand Up @@ -387,7 +434,7 @@ mod tests {
opt_target: OptimizationTarget::Min,
opt_mode: OptimizationMode::Local,
distance_metric: DistanceMetric::CIE76,
fixed_colors: 3,
num_fixed_colors: 3,
},
Xoshiro256StarStar::seed_from_u64(21),
);
Expand All @@ -412,7 +459,7 @@ mod tests {
opt_target: OptimizationTarget::Min,
opt_mode: OptimizationMode::Local,
distance_metric: DistanceMetric::CIE76,
fixed_colors: 1,
num_fixed_colors: 1,
},
Xoshiro256StarStar::seed_from_u64(42),
);
Expand Down

0 comments on commit 8f46d22

Please sign in to comment.