Commit 72955ea9 authored by Jason Rhinelander's avatar Jason Rhinelander

Added hasApprox() methods

They work just like transferApprox(), except only indicate whether the
transfer will succeed rather than actually doing the transfer.
parent 2f188284
......@@ -12,7 +12,7 @@ set(eris_description "Agent-based economic modelling library")
# Eris package version
set(ERIS_VERSION_MAJOR "0")
set(ERIS_VERSION_MINOR "3")
set(ERIS_VERSION_PATCH "3")
set(ERIS_VERSION_PATCH "4")
set(ERIS_VERSION "${ERIS_VERSION_MAJOR}.${ERIS_VERSION_MINOR}.${ERIS_VERSION_PATCH}")
# eris library version (CURRENT.REVISION.AGE), which is totally separate
......@@ -32,7 +32,7 @@ set(ERIS_VERSION "${ERIS_VERSION_MAJOR}.${ERIS_VERSION_MINOR}.${ERIS_VERSION_PAT
# (So something like 3.7.1 indicates the 8th revision of the liberis-3
# interface, and that code that links against liberis-2.* can link against this
# version, but code that links against liberis-1.* cannot.
set(LIBERIS_CURRENT "2")
set(LIBERIS_CURRENT "3")
set(LIBERIS_REVISION "0")
set(LIBERIS_AGE "0")
......
......@@ -190,14 +190,14 @@ class BundleNegative {
/// Scales a BundleNegative's quantities by `1/d`
BundleNegative& operator /= (const double d);
/// The default epsilon for transferApprox(), if not specified.
/// The default epsilon for transferApprox() and hasApprox(), if not specified.
static constexpr double default_transfer_epsilon = 1.0e-12;
/** Transfers (approximately) the given amount between two Bundles. Positive quantities in
* `amount` are transferred from the invoked object to the `to` Bundle; negative quantities
* are transferred from the `to` Bundle to the invoked object. The epsilon parameter is the
* relative amount that the transfer quantities may be adjusted in order to transfer the
* entire quantity away from a Bundle.
* amount, relative to initial bundle quantities, that the transfer quantities may be
* adjusted in order to transfer the entire quantity away from a Bundle.
*
* Calling
*
......@@ -221,11 +221,11 @@ class BundleNegative {
* - If a transfer would result in a good in the source Bundle having a quantity with
* absolute value less than `epsilon` times the pre-transfer quantity, the transferred
* quantity of that good will instead be the total quantity in the source Bundle.
* - Otherwise, if a transfer would results in a good in the destination Bundle having a quantity with
* absolute value less than `epsilon` times the pre-transfer quantity, the transferred
* quantity will be the amount required to reach exactly 0. (Note that this case is only
* possible when the destination is a BundleNegative with a negative quantity, since the
* destination bundle is always the one being added to).
* - Otherwise, if a transfer would results in a good in the destination Bundle having a
* quantity with absolute value less than `epsilon` times the pre-transfer quantity, the
* transferred quantity will be the amount required to reach exactly 0. (Note that this
* case is only possible when the destination is a BundleNegative with a negative
* quantity, since the destination bundle is always the one being added to).
*
* \param amount the amount to transfer. Goods with positive quantities are transferred
* from the object into `to`; negative quantities are transferred frin `to` into the object.
......@@ -242,16 +242,18 @@ class BundleNegative {
* \throws Bundle::negativity_error if either the caller or `to` are actually Bundle objects
* (rather than BundleNegative objects) with insufficient quantities to approximately
* satisfy the transfer.
*
* \sa Bundle::hasApprox(const BundleNegative&, BundleNegative&, double)
*/
BundleNegative transferApprox(BundleNegative amount, BundleNegative &to, double epsilon = default_transfer_epsilon);
BundleNegative transferApprox(const BundleNegative &amount, BundleNegative &to, double epsilon = default_transfer_epsilon);
/** Transfers approximately the given amount from the caller object and returns it. This is
* like the above 3-argument transferApprox(BundleNegative, BundleNegative&, double)
* like the above 3-argument transferApprox(const BundleNegative&, BundleNegative&, double)
* except that the amount is not transferred into a target Bundle but simply returned. Like
* the 3-argument version, negative transfer amounts are added to the calling object and
* will be negative in the returned object.
*
* Any zero-value goods will be removed from `*this` before returning.
* Any zero-quantity goods will be removed from `*this` before returning.
*
* This method is also useful for adding or removing approximate amounts from a bundle by
* simple ignoring the return value. Thus the following:
......@@ -280,8 +282,10 @@ class BundleNegative {
*
* \throws Bundle::negativity_error if either the caller is actually a Bundle object (rather
* than BundleNegative) with insufficient quantities to approximately satisfy the transfer.
*
* \sa Bundle::hasApprox(const BundleNegative&, double)
*/
BundleNegative transferApprox(BundleNegative amount, double epsilon = default_transfer_epsilon);
BundleNegative transferApprox(const BundleNegative &amount, double epsilon = default_transfer_epsilon);
/// Adds two BundleNegative objects together and returns the result.
BundleNegative operator + (const BundleNegative &b) const;
......@@ -679,6 +683,41 @@ class Bundle final : public BundleNegative {
*/
static Bundle reduce(BundleNegative &a, BundleNegative &b);
/** Returns true if the called-upon bundle has approximately enough of each
* positive-quantity good in `amount`, and the bundle `to` has approximately enough of each
* negative-quantity good in `amount`.
*
* a.hasApprox(transfer, b)
*
* is notionally equivalent to:
*
* (a >= transfer.positive() and b >= transfer.negative())
*
* except that it allows slight numerical imprecision when the compared amounts are very
* similar.
*
* If this method returns true, it is a guarantee that transferApprox() called with the same
* arguments will succeed (i.e. without resulting in a BundleNegativity exception).
*/
bool hasApprox(const BundleNegative &amount, const Bundle &to, double epsilon = default_transfer_epsilon) const;
/** Returns true if the called-upon bundle has approximately enough of each
* positive-quantity good in `amount` to complete a transfer via transferApprox(). Negative
* quantites in `amount` are ignored.
*
* a.hasApprox(bundle)
*
* is notionally equivalent to:
*
* (a >= bundle.positive())
*
* except that it allows for numerical error for goods with very similar quantities.
*
* If this method returns true, it is a guarantee that transferApprox() called with the same
* arguments will succeed (i.e. without resulting in a BundleNegativity exception).
*/
bool hasApprox(const BundleNegative &amount, double epsilon = default_transfer_epsilon) const;
/** Overloaded so that a Bundle can be printed nicely with `std::cout << bundle`.
*
* Example outputs:
......
......@@ -390,7 +390,7 @@ void BundleNegative::endEncompassing() {
}
BundleNegative BundleNegative::transferApprox(BundleNegative amount, BundleNegative &to, double epsilon) {
BundleNegative BundleNegative::transferApprox(const BundleNegative &amount, BundleNegative &to, double epsilon) {
beginTransaction(true);
to.beginTransaction(true);
BundleNegative actual;
......@@ -435,7 +435,7 @@ BundleNegative BundleNegative::transferApprox(BundleNegative amount, BundleNegat
return actual;
}
BundleNegative BundleNegative::transferApprox(BundleNegative amount, double epsilon) {
BundleNegative BundleNegative::transferApprox(const BundleNegative &amount, double epsilon) {
beginTransaction(true);
BundleNegative actual;
actual.beginEncompassing();
......@@ -471,6 +471,33 @@ BundleNegative BundleNegative::transferApprox(BundleNegative amount, double epsi
return actual;
}
bool Bundle::hasApprox(const BundleNegative &amount, const Bundle &to, double epsilon) const {
for (auto &g : amount) {
double abs_transfer = fabs(g.second);
if (abs_transfer == 0) continue;
const double &q = g.second > 0 ? (*this)[g.first] : to[g.first];
// If the final amount is lower than -ε*(original q), the transferApprox will fail
if (q - abs_transfer <= -epsilon * q)
return false;
}
return true;
}
bool Bundle::hasApprox(const BundleNegative &amount, double epsilon) const {
for (auto &g : amount) {
if (g.second <= 0) continue;
const double &q = (*this)[g.first];
// If the final amount is lower than -ε*(original q), the transferApprox will fail
if (q - g.second <= -epsilon * q)
return false;
}
return true;
}
// Prints everything *after* the "Bundle" or "BundleNegative" tag, i.e. starting from "(".
void BundleNegative::_print(std::ostream &os) const {
os << "(";
......
......@@ -894,7 +894,7 @@ TEST(Algebra, ConstantTimesBundle) {
EXPECT_EQ(BundleNegative({{1,-7}, {2,1.5}}), -0.5*bn);
EXPECT_EQ(Bundle({{1,7}, {2,1.5}}), 0.5*bp);
EXPECT_THROW(-3 * bp, Bundle::negativity_error);
}
TEST(Algebra, BundleDividedbyConstant) {
......@@ -1018,6 +1018,12 @@ TEST(AlgebraicModifiers, TransferApprox) {
EXPECT_EQ(Bundle({{1, 0}, {2, 0}, {3, 4999}, {4, 1000.5}}), ab);
EXPECT_EQ(Bundle({{1, 5999}, {2, 30001}, {3, 95001}, {4, 0}}), cb);
// Test out hasApprox
Bundle ha {{1, 1001}}, hb {{2, 99}};
BundleNegative ht {{1, 1000}, {2, -99.3}};
EXPECT_TRUE(ha.hasApprox(ht, hb, .01));
EXPECT_FALSE(ha.hasApprox(ht, hb, .00001));
}
TEST(AdvancedAlgebra, BundleCoverage) {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment