Skip to content

Commit

Permalink
gh-167: add example into doc
Browse files Browse the repository at this point in the history
  • Loading branch information
EgorOrachyov committed Aug 31, 2023
1 parent 9d53a9e commit 59f2825
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 11 deletions.
35 changes: 25 additions & 10 deletions python/example.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,31 @@
from pyspla import *

M = Matrix.from_lists([0, 1, 2, 2], [1, 2, 0, 4], [1, 2, 3, 4], (3, 5), INT)
print(M)

N = Matrix.from_lists([0, 1, 2, 3], [1, 2, 0, 3], [2, 3, 4, 5], (4, 5), INT)
print(N)
def bfs(s: int, A: Matrix):
v = Vector(A.n_rows, INT) # to store depths

mask = Matrix.dense((3, 4), INT, fill_value=1)
print(mask)
front = Vector.from_lists([s], [1], A.n_rows, INT) # front of new vertices to study
front_size = 1 # current front size
depth = Scalar(INT, 0) # depth of search
count = 0 # num of reached vertices

R = M.mxmT(mask, N, INT.MULT, INT.PLUS, INT.GTZERO)
print(R)
while front_size > 0: # while have something to study
depth += 1
count += front_size
v.assign(front, depth, op_assign=INT.SECOND, op_select=INT.NQZERO) # assign depths
front = front.vxm(v, A, op_mult=INT.LAND, op_add=INT.LOR, op_select=INT.EQZERO) # do traversal
front_size = front.reduce(op_reduce=INT.PLUS).get() # update front count to end algorithm

R = M.mxmT(mask, N, INT.MULT, INT.PLUS, INT.EQZERO)
print(R)
return v, count, depth.get()


I = [0, 1, 2, 2, 3]
J = [1, 2, 0, 3, 2]
V = [1, 1, 1, 1, 1]
A = Matrix.from_lists(I, J, V, shape=(4, 4), dtype=INT)
print(A)

v, c, d = bfs(0, A)
print(v)
print(c)
print(d)
65 changes: 65 additions & 0 deletions python/pyspla/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,71 @@
| rgg_n_2_23_s0 | 8.4M | 127.0M | 15.1 | 3.9 | 40.0 | [link](https://suitesparse-collection-website.herokuapp.com/MM/DIMACS10/rgg_n_2_23_s0.tar.gz) |
| road_central | 14.1M | 33.9M | 2.4 | 0.9 | 8.0 | [link](http://sparse.tamu.edu/DIMACS10/road_central) |
Example of usage
----------------
Import `spla` package to your python script.
>>> from pyspla import *
Create an adjacency matrix of graph using lists of row-column indices and values.
>>> I = [0, 1, 2, 2, 3]
>>> J = [1, 2, 0, 3, 2]
>>> V = [1, 1, 1, 1, 1]
>>> A = Matrix.from_lists(I, J, V, shape=(4, 4), dtype=INT)
>>> print(A)
'
0 1 2 3
0| . 1 . .| 0
1| . . 1 .| 1
2| 1 . . 1| 2
3| . . 1 .| 3
0 1 2 3
'
The following code snippet shows how to create breadth-first search algoritm using `spla` package API
through masked matrix-vector product. The algorithm accepts starting vertex and an adjacency matrix
of a graph. It raverces graph using `vxm` and assign depth to reached vertices. Mask is used to update
only unvisited vertices reducing number of required computations. More details highlighted in the
comments to the code fragment.
>>> def bfs(s: int, A: Matrix):
>>> v = Vector(A.n_rows, INT) # to store depths
>>>
>>> front = Vector.from_lists([s], [1], A.n_rows, INT) # front of new vertices to study
>>> front_size = 1 # current front size
>>> depth = Scalar(INT, 0) # depth of search
>>> count = 0 # num of reached vertices
>>>
>>> while front_size > 0: # while have something to study
>>> depth += 1
>>> count += front_size
>>> v.assign(front, depth, op_assign=INT.SECOND, op_select=INT.NQZERO) # assign depths
>>> front = front.vxm(v, A, op_mult=INT.LAND, op_add=INT.LOR, op_select=INT.EQZERO) # do traversal
>>> front_size = front.reduce(op_reduce=INT.PLUS).get() # update front count to end algorithm
>>>
>>> return v, count, depth.get()
Run bfs algorithm starting from 0-vertex with the graph adjacency matrix created earlier.
>>> v, c, d = bfs(0, A)
>>> print(v)
'
0| 1
1| 2
2| 3
3| 4
'
Total number of reached vertices.
>>> print(c)
'
4
'
Maximum depth of a discovered vertex.
>>> print(d)
'
4
'
Containers
----------
Expand Down
103 changes: 102 additions & 1 deletion python/pyspla/scalar.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
import ctypes

from .bridge import backend, check
from .type import INT
from .type import INT, FLOAT
from .object import Object


Expand Down Expand Up @@ -66,6 +66,12 @@ def __init__(self, dtype=INT, value=None, hnd=None, label=None):
"""
Creates new scalar of desired type or retains existing C object.
>>> s = Scalar(INT, 10)
>>> print(s)
'
10
'
:param dtype: optional: Type. default: INT.
Type of the scalar value.
Expand Down Expand Up @@ -94,6 +100,12 @@ def __init__(self, dtype=INT, value=None, hnd=None, label=None):
def dtype(self):
"""
Returns the type of stored value in the scalar.
>>> s = Scalar(INT)
>>> print(s.dtype)
'
<class 'pyspla.type.INT'>
'
"""

return self._dtype
Expand All @@ -102,6 +114,12 @@ def dtype(self):
def shape(self):
"""
2-tuple shape of the storage. For scalar object it is always 1 by 1.
>>> s = Scalar(INT)
>>> print(s.shape)
'
(1, 1)
'
"""

return 1, 1
Expand All @@ -110,14 +128,53 @@ def shape(self):
def n_vals(self):
"""
Number of stored values in the scalar. Always 1.
>>> s = Scalar(INT)
>>> print(s.n_vals)
'
1
'
"""

return 1

@classmethod
def from_value(cls, value):
"""
Create scalar and infer type.
>>> s = Scalar.from_value(0.5)
>>> print(s.dtype)
'
<class 'pyspla.type.FLOAT'>
'
:param value: any.
Value to create scalar from.
:return: Scalar with value.
"""

if isinstance(value, float):
return Scalar(dtype=FLOAT, value=value)
elif isinstance(value, int):
return Scalar(dtype=INT, value=value)
elif isinstance(value, bool):
return Scalar(dtype=INT, value=value)
else:
raise Exception("cannot infer type")

def set(self, value=None):
"""
Set the value stored in the scalar. If no value passed the default value is set.
>>> s = Scalar(INT)
>>> s.set(10)
>>> print(s)
'
10
'
:param value: optional: Any. default: None.
Optional value to store in scalar.
"""
Expand All @@ -128,6 +185,12 @@ def get(self):
"""
Read the value stored in the scalar.
>>> s = Scalar(INT, 10)
>>> print(s.get())
'
10
'
:return: Value from scalar.
"""

Expand All @@ -140,3 +203,41 @@ def __str__(self):

def __iter__(self):
return iter([self.get()])

def __add__(self, other):
return Scalar(dtype=self.dtype, value=self.get() + Scalar._value(other))

def __sub__(self, other):
return Scalar(dtype=self.dtype, value=self.get() + Scalar._value(other))

def __mul__(self, other):
return Scalar(dtype=self.dtype, value=self.get() * Scalar._value(other))

def __truediv__(self, other):
return Scalar(dtype=self.dtype, value=self.get() / Scalar._value(other))

def __floordiv__(self, other):
return Scalar(dtype=self.dtype, value=self.get() // Scalar._value(other))

def __iadd__(self, other):
self.set(self.get() + Scalar._value(other))
return self

def __isub__(self, other):
self.set(self.get() - Scalar._value(other))
return self

def __imul__(self, other):
self.set(self.get() * Scalar._value(other))
return self

def __idiv__(self, other):
self.set(self.get() / Scalar._value(other))
return self

@classmethod
def _value(cls, other):
if isinstance(other, Scalar):
return other.get()
else:
return other

0 comments on commit 59f2825

Please sign in to comment.