Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

recursive conversion #149

Closed
rveltz opened this issue Sep 18, 2020 · 11 comments
Closed

recursive conversion #149

rveltz opened this issue Sep 18, 2020 · 11 comments

Comments

@rveltz
Copy link

rveltz commented Sep 18, 2020

Hi,

I have an issue of converting CuArrays to Arrays in order to be able to save to disk. I have an involved composite struct with CuArray appearing in many fields (this is this one).

I would like to be able to do this conversion automatically. I do it by hand using Setfield and this is really tough.

Is there a way to replace automatically and recursively CuArray by Array and making the associated conversion?

Thank you for your help,

Best regards,

@jw3126
Copy link
Owner

jw3126 commented Sep 19, 2020

There is nothing like this implemented in Setfield.jl. But it can be done with a traversal, a multi location variant of a lens. I will try to supply a gist. Feel free to ping me again in a week if I don't.

@jw3126
Copy link
Owner

jw3126 commented Sep 19, 2020

using ConstructionBase

function mapproperties(f, obj)
    pnames = propertynames(obj)
    if isempty(pnames)
        return obj
    else
        ctor = constructorof(typeof(obj))
        new_props = map(pnames) do p
            f(getproperty(obj, p))
        end
        return ctor(new_props...)
    end
end

struct Properties end

function modify(f, o, ::Properties)
    mapproperties(f, o)
end

struct Recursive{Descent, SEC}
    descent_condition::Descent
    sec::SEC
end

function modify(f, obj, r::Recursive)
    modify(obj, r.sec) do o
        if r.descent_condition(o)
            modify(f, o, r)
        else
            f(o)
        end
    end
end

using Test
obj = (a=1, b=2)
@test mapproperties(x -> x+1, obj) === (a=2, b=3)

obj = (a=1, b=(1,2), c=(A=1, B=(1,2,3), D=4))

rp = Recursive(x -> !(x isa Tuple), Properties())
@test modify(collect, obj, rp) == (a = 1, b = [1, 2], c = (A = 1, B = [1, 2, 3], D = 4))

I think this should cover your use case?
FWIW This is a SEC and can be made composable, see also #129

@rveltz
Copy link
Author

rveltz commented Sep 19, 2020

Thank you a lot!!!

I was eager to use it by first

function tocpu(obj)
	rp = Recursive(x -> !(x isa CuArray), Properties())
	modify(x->Array(x), obj, rp)
end

but it fails. Do I use it wrong?

using BifurcationKit, StructArrays
x0 = CuArray([1. 2. 3.])
pt = (a=1,)
T = Float64
bif0 = BifurcationKit.GenericBifPoint(type = :none, idx = 0, param = T(0), norm  = T(0), printsol = pt, x = x0, tau = BorderedArray(x0, T(0)), ind_ev = 0, step = 0, status = :guess, δ = (0,0), precision = T(-1), interval = (T(0), T(0)))
sol = [(x = copy(x0), p = 1., step = 0)]
n_unstable = 0
n_imag = 0
stability = true
_evvectors = (eigenvals = rand(10), eigenvec = nothing, step = 0)
	
br = ContResult(
		branch = StructArray([pt]),
		bifpoint = [bif0],
		foldpoint = [bif0],
		n_imag = [n_imag],
		n_unstable = [n_unstable],
		stability = [stability],
		eig = [_evvectors],
		sol = sol,
		contparams =  ContinuationPar(),
		params = (p = 1.,),
		param_lens = (@lens _))
br1=tocpu(br)

I get

typeof(br1.sol) # = Array{NamedTuple{(:x, :p, :step),Tuple{CuArray{Float64,2},Float64,Int64}},1}

@jw3126
Copy link
Owner

jw3126 commented Sep 19, 2020

So let's see if I understand this correctly: sol is a vector whos elements might contain CuArrays? Then the problem is that rp is designed to access properties, but not to access array elements.
So what you really need is recurse over properties/Array elements? Anything else, where the CuArrays could hide?

@jw3126
Copy link
Owner

