This is the part of the code that gives me problems:
PROGRAM concept
IMPLICIT NONE
REAL, DIMENSION (3,7) :: datin
REAL, DIMENSION (2,4) :: datiel
REAL, DIMENSION (4,4) :: ke1
REAL :: c1, c2
WRITE (*,*) "Inserire il valore di alpha per il primo elemento"
READ (*,*) datiel(1,1)
WRITE (*,*) "alpha1=", datiel(1,1)
WRITE (*,*) "alpha2=", datiel(2,1)
c1=(datiel(1,2))*(datiel(1,3))/(datin(2,1)-datin(1,1))
ke1(1,1)=c1*(cosd(datiel(1,1)))^2
ke1(1,2)=c1*(sind(datiel(1,1)))*(cosd(datiel(1,1)))
ke1(1,3)=c1*(-(cosd(datiel(1,1)))^2)
ke1(1,4)=c1*(-sind(datiel(1,1)))*(cosd(datiel(1,1)))
ke1(2,1)=ke1(1,2)
ke1(2,2)=c1*(sind(datiel(1,1)))^2
ke1(2,3)=ke1(1,4)
ke1(2,4)=c1*(-(sind(datiel(1,1)))^2)
ke1(3,1)=ke1(1,3)
ke1(3,2)=ke1(2,3)
ke1(3,3)=ke1(1,1)
ke1(3,4)=ke1(1,2)
ke1(4,1)=ke1(1,4)
ke1(4,2)=ke1(2,4)
ke1(4,3)=ke1(3,4)
ke1(4,4)=ke1(2,2)
END PROGRAM
When compiling it gives:
I am using a macbook.
I am trying to figure out why it doesn't work, but some help would be appreciated.
There are a number of problems in your code:
power in Fortran isn't ^ but **
sind and cosd should be replaced by sin and cos as the later are standard functions and the versions with the d are extensions. Based on the comment from #kvantour, #evets : Note: sind and cosd are DEC Fortran compiler / gnu compiler extensions which compute the sine and cosine of an angle given in degrees, so some conversion of the argument has to be done as well!
Related
In Newton's method, to solve a nonlinear system of equations we need to find the Jacobian matrix and the determinant of the inverse of the Jacobian matrix.
Here are my component functions,
real function f1(x,y)
parameter (pi = 3.141592653589793)
f1 = log(abs(x-y**2)) - sin(x*y) - sin(pi)
end function f1
real function f2(x,y)
f2 = exp(x*y) + cos(x-y) - 2
end function f2
For the 2x2 case I am computing the Jacobian matrix and determinant of the inverse of Jacobian matrix like this,
x = [2,2]
h = 0.00001
.
.
! calculate approximate partial derivative
! you can make it more accurate by reducing the
! value of h
j11 = (f1(x(1)+h,x(2))-f1(x(1),x(2)))/h
j12 = (f1(x(1),x(2)+h)-f1(x(1),x(2)))/h
j21 = (f2(x(1)+h,x(2))-f2(x(1),x(2)))/h
j22 = (f2(x(1),x(2)+h)-f2(x(1),x(2)))/h
! calculate the Jacobian
J(1,:) = [j11,j12]
J(2,:) = [j21,j22]
! calculate inverse Jacobian
inv_J(1,:) = [J(2,2),-J(1,2)]
inv_J(2,:) = [-J(2,1),J(1,1)]
DET=J(1,1)*J(2,2) - J(1,2)*J(2,1)
inv_J = inv_J/DET
.
.
How do I in Fortran extend this to evaluate a Jacobian for m functions evaluated at n points?
Here is a more flexible jacobian calculator.
The results with the 2×2 test case are what you expect
arguments (x)
2.00000000000000
2.00000000000000
values (y)
1.44994967586787
53.5981500331442
Jacobian
0.807287239448229 3.30728724371454
109.196300248300 109.196300248300
I check the results against a symbolic calculation for the given inputs of
Console.f90
program Console1
use ISO_FORTRAN_ENV
implicit none
! Variables
integer, parameter :: wp = real64
real(wp), parameter :: pi = 3.141592653589793d0
! Interfaces
interface
function fun(x,n,m) result(y)
import
integer, intent(in) :: n,m
real(wp), intent(in) :: x(m)
real(wp) :: y(n)
end function
end interface
real(wp) :: h
real(wp), allocatable :: x(:), y(:), J(:,:)
! Body of Console1
x = [2d0, 2d0]
h = 0.0001d0
print *, "arguments"
print *, x(1)
print *, x(2)
y = test(x,2,2)
print *, "values"
print *, y(1)
print *, y(2)
J = jacobian(test,x,2,h)
print *, "Jacobian"
print *, J(1,:)
print *, J(2,:)
contains
function test(x,n,m) result(y)
! Test case per original question
integer, intent(in) :: n,m
real(wp), intent(in) :: x(m)
real(wp) :: y(n)
y(1) = log(abs(x(1)-x(2)**2)) - sin(x(1)*x(2)) - sin(pi)
y(2) = exp(x(1)*x(2)) + cos(x(1)-x(2)) - 2
end function
function jacobian(f,x,n,h) result(u)
procedure(fun), pointer, intent(in) :: f
real(wp), allocatable, intent(in) :: x(:)
integer, intent(in) :: n
real(wp), intent(in) :: h
real(wp), allocatable :: u(:,:)
integer :: j, m
real(wp), allocatable :: y1(:), y2(:), e(:)
m = size(x)
allocate(u(n,m))
do j=1, m
e = element(j, m) ! Get kronecker delta for j-th value
y1 = f(x-e*h/2,n,m)
y2 = f(x+e*h/2,n,m)
u(:,j) = (y2-y1)/h ! Finite difference for each column
end do
end function
function element(i,n) result(e)
! Kronecker delta vector. All zeros, except the i-th value.
integer, intent(in) :: i, n
real(wp) :: e(n)
e(:) = 0d0
e(i) = 1d0
end function
end program Console1
I will answer about evaluation in different points. This is quite simple. You just need an array of points, and if the points are in some regular grid, you may not even need that.
You may have an array of xs and array of ys or you can have an array of derived datatype with x and y components.
For the former:
real, allocatable :: x(:), y(:)
x = [... !probably read from some data file
y = [...
do i = 1, size(x)
J(i) = Jacobian(f, x(i), y(i))
end do
If you want to have many functions at the same time, the problem is always in representing functions. Even if you have an array of function pointers, you need to code them manually. A different approach is to have a full algebra module, where you enter some string representing a function and you can evaluate such function and even compute derivatives symbolically. That requires a parser, an evaluator, it is a large task. There are libraries for this. Evaluation of such a derivative will be slow unless further optimizing steps (compiling to machine code) are undertaken.
Numerical evaluation of the derivative is certainly possible. It will slow the convergence somewhat, depending on the order of the approximation of the derivative. You do a difference of two points for the numerical derivative. You can make an interpolating polynomial from values in multiple points to get a higher-order approximation (finite difference approximations), but that costs machine cycles.
Normally we can use auto difference tools as #John Alexiou mentioned. However in practise I prefer using MATLAB to analytically solve out the Jacobian and then use its build-in function fortran() to convert the result to a f90 file. Take your function as an example. Just type these into MATLAB
syms x y
Fval=sym(zeros(2,1));
Fval(1)=log(abs(x-y^2)) - sin(x*y) - sin(pi);
Fval(2)=exp(x*y) + cos(x-y) - 2;
X=[x;y];
Fjac=jacobian(Fval,X);
fortran(Fjac)
which will yield
Fjac(1,1) = -y*cos(x*y)-((-(x-y**2)/abs(-x+y**2)))/abs(-x+y**2)
Fjac(1,2) = -x*cos(x*y)+(y*((-(x-y**2)/abs(-x+y**2)))*2.0D0)/abs(-
&x+y**2)
Fjac(2,1) = -sin(x-y)+y*exp(x*y)
Fjac(2,2) = sin(x-y)+x*exp(x*y)
to you. You just get an analytical Jacobian fortran function.
Meanwhile, it is impossible to solve the inverse of a mxn matrix because of rank mismatching. You should simplify the system of equations to get a nxn Jacobin.
Additionally, when we use Newton-Raphson's method we do not solve the inverse of the Jacobin which is time-consuming and inaccurate for a large system. An easy way is to use dgesv in LAPACK for dense Jacobin. As we only need to solve the vector x from system of linear equations
Jx=-F
dgesv use LU decomposition and Gaussian elimination to solve above system of equations which is extremely faster than solving inverse matrix.
If the system of equations is large, you can use UMFPACK and its fortran interface module mUMFPACK to solve the system of equations in which J is a sparse matrix. Or use subroutine ILUD and LUSOL in a wide-spread sparse matrix library SPARSEKIT2.
In addition to these, there are tons of other methods which try to solve the Jx=-F faster and more accurate such as Generalized Minimal Residual (GMRES) and Stabilized Bi-Conjugate Gradient (BICGSTAB) which is a strand of literature.
Why does my Fortran code execute 5 times faster than my C++ code to solve this second order differential equation (for universal gravitation between a planet and a sun) using RK4?
How could I optimize my C++ code, please ?
I have tried changing pow() to x*x with no improvements. Removing the write operations divided execution time by 2 on Fortran, but only made C++ code about 15% faster.
Here are the codes:
C++ (compiled with: c++ -Wall -Wextra equadiff.cpp -o equadiffcpp.x):
#include<iostream>
#include<fstream>
#include<cmath>
#include <chrono> //pour mesurer le temps d'execution
using namespace std;
using namespace std::chrono;
void deriv(double t, double X[], double Xderiv[], int n){
double radius;
radius=sqrt(pow(X[0],2)+pow(X[1],2));
Xderiv[0]=X[2];
Xderiv[1]=X[3];
Xderiv[2]=-X[0]/pow(radius,3);
Xderiv[3]=-X[1]/pow(radius,3);
}
void rk4(double t, double X[], double dt, int n, void deriv(double, double[], double[], int)){
int i;
double ddt;
double Xp[n], k1[n], k2[n], k3[n], k4[n];
ddt=0.5*dt;
deriv(t,X,k1,n);
for(i=0;i<n;i++){
Xp[i]=X[i]+ddt*k1[i];
deriv(t+ddt,Xp,k2,n);
}
for(i=0;i<n;i++){
Xp[i]=X[i]+ddt*k2[i];
deriv(t+ddt,Xp,k3,n);
}
for(i=0;i<n;i++){
Xp[i]=X[i]+dt*k3[i];
deriv(t+dt,Xp,k4,n);
}
for(i=0;i<n;i++){
X[i] = X[i] + dt*(k1[i]+2*k2[i]+2*k3[i]+k4[i])/6;
}
}
int main(void){
double dt, t, tmax;
double X[4];
double Xderiv[4];
dt=0.01;
tmax=1000.0;
X[0]=1.0;
X[1]=0.0;
X[2]=0.0;
X[3]=-0.5;
ofstream fichier ("equadiffrk4cpp.out");
auto start = high_resolution_clock::now();//on commence a compter le temps de mesure
for(t=0.0;t<=tmax;t+=dt){
rk4(t,X,dt,4,deriv);
if((int)(round(t/dt))%10==0){//on n'ecrit qu'une valeur sur 10
fichier <<t<<" "<<X[0]<<" "<<X[1]<<endl;
}
}
auto stop = high_resolution_clock::now();//on arrete de compter le temps d'execution
fichier.close();
auto duration = duration_cast<microseconds>(stop - start);
cout << "Time taken by function: "
<< duration.count() << " microseconds" << endl;
return 0;
}
Fortran90 (compiled with: gfortran equadiff.f90 -o equadifff90.x):
program meca_planet
implicit none
real (8) :: dt ,t
integer :: i
real(8), dimension (4) :: X, Xderiv
external :: euler, deriv_planet, rk4
real :: start, finish!pour compter temps execution du programme
t=0.
dt=0.01
!Initialization
X=(/1.,0.,0.,-0.5/)
open(11,file='equadiffrk4f90.out')
call cpu_time(start)!on commence a compter
do i=1,100000!tmax=0.01*100000=1000
t=t+dt
call rk4(t,X,dt,4,deriv_planet)
if (mod(nint(t/dt),10).eq.0) then
write(11,*) t, X(1),X(2)
endif
enddo
call cpu_time(finish)!on arrete de compter
close (11)
print '("Time = ",f6.3," seconds.")',finish-start
end program meca_planet
subroutine deriv_planet(t,X,Xderiv,n)
implicit none
integer, intent(in) :: n
real(8), intent (in) :: t!pourquoi on definit t dans deriv_planet mais ensuite on ne l'utilise pas?
real(8) :: radius
real(8), dimension(n), intent(in) :: X
real(8), dimension(n), intent(out) :: Xderiv
if (n.ne.4) write (*,*) 'WARNING: dimension de n incorrecte, devrait etre 4'
radius=sqrt(X(1)**2+X(2)**2)
Xderiv(1)=X(3)
Xderiv(2)=X(4)
Xderiv(3)=-X(1)/radius**3
Xderiv(4)=-X(2)/radius**3
end subroutine deriv_planet
subroutine rk4(t,X,dt,n,deriv)
!Runge-Kutta du 4eme ordre
implicit none
integer, intent(in) :: n
real(8), intent(in) :: t, dt
real(8), dimension(n), intent (inout) :: X
real(8) :: ddt
real(8), dimension(n) :: Xp, k1, k2, k3, k4
ddt = 0.5*dt
call deriv(t,X,k1,n); Xp=X+ddt*k1
call deriv(t+ddt,Xp,k2,n); Xp=X+ddt*k2
call deriv(t+ddt,Xp,k3,n); Xp=X+dt*k3
call deriv(t+dt,Xp,k4,n); X = X + dt*(k1+2.0*k2 + 2.0*k3+k4)/6.0
end subroutine rk4
The end goal is to code an N-body problem for a solar system, and then perhaps a galaxy. I was thinking of using C++ but based on this initial assessment I am now leaning towards Fortran90.
The comment of Bob__'s should capture the main culprit: In your loops you call deriv after the update of each coordinate. But only the last call counts. As that is done with the completely set vector, you get overall the correct result. Change to
for(i=0;i<n;i++) {
Xp[i]=X[i]+ddt*k1[i];
}
deriv(t+ddt,Xp,k2,n);
etc. and the time should reduce by a factor of about 3, replacing 3*4+1=13 deriv calls with just 4.
You can reduce the root computation by a square root. Just compute
r3 = pow(X[0]*X[0]+X[1]*X[1], 1.5);
Perhaps also replace the division by a multiplication using
ir3 = pow(X[0]*X[0]+X[1]*X[1], -1.5);
It is an impractical idea to simulate even a large solar system with RK4. You get too many different scales, processes at largely different frequencies. Also, bodies on an significantly excentric orbit will move very slowly at their far point and very quickly at their nearest point, which is poorly captured with a fixed step size. It is better to use solvers with adaptive step sizes, possibly even using separate solvers for inner system, outer system and comets that are connected via their "dense output" interpolations. Look up the "Moving stars around" tutorial, in the end they give some starting ideas how large scale celestial simulations are organized.
I would like to know if it is possible to reproduce the following MWE without the do loops.
Of course, in this simplistic example I can do it manually to avoid the do loop since the matrices MatA, MatBx and MatBy are only 2 by 2, but in my real cases matrices can be much larger.
PROGRAM MAIN
implicit none
real, dimension(2,2) :: MatA
integer, dimension(2,2) :: MatBx
integer, dimension(2,2) :: MatBy
real, dimension(2,2) :: MatC
integer :: x,y
MatC=0.
MatA(1,1)=10
MatA(1,2)=20
MatA(2,1)=30
MatA(2,2)=40
MatBx(1,1)=1
MatBx(1,2)=2
MatBx(2,1)=1
MatBx(2,2)=1
MatBy(1,1)=1
MatBy(1,2)=1
MatBy(2,1)=2
MatBy(2,2)=1
Print*, MatA
do y=1,2
do x=1,2
MatC(x,y)= MatA(MatBx(x,y), MatBy(x,y))
enddo
enddo
Print*, MatC
END PROGRAM MAIN
Executed with the command:
user$ gfortran -o progtest test.f90 && ./progtest
10.0000000 30.0000000 20.0000000 40.0000000
10.0000000 20.0000000 30.0000000 10.0000000
So I was wondering if it was possible to perform this kind of operation on the indexes for the whole matrix directly: something like MatC(:,:)= MatA(MatBx(:,:), MatBy(:,:)) (which is not working).
Precisions
Some precisions that might helping to understand the general case:
In the general case, the matrices can be NxM rather than 2x2.
The general operation is sending information from MatA to MatC according to a transfer matrix named MatB. All the elements of MatA are not necessary received by MatC: in the above example the element MatA(2,2) is not received by MatC.
The following link https://gcc.gnu.org/onlinedocs/gcc-5.4.0/gfortran/MATMUL.html clearly states that gfortran expects matrices input to matmul to be of rank 1 OR 2. However the following snippet wont compile:
Program scratch
real(kind=8) :: A(10)=(/0,1,2,3,4,5,6,7,8,9/)
real(kind=8) :: B(10)=(/0,1,2,3,4,5,6,7,8,9/)
real(kind=8) :: C(10,10)
print *,rank(A),rank(B)
C=matmul(A,B)
End Program scratch
gfortran gives the error:
$gfortran scratch.f90
scratch.f90:6:13:
C=matmul(A,B)
1
Error: ‘matrix_b’ argument of ‘matmul’ intrinsic at (1) must be of rank 2
My gfortran is 5.4.0 (compatible with the link above). Am I doing something really stupid?
You can use RESHAPE to get them into a form MATMUL will like:
Program scratch
real(kind=8) :: A(10)=(/0,1,2,3,4,5,6,7,8,9/)
real(kind=8) :: B(10)=(/0,1,2,3,4,5,6,7,8,9/)
real(kind=8) :: C(10,10)
print *,rank(A),rank(B)
C = matmul( RESHAPE(A,(/10,1/)), RESHAPE(B,(/1,10/)) )
WRITE(*,"(10F7.2)") C
End Program scratch
You must do this for a tensor product of two vectors
Program scratch
integer, parameter :: dp = kind(1.d0)
real(dp) :: A(10,1)=reshape((/0,1,2,3,4,5,6,7,8,9/), (/ 10, 1 /))
real(dp) :: B(1,10)=reshape((/0,1,2,3,4,5,6,7,8,9/), (/ 1, 10 /))
real(dp) :: C(10,10)
print *,rank(A),rank(B)
C=matmul(A,B)
print *, C
End Program scratch
If you do
A(1,10)
B(10,1)
you will get a scalar product. With just two 1D arrays it is not clear which of the two products you want (although for a dot product there is a special function available).
A or B can be a 1D array when you are multiplying a matrix by a vector.
I ran into some piece of Fortran code rather difficult to understand.
1. What is the name of structure of code / (i1,i1=0,nn-1) /?
How can I print it directly in the code to see its content?
2. I'm looking for ways to change value of nn without re-compiling, how should I do this? nn supposed to be the length of array omega.
3. How should I setup the length of omega in case of changeable nn?
I mean when I'll have no parameter (nn=20) anymore.
program test_20140919
! test
implicit none
integer nn
parameter (nn=20)
real omega(nn)
call test_real(nn, 2.0, 4.0, omega)
print *, omega
end program test_20140919
!c ===
subroutine test_real(nn, o1, o2, omega)
integer nn
real o1, o2
real omega(nn)
print *, nn
omega = o1 + (o2*o1)*(/ (i1,i1=0,nn-1) /)/real(nn-1)
print *, real(nn)
return
end
I've compiled this with line gfortran test.f -ffree-form -o test in terminal.
UPD
Revised version of the code due to answers from Vladimir F:
module subs
implicit none
contains
subroutine test_real(nn, o1, o2, omega)
integer nn
real o1, o2
real :: omega(:)
if (.not. allocated(omega)) allocate(omega(nn))
omega = o1 + (o2*o1)*(/ (i1,i1=0,nn-1) /)/real(nn-1)
print *, real(nn)
end subrotine
end module
program test_20140920
! test
use subs
implicit none
integer nn
real, allocatable :: omega(:)
read(*,*) nn
allocate(omega(nn))
call test_real(nn, 2.0, 4.0, omega)
print *, omega
end program test_20140920
1) This is (/ ... /) is an array constructor.
The expression (i1,i1=0,nn-1) is an implied-do loop.
2) Read it using a read statement
integer :: nn
read(*,*) nn
3) Use an allocatable array
real, allocatable :: omega(:)
...
allocate(omega(nn))
I recommend you to read a Fortran tutorial or a Fortran book and familiarize yourself with these concepts.