Skip to content

Commit 5cb2878

Browse files
csharrisonapasel422martinthomson
authored
Fair integer credit assignment (#225)
* Fair integer credit assignment * Code review and flesh out note * Showcase Martin's algo * Apply suggestions from code review Co-authored-by: Andrew Paseltiner <[email protected]> Co-authored-by: Martin Thomson <[email protected]> * Tweaks * Apply suggestions from code review Co-authored-by: Andrew Paseltiner <[email protected]> * Minor tweaks * Apply suggestions from code review Co-authored-by: Andrew Paseltiner <[email protected]> * Update api.bs Co-authored-by: Martin Thomson <[email protected]> --------- Co-authored-by: Andrew Paseltiner <[email protected]> Co-authored-by: Martin Thomson <[email protected]>
1 parent 260bfe4 commit 5cb2878

File tree

1 file changed

+35
-17
lines changed

1 file changed

+35
-17
lines changed

api.bs

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1701,27 +1701,45 @@ To perform <dfn>common matching logic</dfn>, given
17011701

17021702
<div algorithm>
17031703
To <dfn>fairly allocate credit</dfn>, given a [=list=] of doubles |credit| and an integer |value|:
1704+
1. [=Assert=]: |credit| is not [=list/is empty|empty=].
17041705
1. Let |sumCredit| be the sum of |credit|'s [=list/items=].
1705-
1. Let |rawNormalizedCredit| be a new [=list=].
1706-
1. Let |normalizedCredit| be a new [=list=].
1706+
1. Let |roundedCredit| be a new [=list=].
17071707
1. [=list/iterate|For each=] |item| of |credit|:
1708-
1. Let |rawNormalized| be |value| * |item| / |sumCredit|.
1709-
1. [=list/Append=] |rawNormalized| to |rawNormalizedCredit|.
1710-
1. Let |normalized| be |rawNormalized| rounded towards positive Infinity and converted to an integer.
1711-
1. [=list/Append=] |normalized| to |normalizedCredit|.
1712-
1. Let |shuffledFractionalIndices| be [=list/get the indices|the indices=] of |credit|, ordered randomly.
1713-
1. [=list/remove|Remove=] all indices |i| from |shuffledFractionalIndices| where |rawNormalizedCredit|[|i|] has no fractional part.
1714-
1. [=list/iterate|For each=] |index| of |shuffledFractionalIndices|:
1715-
1. If the sum of |normalizedCredit|'s [=list/items=] is equal to |value|, [=iteration/break=].
1716-
1. Decrement |normalizedCredit|[|index|] by 1.
1717-
1. Return |normalizedCredit|.
1708+
1. Let |normalizedCredit| be |value| * |item| / |sumCredit|.
1709+
1. [=list/append|Append=] |normalizedCredit| to |roundedCredit|.
1710+
1. Let |idx1| be 0.
1711+
1. [=list/iterate|For each=] |n| of [=list/get the indices=] of |roundedCredit|, removing the first [=list/item=]:
1712+
1. Let |idx2| be |n|.
1713+
1. Let |frac1| be |roundedCredit|[|idx1|] − floor(|roundedCredit|[|idx1|]).
1714+
1. Let |frac2| be |roundedCredit|[|idx2|] − floor(|roundedCredit|[|idx2|]).
1715+
1. If |frac1| and |frac2| are both equal to zero, [=iteration/continue=].
1716+
1. If |frac1| + |frac2| is greater than 1, let |incr1| be 1 − |frac1| and |incr2| be 1 − |frac2|.
1717+
1. Otherwise, let |incr1| be −|frac1| and |incr2| be −|frac2|.
1718+
<p class=note>|incr1| denotes the amount to increment |roundedCredit|[|idx1|] so that it is integral, and similar for |incr2| and |idx2|.
1719+
Note that these values can be negative.
1720+
1721+
1. Let |p1| be |incr2| / (|incr1| + |incr2|).
1722+
<p class=note>The value |p1| denotes the probability that the [=list/item=] in |idx1| is rounded to an integer.
1723+
Divide by zero occurs when |incr1| + |incr2| is 0, which is only possible if both |frac1| and |frac2|
1724+
are integers (either both 0 or 1 exactly). In this case |idx2| does not need to be rounded, so we just skip it.
1725+
1726+
1. Let |r| be a random double between 0 and 1 (inclusive).
1727+
1. If |r| is less than |p1|, let |incr| be |incr1| and swap the values of |idx1| and |idx2|.
1728+
1. Otherwise, let |incr| be |incr2|.
1729+
1. Increment |roundedCredit|[|idx2|] by |incr|.
1730+
1. Decrement |roundedCredit|[|idx1|] by |incr|.
1731+
1. Let |integerCredit| be the result of converting [=list/iterate|every=] [=list/item=] from |roundedCredit| into an integer by rounding to the nearest integer,
1732+
and rounding halfway cases away from zero.
1733+
1. Return |integerCredit|.
1734+
<p class=note>This final rounding step is only here in cases where tiny floating-point addition and subtraction errors do not completely remove
1735+
the fractional part for |roundedCredit|[|idx1|]. The rounding mode at half will never actually occur, and is chosen to match C++ <code>std::round</code>
1736+
behavior for ease of implementation.
1737+
17181738
<p class=note>The algorithm aims to 1) allocate exactly |value| total value across assignments,
1719-
2) never bias towards any particular credit assignment in rounding, and
1739+
2) maintain the property that the expected value of |integerCredit| is exactly equal to the normalized credit
1740+
(i.e. |credit| * |value| / |sumCredit| where * denotes element-wise multiplication)
17201741
3) never incur an error greater than 1 to any assignment.
1721-
<p class=issue>
1722-
While the algorithm is "unbiased" in the sense that the probability of being decremented is equal
1723-
across entries, ideally the final array of value is unbiased in the sense that its expectation
1724-
should be equal to |rawNormalizedCredit|. This is not achieved by the current algorithm.
1742+
17251743

17261744
</div>
17271745

0 commit comments

Comments
 (0)