jw3126 commented Sep 19, 2020

using ConstructionBase

function mapproperties(f, obj)
    pnames = propertynames(obj)
    if isempty(pnames)
        return obj
    else
        ctor = constructorof(typeof(obj))
        new_props = map(pnames) do p
            f(getproperty(obj, p))
        end
        return ctor(new_props...)
    end
end

const CuArray = Tuple
modify_cuarrays(f, arr::Array) = map(x -> modify_cuarrays(f, x), arr)
modify_cuarrays(f, arr::CuArray) = f(arr)
modify_cuarrays(f, obj) = mapproperties(x -> modify_cuarrays(f, x), obj)

using Test

obj = (a=1, b=(1,2), c=(A=1, B=(1,2,3), D=4, E=[(5,6),7]))
@test modify_cuarrays(collect, obj) == (a = 1, b = [1, 2], c = (A = 1, B = [1, 2, 3], D = 4, E = Any[[5, 6], 7]))

Better?

@rveltz
Copy link
Author

rveltz commented Sep 19, 2020

I needed to add

modify_cuarrays(f, arr::StructArray) = arr

and it then seem to work.

I will close it when I am sure.

Thank you a lot, you saved me from a lot of pain modifying the struct.....

@jw3126
Copy link
Owner

jw3126 commented Sep 20, 2020

Out of curiosity, what goes wrong with StructArray?

@rveltz
Copy link
Author

rveltz commented Sep 20, 2020

I am not sure it was not a mistake of mine actually

julia> tocpu(brm)
ERROR: MethodError: no method matching StructArrays.StructArray(::Array{Float64,1}, ::Array{Float64,1}, ::Array{Int64,1}, ::Array{Float64,1}, ::Array{Float64,1}, ::Array{Int64,1})
Closest candidates are:
  StructArrays.StructArray(::AbstractArray{T,N} where N; unwrap) where T at /home/rveltz/.julia/packages/StructArrays/OtfvU/src/structarray.jl:85
  StructArrays.StructArray(::Any; unwrap) at /home/rveltz/.julia/packages/StructArrays/OtfvU/src/structarray.jl:81
Stacktrace:
 [1] mapproperties(::var"#41#42"{var"#50#52",Recursive{var"#49#51",Properties}}, ::StructArrays.StructArray{NamedTuple{(:x, :param, :itnewton, :ds, :theta, :step),Tuple{Float64,Float64,Int64,Float64,Float64,Int64}},1,NamedTuple{(:x, :param, :itnewton, :ds, :theta, :step),Tuple{Array{Float64,1},Array{Float64,1},Array{Int64,1},Array{Float64,1},Array{Float64,1},Array{Int64,1}}},Int64}) at ./REPL[47]:10
 [2] modify(::Function, ::StructArrays.StructArray{NamedTuple{(:x, :param, :itnewton, :ds, :theta, :step),Tuple{Float64,Float64,Int64,Float64,Float64,Int64}},1,NamedTuple{(:x, :param, :itnewton, :ds, :theta, :step),Tuple{Array{Float64,1},Array{Float64,1},Array{Int64,1},Array{Float64,1},Array{Float64,1},Array{Int64,1}}},Int64}, ::Properties) at ./REPL[44]:2

@rveltz rveltz closed this as completed Sep 20, 2020
@jw3126
Copy link
Owner

jw3126 commented Sep 20, 2020

Okay constructorof would need a special implementation for StructArray.

@rveltz
Copy link
Author

rveltz commented Sep 20, 2020

I really think this is super useful for GPU applications.

@jw3126
Copy link
Owner

jw3126 commented Sep 20, 2020

I think it can be further cleaned up into:

tocpu(x::CuArray) = Array(x)
tocpu(obj) = mapproperties(tocpu, obj)
tocpu(x::Array) = map(tocpu, x)
tocpu(x::StructArray) = x # or better fix constructorof(::StructArray)

Yeah I guess it comes up at quite some places. For instance Flux.jl has some functionality to convert between gpu and cpu I think.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants