Skip to content

Commit

Permalink
added a few test cases to showcase how tests #8 may potentially be fixed
Browse files Browse the repository at this point in the history
  • Loading branch information
renepickhardt committed May 15, 2022
1 parent 8e44490 commit 54dcf4d
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 20 deletions.
63 changes: 43 additions & 20 deletions pickhardtpayments/UncertaintyChannel.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ class UncertaintyChannel(Channel):
on the capacity of the channel the class contains the `capacity` as seen in the funding tx output.
As we also optimize for fees and want to be able to compute the fees of a flow the classe
contains information for the feerate (`ppm`) and the base_fee (`base`).
contains information for the feerate (`ppm`) and the base_fee (`base`).
Most importantly the class stores our belief about the liquidity information of a channel.
This is done by reducing the uncertainty interval from [0,`capacity`] to
This is done by reducing the uncertainty interval from [0,`capacity`] to
[`min_liquidity`, `max_liquidity`].
Additionally we need to know how many sats we currently have allocated via outstanding onions
to the channel which is stored in `inflight`.
Expand Down Expand Up @@ -58,19 +58,40 @@ def in_flight(self):
# FIXME: store timestamps when using setters so that we know when we learnt our belief
@min_liquidity.setter
def min_liquidity(self, value: int):
if type(value) is not int:
raise TypeError(
"value {} needs to be an int but {} given".format(value, type(value)))
if value < 0:
raise ValueError(
"Cannot set minimum liquidity to {} beacuse it is lower than 0".format(value))
if value > self.max_liquidity:
raise ValueError("Cannot set minimum liquidity to {} as that is higher than max_liquidity ({})".format(
value, self.max_liquidity))
self._min_liquidity = value

# FIXME: store timestamps when using setters so that we know when we learnt our belief
@max_liquidity.setter
@ max_liquidity.setter
def max_liquidity(self, value: int):
if type(value) is not int:
raise TypeError(
"value {} needs to be an int but {} given".format(value, type(value)))
if value < 0:
raise ValueError(
"Cannot set maximum liquidity to {} beacuse it is lower than 0".format(value))
if value > self.capacity:
raise ValueError(
"Cannot set maximum liquidity to {} beacuse it is higher than the capacity of {}".format(value, self.capacity))
if value < self.min_liquidity:
raise ValueError("Cannot set maximum liquidity to {} as that is lower than min_liquidity ({})".format(
value, self.min_liquidity))
self._max_liquidity = value

# FIXME: store timestamps when using setters so that we know when we learnt our belief
@in_flight.setter
@ in_flight.setter
def in_flight(self, value: int):
self._in_flight = value

@property
@ property
def conditional_capacity(self, respect_inflight=True):
# FIXME: make sure if respect_inflight=True is needed for linearized cost
if respect_inflight == False:
Expand All @@ -91,12 +112,12 @@ def allocate_amount(self, amt: int):
# FIXME: store timestamps when using setters so that we know when we learnt our belief
def forget_information(self):
"""
resets the information that we belief to have about the channel.
resets the information that we belief to have about the channel.
"""
self.min_liquidity = 0
self.max_liquidity = self.capacity
self._min_liquidity = 0
self._max_liquidity = self.capacity
# FIXME: Is there a case where we want to keep inflight information but reset information?
self.in_flight = 0
self._in_flight = 0

