JuliaPy / pyjulia

python interface to julia
MIT License
884 stars 103 forks source link

Python NumPy array is not modified inside Julia #385

Open alkalinin opened 4 years ago

alkalinin commented 4 years ago

I want to send large NumPy array from Python to Julia by a reference, not by copy. And probably this do not work.

main.py

import numpy as np

from julia import Main

def main():
    Main.include('test.jl')

    x = np.array([0, 1, 2, 3, 4])
    print(x)
    Main.double_array(x)
    print(x)

if __name__ == '__main__':
    main()

test.jl

function double_array(x)
    x[:] = x * 2
end

Result is

$ python main.py
[0 1 2 3 4]
[0 1 2 3 4]

Is there any possibility to send large numpy array from python to julia without copy?

paulmelis commented 4 years ago

Just stumbled upon this myself. See https://github.com/JuliaPy/PyCall.jl#arrays-and-pyarray, seems you need to use a specific type on the Julia side:

From Python to Julia

Multidimensional NumPy arrays (ndarray) are supported and can be converted to the native Julia Array type, which makes a copy of the data.

Alternatively, the PyCall module also provides a new type PyArray (a subclass of AbstractArray) which implements a no-copy wrapper around a NumPy array (currently of numeric types or objects only).

[...]

Edit: hmmm, that only seems to work on the Julia side

paulmelis commented 4 years ago

There's a workaround by passing a pointer and length. But it's a bit suboptimal in having to specify the element type (although there's probably a clever Julia way of improving that):

melis@juggle 21:50:~/concepts/blender-julia-test/test$ cat alter_array.jl 
function fn(a)
    a[:] .= 9
end

function fn(addr, length)
    a = unsafe_wrap(Array{UInt32}, Ptr{UInt32}(addr), length)
    fn(a)
end

melis@juggle 21:51:~/concepts/blender-julia-test/test$ cat t_alter_array.py 
import numpy, ctypes
import julia
from julia.api import Julia

jl = Julia()
from julia import Main

jl.eval('include("alter_array.jl")')

a = numpy.array([1, 2, 3, 4, 5], 'uint32')
print(a)

# Pass directly, array not altered
Main.fn(a)
print(a)

# Pass by address and length, array *is* altered
addr = a.ctypes.data
length = a.shape[0]

Main.fn(addr, length)
print(a)

melis@juggle 21:51:~/concepts/blender-julia-test/test$ python t_alter_array.py 
[1 2 3 4 5]
[1 2 3 4 5]
[9 9 9 9 9]
alkalinin commented 3 years ago

Thanks! It works in my environment:

And I like working with raw C pointer, this explicit conversion is under my full control.