9
1
mirror of https://github.com/QuantumPackage/qp2.git synced 2024-11-07 05:53:37 +01:00

Merge pull request #324 from QuantumPackage/dev-stable
Some checks failed
continuous-integration/drone/push Build is failing

Added the first tutorial for plugins
This commit is contained in:
Anthony Scemama 2024-03-25 17:03:24 +01:00 committed by GitHub
commit 15f435428a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 587 additions and 3 deletions

View File

@ -46,6 +46,7 @@ The following people have contributed to this project (by alphabetical order):
* Nicolas Renon * Nicolas Renon
* Lorenzo Tenti * Lorenzo Tenti
* Julien Toulouse * Julien Toulouse
* Diata Traoré
* Mikaël Véril * Mikaël Véril

View File

@ -39,9 +39,10 @@
programmers_guide/programming programmers_guide/programming
programmers_guide/ezfio programmers_guide/ezfio
programmers_guide/plugins programmers_guide/plugins
programmers_guide/plugins_tuto_intro
programmers_guide/plugins_tuto_I
programmers_guide/new_ks programmers_guide/new_ks
programmers_guide/index programmers_guide/index
programmers_guide/plugins
.. toctree:: .. toctree::
@ -52,5 +53,6 @@
appendix/benchmarks appendix/benchmarks
appendix/license appendix/license
appendix/contributors appendix/contributors
appendix/references

View File

@ -0,0 +1 @@
.. include:: ../../../plugins/local/tuto_plugins/tuto_I/tuto_I.rst

View File

@ -0,0 +1 @@
.. include:: ../../../plugins/README.rst

2
external/irpf90 vendored

@ -1 +1 @@
Subproject commit ba1a2837aa61cb8f9892860cec544d7c6659badd Subproject commit 4ab1b175fc7ed0d96c1912f13dc53579b24157a6

131
plugins/README.rst Normal file
View File

