Stern-Brocot tree¶
This Jupyter notebook provides an example of using the Python package gravis. The .ipynb file can be found here.
It constructs a Stern-Brocot tree with the help of 2x2 matrices and visualizes the tree as directed graph with different layouts. There is also a connection to continued fractions.
References¶
YouTube
Wildberger: The Stern-Brocot tree, matrices and wedges … the implementation here was based on this explanation
Books
Graham, Knuth, Patashnik: Concrete Mathematics (1994), p. 116
Wikipedia
Wolfram MathWorld
Alexander Bogomolny
[1]:
import networkx as nx
import gravis as gv
Standard Stern-Brocot tree¶
A class for 2x2 matrices¶
as a tool for simpler construction of the Stern-Brocot tree
[2]:
class Matrix:
"""A simple class for 2x2 matrices with methods for creating a Stern-Brocot tree."""
def __init__(self, a, b, c, d):
"""Define a 2x2 matrix by providing its 4 elements.
M = |a b|
|c d|
"""
self.a = a
self.b = b
self.c = c
self.d = d
def __repr__(self):
"""Get a string representation of the matrix."""
return '|{} {}|\n|{} {}|'.format(*self.elements)
def __add__(self, other):
"""Calculate matrix addition."""
elements = [e1 + e2 for e1, e2 in zip(self.elements, other.elements)]
matrix = self.__class__(elements)
return matrix
def __mul__(self, other):
"""Calculate matrix multiplication."""
# https://youtu.be/qPeD87HJ0UA?t=230
new_matrix = self.__class__(
self.a * other.a + self.b * other.c, self.a * other.b + self.b * other.d,
self.c * other.a + self.d * other.c, self.c * other.b + self.d * other.d)
return new_matrix
def __eq__(self, other):
"""Check if two matrices are equal."""
return self.elements == other.elements
@property
def lc(self):
"""Create the left child in a Stern-Brocot tree with matrix elements.
References
----------
- https://youtu.be/qPeD87HJ0UA?t=419
"""
return self.__class__(
self.a + self.b, self.b,
self.c + self.d, self.d)
@property
def rc(self):
"""Create the right child in a Stern-Brocot tree with matrix elements."""
return Matrix(self.a, self.a + self.b, self.c, self.c + self.d)
@property
def elements(self):
"""Get the matrix elements as tuple."""
return self.a, self.b, self.c, self.d
@property
def numerator(self):
"""Get the numerator of the fraction."""
return self.a + self.b
@property
def denominator(self):
"""Get the denominator of the fraction."""
return self.c + self.d
@property
def fraction(self):
"""Reconstruct the fraction that corresponds to the matrix."""
return self.numerator, self.denominator
@property
def fraction_string(self):
"""Get a string representation of the fraction."""
return '{}/{}'.format(*self.fraction)
@property
def decimal(self):
"""Get a decimal representation of the fraction."""
return self.numerator / self.denominator
[3]:
# Identity matrix and two operators for creating left and right children
I = Matrix(1, 0, 0, 1)
L = Matrix(1, 0, 1, 1)
R = Matrix(1, 1, 0, 1)
# equivalence of matrix multiplication (star notation) and branching methods (dot notation)
M1 = I * L * L * R * L * R
M2 = I.lc.lc.rc.lc.rc
assert M1 == M2
print('Matrix')
print(M1)
print()
print('Fraction')
print('- as tuple:', M1.fraction)
print('- as numerator and denominator:', M1.numerator, M1.denominator)
print('- as string:', M1.fraction_string)
print('- as decimal number:', M1.decimal)
Matrix
|2 3|
|5 8|
Fraction
- as tuple: (5, 13)
- as numerator and denominator: 5 13
- as string: 5/13
- as decimal number: 0.38461538461538464
[4]:
# Level 0
I.fraction_string
[4]:
'1/1'
[5]:
# Level 1
I.lc.fraction_string, I.rc.fraction_string
[5]:
('1/2', '2/1')
[6]:
# Level 2
I.lc.lc.fraction_string, I.lc.rc.fraction_string, I.rc.lc.fraction_string, I.rc.rc.fraction_string
[6]:
('1/3', '2/3', '3/2', '3/1')
Construction of the tree¶
[7]:
num_levels = 7
dg = nx.DiGraph()
def to_hover_message(matrix):
return 'Matrix:\n{}<br><br>Fraction: {} = {}'.format(matrix, matrix.fraction_string, matrix.decimal)
root = Matrix(1, 0,
0, 1)
sequence = [root]
for level in range(1, num_levels):
new_sequence = []
for i, item in enumerate(sequence):
# Stern-Brocot tree construction
child1, child2 = item.lc, item.rc
new_sequence.append(child1)
new_sequence.append(child2)
# Directed graph construction
dg.add_edge(item.fraction_string, child1.fraction_string)
dg.add_edge(item.fraction_string, child2.fraction_string)
edge1 = dg.edges[(item.fraction_string, child1.fraction_string)]
edge2 = dg.edges[(item.fraction_string, child2.fraction_string)]
edge1['label'] = 'L'
edge1['color'] = 'red'
edge1['label_color'] = 'red'
edge2['label'] = 'R'
# Add node attributes
parent_node = dg.nodes[item.fraction_string]
child_node1 = dg.nodes[child1.fraction_string]
child_node2 = dg.nodes[child2.fraction_string]
parent_node['fraction'] = item.fraction
child_node1['fraction'] = child1.fraction
child_node2['fraction'] = child2.fraction
parent_node['elements'] = item.elements
child_node1['elements'] = child1.elements
child_node2['elements'] = child2.elements
parent_node['hover'] = to_hover_message(item)
child_node1['hover'] = to_hover_message(child1)
child_node2['hover'] = to_hover_message(child2)
parent_node['x'] = (-len(sequence) / 2 + i) * 50
parent_node['y'] = (level - 1) * 50
child_node1['color'] = 'red'
child_node1['label_color'] = 'red'
sequence = new_sequence
# Node attributes for lowest level
for i, item in enumerate(sequence):
parent_node = dg.nodes[item.fraction_string]
parent_node['x'] = (-len(sequence) / 2 + i) * 50
parent_node['y'] = level * 50
Visualization of the tree¶
The following shows different embeddings of the same Stern-Brocot tree.
[8]:
gv.vis(dg, zoom_factor=0.45, show_edge_label=True, edge_label_data_source='label')
[8]:
[9]:
# Assign different coordinates to each node: numerator as x, denominator as y
# - see https://youtu.be/qPeD87HJ0UA?t=1272
dg2 = dg.copy()
for node_id in dg2.nodes:
node = dg2.nodes[node_id]
node['x'] = node['fraction'][0] * 50 - 900
node['y'] = -node['fraction'][1] * 50 + 900
gv.vis(dg2, show_edge_label=True, edge_label_data_source='label')
[9]:
[10]:
# Assign different coordinates to each node: sum of left column as x, sum of right column as y
dg3 = dg.copy()
for node_id in dg3.nodes:
node = dg3.nodes[node_id]
node['x'] = (node['elements'][0] + node['elements'][2]) * 50 - 900
node['y'] = -(node['elements'][1] + node['elements'][3]) * 50 + 900
gv.vis(dg3, show_edge_label=True, edge_label_data_source='label')
[10]:
Connection to continued fractions and mathematical constants¶
See book p. 122
Euler’s number e as continued fraction¶
[11]:
# Continued fraction representations of the irrational number e by an integer sequence with a simple pattern
e_sequence = [
2, 1,
2, 1, 1,
4, 1, 1,
6, 1, 1,
8, 1, 1,
10, 1, 1,
12, 1, 1,
14, 1, 1,
]
[12]:
# Continued fraction
# - Using the integers of the sequence as denominators in a continued fraction
result = None
for element in reversed(e_sequence):
if result is None:
result = 1.0 / element
else:
result = 1.0 / (element + result)
print(1.0 / result)
2.7182818284590455
Euler’s number e as walk in the Stern-Brocot tree¶
[13]:
# Stern-Brocot system
# - Using the integers of the sequence as a series of L and R operations to perform a walk in the tree
def change_or_add_edge(graph, M1, M2, operator_name):
try:
edge = graph.edges[(M1.fraction_string, M2.fraction_string)]
except Exception:
graph.add_edge(M1.fraction_string, M2.fraction_string)
edge = graph.edges[(M1.fraction_string, M2.fraction_string)]
color = 'black' if operator_name == 'R' else 'red'
edge['label'] = operator_name
edge['size'] = 10
edge['color'] = color
edge['label_color'] = 'green'
edge['label_size'] = 20
for M in [M1, M2]:
node = graph.nodes[M.fraction_string]
node['size'] = 18
node['shape'] = 'rectangle'
node['fraction'] = M.fraction
node['elements'] = M.elements
node['hover'] = to_hover_message(M)
if M is M2:
node['color'] = color
node['label_color'] = color
I = Matrix(1, 0,
0, 1)
M = I
operator = R
for element in e_sequence:
for _ in range(element):
M_new = M * operator
op_name = 'R' if operator is R else 'L'
print(op_name, end='')
change_or_add_edge(dg2, M, M_new, op_name) # add the walk into the digraph
M = M_new
operator = L if operator == R else R
print('\n\n{} = {}'.format(M.fraction_string, M.decimal))
RRLRRLRLLLLRLRRRRRRLRLLLLLLLLRLRRRRRRRRRRLRLLLLLLLLLLLLRLRRRRRRRRRRRRRRLR
1286807394/473389985 = 2.718281828459045
[14]:
gv.vis(dg2, show_edge_label=True, edge_label_data_source='label')
[14]:
Generalized Stern-Brocot tree¶
This is an attempt to generalize the Stern-Brocot tree by replacing 2d matrices with 3d matrices and keeping the operations as close to the 2d case as possible. The resulting tree and its embeddings apparently share some similarity to the 2d case. Of course there is more freedom of interpretation due to the higher number of constituents, therefore I’ll leave speculation about possible meanings to the reader.
[15]:
class Matrix3d:
"""A simple class for 3x3 matrices with methods for creating a generalized Stern-Brocot tree."""
def __init__(self, a, b, c, d, e, f, g, h, i):
"""Define a 3x3 matrix by providing its 9 elements.
M = |a b c|
|d e f|
|g h i|
"""
self.a = a
self.b = b
self.c = c
self.d = d
self.e = e
self.f = f
self.g = g
self.h = h
self.i = i
def __repr__(self):
"""Get a string representation of the matrix."""
return '|{} {} {}|\n|{} {} {}|\n|{} {} {}|'.format(*self.elements)
def __add__(self, other):
"""Calculate matrix addition."""
elements = [e1 + e2 for e1, e2 in zip(self.elements, other.elements)]
matrix = self.__class__(elements)
return matrix
def __eq__(self, other):
"""Check if two matrices are equal."""
return self.elements == other.elements
@property
def elements(self):
"""Get the matrix elements as tuple."""
return self.a, self.b, self.c, self.d, self.e, self.f, self.g, self.h, self.i
@property
def lc(self):
"""Create the left child in a generalized Stern-Brocot tree with matrix elements."""
new_matrix = self.__class__(
self.a + self.b + self.c, self.b, self.c,
self.d + self.e + self.f, self.e, self.f,
self.g + self.h + self.i, self.h, self.i)
return new_matrix
@property
def mc(self):
"""Create the middle child in a generalized Stern-Brocot tree with matrix elements."""
new_matrix = self.__class__(
self.a, self.b + self.a + self.c, self.c,
self.d, self.e + self.d + self.f, self.f,
self.g, self.h + self.g + self.i, self.i)
return new_matrix
@property
def rc(self):
"""Create the right child in a generalized Stern-Brocot tree with matrix elements."""
new_matrix = self.__class__(
self.a, self.b, self.c + self.b + self.a,
self.d, self.e, self.f + self.e + self.d,
self.g, self.h, self.i + self.h + self.g)
return new_matrix
@property
def fractions_string(self):
"""Get a string representation of both fractions."""
return '{}, {}'.format(self.fraction1_string, self.fraction2_string)
@property
def numerator1(self):
"""Get the numerator of the first fraction."""
return self.a + self.b + self.c
@property
def denominator1(self):
"""Get the denominator of the first fraction."""
return self.d + self.e + self.f
@property
def fraction1(self):
"""Reconstruct the first fraction that is contained in the matrix."""
return self.numerator1, self.denominator1
@property
def fraction1_string(self):
"""Get a string representation of the first fraction."""
return '{}/{}'.format(*self.fraction1)
@property
def decimal1(self):
"""Get a decimal representation of the first fraction."""
return self.numerator1 / self.denominator1
@property
def numerator2(self):
"""Get the numerator of the second fraction."""
return self.d + self.e + self.f
@property
def denominator2(self):
"""Get the denominator of the second fraction."""
return self.g + self.h + self.i
@property
def fraction2(self):
"""Reconstruct the second fraction that is contained in the matrix."""
return self.numerator2, self.denominator2
@property
def fraction2_string(self):
"""Get a string representation of the second fraction."""
return '{}/{}'.format(*self.fraction2)
@property
def decimal2(self):
"""Get a decimal representation of the second fraction."""
return self.numerator2 / self.denominator2
[16]:
num_levels = 5
dg = nx.DiGraph()
def to_hover_message(matrix):
return 'Matrix:\n{}<br><br>Fraction1: {} = {}<br>Fraction2: {} = {}'.format(
matrix, matrix.fraction1_string, matrix.decimal1, matrix.fraction2_string, matrix.decimal2)
root = Matrix3d(
1, 0, 0,
0, 1, 0,
0, 0, 1)
sequence = [root]
for level in range(1, num_levels):
new_sequence = []
for i, item in enumerate(sequence):
# Stern-Brocot tree construction
child1, child2, child3 = item.lc, item.mc, item.rc
new_sequence.append(child1)
new_sequence.append(child2)
new_sequence.append(child3)
# Directed graph construction
dg.add_edge(item.fractions_string, child1.fractions_string)
dg.add_edge(item.fractions_string, child2.fractions_string)
dg.add_edge(item.fractions_string, child3.fractions_string)
edge1 = dg.edges[(item.fractions_string, child1.fractions_string)]
edge2 = dg.edges[(item.fractions_string, child2.fractions_string)]
edge3 = dg.edges[(item.fractions_string, child3.fractions_string)]
edge1['label'] = 'L'
edge1['color'] = 'red'
edge1['label_color'] = 'red'
edge2['label'] = 'M'
edge2['color'] = 'blue'
edge2['label_color'] = 'blue'
edge3['label'] = 'R'
# Add node attributes
parent_node = dg.nodes[item.fractions_string]
child_node1 = dg.nodes[child1.fractions_string]
child_node2 = dg.nodes[child2.fractions_string]
child_node3 = dg.nodes[child3.fractions_string]
parent_node['elements'] = item.elements
child_node1['elements'] = child1.elements
child_node2['elements'] = child2.elements
child_node3['elements'] = child3.elements
parent_node['hover'] = to_hover_message(item)
child_node1['hover'] = to_hover_message(child1)
child_node2['hover'] = to_hover_message(child2)
child_node3['hover'] = to_hover_message(child3)
parent_node['x'] = (-len(sequence) / 2 + i) * 100
parent_node['y'] = (level - 1) * 50
child_node1['color'] = 'red'
child_node1['label_color'] = 'red'
child_node2['color'] = 'blue'
child_node2['label_color'] = 'blue'
sequence = new_sequence
# Node attributes for lowest level
for i, item in enumerate(sequence):
parent_node = dg.nodes[item.fractions_string]
parent_node['x'] = (-len(sequence) / 2 + i) * 100
parent_node['y'] = level * 50
[17]:
gv.vis(dg, zoom_factor=0.45, show_edge_label=True, edge_label_data_source='label')
[17]:
[18]:
# Assign different 3d coordinates to each node: sum of row 1 as x, row 2 as y, row 3 as z
dg2 = dg.copy()
for node_id in dg2.nodes:
node = dg2.nodes[node_id]
m = node['elements']
x = m[0] + m[1] + m[2]
y = m[3] + m[4] + m[5]
z = m[6] + m[7] + m[8]
node['x'] = x * 50 - 200
node['y'] = -y * 50 + 200
node['z'] = -z * 50 + 200
gv.three(dg2, show_edge_label=True, edge_label_data_source='label', large_graph_threshold=1000)
[18]:
[19]:
# Assign different 3d coordinates to each node: sum of column 1 as x, column 2 as y, column 3 as z
dg2 = dg.copy()
for node_id in dg2.nodes:
node = dg2.nodes[node_id]
m = node['elements']
x = m[0] + m[3] + m[6]
y = m[1] + m[4] + m[7]
z = m[2] + m[5] + m[8]
node['x'] = x * 50 - 200
node['y'] = -y * 50 + 200
node['z'] = -z * 50 + 200
gv.three(dg2, show_edge_label=True, edge_label_data_source='label', large_graph_threshold=1000)
[19]:
[20]:
# Assign different 2d coordinates to each node: fraction1 as x, fraction2 as y
dg2 = dg.copy()
for node_id in dg2.nodes:
node = dg2.nodes[node_id]
m = node['elements']
r1 = m[0] + m[1] + m[2]
r2 = m[3] + m[4] + m[5]
r3 = m[6] + m[7] + m[8]
node['x'] = r1 / r2 * 700
node['y'] = -r2 / r3 * 700
gv.vis(dg2, show_edge_label=True, edge_label_data_source='label', large_graph_threshold=1000)
[20]:
[21]:
# Assign different 2d coordinates to each node: row1 * row as x, fraction2 as y
dg2 = dg.copy()
for node_id in dg2.nodes:
node = dg2.nodes[node_id]
m = node['elements']
r1 = m[0] + m[1] + m[2]
r2 = m[3] + m[4] + m[5]
r3 = m[6] + m[7] + m[8]
node['x'] = (r1 / r2) * r3 * 100
node['y'] = -r1 * (r2 / r3) * 100
gv.vis(dg2, show_edge_label=True, edge_label_data_source='label', large_graph_threshold=1000)
[21]:
Euler’s number e plus 1 as walk in the generalized Stern-Brocot tree¶
[22]:
# Using the integers of the e sequence as a series of L and R operations
# to perform a walk in the generalized tree
def change_or_add_edge(graph, M1, M2, operator_name):
try:
edge = graph.edges[(M1.fractions_string, M2.fractions_string)]
except Exception:
graph.add_edge(M1.fractions_string, M2.fractions_string)
edge = graph.edges[(M1.fractions_string, M2.fractions_string)]
color = 'black' if operator_name == 'R' else 'red'
edge['label'] = operator_name
edge['size'] = 10
edge['color'] = color
edge['label_color'] = 'green'
edge['label_size'] = 20
for M in [M1, M2]:
node = graph.nodes[M.fractions_string]
node['size'] = 18
node['shape'] = 'rectangle'
node['fraction1'] = M.fraction1
node['fraction2'] = M.fraction2
node['elements'] = M.elements
node['hover'] = to_hover_message(M)
if M is M2:
node['color'] = color
node['label_color'] = color
I = Matrix3d(1, 0, 0,
0, 1, 0,
0, 0, 1)
M = I
operator = 'R'
for element in e_sequence:
for _ in range(element):
if operator == 'R':
M_new = M.rc
else:
M_new = M.lc
print(operator, end='')
change_or_add_edge(dg2, M, M_new, operator) # add the walk into the digraph
M = M_new
operator = 'L' if operator == 'R' else 'R'
print('\n\nMatrix:\n{}\n\nFraction 1: {} = {}\nFraction 2: {} = {}'.format(
M, M.fraction1_string, M.decimal1, M.fraction2_string, M.decimal2))
RRLRRLRLLLLRLRRRRRRLRLLLLLLLLRLRRRRRRRRRRLRLLLLLLLLLLLLRLRRRRRRRRRRRRRRLR
Matrix:
|438351041 0 848456353|
|599611376 1 1160586001|
|161260336 0 312129649|
Fraction 1: 1286807394/1760197378 = 0.7310585790453324
Fraction 2: 1760197378/473389985 = 3.718281826346622
[23]:
# How can this be interpreted?
# - Initial observations
# - Apparent: Fraction 2 = 2.7182818 is e + 1
# - Web search: Fraction 1 = 0.7310585 is output of logistic function e(t) / (1 + e(t)) at t=1
# - Deduced
# - Fraction 1 can be expressed with fraction 2
# - Fraction 1 times fraction 2 gives e
# - Numerator of fraction 1 together with denominator of fraction 2 gives e
# - Other observation
# - Web search: 1.7310585 => "tables of angular spheroidal wave functions", "Non-Classical Hydrogen Bonding"
import math
b = math.e + 1 # fraction1 is e+1
a = math.e / (1 + math.e) # fraction2 is the logistic function with t=1: e/(1+e)
assert a == math.e / b # fraction 1 expressed by fraction 2
assert b == math.e / a # fraction 2 expressed by fraction 1
e1 = a * b # e calculated by multiplication of fraction 1 and 2
e2 = 1286807394 / 473389985 # e calculated by using numerator1 and denominator 2
a, b, e1, e2
[23]:
(0.7310585786300049, 3.718281828459045, 2.718281828459045, 2.718281828459045)
[24]:
gv.vis(dg2, show_edge_label=True, edge_label_data_source='label')
[24]:
Further generalization attempts¶
This seems not provide additional structure compared to the 3x3 case.
[25]:
class MatrixGeneral:
"""A simple class for nxn matrices with methods for creating a generalized Stern-Brocot tree."""
def __init__(self, element_list):
"""Define a nxn matrix by providing its 9 elements."""
from math import sqrt
root = int(sqrt(len(element_list)))
if root**2 != len(element_list):
raise ValueError('Invalid number of elements, not an integer sqrt(): {}'.format(
len(elements)))
self.element_list = element_list
self.n = root
def __repr__(self):
"""Get a string representation of the matrix."""
row = '|{}|'.format(' '.join(['{}'] * self.n))
matrix = '\n'.join([row] * self.n)
return matrix.format(*self.elements)
def __add__(self, other):
"""Calculate matrix addition."""
new_elements = [e1 + e2 for e1, e2 in zip(self.element_list, other.element_list)]
matrix = self.__class__(new_elements)
return matrix
def __eq__(self, other):
"""Check if two matrices are equal."""
return self.element_list == other.element_list
@property
def elements(self):
"""Get the matrix elements as tuple."""
return self.element_list
@property
def lc(self):
"""Create the left child in a generalized Stern-Brocot tree with matrix elements."""
new_elements = []
for i in range(self.n):
for j in range(self.n):
if j == 0:
new_element = 0
for k in range(self.n):
new_element += self.element_list[i * self.n + k]
else:
new_element = self.element_list[i * self.n + j]
new_elements.append(new_element)
new_matrix = self.__class__(new_elements)
return new_matrix
@property
def rc(self):
"""Create the right child in a generalized Stern-Brocot tree with matrix elements."""
new_elements = []
for i in range(self.n):
for j in range(self.n):
if j == (self.n - 1):
new_element = 0
for k in range(self.n):
new_element += self.element_list[i * self.n + k]
else:
new_element = self.element_list[i * self.n + j]
new_elements.append(new_element)
new_matrix = self.__class__(new_elements)
return new_matrix
@property
def fractions(self):
pass
[26]:
# Euler sequence with 5x5 matrix ... seems not to add information in comparison to the 3x3 case
I = MatrixGeneral([1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1])
M = I
operator = 'R'
for element in e_sequence:
for _ in range(element):
if operator == 'R':
M_new = M.rc
else:
M_new = M.lc
print(operator, end='')
M = M_new
operator = 'L' if operator == 'R' else 'R'
print('\n\nMatrix:\n{}'.format(M))
RRLRRLRLLLLRLRRRRRRLRLLLLLLLLRLRRRRRRRRRRLRLLLLLLLLLLLLRLRRRRRRRRRRRRRRLR
Matrix:
|438351041 0 0 848456353|
|599611376 1 0 1160586001|
|599611376 0 1 1160586001|
|161260336 0 0 312129649|
[27]:
# Euler sequence with 5x5 matrix ... seems not to add information in comparison to the 3x3 case
I = MatrixGeneral([1, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, 1, 0,
0, 0, 0, 0, 1])
M = I
operator = 'R'
for element in e_sequence:
for _ in range(element):
if operator == 'R':
M_new = M.rc
else:
M_new = M.lc
print(operator, end='')
M = M_new
operator = 'L' if operator == 'R' else 'R'
print('\n\nMatrix:\n{}'.format(M))
RRLRRLRLLLLRLRRRRRRLRLLLLLLLLRLRRRRRRRRRRLRLLLLLLLLLLLLRLRRRRRRRRRRRRRRLR
Matrix:
|438351041 0 0 0 848456353|
|599611376 1 0 0 1160586001|
|599611376 0 1 0 1160586001|
|599611376 0 0 1 1160586001|
|161260336 0 0 0 312129649|