Just to answer Marcello's question from this evening about optimal coin spending:
We have:
a set D of denominations,
a value function v : D -> R,
a deposit fee function f : D -> R,
a maximum deposit fee m>=0,
a refresh/melt fee function r : D -> R,
a wallet function w : D -> Z,
an unknown constant c>0 but very small,
and a price>0
where f, r, and w are non-negative. And R and Z denote the reals and integers, respectively.
We want to select two functions
spend total t : D -> Z,
spend partial p : D -> Z, and
partial value v' : D -> R
so as to minimize the function :
max(0, -m + sum_x f[x]*d[x])
+ sum_x r[x]*p[x]
+ c * sum_x (d+p)[x]
subject to the constraints
t[x] >= 0
p[x] >= 0
(t+p)[x] <= w[x]
sum_x v[x]*d[x] >= price
sum_x (v[x]*t[x] + v'[x]) <= price
v'[x] <= v[x]
p[x] <= 1
where d = t+p is a convenient notation.
We assume these last two because if p[x] > 1 then you could refresh one less coin. In fact, you should never refresh more than one coin, so that's sum_x p[x] <= 1, but not sure we need that much.
1. Dynamic Programming
There is a solution using dynamic programming because the problem has the optimal substructure property :
If the above parameters have an optimal assignment, then replacing
price := price - v[x]*t[x] - v'[x],
t[x] := 0,
p[x] := 0, and
v'[x] := 0
gives another optimal solution, as otherwise we'd get a better one for the first situation.
There is however no assurence that t[x] = price mod v[x] for some x in D, so naively such solutions give you running times like O(price * |D|), which kinda sucks actually. Just one simplified example :
but presumably it assumes v[x] divides v[y] whenever v[y]>v[x] or something similar.
There are crazy coin systems that violate this assumption, which impacts Taler in two ways :
First, an obnoxious mint could make insane denominations that forced customers and merchants to spend slightly more, while nominally claiming fees competitive with other mints. We can ignore this as it's rather obvious manipulation and an obnoxious mint cannot charge much more. I think an approximation result says the mint cannot even double the fees.
Second, there could be two mints whose denominations together formed an insane set that caused bad coin usage. Again the results should not be too bad, but one could combat this by processing mints individually before considering them together.
Now there are some additional complexities related to giving change and not knowing how to order the denominations, so maybe worth a bit more formalism first.
3. Integer linear programming
(It helps with understanding the greedy algorithm too)
We almost have an integer linear program because all these functions parameterized by D are simply vectors. We just need to eliminate that annoying max(0, ), which we do by introducing a variable z. I'm going to switch from t[x] to d[x] = t[x] + p[x] and suppress the indexes [x] here since only m, c, and z lack indexes.
It's maybe now easier to visualize the greedy algorithm working when you think about that sum_x v*d together with this simple objective function. As a bonus, we observe that c got folded into r and f, which simplifies implementing stuff.
If we're only allowed to spend one denomination at some price, then we shown the minimum is achieved when that denomination x in D is chosen to minimize
where p[x] = max(1,price mod v[x]). We could approximate this by (f[x]+c)/v[x] under several reasonable hypotheses, not unfortunately r >> f, but price >> v[x] still helps. In any case, there are many situations where minimizing (f[x]+c)/v[x] handles this single denomination spend.
We know from our optimal substructure property that, for an optimal allocation, there is a denomination x such that zeroing out t[y], p[y], and v'[y] for y not x, and adjusting the price accordingly, gives an optimal allocation. It follows that a greedy algorithm that uses D sorted by increasing (f[x]+c)/v[x] frequently works, although not when mints charge too much for refreshes