Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Changes In Branch async-simplify Excluding Merge-Ins
This is equivalent to a diff from 417b0d197c to c2b6e5cb87
|
2020-06-13
| ||
| 07:46 | Merge async-simplify check-in: 91e0ca1aed user: fifr tags: async | |
| 07:45 | Remove redundant clone Closed-Leaf check-in: c2b6e5cb87 user: fifr tags: async-simplify | |
| 07:44 | Remove some redundant imports check-in: 845c5987a4 user: fifr tags: async-simplify | |
|
2020-06-10
| ||
| 15:31 | asyn: simplify API for submodels check-in: 9195911462 user: fifr tags: async-simplify | |
| 08:52 | Use `float-pretty-print` for formatted info output check-in: 417b0d197c user: fifr tags: async | |
|
2020-05-17
| ||
| 10:59 | Merge trunk check-in: 845692d3a6 user: fifr tags: async | |
Changes to src/master/boxed/unconstrained/cpx.rs.
| ︙ | ︙ | |||
23 24 25 26 27 28 29 |
use c_str_macro::c_str;
use cplex_sys as cpx;
use cplex_sys::trycpx;
use either::Either;
use log::{debug, warn};
| < | 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
use c_str_macro::c_str;
use cplex_sys as cpx;
use cplex_sys::trycpx;
use either::Either;
use log::{debug, warn};
use std::collections::VecDeque;
use std::f64::NEG_INFINITY;
use std::iter::{once, repeat};
use std::ops::{Deref, DerefMut};
use std::os::raw::{c_char, c_int};
use std::ptr;
use std::sync::Arc;
|
| ︙ | ︙ |
Changes to src/mcf/solver.rs.
|
| | | 1 2 3 4 5 6 7 8 | // Copyright (c) 2016, 2017, 2018, 2019, 2020 Frank Fischer <frank-fischer@shadow-soft.de> // // This program is free software: you can redistribute it and/or // modify it under the terms of the GNU General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, but |
| ︙ | ︙ | |||
18 19 20 21 22 23 24 |
use crate::{DVector, Real};
use c_str_macro::c_str;
use cplex_sys as cpx;
use cplex_sys::trycpx;
| < | 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
use crate::{DVector, Real};
use c_str_macro::c_str;
use cplex_sys as cpx;
use cplex_sys::trycpx;
use std::ffi::CString;
use std::ptr;
use std::result;
use std::os::raw::{c_char, c_double, c_int};
#[derive(Debug)]
|
| ︙ | ︙ |
Changes to src/solver/asyn.rs.
| ︙ | ︙ | |||
20 21 22 23 24 25 26 |
#[cfg(feature = "crossbeam")]
use rs_crossbeam::channel::{unbounded as channel, RecvError};
#[cfg(not(feature = "crossbeam"))]
use std::sync::mpsc::{channel, RecvError};
use float_pretty_print::PrettyPrintFloat;
use log::{debug, info, warn};
| < | | | | 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 |
#[cfg(feature = "crossbeam")]
use rs_crossbeam::channel::{unbounded as channel, RecvError};
#[cfg(not(feature = "crossbeam"))]
use std::sync::mpsc::{channel, RecvError};
use float_pretty_print::PrettyPrintFloat;
use log::{debug, info, warn};
use num_traits::{Float, ToPrimitive, Zero};
use std::iter::repeat;
use std::sync::Arc;
use std::time::Instant;
use threadpool::ThreadPool;
use crate::{DVector, Real};
use super::channels::{
ChannelResultSender, ChannelUpdateSender, ClientReceiver, ClientSender, EvalResult, Message, Update,
};
use super::masterprocess::{self, MasterConfig, MasterProcess, MasterResponse, Response};
use crate::data::Minorant;
use crate::master::{Builder as MasterBuilder, MasterProblem};
use crate::problem::{FirstOrderProblem, UpdateState};
use crate::terminator::{StandardTerminatable, StandardTerminator, Terminator};
use crate::weighter::{HKWeightable, HKWeighter, Weighter};
pub mod guessmodels;
use guessmodels::{Guess, GuessModel, NearestValue};
/// The default iteration limit.
pub const DEFAULT_ITERATION_LIMIT: usize = 10_000;
/// The default solver.
pub type DefaultSolver<P> = Solver<P, StandardTerminator, HKWeighter, crate::master::FullMasterBuilder>;
|
| ︙ | ︙ | |||
185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
#[derive(Clone, Copy, Debug)]
struct EvalId {
/// The index of the subproblem.
subproblem: usize,
/// The index of the candidate at which the subproblem is evaluated.
candidate_index: usize,
}
/// Parameters for tuning the solver.
#[derive(Debug, Clone)]
pub struct Parameters {
/// The descent step acceptance factors, must be in (0,1).
///
/// The default value is 0.1.
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 |
#[derive(Clone, Copy, Debug)]
struct EvalId {
/// The index of the subproblem.
subproblem: usize,
/// The index of the candidate at which the subproblem is evaluated.
candidate_index: usize,
}
/// An evaluation point.
#[derive(Clone)]
pub struct Point {
/// The globally unique index of the evaluation point.
index: usize,
/// The evaluation point itself.
point: Arc<DVector>,
}
impl Point {
fn distance(&self, p: &Point) -> Real {
if self.index != p.index {
let mut d = self.point.as_ref().clone();
d.add_scaled(-1.0, &p.point);
d.norm2()
} else {
Real::zero()
}
}
}
impl Default for Point {
fn default() -> Point {
Point {
index: 0,
point: Arc::new(dvec![]),
}
}
}
/// Parameters for tuning the solver.
#[derive(Debug, Clone)]
pub struct Parameters {
/// The descent step acceptance factors, must be in (0,1).
///
/// The default value is 0.1.
|
| ︙ | ︙ | |||
248 249 250 251 252 253 254 |
Descent,
/// No step but the algorithm has been terminated.
Term,
}
pub struct SolverData {
/// Current center of stability.
| | > > > > > > | 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 |
Descent,
/// No step but the algorithm has been terminated.
Term,
}
pub struct SolverData {
/// Current center of stability.
cur_y: Point,
/// Function value in the current point.
cur_val: Real,
/// Step direction (i.e. nxt_y - cur_y).
nxt_d: Arc<DVector>,
/// Current candidate.
nxt_y: Point,
/// Function value at the current candidate.
nxt_val: Real,
/// Model value at the current candidate.
nxt_mod: Real,
|
| ︙ | ︙ | |||
288 289 290 291 292 293 294 |
/// Number of inner model updates.
cnt_updates: usize,
/// Number of descent steps.
cnt_descent: usize,
| | | | | | | < < < < < < | | | > < > > | < < > > | | 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 |
/// Number of inner model updates.
cnt_updates: usize,
/// Number of descent steps.
cnt_descent: usize,
/// The number of subproblems with insufficient evaluation data.
num_insufficient_candidates: usize,
/// The number of subproblems that have not been evaluated exactly in the center.
num_inexact_center: usize,
/// Whether the next step should be a forced descent step.
force_descent: bool,
/// Subproblem data.
subs: Vec<SubData>,
/// The list of all evaluation points.
candidates: Vec<Point>,
/// Whether we need a new update
need_update: bool,
/// Whether a problem update is currently in progress.
update_in_progress: bool,
}
impl SolverData {
/// Reset solver data to initial values.
///
/// This means that almost everything is set to +infinity so that
/// a null-step is forced after the first evaluation.
fn init(&mut self, y: Point) {
self.cnt_descent = 0;
self.cur_y = y.clone();
self.cur_val = Real::infinity();
self.nxt_y = y;
self.nxt_val = Real::infinity();
self.nxt_mod = -Real::infinity();
self.nxt_submods = vec![-Real::infinity(); self.nxt_submods.len()];
self.expected_progress = Real::infinity();
self.error_bound = Real::infinity();
self.candidates.clear();
self.sgnorm = Real::infinity();
self.cur_weight = 1.0;
self.num_insufficient_candidates = 0;
self.num_inexact_center = self.nxt_submods.len();
self.force_descent = false;
}
}
impl Default for SolverData {
fn default() -> SolverData {
SolverData {
cur_y: Point::default(),
cur_val: 0.0,
nxt_val: 0.0,
nxt_mod: 0.0,
nxt_submods: vec![],
expected_progress: 0.0,
sgnorm: 0.0,
error_bound: Real::infinity(),
cur_weight: 1.0,
num_insufficient_candidates: 0,
num_inexact_center: 0,
force_descent: false,
candidates: vec![],
max_iter: 0,
cnt_descent: 0,
cnt_updates: 0,
subs: vec![],
nxt_d: Arc::new(dvec![]),
nxt_y: Point::default(),
need_update: true,
update_in_progress: false,
}
}
}
impl StandardTerminatable for SolverData {
|
| ︙ | ︙ | |||
384 385 386 387 388 389 390 |
impl HKWeightable for SolverData {
fn current_weight(&self) -> Real {
self.cur_weight
}
fn center(&self) -> &DVector {
| | | 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 |
impl HKWeightable for SolverData {
fn current_weight(&self) -> Real {
self.cur_weight
}
fn center(&self) -> &DVector {
&self.cur_y.point
}
fn center_value(&self) -> Real {
self.cur_val
}
fn candidate_value(&self) -> Real {
|
| ︙ | ︙ | |||
444 445 446 447 448 449 450 |
}
fn aggregated_primal(&self, i: usize) -> &Pr {
&self.primal_aggrs[i]
}
}
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | > > | | > > | > | > < < < | < < < < < < < | < < | < < < | < < < < < < < < < < < < < | < < < < < < < < < | < < | | < < < < < < | < | > > > > > > > > > | | | | | | | | | | > > | > | | | | | | > > > > > > > > | | > > > > > > > > > > | > | | | > > > > > > > > > > > > > | > > | > > | 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 |
}
fn aggregated_primal(&self, i: usize) -> &Pr {
&self.primal_aggrs[i]
}
}
/// Model data of a single subproblem.
///
/// This struct does not handle the subproblem model itself. However, it handles
/// the asynchronous precision data, i.e. the guessed Lipschitz-constant and the
/// distance of the evaluation points to the candidate.
///
/// The concrete model used for computing the guessed values in the candidate
/// and the center must be provided by an implementation of `SubProblem`.
struct SubData {
/// The index associated with this subproblem.
fidx: usize,
/// The subproblem.
sub: Box<dyn GuessModel>,
/// The current center.
center: Point,
/// The current candidate.
candidate: Point,
/// The last index at which the evaluation has been started.
last_eval_index: usize,
/// Whether a subproblem evaluation is currently running.
is_running: bool,
/// Whether the last evaluation has been sufficiently close.
is_close_enough: bool,
/// The original guess value and its evaluation distance in the current center.
center_guess: Guess,
/// The current guess of the Lipschitz constant.
l_guess: Real,
}
impl SubData {
fn new(fidx: usize, sub: Box<dyn GuessModel>, y: &Point) -> SubData {
SubData {
fidx,
sub,
last_eval_index: 0,
center: y.clone(),
candidate: y.clone(),
is_running: false,
is_close_enough: false,
center_guess: Guess::default(),
l_guess: 0.0,
}
}
/// Set the center of this model.
///
/// If `update_l_guess` is true also update the guess of the Lipschitz constant.
fn move_center(&mut self, y: &Point, update_l_guess: bool) {
assert_eq!(y.index, self.candidate.index, "Must move to current candidate");
// The guess value used in the current (i.e. old) center
let old_guess = self.center_guess;
// The cut value now known for the center.
let old_cutvalue = self.sub.get_lower_bound(&self.center);
// There has been a previous evaluation, so first update the Lipschitz guess ...
if update_l_guess && old_guess.dist > Real::zero() {
debug!(
"L-guess fidx:{} guess:{} cut:{}",
self.fidx, old_guess.value, old_cutvalue
);
let new_l_guess = if old_guess.dist.is_finite() {
(old_cutvalue - old_guess.value) / old_guess.dist
} else {
Real::zero()
};
if new_l_guess > self.l_guess {
debug!(
"New l_guess fidx:{} old-L:{} L:{}",
self.fidx, self.l_guess, new_l_guess
);
self.l_guess = new_l_guess;
}
}
// Save the new center
self.center = y.clone();
// Save guess value of the candidate/new center
self.center_guess = self.sub.get_guess_value(&self.center);
}
/// Set the candidate of this model.
fn update_candidate(&mut self, y: &Point, accept_factor: Real) {
self.candidate = y.clone();
self.update_close_enough(accept_factor);
}
/// Add a function value to this model.
///
/// The `accept_factor` is a parameter for possibly accepting
/// the candidate guess value as "good enough" (if it has been
/// changed by the new minorant).
fn add_function_value(&mut self, y: &Point, value: Real, accept_factor: Real) {
self.sub.add_function_value(y, value);
self.update_close_enough(accept_factor)
}
/// Add a minorant to this model.
///
/// The `accept_factor` is a parameter for possibly accepting
/// the candidate guess value as "good enough" (if it has been
/// changed by the new minorant).
///
/// The minorant must be centered at the global 0.
fn add_minorant(&mut self, y: &Point, m: &Arc<Minorant>, accept_factor: Real) {
self.sub.add_minorant(y, m);
self.update_close_enough(accept_factor)
}
/// Return the current guess value at the given point.
fn get_guess_value(&mut self, y: &Point) -> Guess {
self.sub.get_guess_value(y)
}
/// Return the lower bound at the given point.
fn get_lower_bound(&mut self, y: &Point) -> Real {
self.sub.get_lower_bound(y)
}
fn update_close_enough(&mut self, accept_factor: Real) {
let g = self.sub.get_guess_value(&self.candidate);
self.is_close_enough = g.is_exact() || g.dist * self.l_guess <= accept_factor
}
/// Return the error estimation in the center.
///
/// This is the difference between the (current) lower bound and the used
/// guess value.
fn error_estimate(&mut self) -> Real {
self.sub.get_lower_bound(&self.center) - self.center_guess.value
}
fn center_guess_value(&self) -> Real {
self.center_guess.value
}
}
/// Implementation of a parallel bundle method.
pub struct Solver<P, T = StandardTerminator, W = HKWeighter, M = crate::master::FullMasterBuilder>
where
P: FirstOrderProblem,
|
| ︙ | ︙ | |||
763 764 765 766 767 768 769 |
/// The master problem process.
master_proc: Option<MasterProcess<P, M::MasterProblem>>,
/// Whether there is currently a master computation running.
master_running: bool,
/// Whether the master problem has been changed.
| | | 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 |
/// The master problem process.
master_proc: Option<MasterProcess<P, M::MasterProblem>>,
/// Whether there is currently a master computation running.
master_running: bool,
/// Whether the master problem has been changed.
master_has_changed: bool,
/// The channel to receive the evaluation results from subproblems.
client_tx: Option<ClientSender<EvalId, P, M::MasterProblem>>,
/// The channel to receive the evaluation results from subproblems.
client_rx: Option<ClientReceiver<EvalId, P, M::MasterProblem>>,
|
| ︙ | ︙ | |||
808 809 810 811 812 813 814 |
weighter: Default::default(),
problem,
data: SolverData::default(),
threadpool: ThreadPool::with_name("Parallel bundle solver".to_string(), ncpus),
master,
master_proc: None,
| | | 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 |
weighter: Default::default(),
problem,
data: SolverData::default(),
threadpool: ThreadPool::with_name("Parallel bundle solver".to_string(), ncpus),
master,
master_proc: None,
master_has_changed: false,
master_running: false,
client_tx: None,
client_rx: None,
start_time: Instant::now(),
}
|
| ︙ | ︙ | |||
856 857 858 859 860 861 862 |
/// This function is automatically called by [`Solver::solve`].
pub fn init(&mut self) -> Result<(), P, M> {
debug!("Initialize solver");
let n = self.problem.num_variables();
let m = self.problem.num_subproblems();
| | > > > | 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 |
/// This function is automatically called by [`Solver::solve`].
pub fn init(&mut self) -> Result<(), P, M> {
debug!("Initialize solver");
let n = self.problem.num_variables();
let m = self.problem.num_subproblems();
self.data.init(Point {
index: 1,
point: Arc::new(dvec![Real::zero(); n]),
});
let (tx, rx) = channel();
self.client_tx = Some(tx.clone());
self.client_rx = Some(rx);
let master_config = MasterConfig {
num_subproblems: m,
|
| ︙ | ︙ | |||
895 896 897 898 899 900 901 902 903 |
self.master.build().map_err(Error::BuildMaster)?,
master_config,
tx,
&mut self.threadpool,
));
debug!("Initial problem evaluation");
// We need an initial evaluation of all oracles for the first center.
self.data.subs = (0..m)
| > > | < < < < < < < < < < < < | | | < < < < < < < | < | < < < < | | | < | < > | > > > > > > > > > > > > > > > > > | > | | > > > > > | 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 |
self.master.build().map_err(Error::BuildMaster)?,
master_config,
tx,
&mut self.threadpool,
));
debug!("Initial problem evaluation");
// The initial evaluation point.
self.data.nxt_y = self.data.cur_y.clone();
// We need an initial evaluation of all oracles for the first center.
self.data.subs = (0..m)
.map(|fidx| SubData::new(fidx, Box::new(NearestValue::new()), &self.data.cur_y))
.collect();
// This could be done better: the initial evaluation has index 1!
self.data.candidates.push(self.data.nxt_y.clone());
self.data.candidates.push(self.data.nxt_y.clone());
for i in 0..m {
self.evaluate_subproblem(i)?;
}
self.start_time = Instant::now();
// wait for all subproblem evaluations.
let mut cnt_remaining = self.problem.num_subproblems();
let master = self.master_proc.as_mut().ok_or(Error::NotInitialized)?;
let client_rx = self.client_rx.as_ref().ok_or(Error::NotInitialized)?;
while cnt_remaining > 0 {
let msg = client_rx.recv();
match msg? {
Message::Eval(m) => match m {
EvalResult::ObjectiveValue { index, value } => {
assert_eq!(
index.candidate_index, self.data.nxt_y.index,
"Receive objective value for unexpected candidate"
);
self.data.subs[index.subproblem].add_function_value(&self.data.nxt_y, value, 0.0);
}
EvalResult::Minorant {
index,
mut minorant,
primal,
} => {
assert_eq!(
index.candidate_index, self.data.nxt_y.index,
"Receive objective value for unexpected candidate"
);
// Add the minorant to the master problem.
// The minorant is centered at the candidate == center, so it does
// not need to be moved.
master.add_minorant(index.subproblem, minorant.clone(), primal)?;
self.master_has_changed = true;
// Center the minorant at 0.
minorant.move_center(-1.0, &self.data.nxt_y.point);
self.data.subs[index.subproblem].add_minorant(&self.data.nxt_y, &Arc::new(minorant), 0.0);
}
EvalResult::Done { index } => {
self.data.subs[index.subproblem].is_running = false;
cnt_remaining -= 1;
}
EvalResult::Error { err, index } => {
self.data.subs[index.subproblem].is_running = false;
return Err(Error::Evaluation(err));
}
},
Message::Update(_) => {
unreachable!("Receive update response during initialization");
}
Message::Master(_) => {
unreachable!("Receive master response during initialization");
}
}
}
// Set the initial values.
// For the center this is the current lower bound (cut value),
// for the candidate it is the model candidate value.
//
// Note that both are the same ...
self.data.cur_val = 0.0;
self.data.nxt_val = 0.0;
for s in &mut self.data.subs {
self.data.cur_val += s.get_lower_bound(&self.data.nxt_y);
self.data.nxt_val += s.get_guess_value(&self.data.nxt_y).value;
}
assert!((self.data.cur_val - self.data.nxt_val).abs() < 1e-6);
// This is the first evaluation. We effectively get
// the function value at the current center but
// we do not have a model estimate yet. Hence, we do not know
// a good guess for the weight.
self.data.cur_weight = Real::infinity();
self.data.need_update = true;
self.update_problem(Step::Descent)?;
debug!("First Step");
debug!(" cur_val={}", self.data.cur_val);
self.data.cnt_descent += 1;
// compute the initial candidate
self.update_candidate()?;
self.show_info(Step::Descent);
debug!("Initialization complete");
Ok(())
}
/// Reset data for new iterations.
fn reset_iteration_data(&mut self, max_iter: usize) {
let num_variables = self.problem.num_variables();
self.data.max_iter = max_iter;
self.data.cnt_updates = 0;
self.data.nxt_d = Arc::new(dvec![0.0; num_variables]);
self.data.nxt_y = self.data.cur_y.clone();
let nxt_y = &self.data.nxt_y;
self.data.nxt_val = self
.data
.subs
.iter_mut()
.map(|s| s.get_guess_value(&nxt_y).value)
.sum::<Real>();
self.data.need_update = true;
self.data.update_in_progress = false;
}
/// Solve the problem with the default maximal iteration limit [`DEFAULT_ITERATION_LIMIT`].
pub fn solve(&mut self) -> Result<(), P, M> {
self.solve_with_limit(DEFAULT_ITERATION_LIMIT)
|
| ︙ | ︙ | |||
1057 1058 1059 1060 1061 1062 1063 |
loop {
let msg = self.client_rx.as_ref().ok_or(Error::NotInitialized)?.recv()?;
match msg {
Message::Eval(m) => {
// Receive a evaluation result
if self.handle_client_response(m)? {
| | | | 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 |
loop {
let msg = self.client_rx.as_ref().ok_or(Error::NotInitialized)?.recv()?;
match msg {
Message::Eval(m) => {
// Receive a evaluation result
if self.handle_client_response(m)? {
return Ok(true);
}
}
Message::Update(msg) => {
debug!("Receive update response");
if self.handle_update_response(msg)? {
// The master problem has been changed so we need a new
// candidate as well.
self.update_candidate()?;
}
}
Message::Master(mresponse) => {
debug!("Receive master response");
// Receive result (new candidate) from the master
if self.handle_master_response(mresponse)? {
return Ok(true);
|
| ︙ | ︙ | |||
1125 1126 1127 1128 1129 1130 1131 |
}
}
/// A new objective value has been computed.
fn handle_new_objective(&mut self, id: EvalId, value: Real) -> Result<bool, P, M> {
debug!(
"Receive objective from subproblem:{} candidate:{} current:{} obj:{}",
| | | < < | | < > | | < < < < | | < | | < < | < < < < < < | < < < < < < < < < < < < < < < | | | | < < < | < | < < < | | < | < < | < < | | < < < | | < < < < | < < < < < < < < | < < | < < < < | < | | < < > | | < < | < < < | | < > | | < < < < < | | < < < < > | < < < < > | > > | 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 |
}
}
/// A new objective value has been computed.
fn handle_new_objective(&mut self, id: EvalId, value: Real) -> Result<bool, P, M> {
debug!(
"Receive objective from subproblem:{} candidate:{} current:{} obj:{}",
id.subproblem, id.candidate_index, self.data.nxt_y.index, value
);
// check if new evaluation is closer to the current candidate
let sub = &mut self.data.subs[id.subproblem];
// Whether the subproblem evaluation had been good enough.
let was_close_enough = sub.is_close_enough;
if let Some(nxt) = self.data.candidates.get_mut(id.candidate_index) {
let accept_factor =
self.params.imprecision_factor * self.params.acceptance_factor * self.data.expected_progress
/ self.problem.num_subproblems().to_f64().unwrap();
let old_can_val = sub.get_guess_value(&self.data.nxt_y).value;
let old_cen_val = sub.get_lower_bound(&self.data.cur_y);
sub.add_function_value(nxt, value, accept_factor);
let new_can_val = sub.get_guess_value(&self.data.nxt_y).value;
let new_cen_val = sub.get_lower_bound(&self.data.cur_y);
self.data.nxt_val += new_can_val - old_can_val;
self.data.cur_val += new_cen_val - old_cen_val;
// This is just a safe-guard: if the function has been evaluated at
// the current candidate, the evaluation *must* be good enough.
assert!(
sub.is_close_enough || sub.last_eval_index < self.data.nxt_y.index,
"Unexpected insufficiency fidx:{} l:{}",
id.subproblem,
sub.l_guess,
);
} else {
// unknown candidate -> ignore objective value
warn!("Ignore unknown candidate index:{}", id.candidate_index);
return Ok(false);
}
// Test if the new candidate is close enough for the asynchronous
// precision test.
if !was_close_enough && sub.is_close_enough {
debug!(
"Accept result fidx:{} index:{} candidate:{} (remaining insufficient: {})",
id.subproblem, id.candidate_index, self.data.nxt_y.index, self.data.num_insufficient_candidates
);
}
self.maybe_do_step(false)
}
/// Add a new minorant.
///
/// The minorant is added to the submodel as well as the master problem. The modified submodel
/// may then lead to a potential null/descent-step.
///
/// Return values
/// - `Ok(true)` if the termination criterion has been satisfied,
/// - `Ok(false)` if the termination criterion has not been satisfied,
/// - `Err(_)` on error.
fn handle_new_minorant(&mut self, id: EvalId, minorant: Minorant, primal: P::Primal) -> Result<bool, P, M> {
debug!(
"Receive minorant subproblem:{} candidate:{} current:{} center:{}",
id.subproblem, id.candidate_index, self.data.nxt_y.index, self.data.cur_y.index,
);
let accept_factor =
self.params.imprecision_factor * self.params.acceptance_factor * self.data.expected_progress
/ self.problem.num_subproblems().to_f64().unwrap();
let sub = &mut self.data.subs[id.subproblem];
let mut minorant = minorant;
if let Some(point) = self.data.candidates.get_mut(id.candidate_index) {
// center the minorant at 0
minorant.move_center(-1.0, &point.point);
// add minorant to submodel
let old_can_val = sub.get_guess_value(&self.data.nxt_y).value;
let old_cen_val = sub.get_lower_bound(&self.data.cur_y);
sub.add_minorant(&point, &Arc::new(minorant.clone()), accept_factor);
let new_can_val = sub.get_guess_value(&self.data.nxt_y).value;
let new_cen_val = sub.get_lower_bound(&self.data.cur_y);
self.data.nxt_val += new_can_val - old_can_val;
self.data.cur_val += new_cen_val - old_cen_val;
// center the minorant at the current center
minorant.move_center(1.0, &self.data.cur_y.point);
} else {
warn!("Ignore unknown candidate index:{}", id.candidate_index);
return Ok(false);
}
// add minorant to master problem
let master = self.master_proc.as_mut().ok_or(Error::NotInitialized)?;
master.add_minorant(id.subproblem, minorant, primal)?;
self.master_has_changed = true;
self.maybe_do_step(false)
}
/// Handle a response `master_res` from the master problem process.
///
/// The master response is the new candidate point. The method updates the
/// algorithm state with the data of the new candidate (e.g. the model value
/// `nxt_mod` in the point or the expected progress). Then it tests whether
/// a termination criterion is satisfied. This is only the case if there is
/// no pending problem update.
///
/// Finally the master problem starts the evaluation of all subproblems at
/// the new candidate.
///
/// The new candidate is immediately checked for a potential new test.
///
/// Return values
/// - `Ok(true)` if the termination criterion has been satisfied,
/// - `Ok(false)` if the termination criterion has not been satisfied,
/// - `Err(_)` on error.
fn handle_master_response(&mut self, master_res: MasterResponse<P, M::MasterProblem>) -> Result<bool, P, M> {
match master_res {
|
| ︙ | ︙ | |||
1339 1340 1341 1342 1343 1344 1345 |
self.data.sgnorm = sgnorm;
self.data.cnt_updates = cnt_updates;
self.data.nxt_submods.clear();
self.data.nxt_submods.extend(nxt_submods);
debug!(
"Master Response current_center:{} current_candidate:{} res_center:{} nxt_mod:{}",
| | > | < < < < < < < < < < < < < < | | | | | > > | | | > | < > | > > | < < < < < < < | < < < < < < < | < < | | < < < < < < < | < < < < < < < < > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | | | < | > > | 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 |
self.data.sgnorm = sgnorm;
self.data.cnt_updates = cnt_updates;
self.data.nxt_submods.clear();
self.data.nxt_submods.extend(nxt_submods);
debug!(
"Master Response current_center:{} current_candidate:{} res_center:{} nxt_mod:{}",
self.data.cur_y.index, self.data.nxt_y.index, center_index, self.data.nxt_mod
);
}
};
self.data.expected_progress = self.data.cur_val - self.data.nxt_mod;
self.master_running = false;
let master = self.master_proc.as_mut().ok_or(Error::NotInitialized)?;
// If this is the very first solution of the model,
// we use its result as to make a good guess for the initial weight
// of the proximal term and resolve.
if self.data.cur_weight.is_infinite() {
self.data.cur_weight = self.weighter.initial_weight(&self.data);
master.set_weight(self.data.cur_weight)?;
self.master_has_changed = true;
self.update_candidate()?;
return Ok(false);
}
// Compress bundle
master.compress()?;
// Check if new variables had been added. In this case, resize cur_y.
if self.data.nxt_d.len() > self.data.cur_y.point.len() {
let nnew = self.data.nxt_d.len() - self.data.cur_y.point.len();
if nnew != self.data.cur_y.point.len() {
let mut cur_y = self.data.cur_y.point.as_ref().clone();
cur_y.extend(repeat(0.0).take(nnew));
self.data.cur_y.point = Arc::new(cur_y);
}
}
// Compute new candidate.
let mut next_y = dvec![];
next_y.add(&self.data.cur_y.point, &self.data.nxt_d);
#[cfg(debug_assertions)]
{
if self.data.nxt_y.point.len() == next_y.len() {
let mut diff = self.data.nxt_y.point.as_ref().clone();
diff.add_scaled(-1.0, &next_y);
debug!("Candidates move distance:{}", diff.norm2());
}
}
self.data.nxt_y.point = Arc::new(next_y);
self.data.nxt_y.index += 1;
// Add the new candidate to the list of candidates.
debug_assert_eq!(self.data.nxt_y.index, self.data.candidates.len());
self.data.candidates.push(self.data.nxt_y.clone());
// Update the candidate in all submodels and
// compute first guess value for new candidate.
let accept_factor =
self.params.imprecision_factor * self.params.acceptance_factor * self.data.expected_progress
/ self.problem.num_subproblems().to_f64().unwrap();
self.data.nxt_val = Real::zero();
for sub in self.data.subs.iter_mut() {
sub.update_candidate(&self.data.nxt_y, accept_factor);
self.data.nxt_val += sub.get_guess_value(&self.data.nxt_y).value;
}
// Start evaluation of all subproblems at the new candidate.
for i in 0..self.data.subs.len() {
self.evaluate_subproblem(i)?;
}
self.maybe_do_step(true)
}
/// Do a descent or null step if precision is sufficient.
///
/// Also checks if the termination criterion is satisfied.
///
/// Return values
/// - `Ok(true)` if the termination criterion has been satisfied,
/// - `Ok(false)` if the termination criterion has not been satisfied,
/// - `Err(_)` on error.
fn maybe_do_step(&mut self, check_termination: bool) -> Result<bool, P, M> {
// No step if there is no real new candidate
if self.data.nxt_y.index == self.data.cur_y.index {
return Ok(false);
}
self.data.num_insufficient_candidates = self.data.subs.iter().filter(|s| !s.is_close_enough).count();
let nxt_y = &self.data.nxt_y;
let num_exact = self
.data
.subs
.iter_mut()
.map(|s| s.get_guess_value(nxt_y).is_exact())
.filter(|&is_exact| is_exact)
.count();
let sum_dist = self
.data
.subs
.iter_mut()
.map(|s| s.get_guess_value(nxt_y).dist)
.sum::<Real>();
debug!(
"Number of insufficient subproblems: {} num exact: {} sum dist: {}",
self.data.num_insufficient_candidates, num_exact, sum_dist,
);
// test if we should terminate
if check_termination
&& self.terminator.terminate(&self.data)
&& !self.data.update_in_progress
&& self.data.cnt_descent > 2
&& !self.data.need_update
{
if self.data.num_inexact_center.is_zero() {
self.show_info(Step::Term);
info!("Termination criterion satisfied");
return Ok(true);
}
// The termination criterion has been satisfied, but the current
// center evaluations are not exact. We force the current
// *candidate* evaluations to be exact and do a forced descent step.
// This causes the next center to be exact and hopefully satisfying
// the termination criterion, too.
let num_inexact = self.data.subs.len() - num_exact;
debug!(
"Termination criterion satisfied with {} inexact evaluations",
num_inexact
);
// Current candidate is exact, force a descent step.
self.data.force_descent = true;
if num_inexact.is_zero() {
return self.do_step();
}
// Otherwise just wait for the exact evaluations.
return Ok(false);
}
if check_termination {
self.data.force_descent = false;
}
if self.data.num_insufficient_candidates == 0 {
self.do_step()
} else {
Ok(false)
}
}
/// Do a null-step or descent-step based on the current candidate data.
fn do_step(&mut self) -> Result<bool, P, M> {
let master = self.master_proc.as_mut().ok_or(Error::NotInitialized)?;
let descent_bnd = Self::get_descent_bound(self.params.acceptance_factor, &self.data);
debug!(
"Try step from center:{} to candidate:{}",
self.data.cur_y.index, self.data.nxt_y.index
);
// Test whether we do a descent step
if self.data.nxt_val <= descent_bnd || self.data.force_descent {
debug!("Descent Step{}", if self.data.force_descent { " (forced)" } else { "" });
debug!(" cur_val ={}", self.data.cur_val);
debug!(" nxt_mod ={}", self.data.nxt_mod);
debug!(" nxt_ub ={}", self.data.nxt_val);
debug!(" descent_bnd={}", descent_bnd);
self.data.force_descent = false;
self.data.cnt_descent += 1;
self.data.cur_y = self.data.nxt_y.clone();
let cur_y = &self.data.cur_y;
self.data.num_inexact_center = self
.data
.subs
.iter_mut()
.map(|s| !s.get_guess_value(&cur_y).is_exact())
.filter(|&is_exact| is_exact)
.count();
// Note that we must update the weight *before* we
// change the internal data, so the old information
// that caused the descent step is still available.
self.data.cur_weight = self.weighter.descent_weight(&self.data);
// The new value in the center is the model value in the candidate.
// In particular, it is a lower bound on the real function value.
//
// Note that we do not use the model value `nxt_mod`, but the
// sum of the single model values, because the latter might be higher
// in case of an aggregated model.
self.data.cur_val = self.data.nxt_submods.iter().sum();
// Check if the progress of the last decent step was large enough
// when using the lower bound in the center instead of the former
// guess value.
let error = self.data.subs.iter_mut().map(SubData::error_estimate).sum::<Real>();
let update_l_guess = error > self.data.error_bound;
// save new error bound
self.data.error_bound = self.data.expected_progress * self.params.acceptance_factor;
// Move all subproblems.
self.data.nxt_val = Real::zero();
for sub in &mut self.data.subs {
sub.move_center(&self.data.cur_y, update_l_guess);
self.data.nxt_val += sub.get_guess_value(&self.data.nxt_y).value;
}
self.data.need_update = true;
master.move_center(1.0, self.data.nxt_d.clone(), self.data.cur_y.index)?;
master.set_weight(self.data.cur_weight)?;
self.master_has_changed = true;
self.show_info(Step::Descent);
self.update_problem(Step::Descent)?;
// We need a new candidate.
self.update_candidate()?;
Ok(self.data.cnt_descent >= self.data.max_iter)
} else {
debug!("Null Step nxt_val:{} descent_bnd:{}", self.data.nxt_val, descent_bnd);
// No descent-step, so this is declared a null step
self.data.cur_weight = self.weighter.null_weight(&self.data);
self.show_info(Step::Null);
self.update_problem(Step::Null)?;
// After a null step we need a new candidate, too.
self.update_candidate()?;
Ok(false)
}
}
/// Start evaluation of a subproblem if it is not running.
///
/// The evaluation is started at the current candidate. The candidate
/// is added to the subproblem's candidate list.
///
/// Returns `true` iff a new evaluations has been started.
fn evaluate_subproblem(&mut self, subproblem: usize) -> Result<bool, P, M> {
let sub = &mut self.data.subs[subproblem];
if !sub.is_running && sub.last_eval_index < self.data.nxt_y.index {
sub.is_running = true;
sub.last_eval_index = sub.last_eval_index.max(self.data.nxt_y.index);
self.problem
.evaluate(
subproblem,
self.data.nxt_y.point.clone(),
ChannelResultSender::new(
EvalId {
subproblem,
candidate_index: self.data.nxt_y.index,
},
self.client_tx.as_ref().ok_or(Error::NotInitialized)?.clone(),
),
)
.map_err(Error::Evaluation)?;
debug!("Evaluate fidx:{} candidate:{}", subproblem, self.data.nxt_y.index);
Ok(true)
} else {
assert!(sub.is_running || sub.is_close_enough);
Ok(false)
}
}
/// Possibly start a new master process computation.
fn update_candidate(&mut self) -> Result<(), P, M> {
if !self.master_running && self.master_has_changed {
debug!("Start master problem");
self.master_running = true;
self.master_has_changed = false;
self.master_proc
.as_mut()
.ok_or(Error::NotInitialized)?
.solve(self.data.cur_val)?;
}
Ok(())
}
|
| ︙ | ︙ | |||
1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 |
// only one update may be running at the same time
if self.data.update_in_progress {
return Ok(false);
}
// Ok, we are doing a new update now ...
self.data.need_update = false;
let master_proc = self.master_proc.as_mut().unwrap();
self.problem
.update(
UpdateData {
| > > > | | | | 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 |
// only one update may be running at the same time
if self.data.update_in_progress {
return Ok(false);
}
// Ok, we are doing a new update now ...
self.data.need_update = false;
// TODO: fix this
return Ok(false);
let master_proc = self.master_proc.as_mut().unwrap();
self.problem
.update(
UpdateData {
cur_y: self.data.cur_y.point.clone(),
nxt_y: self.data.nxt_y.point.clone(),
step,
primal_aggrs: (0..self.problem.num_subproblems())
.map(|i| {
master_proc
.get_aggregated_primal(i)
.map_err(|_| "get_aggregated_primal".to_string())
.expect("Cannot get aggregated primal from master process")
})
.collect(),
},
ChannelUpdateSender::new(
EvalId {
subproblem: 0,
candidate_index: self.data.cur_y.index,
},
self.client_tx.clone().ok_or(Error::NotInitialized)?,
),
)
.map_err(Error::Update)?;
self.data.update_in_progress = true;
Ok(true)
|
| ︙ | ︙ | |||
1609 1610 1611 1612 1613 1614 1615 |
if step == Step::Descent { "*" } else { " " },
PrettyPrintFloat(self.data.cur_weight),
PrettyPrintFloat(self.data.expected_progress()),
PrettyPrintFloat(self.data.cur_val - self.data.nxt_val),
PrettyPrintFloat(self.data.nxt_mod),
PrettyPrintFloat(self.data.nxt_val),
PrettyPrintFloat(self.data.cur_val),
| | | 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 |
if step == Step::Descent { "*" } else { " " },
PrettyPrintFloat(self.data.cur_weight),
PrettyPrintFloat(self.data.expected_progress()),
PrettyPrintFloat(self.data.cur_val - self.data.nxt_val),
PrettyPrintFloat(self.data.nxt_mod),
PrettyPrintFloat(self.data.nxt_val),
PrettyPrintFloat(self.data.cur_val),
PrettyPrintFloat(self.data.subs.iter().map(|s| s.center_guess_value()).sum::<Real>()),
);
}
/// Return the aggregated primal of the given subproblem.
pub fn aggregated_primal(&self, subproblem: usize) -> Result<P::Primal, P, M> {
Ok(self
.master_proc
.as_ref()
.ok_or(Error::NotSolved)?
.get_aggregated_primal(subproblem)?)
}
}
|
Added src/solver/asyn/guessmodels.rs.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
/*
* Copyright (c) 2020 Frank Fischer <frank-fischer@shadow-soft.de>
*
* This program is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
use super::Point;
use crate::{data::Minorant, Real};
use num_traits::{Float, Zero};
use std::sync::Arc;
mod nearestvalue;
pub use nearestvalue::NearestValue;
/// A guessed function value.
#[derive(Clone, Copy)]
pub struct Guess {
/// The guessed value.
pub value: Real,
/// The accuracy distance.
pub dist: Real,
}
impl Guess {
/// Create a new guess value.
///
/// If `dist` is zero the value is assumed to be exact.
pub fn new(value: Real, dist: Real) -> Guess {
Guess { value, dist }
}
/// Create an approximate guess value.
pub fn new_approx(value: Real, dist: Real) -> Guess {
Guess { value, dist }
}
/// Create an exact guess value.
pub fn new_exact(value: Real) -> Guess {
Guess {
value,
dist: Real::zero(),
}
}
/// Return `true` if this is an exact guess value.
///
/// In other words, the value is not `guessed` anymore.
pub fn is_exact(&self) -> bool {
self.dist.is_zero()
}
}
impl Default for Guess {
fn default() -> Guess {
Guess {
value: Real::zero(),
dist: Real::infinity(),
}
}
}
/// A subproblem model for guessing candidate and center values.
pub trait GuessModel {
/// Add a function value to this model.
fn add_function_value(&mut self, y: &Point, value: Real);
/// Add a minorant to this model.
///
/// The minorant must be centered at the global 0.
fn add_minorant(&mut self, y: &Point, m: &Arc<Minorant>);
/// Return a guess value at the given point.
///
/// A guess value is an approximation of the function value in
/// the given point. If the returned guess value has distance
/// zero, it must be exact.
fn get_guess_value(&mut self, y: &Point) -> Guess;
/// Return a lower bound at the given point.
fn get_lower_bound(&mut self, y: &Point) -> Real;
}
|
Added src/solver/asyn/guessmodels/nearestvalue.rs.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
/*
* Copyright (c) 2020 Frank Fischer <frank-fischer@shadow-soft.de>
*
* This program is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
//! Asynchronous subproblem with zero-order approximation.
use num_traits::{Float, Zero};
use std::collections::VecDeque;
use std::sync::Arc;
use crate::data::Minorant;
use crate::Real;
use super::{Guess, GuessModel, Point};
/// The maximal number of last evaluation points to be kept in the model.
#[allow(non_upper_case_globals)]
const MaxEvalPoints: usize = 5;
/// The maximal number of last minorants to be kept in the model.
#[allow(non_upper_case_globals)]
const MaxMinorants: usize = 5;
/// Information associated with one subproblem.
pub struct NearestValue {
/// The last evaluation points (y, function_value).
eval_points: VecDeque<(Point, Real)>,
/// The last minorants.
minorants: VecDeque<Arc<Minorant>>,
/// The last computed guess value (point, guess).
last_guess: Option<(Point, Guess)>,
/// The last computed lower bound (point, value).
last_lower_bound: Option<(Point, Real)>,
}
impl NearestValue {
pub fn new() -> NearestValue {
NearestValue {
eval_points: VecDeque::new(),
minorants: VecDeque::new(),
last_guess: None,
last_lower_bound: None,
}
}
}
impl Default for NearestValue {
fn default() -> NearestValue {
NearestValue::new()
}
}
impl GuessModel for NearestValue {
fn add_function_value(&mut self, y: &Point, value: Real) {
// update value at last guess point
if let Some(ref mut g) = self.last_guess {
let dist = y.distance(&g.0);
if dist.is_zero() || dist < g.1.dist {
g.1 = Guess::new(value, dist);
}
}
// Add evaluation point to list.
self.eval_points.push_back((y.clone(), value));
while self.eval_points.len() > MaxEvalPoints {
self.eval_points.pop_front();
}
}
fn add_minorant(&mut self, _y: &Point, m: &Arc<Minorant>) {
// update value at last lower-bound point
if let Some(ref mut lb) = self.last_lower_bound {
lb.1 = lb.1.max(m.eval(&lb.0.point));
}
// Add minorant to list.
self.minorants.push_back(m.clone());
while self.minorants.len() > MaxMinorants {
self.minorants.pop_front();
}
}
fn get_guess_value(&mut self, y: &Point) -> Guess {
if let Some(ref g) = self.last_guess {
if g.0.index == y.index {
return g.1;
}
}
let g = self
.eval_points
.iter()
.map(|(x, value)| Guess::new(*value, x.distance(y)))
.min_by(|a, b| a.dist.partial_cmp(&b.dist).unwrap())
.unwrap_or_else(Guess::default);
self.last_guess = Some((y.clone(), g));
g
}
fn get_lower_bound(&mut self, y: &Point) -> Real {
// check if y equals the last evaluation point
if let Some(ref lb) = self.last_lower_bound {
if lb.0.index == y.index {
// ... yes, so just return that value
return lb.1;
}
}
// full computation of lower bound at y
let lb = self
.minorants
.iter()
.map(|m| m.eval(&y.point))
.max_by(|a, b| a.partial_cmp(b).unwrap())
.unwrap_or_else(|| -Real::infinity());
// save last evaluation
self.last_lower_bound = Some((y.clone(), lb));
lb
}
}
|
Deleted src/solver/asyn/subzero.rs.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to src/solver/sync.rs.
1 | /* | | | 1 2 3 4 5 6 7 8 9 | /* * Copyright (c) 2019, 2020 Frank Fischer <frank-fischer@shadow-soft.de> * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but |
| ︙ | ︙ | |||
19 20 21 22 23 24 25 |
#[cfg(feature = "crossbeam")]
use rs_crossbeam::channel::{unbounded as channel, RecvError};
#[cfg(not(feature = "crossbeam"))]
use std::sync::mpsc::{channel, RecvError};
use log::{debug, info, warn};
| < | 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
#[cfg(feature = "crossbeam")]
use rs_crossbeam::channel::{unbounded as channel, RecvError};
#[cfg(not(feature = "crossbeam"))]
use std::sync::mpsc::{channel, RecvError};
use log::{debug, info, warn};
use num_traits::Float;
use std::sync::Arc;
use std::time::Instant;
use threadpool::ThreadPool;
use crate::{DVector, Real};
|
| ︙ | ︙ |