def entropy(self):
"""
Expand All @@ -117,7 +138,7 @@ def success_probability(self, amt: int = None):
actual min cost flow computation as we linearize this to an integer unit cost
In particular the conditional probability P(X>=a | min_liquidity < X < max_liquidity)
is computed based on our belief also respecting how many satoshis we have currently
is computed based on our belief also respecting how many satoshis we have currently
outstanding and allocated. Thus it is possible that testing for the `amt=0` that the success probability
is zero and in particular not `1`.
Expand Down Expand Up @@ -183,7 +204,7 @@ def linearized_routing_cost_msat(self, amt: int):
"""
Linearizing the routing cost by ignoring the base fee.
Note that one can still include channels with small base fees to the computation the base
Note that one can still include channels with small base fees to the computation the base
will just be excluded in the computation and has to be paid later anyway. If as developers
we go down this road this will allow routing node operators to game us with the base fee
thus it seems reasonable in routing computations to just ignore channels that charge a base fee.
Expand Down Expand Up @@ -231,26 +252,28 @@ def get_piecewise_linearized_costs(self, number_of_pieces: int = DEFAULT_N,
return pieces

"""
#FIXME: interestingly the following feature engineering does not work at all
TODO: Look at more standard Univariate Transformations on Numerical Data techniques as described at
# FIXME: interestingly the following feature engineering does not work at all
TODO: Look at more standard Univariate Transformations on Numerical Data techniques as described at
https://www.kaggle.com/code/milankalkenings/comprehensive-tutorial-feature-engineering/notebook
def get_piecewise_linearized_costs(self,number_of_pieces : int = DEFAULT_N,
mu : int = DEFAULT_MU,
quantization : int = DEFAULT_QUANTIZATION):
#FIXME: compute smarter linearization eg: http://www.iaeng.org/publication/WCECS2008/WCECS2008_pp1191-1194.pdf
# FIXME: compute smarter linearization eg: http://www.iaeng.org/publication/WCECS2008/WCECS2008_pp1191-1194.pdf
pieces = []*number_of_pieces
#using certainly available liquidity costs us nothing but fees
# using certainly available liquidity costs us nothing but fees
if int((self.min_liquidity-self.in_flight)/quantization) > 0:
uncertintay_unit_cost = 0 #is zero as we have no uncertainty in this case!
pieces.append((int((self.min_liquidity-self.in_flight)/quantization),uncertintay_unit_cost + mu * self.linearized_integer_routing_unit_cost()))
pieces.append((int((self.min_liquidity-self.in_flight)/quantization),
uncertintay_unit_cost + mu * self.linearized_integer_routing_unit_cost()))
number_of_pieces-=1
# FIXME: include the in_flight stuff
if int(self.conditional_capacity/quantization) > 0 and number_of_pieces > 0:
capacity = int(self.conditional_capacity/(number_of_pieces*quantization))
capacity = int(self.conditional_capacity/ \
(number_of_pieces*quantization))
uncertintay_unit_cost = self.linearized_integer_uncertainty_unit_cost()
for i in range(number_of_pieces):
a = (i+1)*uncertintay_unit_cost+1
Expand Down Expand Up @@ -278,7 +301,7 @@ def learn_n_bits(self, oracle: OracleLightningNetwork, n: int = 1):
conducts n probes of channel via binary search starting from our belief
This of course only learns `n` bits if we use a uniform success probability as a prior
thus this method will not work if a different prior success probability is assumed
thus this method will not work if a different prior success probability is assumed
"""
if n <= 0:
return
Expand Down
71 changes: 71 additions & 0 deletions test/testUncertaintyChannel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from pickhardtpayments.UncertaintyChannel import UncertaintyChannel
from pickhardtpayments.Channel import Channel
import unittest
import sys
sys.path.append(r'../pickhardtpayments')


class UncertaintyChannelTestCases(unittest.TestCase):
def __init__(self, *args, **kwargs):
super(UncertaintyChannelTestCases, self).__init__(*args, **kwargs)
channel_jsn = {"satoshis": 9}
self._channel = UncertaintyChannel(Channel(channel_jsn))

def test_min_liquidity(self):
with self.assertRaises(ValueError) as exception:
self._channel.min_liquidity = -1

with self.assertRaises(ValueError) as exception:
self._channel.min_liquidity = self._channel.max_liquidity + 1

with self.assertRaises(TypeError) as exception:
self._channel.min_liquidity = "3.4"

with self.assertRaises(TypeError) as exception:
self._channel.min_liquidity = 3.4

def test_max_liquidity(self):
with self.assertRaises(ValueError) as exception:
self._channel.max_liquidity = -1

with self.assertRaises(ValueError) as exception:
self._channel.max_liquidity = self._channel.min_liquidity - 1

with self.assertRaises(ValueError) as exception:
self._channel.max_liquidity = self._channel.capacity + 1

with self.assertRaises(TypeError) as exception:
self._channel.max_liquidity = "3.4"

with self.assertRaises(TypeError) as exception:
self._channel.max_liquidity = 3.4

def test_success_probability(self):
# channel = UncertaintyChannel(Channel(channel_jsn))

# can't deliver more than capacity
assert self._channel.success_probability(self._channel.capacity+1) == 0

# does basic computation work?
assert self._channel.success_probability(1) == float(9.0/10)

# do simple conditional conditional probabilities work?
self._channel.min_liquidity = 2
assert self._channel.success_probability(1) == 1.0
assert self._channel.success_probability(2) == 1.0
assert self._channel.success_probability(3) == float(7.0/8)

# does in flight consideration work?
self._channel.min_liquidity = 2
p = self._channel.success_probability(3)
self._channel.in_flight = 3
assert self._channel.success_probability(0) == p

def test_conditionals(self):
self._channel.min_liquidity = self._channel.capacity
with self.assertRaises(ValueError) as exception:
self._channel.max_liquidity = 0


if __name__ == '__main__':
unittest.main()

0 comments on commit 54dcf4d

Please sign in to comment.