@ -0,0 +1,131 @@
==============================
Tutorial for creating a plugin
==============================
Introduction: what is a plugin, and what tutorial will be about ?
=================================================================
The |QP| is split into two kinds of routines/global variables (i.e. *providers*):
1) the **core modules** locatedin qp2/src/, which contains all the bulk of a quantum chemistry software (integrals, matrix elements between Slater determinants, linear algebra routines, DFT stuffs etc..)
2) the **plugins** which are external routines/*providers* connected to the qp2/src/ routines/*providers*.
More precisely, a **plugin** of the |QP| is a directory where you can create routines,
providers and executables that use all the global variables/functions/routines already created
in the modules of qp2/src or in other plugins.
Instead of giving a theoretical lecture on what is a plugin,
we will go through a series of examples that allow you to do the following thing:
1) print out **one- and two-electron integrals** on the AO/MO basis, creates two providers which manipulate these objects, print out these providers,
2) browse the **Slater determinants stored** in the |EZFIO| wave function and compute their matrix elements,
3) build the **Hamiltonian matrix** and **diagonalize** it either with **Lapack or Davidson**,
4) print out the **one- and two-electron rdms**,
5) obtain the **AOs** and **MOs** on the **DFT grid**, together with the **density**,
How the tutorial will be done
-----------------------------
This tuto is as follows:
1) you **READ THIS FILE UNTIL THE END** in order to get the big picture and vocabulary,
2) you go to the directory :file:`qp2/plugins/tuto_plugins/` and you will find detailed tutorials for each of the 5 examples.
Creating a plugin: the basic
----------------------------
The first thing to do is to be in the QPSH mode: you execute the qp2/bin/qpsh script that essentially loads all
the environement variables and allows for the completion of command lines in bash (that is an AMAZING feature :)
Then, you need to known **where** you want to create your plugin, and what is the **name** of the plugin.
.. important::
The plugins are **NECESSARILY** located in qp2/plugins/, and from there you can create any structures of directories.
Ex: If you want to create a plugin named "my_fancy_plugin" in the directory plugins/plugins_test/,
this goes with the command
.. code:: bash
qp plugins create -n my_fancy_plugin -r plugins_test/
Then, to create the plugin of your dreams, the two questions you need to answer are the following:
1) What do I **need** to compute what I want, which means what are the **objects** that I need ?
There are two kind of objects:
+ the *routines/functions*:
Ex: Linear algebra routines, integration routines etc ...
+ the global variables which are called the *providers*:
Ex: one-electron integrals, Slater determinants, density matrices etc ...
2) **Where do I find** these objects ?
The objects (routines/functions/providers) are necessarily created in other *modules/plugins*.
.. seealso::
The routine :c:func:`lapack_diagd` (which diagonalises a real hermitian matrix) is located in the file
:file:`qp2/src/utils/linear_algebra.irp.f`
therefore it "belongs" to the module :ref:`module_utils`
The routine :c:func:`ao_to_mo` (which converts a given matrix A from the AO basis to the MO basis) is located in the file
:file:`qp2/src/mo_one_e_ints/ao_to_mo.irp.f`
therefore it "belongs" to the module :ref:`module_mo_one_e_ints`
The provider :c:data:`ao_one_e_integrals` (which is the integrals of one-body part of H on the AO basis) is located in the file
:file:`qp2/src/ao_one_e_ints/ao_one_e_ints.irp.f`
therefore it belongs to the module :ref:`module_ao_one_e_ints`
The provider :c:data:`one_e_dm_mo_beta_average` (which is the state average beta density matrix on the MO basis) is located in the file
:file:`qp2/src/determinants/density_matrix.irp.f`
therefore it belongs to the module :ref:`module_determinants`
To import all the variables that you need, you just need to write the name of the plugins in the :file:`NEED` file .
To import all the variables/routines of the module :ref:`module_utils`, :ref:`module_determinants` and :ref:`module_mo_one_e_ints`, the :file:`NEED` file you will need is simply the following:
.. code:: bash
cat NEED
utils
determinants
mo_one_e_ints
.. important::
There are **many** routines/providers in the core modules of QP.
Nevertheless, as everything is coded with the |IRPF90|, you can use the following amazing tools: :command:`irpman`
:command:`irpman` can be used in command line in bash to obtain all the info on a routine or variable !
Example: execute the following command line :
.. code:: bash
irpman ao_one_e_integrals
Then all the information you need on :c:data:`ao_one_e_integrals` will appear on the screen.
This includes
- **where** the provider is created, (*i.e.* the actual file where the provider is designed)
- the **type** of the provider (*i.e.* a logical, integer etc ...)
- the **dimension** if it is an array,
- what other *providers* are **needed** to build this provider,
- what other *providers* **need** this provider.

View File

@ -960,7 +960,7 @@ subroutine fill_buffer_double(i_generator, sp, h1, h2, bannedOrb, banned, fock_d
! endif ! endif
e_pert(istate) = 0.25 * val / delta_E e_pert(istate) = 0.25 * val / delta_E
! e_pert(istate) = 0.5d0 * (tmp - delta_E) ! e_pert(istate) = 0.5d0 * (tmp - delta_E)
if(dsqrt(dabs(tmp)).gt.1.d-4.and.dabs(alpha_h_psi).gt.1.d-4)then if(dsqrt(tmp).gt.1.d-4.and.dabs(psi_h_alpha).gt.1.d-4)then
coef(istate) = e_pert(istate) / psi_h_alpha coef(istate) = e_pert(istate) / psi_h_alpha
else else
coef(istate) = alpha_h_psi / delta_E coef(istate) = alpha_h_psi / delta_E

View File

@ -0,0 +1,6 @@
2
H2, equilibrium geometry
H 0.0 0.0 0.
H 0.0 0.0 0.74

View File

@ -0,0 +1,4 @@
2
N2 Geo: Experiment Mult: 1 symmetry: 14
N 0.0 0.0 0.5488
N 0.0 0.0 -0.5488

View File

@ -0,0 +1,20 @@
program my_program_to_print_stuffs
implicit none
BEGIN_DOC
! TODO : Put the documentation of the program here
END_DOC
integer :: i,j
print*,'AO integrals '
do i = 1, ao_num
do j = 1, ao_num
print*,j,i,ao_one_e_integrals(j,i)
enddo
enddo
print*,'MO integrals '
do i = 1, mo_num
do j = 1, mo_num
print*,j,i,mo_one_e_integrals(j,i)
enddo
enddo
end

View File

@ -0,0 +1,24 @@
program my_program
implicit none
BEGIN_DOC
! This program is there essentially to show how one can use providers in programs
END_DOC
integer :: i,j
double precision :: accu
print*,'Trace on the AO basis '
print*,trace_ao_one_e_ints
print*,'Trace on the AO basis after projection on the MO basis'
print*,trace_ao_one_e_ints_from_mo
print*,'Trace of MO integrals '
print*,trace_mo_one_e_ints
print*,'ao_num = ',ao_num
print*,'mo_num = ',mo_num
if(ao_num .ne. mo_num)then
print*,'The AO basis and MO basis are different ...'
print*,'Trace on the AO basis should not be the same as Trace of MO integrals'
print*,'Only the second one must be equal to the trace on the MO integrals'
else
print*,'The AO basis and MO basis are the same !'
print*,'All traces should coincide '
endif
end

View File

@ -0,0 +1,32 @@
program my_program_to_print_stuffs
implicit none
BEGIN_DOC
! TODO : Put the documentation of the program here
END_DOC
integer :: i,j,k,l
double precision :: integral
double precision :: get_ao_two_e_integral, get_two_e_integral ! declaration of the functions
print*,'AO integrals, physicist notations : <i j|k l>'
do i = 1, ao_num
do j = 1, ao_num
do k = 1, ao_num
do l = 1, ao_num
integral = get_ao_two_e_integral(i, j, k, l, ao_integrals_map)
print*,i,j,k,l,integral
enddo
enddo
enddo
enddo
print*,'MO integrals, physicist notations : <i j|k l>'
do i = 1, mo_num
do j = 1, mo_num
do k = 1, mo_num
do l = 1, mo_num
integral = get_two_e_integral(i, j, k, l, mo_integrals_map)
print*,i,j,k,l,integral
enddo
enddo
enddo
enddo
end

View File

@ -0,0 +1,111 @@
! This file is an example of the kind of manipulations that you can do with providers
!
!!!!!!!!!!!!!!!!!!!!!!!!!! Main providers useful for the program !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!! type name
BEGIN_PROVIDER [ double precision, trace_mo_one_e_ints]
implicit none
BEGIN_DOC
! trace_mo_one_e_ints = Trace of the one-electron integrals on the MO basis
!
! = sum_i mo_one_e_integrals(i,i)
END_DOC
integer :: i
trace_mo_one_e_ints = 0.d0
do i = 1, mo_num
trace_mo_one_e_ints += mo_one_e_integrals(i,i)
enddo
END_PROVIDER
BEGIN_PROVIDER [ double precision, trace_ao_one_e_ints]
implicit none
BEGIN_DOC
! trace_ao_one_e_ints = Trace of the one-electron integrals on the AO basis taking into account the non orthogonality
!
! Be aware that the trace of an operator in a non orthonormal basis is Tr(A S^{-1}) = \sum_{m,n}(A_mn S^{-1}_mn)
!
! WARNING: it is equal to the trace on the MO basis if and only if the AO basis and MO basis
! have the same number of functions
END_DOC
integer :: i,j
double precision, allocatable :: inv_overlap_times_integrals(:,:) ! = h S^{-1}
allocate(inv_overlap_times_integrals(ao_num,ao_num))
! routine that computes the product of two matrices, you can check it with
! irpman get_AB_prod
call get_AB_prod(ao_one_e_integrals,ao_num,ao_num,s_inv,ao_num,inv_overlap_times_integrals)
! Tr(inv_overlap_times_integrals) = Tr(h S^{-1})
trace_ao_one_e_ints = 0.d0
do i = 1, ao_num
trace_ao_one_e_ints += inv_overlap_times_integrals(i,i)
enddo
!
! testing the formula Tr(A S^{-1}) = \sum_{m,n}(A_mn S^{-1}_mn)
double precision :: test
test = 0.d0
do i = 1, ao_num
do j = 1, ao_num
test += ao_one_e_integrals(j,i) * s_inv(i,j)
enddo
enddo
if(dabs(accu - trace_ao_one_e_ints).gt.1.d-12)then
print*,'Warning ! '
print*,'Something is wrong because Tr(AB) \ne sum_{mn}A_mn B_nm'
endif
END_PROVIDER
BEGIN_PROVIDER [ double precision, trace_ao_one_e_ints_from_mo]
implicit none
BEGIN_DOC
! trace_ao_one_e_ints_from_mo = Trace of the one-electron integrals on the AO basis after projection on the MO basis
!
! = Tr([SC h {SC}^+] S^{-1})
!
! = Be aware that the trace of an operator in a non orthonormal basis is = Tr(A S^{-1}) where S is the metric
! Must be equal to the trace_mo_one_e_ints
END_DOC
integer :: i
double precision, allocatable :: inv_overlap_times_integrals(:,:)
allocate(inv_overlap_times_integrals(ao_num,ao_num))
! Using the provider ao_one_e_integrals_from_mo = [SC h {SC}^+]
call get_AB_prod(ao_one_e_integrals_from_mo,ao_num,ao_num,s_inv,ao_num,inv_overlap_times_integrals)
! inv_overlap_times_integrals = [SC h {SC}^+] S^{-1}
trace_ao_one_e_ints_from_mo = 0.d0
! Computing the trace
do i = 1, ao_num
trace_ao_one_e_ints_from_mo += inv_overlap_times_integrals(i,i)
enddo
END_PROVIDER
!!!!!!!!!!!!!!!!!!!!!!!!!!! Additional providers to check some stuffs !!!!!!!!!!!!!!!!!!!!!!!!!
BEGIN_PROVIDER [ double precision, ao_one_e_int_no_ov_from_mo, (ao_num, ao_num) ]
BEGIN_DOC
! ao_one_e_int_no_ov_from_mo = C mo_one_e_integrals C^T
!
! WARNING : NON EQUAL TO ao_one_e_integrals due to the non orthogonality
END_DOC
call mo_to_ao_no_overlap(mo_one_e_integrals,mo_num,ao_one_e_int_no_ov_from_mo,ao_num)
END_PROVIDER
BEGIN_PROVIDER [ double precision, ao_one_e_int_no_ov_from_mo_ov_ov, (ao_num, ao_num)]
BEGIN_DOC
! ao_one_e_int_no_ov_from_mo_ov_ov = S ao_one_e_int_no_ov_from_mo S = SC mo_one_e_integrals (SC)^T
!
! EQUAL TO ao_one_e_integrals ONLY IF ao_num = mo_num
END_DOC
double precision, allocatable :: tmp(:,:)
allocate(tmp(ao_num, ao_num))
call get_AB_prod(ao_overlap,ao_num,ao_num,ao_one_e_int_no_ov_from_mo,ao_num,tmp)
call get_AB_prod(tmp,ao_num,ao_num,ao_overlap,ao_num,ao_one_e_int_no_ov_from_mo_ov_ov)
END_PROVIDER
BEGIN_PROVIDER [ double precision, c_t_s_c, (mo_num, mo_num)]
implicit none
BEGIN_DOC
! C^T S C = should be the identity
END_DOC
call get_AB_prod(mo_coef_transp,mo_num,ao_num,S_mo_coef,mo_num,c_t_s_c)
END_PROVIDER

View File

@ -0,0 +1,218 @@
=============================================
Tuto I: One- and two-e integrals (20 minutes)
=============================================
Requirements
------------
1) You know how to create an |EZFIO| file and run calculations with |QP| (check the tuto: `<https://quantumpackage.github.io/qp2/post/hartree-fock/>`_),
2) You have an |EZFIO| file with MOs created (with the :ref:`scf` executable for instance). As we are going to print out some integrals, don't take a too large system/basis (Ex: H2, cc-pVDZ is ok :)
3) You made an qp set_file YOUR_EZFIO_FILE_FOR_H2 in order to work on that ezfio folder.
4) You have READ the :file:`qp2/plugins/README.rst` file to HAVE THE **VOCABULARY**.
Our goals:
----------
We want to create a plugin to do the following things:
1) print out one- and two-electron integrals on the AO/MO basis,
2) creates two providers which manipulate these objects,
3) print out these providers.
I) Getting started: creating the plugin
---------------------------------------
We will go step-by-step through these plugins.
We will create a plugin named "plugin_I", and its location will be in "tuto_plugins".
Therefore to create the plugin, we do:
.. code:: bash
qp plugins create -n plugin_I -r tuto_plugins
Then do an "ls" in qp2/plugins/tuto_plugins/ and you will find a directory called "plugin_I".
In that directory you will find:
1) a :file:`NEED` file that will eventually contain all the other modules/plugins needed by our "plugin_I",
2) a :file:`README.rst` file that you can and **SHOULD** modify in order to **DOCUMENT** what is doing the plugin,
3) a :file:`plugin_I.irp.f` file that is a program to be compiled and just printing "Hello world"
II) Specifying the dependencies
-------------------------------
The next step is to know what are the other modules/plugins that we need to do what we want.
We need here
a) the one-electron integrals on the AO basis, which are computed in :file:`qp2/src/ao_one_e_ints/`
b) the one-electron integrals on the MO basis, which are computed in :file:`qp2/src/mo_one_e_ints/`
c) the two-electron integrals on the AO basis, which are computed in :file:`qp2/src/ao_two_e_ints/`
d) the two-electron integrals on the MO basis, which are computed in :file:`qp2/src/mo_two_e_ints/`
Therefore, we will need the following four modules:
a) ao_one_e_ints
b) mo_one_e_ints
c) ao_two_e_ints
d) mo_two_e_ints
You can then create the following "NEED" file by executing the following command
.. code:: bash
cat <<EOF > NEED
ao_one_e_ints
mo_one_e_ints
ao_two_e_ints
mo_two_e_ints
EOF
II) Installing the plugin
-------------------------
Now that we have specified the various depenencies we need now to INSTALL the plugin, which means to create the equivalent of a Makefile for the compilation.
To do it we simply do
.. code:: bash
qp plugins install plugin_I
III) Compiling the void plugin
------------------------------
It is customary to compile first your "void" plugin, void in the sense that it does not contain anything else than the program printing "Hello world".
To do so, just go in the plugin and execute the following command
.. code:: bash
ninja
It does a lot of stuffs, but it must conclude with something like
.. code:: bash
make: Leaving directory 'SOME_PATH_TOWARD_YOUR_QP2_DIRECTORY/qp2/ocaml'
Since that it has compiled, an executable "plugin_I" has been created.
Also, if you make "ls" in the "plugin_I" you will notice that many symbolink links have been created, and among which the four modules that you included in the NEED file.
All the other modules (Ex::ref:`module_ao_basis`, :ref:`module_utils`) are here because they are need by some of the four modules that you need.
The variables that we need are
:data:`ao_one_e_integrals`
:data:`mo_one_e_integrals`
You can check them with
.. code:: bash
irpman ao_one_e_integrals
.. code:: bash
irpman mo_one_e_integrals
in order to get some information on where they are created, and many more information.
We will now create an executable such that it prints out the integrals.
IV) Printing out the one-electron integrals
--------------------------------------------
We will now create a program that will print out the one-electron integrals on the AO and MO basis.
You can then copy the file :file:`qp2/plugins/tuto_plugins/tuto_I/print_one_e_h.irp.f` in your plugin.
In this file you will see that we simply browse the two arrays :data:`ao_one_e_integrals` and :data:`mo_one_e_integrals`, which are the providers and we browse them until either :data:`ao_num` or :data:`mo_num` which are also providers representing the number of AOs or MOs.
.. seealso::
You can check these variables with :command:`irpman` !
If you recompile using |ninja| as before, and another executable has been created "print_one_e_h".
Then, you can run the program on the ezfio file by doing
.. code:: bash
qp run print_one_e_h
and will print out the data you need :)
By the way, as the file :file:`plugin_I.irp.f` contains nothing but a "Hello world" print, you can simply remove it if you want.
V) Printing out the two-electron integrals
------------------------------------------
We will now create a file that prints out the two-electron integrals in the AO and MO basis.
These can be accessed with the following subroutines :
1- :c:func:`get_ao_two_e_integral` for the AO basis
2- :c:func:`get_two_e_integral` for the MO basis
.. seealso::
check them with irpman !
To print the two-electron integrals, you can copy the file :file:`qp2/plugins/tuto_plugins/tuto_I/print_two_e_h.irp.f` in your plugin and recompile with |ninja|.
Then just run the program
.. code:: bash
qp run print_two_e_h
and it will print all the things you want :)
VI) Creating new providers and a program to print them
------------------------------------------------------
We will now create new providers that manipulates the objects that we just printed.
As an example, we will compute the trace of the one electron integrals in the AO and MO basis.
In the file :file:`qp2/plugins/tuto_plugins/tuto_I/traces_one_e.irp.f` you will find the several new providers among which
1- :c:data:`trace_mo_one_e_ints` : simply the sum of the diagonal matrix element of the one-electron integrals
2- :c:data:`trace_ao_one_e_ints` : the corresponding trace on the AO basis
.. math::
\text{Tr}({\bf h}{\bf S}^{-1}) = \sum_{m,n} S^{-1}_{mn} h_{mn}
3- :c:data:`trace_ao_one_e_ints_from_mo` : the trace on the AO basis with the integrals obtained first from the MO basis
.. math::
\text{Tr}({\bf \tilde{h}}{\bf S}^{-1}) = \text{Tr}\big({\bf SC h}({\bf SC }^T){\bf S}^{-1}\big)
Just copy the :file:`qp2/plugins/tuto_plugins/tuto_I/traces_one_e.irp.f` in your plugin and recompile.
.. seealso::
Once it has compiled, check your new providers with :command:`irpman` !
As explained in the files :file:`qp2/plugins/tuto_plugins/tuto_I/traces_one_e.irp.f` and :file:`qp2/plugins/tuto_plugins/tuto_I/print_traces_on_e.irp.f`, :c:data:`trace_mo_one_e_ints` is equal to :c:data:`trace_ao_one_e_ints` only if the number of AO basis functions is equal to the number of MO basis functions, which means if you work with cartesian functions.
.. seealso::
You can check with :command:`qp create_ezfio -h` for the option to create an |EZFIO| with cartesian basis functions
In the file :file:`qp2/plugins/tuto_plugins/tuto_I/print_traces_on_e.irp.f` you will find an example of executable that prints out the various providers.
Copy these two files in your plugin and recompile to execute it.
Execute the program print_traces_on_e and check for the results with
.. code:: bash
qp run print_traces_on_e
The code in :file:`qp2/plugins/tuto_plugins/tuto_I/print_traces_on_e.irp.f` should be easy to read, I let the reader interpret it.

View File

@ -45,3 +45,13 @@ BEGIN_PROVIDER [ double precision, ao_one_e_integrals_imag,(ao_num,ao_num)]
END_PROVIDER END_PROVIDER
BEGIN_PROVIDER [ double precision, ao_one_e_integrals_from_mo, (ao_num, ao_num)]
implicit none
BEGIN_DOC
! Integrals of the one e hamiltonian obtained from the integrals on the MO basis
!
! WARNING : this is equal to ao_one_e_integrals only if the AO and MO basis have the same number of functions
END_DOC
call mo_to_ao(mo_one_e_integrals,mo_num,ao_one_e_integrals_from_mo,ao_num)
END_PROVIDER

View File

@ -166,6 +166,10 @@
if(frozen_orb_scf)then if(frozen_orb_scf)then
integer :: iorb,jorb integer :: iorb,jorb
! active|core|active
!active | | 0 |
!core | 0 | | 0
!active | | 0 |
do i = 1, n_core_orb do i = 1, n_core_orb
iorb = list_core(i) iorb = list_core(i)
do j = 1, n_act_orb do j = 1, n_act_orb

View File

@ -2041,3 +2041,22 @@ subroutine get_A_squared(A,n,A2)
double precision, intent(out):: A2(n,n) double precision, intent(out):: A2(n,n)
call dgemm('N','N',n,n,n,1.d0,A,size(A,1),A,size(A,1),0.d0,A2,size(A2,1)) call dgemm('N','N',n,n,n,1.d0,A,size(A,1),A,size(A,1),0.d0,A2,size(A2,1))
end end
subroutine get_AB_prod(A,n,m,B,l,AB)
implicit none
BEGIN_DOC
! AB = A B where A is n x m, B is m x l. Use the dgemm routine
END_DOC
double precision, intent(in) :: A(n,m),B(m,l)
integer, intent(in) :: n,m,l
double precision, intent(out):: AB(n,l)
if(size(A,2).ne.m.or.size(B,1).ne.m)then
print*,'error in get_AB_prod ! '
print*,'matrices do not have the good dimension '
print*,'size(A,2) = ',size(A,2)
print*,'size(B,1) = ',size(B,1)
print*,'m = ',m
stop
endif
call dgemm('N','N',n,l,m,1.d0,A,size(A,1),B,size(B,1),0.d0,AB,size(AB,1))
end