double weakBoundDistance(const VList & oldV, const VList & newV) { // Here we implement a weak bound (can also be seen in Cassandra's code) // This is mostly because a strong bound is more costly (it requires performing // multiple LPs) and also the code at the moment does not support it cleanly, so // I prefer waiting until I have a good implementation of an LP class that hides // complexity from here. // // The logic of the weak bound is the following: the variation between the old // VList and the new one is equal to the maximum distance between a ValueFunction // in the old VList with its closest match in the new VList. So the farthest from // closest. // // We define distance between two ValueFunctions as the maximum between their // element-wise difference. if ( !oldV.size() ) return 0.0; double distance = 0.0; for ( const auto & newVE : newV ) { // Initialize closest distance for newVE as infinity double closestDistance = std::numeric_limits<double>::infinity(); for ( const auto & oldVE : oldV ) { // Compute the distance, we pick the max double distance = (newVE.values - oldVE.values).cwiseAbs().maxCoeff(); // Keep the closest, we pick the min closestDistance = std::min(closestDistance, distance); } // Keep the maximum distance between a new VList and its closest old VList distance = std::max(distance, closestDistance); } return distance; }
void N2FNewsManager::SortByDate() { VList tempList; while (messages->Size()) { tempList.PushBack(*(messages->Begin())); messages->Erase(messages->Begin()); } while (tempList.Size()) { VList::Iterator maxIt; N2FMessage *curMax = NULL; for (VList::Iterator it = tempList.Begin(); it != tempList.End(); it++) { N2FMessage *tmp = (N2FMessage*)(*it); if (!curMax || IsHigher(curMax, tmp)) { curMax = tmp; maxIt = it; } } messages->PushBack(curMax); tempList.Erase(maxIt); } }
VList IncrementalPruning::crossSum(const VList & l1, const VList & l2, size_t a, bool order) { VList c; if ( ! ( l1.size() && l2.size() ) ) return c; // We can get the sizes of the observation vectors // outside since all VEntries for our input VLists // are guaranteed to be sized equally. const auto O1size = std::get<OBS>(l1[0]).size(); const auto O2size = std::get<OBS>(l2[0]).size(); for ( const auto & v1 : l1 ) { auto O1begin = std::begin(std::get<OBS>(v1)); auto O1end = std::end (std::get<OBS>(v1)); for ( const auto & v2 : l2 ) { auto O2begin = std::begin(std::get<OBS>(v2)); auto O2end = std::end (std::get<OBS>(v2)); // Cross sum auto v = std::get<VALUES>(v1) + std::get<VALUES>(v2); // This step now depends on which order the two lists // are. This function is only used in this class, so we // know that the two lists are "adjacent"; however one // is after the other. `order` tells us which one comes // first, and we join the observation vectors accordingly. VObs obs; obs.reserve(O1size + O2size); if ( order ) { obs.insert(std::end(obs),O1begin, O1end); obs.insert(std::end(obs),O2begin, O2end); } else { obs.insert(std::end(obs),O2begin, O2end); obs.insert(std::end(obs),O1begin, O1end); } c.emplace_back(std::move(v), a, std::move(obs)); } } return c; }
std::tuple<double, ValueFunction> IncrementalPruning::operator()(const M & model) { // Initialize "global" variables S = model.getS(); A = model.getA(); O = model.getO(); auto v = makeValueFunction(S); // TODO: May take user input unsigned timestep = 0; Pruner prune(S); Projecter projecter(model); const bool useTolerance = checkDifferentSmall(tolerance_, 0.0); double variation = tolerance_ * 2; // Make it bigger while ( timestep < horizon_ && ( !useTolerance || variation > tolerance_ ) ) { ++timestep; // Compute all possible outcomes, from our previous results. // This means that for each action-observation pair, we are going // to obtain the same number of possible outcomes as the number // of entries in our initial vector w. auto projs = projecter(v[timestep-1]); size_t finalWSize = 0; // In this method we split the work by action, which will then // be joined again at the end of the loop. for ( size_t a = 0; a < A; ++a ) { // We prune each outcome separately to be sure // we do not replicate work later. for ( size_t o = 0; o < O; ++o ) { const auto begin = std::begin(projs[a][o]); const auto end = std::end (projs[a][o]); projs[a][o].erase(prune(begin, end, unwrap), end); } // Here we reduce at the minimum the cross-summing, by alternating // merges. We pick matches like a reverse binary tree, so that // we always pick lists that have been merged the least. // // Example for O==7: // // 0 <- 1 2 <- 3 4 <- 5 6 // 0 ------> 2 4 ------> 6 // 2 <---------------- 6 // // In particular, the variables are: // // - oddOld: Whether our starting step has an odd number of elements. // If so, we skip the last one. // - front: The id of the element at the "front" of our current pass. // note that since passes can be backwards this can be high. // - back: Opposite of front, which excludes the last element if we // have odd elements. // - stepsize: The space between each "first" of each new merge. // - diff: The space between each "first" and its match to merge. // - elements: The number of elements we have left to merge. bool oddOld = O % 2; int i, front = 0, back = O - oddOld, stepsize = 2, diff = 1, elements = O; while ( elements > 1 ) { for ( i = front; i != back; i += stepsize ) { projs[a][i] = crossSum(projs[a][i], projs[a][i + diff], a, stepsize > 0); const auto begin = std::begin(projs[a][i]); const auto end = std::end (projs[a][i]); projs[a][i].erase(prune(begin, end, unwrap), end); --elements; } const bool oddNew = elements % 2; const int tmp = back; back = front - ( oddNew ? 0 : stepsize ); front = tmp - ( oddOld ? 0 : stepsize ); stepsize *= -2; diff *= -2; oddOld = oddNew; } // Put the result where we can find it if (front != 0) projs[a][0] = std::move(projs[a][front]); finalWSize += projs[a][0].size(); } VList w; w.reserve(finalWSize); // Here we don't have to do fancy merging since no cross-summing is involved for ( size_t a = 0; a < A; ++a ) w.insert(std::end(w), std::make_move_iterator(std::begin(projs[a][0])), std::make_move_iterator(std::end(projs[a][0]))); // We have them all, and we prune one final time to be sure we have // computed the parsimonious set of value functions. const auto begin = std::begin(w); const auto end = std::end (w); w.erase(prune(begin, end, unwrap), end); v.emplace_back(std::move(w)); // Check convergence if ( useTolerance ) variation = weakBoundDistance(v[timestep-1], v[timestep]); } return std::make_tuple(useTolerance ? variation : 0.0, v); }