Permalink
Browse files

Bump to 0.2

This switches the CurvatureBounds enum to a set of traits, with derive
macros.

Also updates slog.
  • Loading branch information...
emallson committed Apr 18, 2017
1 parent 76ab066 commit 5baf300491e36bcfde2b75e462c4a52f45f9d1e7
Showing with 177 additions and 41 deletions.
  1. +4 −3 Cargo.toml
  2. +11 −0 avarice-derive/Cargo.toml
  3. +57 −0 avarice-derive/src/lib.rs
  4. +11 −14 src/greedy.rs
  5. +61 −0 src/lib.rs
  6. +33 −24 src/objective.rs
View
@@ -1,15 +1,16 @@
[package]
authors = ["J David Smith <emallson@atlanis.net>"]
name = "avarice"
-version = "0.1.0"
+version = "0.2.0"
[dependencies]
error-chain = "0.10.0"
fnv = "1.0.5"
rand = "0.3.15"
rayon = "0.6.0"
-slog = "1.5.2"
-slog-stdlog = "1.1.0"
+slog = "~2.0"
+slog-stdlog = "~2.0.0-0.2"
+avarice-derive = { path = "avarice-derive" }
[dev-dependencies]
quickcheck = {version = "0.4.1", git="https://github.com/emallson/quickcheck.git"}
View
@@ -0,0 +1,11 @@
+[package]
+authors = ["J David Smith <emallson@atlanis.net>"]
+name = "avarice-derive"
+version = "0.1.0"
+
+[lib]
+proc-macro = true
+
+[dependencies]
+quote = "0.3.15"
+syn = "0.11.10"
View
@@ -0,0 +1,57 @@
+extern crate proc_macro;
+extern crate syn;
+#[macro_use]
+extern crate quote;
+
+use proc_macro::TokenStream;
+use quote::Ident;
+
+#[proc_macro_derive(Submodular)]
+pub fn submodular(input: TokenStream) -> TokenStream {
+ let s = input.to_string();
+ let ast = syn::parse_derive_input(&s).unwrap();
+ let expanded = expand_bounds(&ast,
+ Ident::new("Submodular"),
+ Ident::new("None"),
+ Ident::new("Some(1.0)"));
+
+ expanded.parse().unwrap()
+}
+
+#[proc_macro_derive(Supermodular)]
+pub fn supermodular(input: TokenStream) -> TokenStream {
+ let s = input.to_string();
+ let ast = syn::parse_derive_input(&s).unwrap();
+ let expanded = expand_bounds(&ast,
+ Ident::new("Supermodular"),
+ Ident::new("Some(1.0)"),
+ Ident::new("None"));
+
+ expanded.parse().unwrap()
+}
+
+#[proc_macro_derive(Modular)]
+pub fn modular(input: TokenStream) -> TokenStream {
+ let s = input.to_string();
+ let ast = syn::parse_derive_input(&s).unwrap();
+ let expanded = expand_bounds(&ast,
+ Ident::new("Modular"),
+ Ident::new("Some(1.0)"),
+ Ident::new("Some(1.0)"));
+
+ expanded.parse().unwrap()
+}
+
+fn expand_bounds(ast: &syn::DeriveInput, ty: Ident, low: Ident, high: Ident) -> quote::Tokens {
+ let name = &ast.ident;
+ let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
+
+ quote! {
+ impl #impl_generics ::objective::curvature::#ty for #name #ty_generics #where_clause {}
+ impl #impl_generics ::objective::curvature::Bounded for #name #ty_generics #where_clause {
+ fn bounds() -> (Option<f64>, Option<f64>) {
+ (#low, #high)
+ }
+ }
+ }
+}
View
@@ -14,7 +14,7 @@
//! implementation still includes an avoidable copy.
use std::collections::BinaryHeap;
use std::cmp::Ordering;
-use slog::{DrainExt, Logger};
+use slog::{Drain, Logger};
use slog_stdlog::StdLog;
use objective::*;
use errors::*;
@@ -137,14 +137,11 @@ pub fn greedy<O: Objective + Sync>(obj: &O,
/// if high-value elements are dependent primarily on low-value elements, then this will eliminate
/// most insertions. On the other hand, if the highest-value elements have strong dependencies,
/// this will eliminate nearly none of the insertions.
-///
-/// # Panics
-/// Panics if the objective is not submodular.
-pub fn lazy_greedy<O: Objective>(obj: &O,
- k: usize,
- log: Option<Logger>)
- -> Result<(f64, Vec<O::Element>, O::State)> {
- assert!(O::curv_bounds() == Curvature::Submodular);
+pub fn lazy_greedy<O: Objective + curvature::Submodular>
+ (obj: &O,
+ k: usize,
+ log: Option<Logger>)
+ -> Result<(f64, Vec<O::Element>, O::State)> {
let log = log.unwrap_or_else(|| Logger::root(StdLog.fuse(), o!()));
let mut state = O::State::default();
let mut solset = Set::default();
@@ -250,11 +247,11 @@ pub fn lazy_greedy<O: Objective>(obj: &O,
///
/// # Panics
/// Panics if the objective is not submodular.
-pub fn lazier_greedy<O: Objective + LazyObjective>(obj: &O,
- k: usize,
- log: Option<Logger>)
- -> Result<(f64, Vec<O::Element>, O::State)> {
- assert!(O::curv_bounds() == Curvature::Submodular);
+pub fn lazier_greedy<O: Objective + LazyObjective + curvature::Submodular>
+ (obj: &O,
+ k: usize,
+ log: Option<Logger>)
+ -> Result<(f64, Vec<O::Element>, O::State)> {
let log = log.unwrap_or_else(|| Logger::root(StdLog.fuse(), o!()));
let mut state = O::State::default();
let mut solset = Set::default();
View
@@ -12,8 +12,69 @@ extern crate fnv;
#[cfg(test)]
#[macro_use]
extern crate quickcheck;
+#[cfg(test)]
+#[macro_use]
+extern crate avarice_derive;
pub mod greedy;
pub mod objective;
pub mod errors;
pub mod macros;
+
+#[cfg(test)]
+mod test {
+ use objective::*;
+ use objective::curvature::Bounded;
+ use errors::*;
+ use std::iter;
+
+ macro_rules! obj {
+ ($tr:ident, $name:ident) => {
+ #[derive($tr, Debug, Clone)]
+ struct $name {}
+
+ impl Objective for $name {
+ type Element = ();
+ type State = ();
+
+ fn elements(&self) -> ElementIterator<Self> {
+ Box::new(iter::empty())
+ }
+
+ fn depends(&self, _u: Self::Element, _st: &Self::State) -> Result<ElementIterator<Self>> {
+ Ok(Box::new(iter::empty()))
+ }
+
+ fn benefit(&self, _s: &Set<Self::Element>, _st: &Self::State) -> Result<f64> {
+ Ok(0.0)
+ }
+
+ fn insert_mut(&self, _u: Self::Element, _st: &mut Self::State) -> Result<()> {
+ Ok(())
+ }
+ }
+
+ }
+ }
+
+ obj!(Submodular, SubObj);
+
+ #[test]
+ fn submod_derived_bounds() {
+ assert!(SubObj::bounds() == (None, Some(1.0)));
+ }
+
+ obj!(Supermodular, SupObj);
+
+ #[test]
+ fn supmod_derived_bounds() {
+ assert!(SupObj::bounds() == (Some(1.0), None));
+ }
+
+ obj!(Modular, ModObj);
+
+ #[test]
+ fn mod_derived_bounds() {
+ assert!(ModObj::bounds() == (Some(1.0), Some(1.0)));
+ }
+}
View
@@ -20,26 +20,41 @@ pub type Set<E> = FnvHashSet<E>;
pub type ElementIterator<'a, O> = Box<Iterator<Item = <O as Objective>::Element> + 'a>;
-/// The curvature boundaries of an objective. Defaults to `Unbounded`.
-///
-/// This can be used to verify that any function taking an objective is actually applicable to that
-/// objective.
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
-pub enum Curvature {
- /// No known bounds
- Unbounded,
- /// Bounded either below, above, or both.
- Bounded(Option<usize>, Option<usize>),
- /// Curvature bounded above by 1
- Submodular,
- /// Curvature bounded below by 1
- Supermodular,
-}
+pub mod curvature {
+ //! Marker traits for Curvature bounds.
+ //! These allow compile-time verification that an algorithm called on an `Objective` has its
+ //! curvature constraints satisfied.
+ //!
+ //! As a concrete example, the `greedy::lazier_greedy` method requires a `Submodular`
+ //! objective. Previously, this was done by checking an enum and panicking if it failed. Marker
+ //! traits move the error from runtime to compilation.
-impl Default for Curvature {
- fn default() -> Self {
- Curvature::Unbounded
+ /// An objective with fixed bounds that are not the (0, 1) of `Submodular` or (1, ∞) of
+ /// `Supermodular`.
+ ///
+ /// Ideally, this trait would be implemented automatically for any `Submodular` or
+ /// `Supermodular` type. This may be done with an `avarice-derive` crate in the future.
+ pub trait Bounded: super::Objective {
+ fn bounds() -> (Option<f64>, Option<f64>);
}
+
+ /// A submodular objective, which satisfies `∀ S ⊆ T: f(S ∪ {e}) - f(S) ≥ f(T ∪ {e}) - f(T)` or (equivalently) has curvature bounded above by `1`.
+ ///
+ /// The `Bounded` implementation should return a fixed `(None, Some(1.0))`.
+ pub trait Submodular: Bounded {}
+
+ /// A supermodular objective, which satisfies `∀ S ⊆ T: f(S ∪ {e}) - f(S) ≤ f(T ∪ {e}) - f(T)` or (equivalently) has curvature bounded below by `1`.
+ ///
+ /// The `Bounded` implementation should return a fixed `(Some(1.0), None)`.
+ pub trait Supermodular: Bounded {}
+
+ /// A modular objective. The `Bounded` implementation should return a fixed `(Some(1.0),
+ /// Some(1.0))`.
+ ///
+ /// This trait is the least common. The semantics of this type of objective are best understood
+ /// by the equation `f(S) = Σf(e)`. That is, the objective is exactly the sum of the weights of
+ /// the elements of the set `S`.
+ pub trait Modular: Bounded {}
}
/// An objective to be optimized.
@@ -57,12 +72,6 @@ pub trait Objective: Sized {
/// cacheing.
type State: Default + Clone;
- /// The curvature structure of the objective. Used for some optimizations, defaults to
- /// `Unbounded` (which has no optimizations).
- fn curv_bounds() -> Curvature {
- Curvature::default()
- }
-
/// The domain of the solution.
fn elements(&self) -> ElementIterator<Self>;

0 comments on commit 5baf300

Please sign in to comment.