Векторизация групп вызовов функций - PullRequest
0 голосов
/ 14 ноября 2018

Я пытаюсь написать функциональность (используя макрос, сгенерированную функцию или что-то еще), которая эффективно векторизует вызовы функций Джулии для написанных мной функций.По сути, я пытаюсь написать свою собственную версию @.макрос, но вместо этого я хотел бы, чтобы он принимал функции вместо цикла for --- если я правильно понимаю.Вот некоторые документы, которые я прочитал по этому вопросу:

https://docs.julialang.org/en/v1/manual/functions/#man-vectorized-1

https://github.com/JuliaLang/julia/blob/master/base/broadcast.jl

https://julialang.org/blog/2017/01/moredots

https://docs.julialang.org/en/v1/manual/metaprogramming/index.html#Code-Generation-1

Вот мой предварительный игрушечный пример, с которым я работаю для достижения такой функциональности:

function add!(v_add::Vector{Float64}, a_add::Float64, j::Int64)
  v_add[j] = v_add[j]+a_add
end

function add!(v_add::Vector{Float64}, a_add::Float64)
  for j in 1:length(v_add)
    v_add[j] = v_add[j]+a_add
  end
end

macro vectorize(args)
  print("\n****************** args\n")
  print(args)
  print("\n******************\n")
  e = :(:call,
    $args[1],
    $args[2],
    $args[3])
  print("\n****************** expression\n")
  show(e)
  print(e)
  print("\n******************\n")
  return e
end

function test!(v_test, a_test)
  # # Traverse vector twice
  # add!(v_test, a_test)
  # add!(v_test, a_test)
  # Traverse vector once
  args = [
  add!, v_test, a_test,
  add!, v_test, a_test
  ]
  e = @vectorize(args)
  # eval(e) # Next step
end

v_main = Vector([Float64(i) for i in 1:3])
a_main = Float64(2.0)
print("\n",v_main, "\n")
Main.test!(v_main, a_main)
print("\n",v_main, "\n")

Проблема, с которой я до сих пор сталкиваюсь, заключается в том, что я не могу даже получитьверсия, работающая с использованием макросов.Этот пример приводит к тому, что LoadError: UndefVarError: args не определено.Я определенно буду признателен за любую помощь в том, чтобы этот скрипт работал должным образом (входные данные [1, 2, 3], а выходные данные должны быть [5, 6, 7]).

Любая помощь / предложения приветствуются.

Обновление

Более конкретно, учитывая следующие определенные функции:

function add!(v::Vector{Float64}, a::Float64)
  for j in 1:length(v)
    v[j]+= a
  end
end
function add!(v::Vector{Float64}, a::Float64, j::Int64)
  v[j]+= a
end

Я хотел бы иметь возможность использовать макрос для преобразованияследующие строки кода:

v = [Float64(j) for j in 1:10]
a = 1
b = 2
@vectorize_I_would_like_to_define(
# I don't know the exact form that the args to this macro should take.
add!(v, a),
add!(v, b)
)

Чтобы сгенерировать код, скомпилированный следующим образом:

v = [Float64(j) for j in 1:10]
a = 1
b = 2
for j in 1:length(v)
  add!(v, a, j)
  add!(v, b, j)
end

Моя цель - написать код, требующий одного обхода памяти.

Еще лучше, если бы я мог генерировать код, который выглядит так во время компиляции:

v = [Float64(j) for j in 1:10]
a = 1
b = 2
for j in 1:length(v)
  v[j]+= a # taken from add!(v::Vector{Float64}, a::Float64, j::Int64)
  v[j]+= b # taken from add!(v::Vector{Float64}, a::Float64, j::Int64)
end

Но я не уверен, насколько это возможно для более сложных случаев, которые я рассматриваю по сравнению с этимпример с игрушкой.

** Обновление 2 **

