Multiplication of two integer numbers is pretty simple. Additional information can be found in the documentation.
In [1]:
from ctypes import cdll
In [2]:
basics = cdll.LoadLibrary('./lib_basics.so')
In [3]:
basics.mul(2, 5)
Out[3]:
See? It's easy
In [4]:
from ctypes import c_int
Say, we need to multiply 3-dimensional vectors
In [5]:
first = (c_int * 3)(1, 2, 3)
We can create an alias for this data type and use it
In [6]:
vector3D = c_int * 3
second = vector3D(4, 5, 6)
In [7]:
c_result = basics.dot(first, second, 3)
python_result = sum(a * b for a, b in zip([1, 2, 3], [4, 5, 6]))
print('C code returned', c_result, 'and Python code returned', python_result)
In [8]:
basics.dot((c_int*1)(2), (c_int*1)(*[3]), 1)
Out[8]:
Following examples will cause errors
In [9]:
try:
vector3D([1, 2, 3])
except:
print('You cannot pass lists')
try:
vector3D(0, 1, 2, 3)
except:
print('Forbidden to provide more elements than it should accept')
Following types can be used to pass arguments to C functions
| ctypes | type | C type Python type |
|---|---|---|
| c_bool | _Bool | bool (1) |
| c_char | char | 1-character bytes object |
| c_wchar | wchar_t | 1-character string |
| c_byte | char | int |
| c_ubyte | unsigned | char int |
| c_short | short | int |
| c_ushort | unsigned | short int |
| c_int | int | int |
| c_uint | unsigned | int int |
| c_long | long | int |
| ctypes | type | C type Python type |
|---|---|---|
| c_ulong | unsigned long | int |
| c_longlong | __int64 or long long | int |
| c_ulonglong | unsigned __int64 or unsigned long long | int |
| c_size_t | size_t | int |
| c_ssize_t | ssize_t or Py_ssize_t | int |
| c_float | float | float |
| c_double | double | float |
| c_longdouble | long double | float |
| c_char_p | char * (NUL terminated) | bytes object or None |
| c_wchar_p | wchar_t * (NUL terminated) | string or None |
| c_void_p | void * | int or None |
Large numbers (bignum) are represented as arrays of longs in Python:
{d0, d1, d2, ...}
We can change each one of them, so why not?
Read the documentation about Python API for C.
File should be compiled to shared library (dll in Windows).
Makefile for Linux:
FLAGS=-shared
LIBRARIES=-I/usr/include/python3.4
BUILD_LIBRARY=gcc $(FLAGS) $(LIBRARIES)
all:
$(BUILD_LIBRARY) setters.c -o lib_setters.so
In [10]:
from ctypes import cdll, c_long, c_size_t, c_voidp
setters = cdll.LoadLibrary('./lib_setters.so')
def change_long(a, b=0, digit=0):
setters.set_long(c_voidp(id(a)), c_long(b), c_size_t(digit))
Don't forget that Python interpreter will not create new objects for small integers like 0, so we should avoid assigning new values to such numbers, because they will be changed everywhere they're used
In [11]:
from ctypes import c_long, c_size_t, c_voidp
def change_long(a, b=0, digit=0):
args = (a, b, digit)
if not all(type(a) is int for a in args):
raise TypeError('All parameters should be of type "int", '
'but {} provided'.format(map(type, args)))
if a + 0 is a:
raise ValueError('No way. You don\'t want to break '
'your interpreter, right?')
setters.set_long(c_voidp(id(a)), c_long(b), c_size_t(digit))
Recall that we cannot change values of integers inside the Python functions
In [12]:
def variable_info(text, variable):
print('{:^30}: {:#05x} ({:#x})'.format(text, variable, id(variable)))
def foo(a, new_value):
a = new_value
a = 2**10
variable_info('Before function call', a)
foo(a, 5)
variable_info('After function call', a)
Now forget it and take a look at what we've done
In [13]:
a = 2**10
b = a
variable_info('Before function call', a)
change_long(a, 2, 0)
variable_info('After function call', a)
variable_info('What\'s about b? Here it is', b)
In [14]:
from numpy import array, cross
basis = [
[1, 0, 0],
[0, 1, 0],
[0, 0, 1]
]
In [15]:
def py_cross(u, v):
return [
u[1] * v[2] - u[2] * v[1],
u[2] * v[0] - u[0] * v[2],
u[0] * v[1] - u[1] * v[0]
]
py_cross(basis[0], basis[1])
Out[15]:
In [16]:
cross(array(basis[0]), array(basis[1]))
Out[16]:
In [17]:
from ctypes import cdll
from numpy import empty_like
c_cross = cdll.LoadLibrary('./lib_cross.so')
u = array(basis[0]).astype('f')
v = array(basis[1]).astype('f')
w = empty_like(u)
def cross_wrapper(u, v, w):
return c_cross.cross(u.ctypes.get_as_parameter(),
v.ctypes.get_as_parameter(),
w.ctypes.get_as_parameter())
cross_wrapper(u, v, w)
print(w)
In [18]:
from numpy.random import rand
BIG_ENOUGH_INTEGER = int(1E5)
vectors_u = rand(BIG_ENOUGH_INTEGER, 3).astype('f')
vectors_v = rand(BIG_ENOUGH_INTEGER, 3).astype('f')
print('Vectors u:', vectors_u)
In [19]:
%%timeit
for i in range(BIG_ENOUGH_INTEGER):
py_cross(vectors_u[i], vectors_v[i])
In [20]:
%%timeit
cross(vectors_u, vectors_v)
In [21]:
%%timeit
vectors_w = empty_like(vectors_u)
for i in range(BIG_ENOUGH_INTEGER):
cross_wrapper(vectors_u[i], vectors_v[i], vectors_w[i])
In [22]:
from numpy import allclose
np_result = cross(vectors_u, vectors_v)
py_result = [py_cross(vectors_u[i], vectors_v[i])
for i in range(BIG_ENOUGH_INTEGER)]
print(allclose(np_result, py_result))
vectors_w = empty_like(vectors_u)
assert sum([cross_wrapper(vectors_u[i], vectors_v[i], vectors_w[i])
for i in range(BIG_ENOUGH_INTEGER)]) == 0
print(allclose(np_result, vectors_w))
It's better to compile with optimization. Also to use -fPIC flag to avoid following compilation error
relocation against symbol `cross' can not be used when making a shared object
What we get:
gcc -shared -fPIC cross.c -O3 -o lib_cross.so
Numpy arrays are flattened when got as C arrays.
Also len operator returns amount of rows of matrix. If you want to get the total amount of elements, you should use size method.
In [23]:
vectors_w = empty_like(vectors_u)
c_vectors_u = vectors_u.ctypes.get_as_parameter()
c_vectors_v = vectors_v.ctypes.get_as_parameter()
c_vectors_w = vectors_w.ctypes.get_as_parameter()
In [24]:
%%timeit
vectors_w = empty_like(vectors_u)
c_vectors_w = vectors_w.ctypes.get_as_parameter()
c_cross.cross_vectors(c_vectors_u, c_vectors_v, c_vectors_w, len(vectors_u))
In [25]:
c_cross.cross_vectors(c_vectors_u, c_vectors_v, c_vectors_w, len(vectors_u))
print(allclose(np_result, vectors_w))
Are you surprised?