pub struct Model { /* private fields */ }
Expand description
Library entry point used to declare decision variables and constraints, and configure search.
Optimization problems are modeled with base decision variables and derived expressions. You can use constraints to restrict which values are considered valid assignments. An assignment that satisfies all constraints is called “feasible”. Once all decision variables and constraints have been declared, call one of the following:
- solve: get the first feasible assignment
- enumerate: iterate over all feasible assignments
- minimize: find the assignment that minimizes the provided expression
- maximize: find the assignment that maximizes the provided expression
- minimize_and_iterate: iterate over feasible assignments while minimizing an expression
- maximize_and_iterate: iterate over feasible assignments while maximizing an expression
Here is an example to describe how a typical model is formulated. It is a rendition of a combinatorial optimization classic: the Knapsack problem.
Let’s say we are building a brand new PC. We want to play AAA games without going bankrupt.
// All problem formulations will start with a model object
let mut m = copper::Model::default();
Variables and expressions
Decision variables represent a decision: they are declared with a domain.
// How many monitors do we buy: we need at least one, but not more than three
let n_monitors = m.new_var(1, 3).unwrap();
// All monitors cost the same, and each additional monitor provides the same bump to our score
let monitor_price = 100;
let monitor_score = 250;
// Each GPU model has a fixed price, and an associated benchmark score
let gpu_prices = [150, 250, 500];
let gpu_scores = [100, 400, 800];
// We use binary decision variables to represent "do I pick this GPU?"
let gpus: Vec<_> = m.new_vars_binary(gpu_scores.len()).collect();
Using variables as building blocks, we can create expressions to represent other quantities.
// Extension trait, used here to scale our decision variables by a constant (`times` method)
use copper::views::ViewExt;
// For each potential GPU, we multiply its price (and score) by whether or not it is selected.
// The sum of these terms gives us the price and score of the selected GPU.
let gpu_price = m.sum_iter(gpus.iter().zip(gpu_prices).map(|(x, price)| x.times(price)));
let gpu_score = m.sum_iter(gpus.iter().zip(gpu_scores).map(|(x, score)| x.times(score)));
// This expression is the overall price of our build
let price = m.add(gpu_price, n_monitors.times(monitor_price));
// We want to maximize this score: how much we'll value this particular build
let score = m.add(gpu_score, n_monitors.times(monitor_score));
Constraints
Constraints establish relationships between variables, and restrict feasible values.
// Exactly one GPU: we want to run Crysis, but our case must fit under the desk
let n_gpus = m.sum(&gpus);
m.equals(n_gpus, 1);
// Grandma got us some money for our birthday, that will be our budget
m.less_than_or_equals(price, 600);
Search
While constraints define feasibility, objectives are soft: they only determine optimality.
// Let the solver find the assignment that upholds our constraints and maximizes our score
let solution = m.maximize(score).unwrap();
// Our optimal build has three monitors and a mid-tier GPU. We even have some left-over cash!
assert_eq!(solution[n_monitors], 3);
assert_eq!(solution.get_values_binary(&gpus), vec![false, true, false]);
assert_eq!(solution[score], 1150);
assert_eq!(solution[price], 550);
Find the full code in the examples directory.
Implementations§
source§impl Model
impl Model
sourcepub fn new_var(&mut self, min: i32, max: i32) -> Option<VarId>
pub fn new_var(&mut self, min: i32, max: i32) -> Option<VarId>
Create a new integer decision variable, with the provided domain bounds.
Both lower and upper bounds are included in the domain.
This function will only create a decision variable if min < max
.
Examples found in repository?
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
fn main() {
// All problem formulations will start with a model object
let mut m = copper::Model::default();
// How many monitors do we buy: we need at least one, but not more than three
let n_monitors = m.new_var(1, 3).unwrap();
// All monitors cost the same, and each additional monitor provides the same bump to our score
let monitor_price = 100;
let monitor_score = 250;
// Each GPU model has a fixed price, and an associated benchmark score
let gpu_prices = [150, 250, 500];
let gpu_scores = [100, 400, 800];
// We use binary decision variables to represent "do I pick this GPU?"
let gpus: Vec<_> = m.new_vars_binary(gpu_scores.len()).collect();
// For each potential GPU, we multiply its price (and score) by whether or not it is selected.
// The sum of these terms gives us the price and score of the selected GPU.
let gpu_price = m.sum_iter(gpus.iter().zip(gpu_prices).map(|(x, price)| x.times(price)));
let gpu_score = m.sum_iter(gpus.iter().zip(gpu_scores).map(|(x, score)| x.times(score)));
// This expression is the overall price of our build
let price = m.add(gpu_price, n_monitors.times(monitor_price));
// We want to maximize this score: how much we'll value this particular build
let score = m.add(gpu_score, n_monitors.times(monitor_score));
// Exactly one GPU: we want to run Crysis, but our case must fit under the desk
let n_gpus = m.sum(&gpus);
m.equals(n_gpus, 1);
// Grandma got us some money for our birthday, that will be our budget
m.less_than_or_equals(price, 600);
// Let the solver find the assignment that upholds our constraints and maximizes our score
let solution = m.maximize(score).unwrap();
// Our optimal build has three monitors and a mid-tier GPU. We even have some left-over cash!
assert_eq!(solution[n_monitors], 3);
assert_eq!(solution.get_values_binary(&gpus), vec![false, true, false]);
assert_eq!(solution[score], 1150);
assert_eq!(solution[price], 550);
}
sourcepub fn new_vars(
&mut self,
n: usize,
min: i32,
max: i32
) -> Option<impl Iterator<Item = VarId> + '_>
pub fn new_vars( &mut self, n: usize, min: i32, max: i32 ) -> Option<impl Iterator<Item = VarId> + '_>
Create new integer decision variables, with the provided domain bounds.
All created variables will have the same starting domain bounds.
Both lower and upper bounds are included in the domain.
This function will only create decision variables if min < max
.
sourcepub fn new_var_binary(&mut self) -> VarIdBinary
pub fn new_var_binary(&mut self) -> VarIdBinary
Create a new binary decision variable.
sourcepub fn new_vars_binary(
&mut self,
n: usize
) -> impl Iterator<Item = VarIdBinary> + '_
pub fn new_vars_binary( &mut self, n: usize ) -> impl Iterator<Item = VarIdBinary> + '_
Create new binary decision variables.
Examples found in repository?
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
fn main() {
// All problem formulations will start with a model object
let mut m = copper::Model::default();
// How many monitors do we buy: we need at least one, but not more than three
let n_monitors = m.new_var(1, 3).unwrap();
// All monitors cost the same, and each additional monitor provides the same bump to our score
let monitor_price = 100;
let monitor_score = 250;
// Each GPU model has a fixed price, and an associated benchmark score
let gpu_prices = [150, 250, 500];
let gpu_scores = [100, 400, 800];
// We use binary decision variables to represent "do I pick this GPU?"
let gpus: Vec<_> = m.new_vars_binary(gpu_scores.len()).collect();
// For each potential GPU, we multiply its price (and score) by whether or not it is selected.
// The sum of these terms gives us the price and score of the selected GPU.
let gpu_price = m.sum_iter(gpus.iter().zip(gpu_prices).map(|(x, price)| x.times(price)));
let gpu_score = m.sum_iter(gpus.iter().zip(gpu_scores).map(|(x, score)| x.times(score)));
// This expression is the overall price of our build
let price = m.add(gpu_price, n_monitors.times(monitor_price));
// We want to maximize this score: how much we'll value this particular build
let score = m.add(gpu_score, n_monitors.times(monitor_score));
// Exactly one GPU: we want to run Crysis, but our case must fit under the desk
let n_gpus = m.sum(&gpus);
m.equals(n_gpus, 1);
// Grandma got us some money for our birthday, that will be our budget
m.less_than_or_equals(price, 600);
// Let the solver find the assignment that upholds our constraints and maximizes our score
let solution = m.maximize(score).unwrap();
// Our optimal build has three monitors and a mid-tier GPU. We even have some left-over cash!
assert_eq!(solution[n_monitors], 3);
assert_eq!(solution.get_values_binary(&gpus), vec![false, true, false]);
assert_eq!(solution[score], 1150);
assert_eq!(solution[price], 550);
}
sourcepub fn add(&mut self, x: impl View, y: impl View) -> VarId
pub fn add(&mut self, x: impl View, y: impl View) -> VarId
Create an expression of two views added together.
Examples found in repository?
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
fn main() {
// All problem formulations will start with a model object
let mut m = copper::Model::default();
// How many monitors do we buy: we need at least one, but not more than three
let n_monitors = m.new_var(1, 3).unwrap();
// All monitors cost the same, and each additional monitor provides the same bump to our score
let monitor_price = 100;
let monitor_score = 250;
// Each GPU model has a fixed price, and an associated benchmark score
let gpu_prices = [150, 250, 500];
let gpu_scores = [100, 400, 800];
// We use binary decision variables to represent "do I pick this GPU?"
let gpus: Vec<_> = m.new_vars_binary(gpu_scores.len()).collect();
// For each potential GPU, we multiply its price (and score) by whether or not it is selected.
// The sum of these terms gives us the price and score of the selected GPU.
let gpu_price = m.sum_iter(gpus.iter().zip(gpu_prices).map(|(x, price)| x.times(price)));
let gpu_score = m.sum_iter(gpus.iter().zip(gpu_scores).map(|(x, score)| x.times(score)));
// This expression is the overall price of our build
let price = m.add(gpu_price, n_monitors.times(monitor_price));
// We want to maximize this score: how much we'll value this particular build
let score = m.add(gpu_score, n_monitors.times(monitor_score));
// Exactly one GPU: we want to run Crysis, but our case must fit under the desk
let n_gpus = m.sum(&gpus);
m.equals(n_gpus, 1);
// Grandma got us some money for our birthday, that will be our budget
m.less_than_or_equals(price, 600);
// Let the solver find the assignment that upholds our constraints and maximizes our score
let solution = m.maximize(score).unwrap();
// Our optimal build has three monitors and a mid-tier GPU. We even have some left-over cash!
assert_eq!(solution[n_monitors], 3);
assert_eq!(solution.get_values_binary(&gpus), vec![false, true, false]);
assert_eq!(solution[score], 1150);
assert_eq!(solution[price], 550);
}
sourcepub fn sum(&mut self, xs: &[impl View]) -> VarId
pub fn sum(&mut self, xs: &[impl View]) -> VarId
Create an expression of the sum of a slice of views.
Examples found in repository?
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
fn main() {
// All problem formulations will start with a model object
let mut m = copper::Model::default();
// How many monitors do we buy: we need at least one, but not more than three
let n_monitors = m.new_var(1, 3).unwrap();
// All monitors cost the same, and each additional monitor provides the same bump to our score
let monitor_price = 100;
let monitor_score = 250;
// Each GPU model has a fixed price, and an associated benchmark score
let gpu_prices = [150, 250, 500];
let gpu_scores = [100, 400, 800];
// We use binary decision variables to represent "do I pick this GPU?"
let gpus: Vec<_> = m.new_vars_binary(gpu_scores.len()).collect();
// For each potential GPU, we multiply its price (and score) by whether or not it is selected.
// The sum of these terms gives us the price and score of the selected GPU.
let gpu_price = m.sum_iter(gpus.iter().zip(gpu_prices).map(|(x, price)| x.times(price)));
let gpu_score = m.sum_iter(gpus.iter().zip(gpu_scores).map(|(x, score)| x.times(score)));
// This expression is the overall price of our build
let price = m.add(gpu_price, n_monitors.times(monitor_price));
// We want to maximize this score: how much we'll value this particular build
let score = m.add(gpu_score, n_monitors.times(monitor_score));
// Exactly one GPU: we want to run Crysis, but our case must fit under the desk
let n_gpus = m.sum(&gpus);
m.equals(n_gpus, 1);
// Grandma got us some money for our birthday, that will be our budget
m.less_than_or_equals(price, 600);
// Let the solver find the assignment that upholds our constraints and maximizes our score
let solution = m.maximize(score).unwrap();
// Our optimal build has three monitors and a mid-tier GPU. We even have some left-over cash!
assert_eq!(solution[n_monitors], 3);
assert_eq!(solution.get_values_binary(&gpus), vec![false, true, false]);
assert_eq!(solution[score], 1150);
assert_eq!(solution[price], 550);
}
sourcepub fn sum_iter(&mut self, xs: impl IntoIterator<Item = impl View>) -> VarId
pub fn sum_iter(&mut self, xs: impl IntoIterator<Item = impl View>) -> VarId
Create an expression of the sum of an iterator of views.
Examples found in repository?
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
fn main() {
// All problem formulations will start with a model object
let mut m = copper::Model::default();
// How many monitors do we buy: we need at least one, but not more than three
let n_monitors = m.new_var(1, 3).unwrap();
// All monitors cost the same, and each additional monitor provides the same bump to our score
let monitor_price = 100;
let monitor_score = 250;
// Each GPU model has a fixed price, and an associated benchmark score
let gpu_prices = [150, 250, 500];
let gpu_scores = [100, 400, 800];
// We use binary decision variables to represent "do I pick this GPU?"
let gpus: Vec<_> = m.new_vars_binary(gpu_scores.len()).collect();
// For each potential GPU, we multiply its price (and score) by whether or not it is selected.
// The sum of these terms gives us the price and score of the selected GPU.
let gpu_price = m.sum_iter(gpus.iter().zip(gpu_prices).map(|(x, price)| x.times(price)));
let gpu_score = m.sum_iter(gpus.iter().zip(gpu_scores).map(|(x, score)| x.times(score)));
// This expression is the overall price of our build
let price = m.add(gpu_price, n_monitors.times(monitor_price));
// We want to maximize this score: how much we'll value this particular build
let score = m.add(gpu_score, n_monitors.times(monitor_score));
// Exactly one GPU: we want to run Crysis, but our case must fit under the desk
let n_gpus = m.sum(&gpus);
m.equals(n_gpus, 1);
// Grandma got us some money for our birthday, that will be our budget
m.less_than_or_equals(price, 600);
// Let the solver find the assignment that upholds our constraints and maximizes our score
let solution = m.maximize(score).unwrap();
// Our optimal build has three monitors and a mid-tier GPU. We even have some left-over cash!
assert_eq!(solution[n_monitors], 3);
assert_eq!(solution.get_values_binary(&gpus), vec![false, true, false]);
assert_eq!(solution[score], 1150);
assert_eq!(solution[price], 550);
}
sourcepub fn equals(&mut self, x: impl View, y: impl View)
pub fn equals(&mut self, x: impl View, y: impl View)
Declare two expressions to be equal.
Examples found in repository?
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
fn main() {
// All problem formulations will start with a model object
let mut m = copper::Model::default();
// How many monitors do we buy: we need at least one, but not more than three
let n_monitors = m.new_var(1, 3).unwrap();
// All monitors cost the same, and each additional monitor provides the same bump to our score
let monitor_price = 100;
let monitor_score = 250;
// Each GPU model has a fixed price, and an associated benchmark score
let gpu_prices = [150, 250, 500];
let gpu_scores = [100, 400, 800];
// We use binary decision variables to represent "do I pick this GPU?"
let gpus: Vec<_> = m.new_vars_binary(gpu_scores.len()).collect();
// For each potential GPU, we multiply its price (and score) by whether or not it is selected.
// The sum of these terms gives us the price and score of the selected GPU.
let gpu_price = m.sum_iter(gpus.iter().zip(gpu_prices).map(|(x, price)| x.times(price)));
let gpu_score = m.sum_iter(gpus.iter().zip(gpu_scores).map(|(x, score)| x.times(score)));
// This expression is the overall price of our build
let price = m.add(gpu_price, n_monitors.times(monitor_price));
// We want to maximize this score: how much we'll value this particular build
let score = m.add(gpu_score, n_monitors.times(monitor_score));
// Exactly one GPU: we want to run Crysis, but our case must fit under the desk
let n_gpus = m.sum(&gpus);
m.equals(n_gpus, 1);
// Grandma got us some money for our birthday, that will be our budget
m.less_than_or_equals(price, 600);
// Let the solver find the assignment that upholds our constraints and maximizes our score
let solution = m.maximize(score).unwrap();
// Our optimal build has three monitors and a mid-tier GPU. We even have some left-over cash!
assert_eq!(solution[n_monitors], 3);
assert_eq!(solution.get_values_binary(&gpus), vec![false, true, false]);
assert_eq!(solution[score], 1150);
assert_eq!(solution[price], 550);
}
sourcepub fn less_than_or_equals(&mut self, x: impl View, y: impl View)
pub fn less_than_or_equals(&mut self, x: impl View, y: impl View)
Declare constraint x <= y
.
Examples found in repository?
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
fn main() {
// All problem formulations will start with a model object
let mut m = copper::Model::default();
// How many monitors do we buy: we need at least one, but not more than three
let n_monitors = m.new_var(1, 3).unwrap();
// All monitors cost the same, and each additional monitor provides the same bump to our score
let monitor_price = 100;
let monitor_score = 250;
// Each GPU model has a fixed price, and an associated benchmark score
let gpu_prices = [150, 250, 500];
let gpu_scores = [100, 400, 800];
// We use binary decision variables to represent "do I pick this GPU?"
let gpus: Vec<_> = m.new_vars_binary(gpu_scores.len()).collect();
// For each potential GPU, we multiply its price (and score) by whether or not it is selected.
// The sum of these terms gives us the price and score of the selected GPU.
let gpu_price = m.sum_iter(gpus.iter().zip(gpu_prices).map(|(x, price)| x.times(price)));
let gpu_score = m.sum_iter(gpus.iter().zip(gpu_scores).map(|(x, score)| x.times(score)));
// This expression is the overall price of our build
let price = m.add(gpu_price, n_monitors.times(monitor_price));
// We want to maximize this score: how much we'll value this particular build
let score = m.add(gpu_score, n_monitors.times(monitor_score));
// Exactly one GPU: we want to run Crysis, but our case must fit under the desk
let n_gpus = m.sum(&gpus);
m.equals(n_gpus, 1);
// Grandma got us some money for our birthday, that will be our budget
m.less_than_or_equals(price, 600);
// Let the solver find the assignment that upholds our constraints and maximizes our score
let solution = m.maximize(score).unwrap();
// Our optimal build has three monitors and a mid-tier GPU. We even have some left-over cash!
assert_eq!(solution[n_monitors], 3);
assert_eq!(solution.get_values_binary(&gpus), vec![false, true, false]);
assert_eq!(solution[score], 1150);
assert_eq!(solution[price], 550);
}
sourcepub fn greater_than_or_equals(&mut self, x: impl View, y: impl View)
pub fn greater_than_or_equals(&mut self, x: impl View, y: impl View)
Declare constraint x >= y
.
sourcepub fn greater_than(&mut self, x: impl View, y: impl View)
pub fn greater_than(&mut self, x: impl View, y: impl View)
Declare constraint x > y
.
sourcepub fn minimize(self, objective: impl View) -> Option<Solution>
pub fn minimize(self, objective: impl View) -> Option<Solution>
Find assignment that minimizes objective expression while satisfying all constraints.
sourcepub fn minimize_and_iterate(
self,
objective: impl View
) -> impl Iterator<Item = Solution>
pub fn minimize_and_iterate( self, objective: impl View ) -> impl Iterator<Item = Solution>
Enumerate assignments that satisfy all constraints, while minimizing objective expression.
The order in which assignments are yielded is not stable.
sourcepub fn maximize(self, objective: impl View) -> Option<Solution>
pub fn maximize(self, objective: impl View) -> Option<Solution>
Find assignment that maximizes objective expression while satisfying all constraints.
Examples found in repository?
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
fn main() {
// All problem formulations will start with a model object
let mut m = copper::Model::default();
// How many monitors do we buy: we need at least one, but not more than three
let n_monitors = m.new_var(1, 3).unwrap();
// All monitors cost the same, and each additional monitor provides the same bump to our score
let monitor_price = 100;
let monitor_score = 250;
// Each GPU model has a fixed price, and an associated benchmark score
let gpu_prices = [150, 250, 500];
let gpu_scores = [100, 400, 800];
// We use binary decision variables to represent "do I pick this GPU?"
let gpus: Vec<_> = m.new_vars_binary(gpu_scores.len()).collect();
// For each potential GPU, we multiply its price (and score) by whether or not it is selected.
// The sum of these terms gives us the price and score of the selected GPU.
let gpu_price = m.sum_iter(gpus.iter().zip(gpu_prices).map(|(x, price)| x.times(price)));
let gpu_score = m.sum_iter(gpus.iter().zip(gpu_scores).map(|(x, score)| x.times(score)));
// This expression is the overall price of our build
let price = m.add(gpu_price, n_monitors.times(monitor_price));
// We want to maximize this score: how much we'll value this particular build
let score = m.add(gpu_score, n_monitors.times(monitor_score));
// Exactly one GPU: we want to run Crysis, but our case must fit under the desk
let n_gpus = m.sum(&gpus);
m.equals(n_gpus, 1);
// Grandma got us some money for our birthday, that will be our budget
m.less_than_or_equals(price, 600);
// Let the solver find the assignment that upholds our constraints and maximizes our score
let solution = m.maximize(score).unwrap();
// Our optimal build has three monitors and a mid-tier GPU. We even have some left-over cash!
assert_eq!(solution[n_monitors], 3);
assert_eq!(solution.get_values_binary(&gpus), vec![false, true, false]);
assert_eq!(solution[score], 1150);
assert_eq!(solution[price], 550);
}
sourcepub fn maximize_and_iterate(
self,
objective: impl View
) -> impl Iterator<Item = Solution>
pub fn maximize_and_iterate( self, objective: impl View ) -> impl Iterator<Item = Solution>
Enumerate assignments that satisfy all constraints, while maximizing objective expression.
The order in which assignments are yielded is not stable.