I am trying to write a functionality, (using macro, generated function or something), that effectively vectorizes Julia function calls to functions that I've written. Basically, I'm trying to write my own version of the #. macro, but instead, I'd like it to accept functions instead of a for loop--- if I understand this correctly. Here are some documents that I've read on the subject:
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
Here is my preliminary toy example that I'm working with to achieve such a functionality:
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")
The problem I'm having so far is that I can't even get the de-vectorized version running using macros. This example results in the LoadError: UndefVarError: args not defined. I would definitely appreciate any help in getting this script working as expected (input is [1, 2, 3], and output should be [5, 6, 7]).
Any help/suggestions are greatly appreciated.
Update
More concretely, given the following defined functions:
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
I would like to be able to use a macro to convert the following lines of code:
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)
)
To generate code that is compiled like this:
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
My goal is to write code that requires a single memory traversal.
Even better, if I could generate code that looks like this at compile time:
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
But I'm not sure if this is as feasable for more complex cases that I'm considering compared to this toy example.
** Update 2**
Here is a MWE of #Bogumił Kamiński's solution---except that I've moved the macro call into a function, so now it doesn't work because it complains that v_test is not defined.
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)
Is this what you want?
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
and now
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))
Note that I have changed a and b definitions as your add! required Float64 as a second argument.
EDIT: If you want to use this macro inside a function the simplest thing to do is to esc its whole return value:
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
Then you can define e.g.:
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
and run f() to get the same result as above in global scope.
EDIT 2: I just realized that actually I have to sanitize j as otherwise the following code will fail:
test!(v_test, j, b_test) =
#vectorize(add!(v_test, j), add!(v_test, b_test))
Here is how you should do it:
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
As you can see developing macros is a non-obvious task (hopefully the final recipe is bug-free :)).
EDIT 3: Here is the code that correctly handles length. Also now in each expression actually you can pass a different value as a first argument (so you can independently process different vectors). If you do want to process the same vector check is a.args[2] is always the same symbol:
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
Related
I would like to use a function that behaves by the following pseudocode:
f = #(x,y) if x*y > 0 then 1 else 0;
How can I implement this in one line in Octave?
Maybe you can try
f = 1*(x*y>0)
or
f = #(x,y) 1*(x*y>0)
How to make a function func2(func1,t,y0) which receives another function func1 as an argument, but where func1 is a function that returns a 1D real(kind=8), dimension(:) array?
I have the following code written in Matlab, and I would like to write an equivalent one in Modern Fortran for speed and portability. I have written one for first order differential equations, but I'm struggling with the task of writing the code for a code for second and higher order differential equations because the external variable corresponding to differential equations must return an array with dimension(:). I want a code to be general purpose, i.e. I want a function or subroutine to which I can pass any differential equation.
The MatLab code is:
%---------------------------------------------------------------------------
clear all
close all
clc
t = [0:0.01:20]';
y0 = [2, 0]';
y = func_runge_kutta(#func_my_ode,t,y0);
function dy=func_my_ode(t,y)
% Second order differential equation y'' - (1-y^2)*y'+y = 0
dy = zeros(size(y));
dy(1) = y(2);
dy(2) = (1-y(1)^2)*y(2)-y(1);
end
function y = func_runge_kutta(func_my_ode,t,y0)
y = zeros(length(t),length(y0));
y(1,:) = y0';
for i=1:(length(t)-1)
h = t(i+1)-t(i);
F_1 = func_my_ode(t(i),y(i,:)');
F_2 = func_my_ode(t(i)+h/2,y(i,:)'+h/2*F_1);
F_3 = func_my_ode(t(i)+h/2,y(i,:)'+h/2*F_2);
F_4 = func_my_ode(t(i)+h,y(i,:)'+h*F_3);
y(i+1,:) = y(i,:)+h/6*(F_1+2*F_2+2*F_3+F_4)';
end
end
%---------------------------------------------------------------------------
If a function returns an array its interface must be explicit in the caller. The easiest way to achieve this for a dummy argument function is to use the PROCEDURE statement to clone the interface from a function that may be used as an actual argument. Starting with your code, translating to Fortran and adding declarations, we get:
module everything
use ISO_FORTRAN_ENV, only : wp => REAL64
implicit none
contains
function func_my_ode_1(t,y) result(dy)
! Second order differential equation y'' - (1-y**2)*y'+y = 0
real(wp) t
real(wp) y(:)
real(wp) dy(size(y))
dy(1) = y(2);
dy(2) = (1-y(1)**2)*y(2)-y(1);
end
function func_runge_kutta(func_my_ode,t,y0) result(y)
procedure(func_my_ode_1) func_my_ode
real(wp) t(:)
real(wp) y0(:)
real(wp) y(size(t),size(y0))
integer i
real(wp) h
real(wp) F_1(size(y0)),F_2(size(y0)),F_3(size(y0)),F_4(size(y0))
y(1,:) = y0;
do i=1,(size(t)-1)
h = t(i+1)-t(i);
F_1 = func_my_ode(t(i),y(i,:));
F_2 = func_my_ode(t(i)+h/2,y(i,:)+h/2*F_1);
F_3 = func_my_ode(t(i)+h/2,y(i,:)+h/2*F_2);
F_4 = func_my_ode(t(i)+h,y(i,:)+h*F_3);
y(i+1,:) = y(i,:)+h/6*(F_1+2*F_2+2*F_3+F_4);
end do
end
end module everything
program main
!clear all
!close all
!clc
use everything
implicit none
real(wp), allocatable :: t(:)
real(wp), allocatable :: y0(:)
real(wp), allocatable :: y(:,:)
integer i
integer iunit
t = [(0+0.01_wp*i,i=0,nint(20/0.01_wp))];
y0 = [2, 0];
y = func_runge_kutta(func_my_ode_1,t,y0);
open(newunit=iunit,file='rk4.txt',status='replace')
do i = 1,size(t)
write(iunit,*) t(i),y(i,1)
end do
end program main
I had Matlab read the data file and it plotted the same picture as the original Matlab program would have, had it plotted its results.
I would like to implement a function duration = timer(n, f, arguments_of_f) that would measure how much time does a method f with arguments arguments_of_f need to run n times. My attempt was the following:
function duration = timer(n, f, arguments_of_f)
duration = 0;
for i=1:n
t0 = cputime;
f(arguments_of_f);
t1 = cputime;
duration += t1 - t0;
end
In another file, I have
function y = f(x)
y = x + 1;
end
The call d1 = timer(100, #f, 3); works as expected.
In another file, I have
function y = g(x1, x2)
y = x1 + x2;
end
but the call d2 = timer(100, #g, 1, 2); gives an error about undefined
argument x2, which is, when I look back, somehow expected, since I pass only
1 to g and 2 is never used.
So, how to implement the function timer in Octave, so that the call like
timer(4, #g, x1, ... , xK) would work? How can one pack the xs together?
So, I am looking for the analogue of Pythons *args trick:
def use_f(f, *args):
f(*args)
works if we define def f(x, y): return x + y and call use_f(f, 3, 4).
You don't need to pack all the arguments together, you just need to tell Octave that there is more than one argument coming and that they are all necessary. This is very easy to do using variadic arguments.
Your original implementation is nearly spot on: the necessary change is minimal. You need to change the variable arguments_to_f to the special name varargin, which is a magical cell array containing all your arbitrary undeclared arguments, and pass it with expansion instead of directly:
function duration = timer(n, f, varargin)
duration = 0;
for i=1:n
t0 = cputime;
f(varargin{:});
t1 = cputime;
duration += t1 - t0;
end
That's it. None of the other functions need to change.
I have an assignment to make a macro in LibreOffice that I have to code a text using XOR operations. the instructions are following.
izracunajHash function (password zacetni_hash) - This function will implement a hash function that will calculate the user's password from the initial seed for the pseudo-random number generator. To receive functions are set following additional requirements:
Variable zacetni_hash to the setpoint 17520
The function of taking the characters from the first character to the last
Walk through the characters need to implement the loop for
function encode (string) - output of the function will be encoded / decoded input string. Assume that the random number generator are set to the correct value. Encode only the characters whose ascii value is greater than 31. For admission functions are set following additional requirements:
The function of taking the characters from the last character to the first
Walk through the characters you must be implemented with the Do While Loop
sub kodiraj_besedilo (seed) - This subroutine ensures that encode all the paragraphs in plain text. Also in this subprogram the initialisation of the random value
kodiraj_UI sub - this subroutine contains a user interface for your macro is basically the main program. You will require a password for encryption and call routines sooner Headquarters
I have written to this point:
REM ***** BASIC *****
function izracunajHash(geslo, zacetni_hash)
zacetni_hash = 17520
hash = zacetni_hash
mask = &H00FFFFFF
dolzina = len(geslo)
If dolzina > 0 Then
for f=1 to dolzina step +1
podniz = mid(geslo,dolzina,1)
char = Asc("podniz")
hash = 33*hash + char
hash = hash AND mask
dolzina = dolzina +1
hash = hash AND &H00FFFFFF
next f
End If
izracunajHash = hash
End function
function kodiraj(niz)
y = 1
if Len(niz) > 0 Then
x = Len(niz)
Do While y > (x+1)
sign = Mid(niz, y, 1)
z1 = Asc(sign)
if z1 > 31 Then
z2 = (CInt(rnd()*31))
z1 = z1 XOR z2
z1 = Chr(z1)
Mid(niz,y,1,z1)
End If
y = y + 1
Loop
End If
kodiraj = niz
End function
Sub kodiraj_besedilo(seme)
Randomize(seme)
oParEnum = ThisComponent.Text.createEnumeration()
Do While oParEnum.hasMoreElements()
oPar = oParEnum.nextElement()
If oPar.supportsService("com.sun.star.text.Paragraph") Then
oPar.String = kodiraj(oPar.String)
End If
Loop
End Sub
Sub kodiraj_UI
geslo = InputBox("Vnesi niz:", "Geslo", "E1087385")
hash = izracunajHash(geslo, 17520)
kodiraj_besedilo(hash)
msgBox("Končaj")
End Sub
I have the following class in matlab:
classdef floating_search
properties
S,M;
end
methods
function s = support(x,y)
for i=1:length(x)
if(y(i)~=1)
s = x(i);
end
end
end
end
end
Now, in the command winows, I did the following:
>> x=1:10;
>> floating_search.S = x;
>> y=trapmf(x,[1 3 5 9])
y =
Columns 1 through 7
0 0.5000 1.0000 1.0000 1.0000 0.7500 0.5000
Columns 8 through 10
0.2500 0 0
>> floating_search.M = y;
>> floating_search.support(floating_search.S, floating_search.M)
??? Reference to non-existent field 'support'.
For the last command, why did I get this error? Am I calling the function wrong? How can I pass th values floating_search.S and floating_search.M to the function and retrieve the values of S for which Y~=1?
Thanks.
You never initialize your object.
Furthermore, I think you should reconcider using your code as a non-static method:
classdef floating_search
properties
S
M
end
methods
function s = support(obj)
for i=1:length(obj.S)
if(obj.M(i)~=1)
s = obj.S(i);
end
end
end
end
end
Then execute:
x = 1:10;
y = trapmf(x,[1 3 5 9])
myInstance = floating_search()
myInstance.S = x;
myInstance.M = y;
myInstance.support()
your class is missing a constructor.
furthermore you never initialize any object.
your floating_search.S = x; statement generates a struct called floating_search:
>> whos floating_search
Name Size Bytes Class Attributes
floating_search 1x1 256 struct
Try this instead (save file as floating_search.m):
classdef floating_search
properties
S;
M;
end
methods
% constructor - place to initialize things
function obj = floating_search()
end
% you need the first input argument 'obj', since this is a value class
% see http://www.mathworks.de/de/help/matlab/matlab_oop/comparing-handle-and-value-classes.html
function s = support(obj, x, y)
for i=1:length(x)
if(y(i)~=1)
s = x(i);
end
end
end
end
end
and then run the code:
% generate your data
x = 1:10;
y = trapmf(x,[1 3 5 9]);
# initialize object
a = floating_search()
a.S = x;
a.M = y;
a.support(a.S, a.M)