Вот MWE решения @ Bogumił Kamiński - за исключением того, что я переместил вызов макроса в функцию, так что теперь он нене работает, потому что жалуется, что v_test не определено.

macro vectorize(args...)
    expr = :()
    for arg in args
        a = deepcopy(arg) # for safety in case arg is also used somewhere else
        push!(a.args, :j)
        expr = :($expr; $a)
    end
    quote
        for j in 1:length(v)
            $expr
        end
    end
end

function add!(v::Vector{Float64}, a::Float64)
  for j in 1:length(v)
    v[j]+= a
  end
end

function add!(v::Vector{Float64}, a::Float64, j::Int64)
  v[j]+= a
end

v = [Float64(j) for j in 1:10]
a = 1.0
b = 2.0

function test!(v_test, a_test, b_test)
  @vectorize(
  add!(v_test, a_test),
  add!(v_test, b_test)
  )
end

test!(v, a, b)

1 Ответ

0 голосов
/ 15 ноября 2018

Это то, что вы хотите?

macro vectorize(args...)
    expr = :()
    for arg in args
        a = deepcopy(arg) # for safety in case arg is also used somewhere else
        push!(a.args, :j)
        expr = :($expr; $a)
    end
    quote
        for j in 1:length(v)
            $expr
        end
    end
end

и сейчас

function add!(v::Vector{Float64}, a::Float64)
  for j in 1:length(v)
    v[j]+= a
  end
end

function add!(v::Vector{Float64}, a::Float64, j::Int64)
  v[j]+= a
end

v = [Float64(j) for j in 1:10]
a = 1.0
b = 2.0

@vectorize(add!(v, a), add!(v, b))

Обратите внимание, что я изменил определения a и b, поскольку в качестве второго аргумента add! требуется Float64.

РЕДАКТИРОВАТЬ: Если вы хотите использовать этот макрос внутри функции, самое простое, что нужно сделать, это esc полное возвращаемое значение:

macro vectorize(args...)
    expr = :()
    for arg in args
        a = deepcopy(arg) # for safety in case arg is also used somewhere else
        push!(a.args, :j)
        expr = :($expr; $a)
    end
    esc(quote
        for j in 1:length(v)
            $expr
        end
    end)
end

Тогда вы можете определить, например ::

function f()
    v = [Float64(j) for j in 1:10]
    a = 1.0
    b = 2.0
    @vectorize(add!(v, a), add!(v, b))
    v
end

и запустите f(), чтобы получить тот же результат, что и выше, в глобальной области видимости.

РЕДАКТИРОВАТЬ 2: Я только что понял, что на самом деле я должен очистить j, иначе следующий код не будет работать:

test!(v_test, j, b_test) =
    @vectorize(add!(v_test, j), add!(v_test, b_test))

Вот как вы должны это сделать:

macro vectorize(args...)
    expr = :()
    j = gensym()
    for arg in args
        a = deepcopy(arg) # for safety in case arg is also used somewhere else
        push!(a.args, j)
        expr = :($expr; $a)
    end
    esc(quote
        for $j in 1:length(v)
            $expr
        end
    end)
end

Как видите, разработка макросов - неочевидная задача (надеюсь, окончательный рецепт не содержит ошибок:)).

РЕДАКТИРОВАТЬ 3: Вот код, который правильно обрабатывает length. Кроме того, теперь в каждом выражении вы можете передать различное значение в качестве первого аргумента (чтобы вы могли независимо обрабатывать разные векторы). Если вы хотите обработать одну и ту же векторную проверку, то a.args[2] - это всегда один и тот же символ:

macro vectorize(args...)
    expr = :()
    j = gensym()
    for arg in args
        a = deepcopy(arg) # for safety in case arg is also used somewhere else
        var = a.args[2]
        push!(a.args, j)
        q = quote
            for $j in 1:length($var)
                $a
            end
        end
        expr = :($expr; $q)
    end
    esc(expr)
end
...