Esempio n. 1
0
def test_M_shift_monopole():
    order = 2
    source = 0
    array_length = Nterms(order) - Nterms(source - 1)

    M = sp.MatrixSymbol('M', Nterms(order), 1)
    M_dict, _ = gen.generate_mappings(order,
                                      symbols,
                                      key='grevlex',
                                      source_order=source)
    # Check monopole term
    assert exp.M_shift((0, 0, 0), order, symbols, M_dict,
                       source_order=source) == M[0]
    # Check dipole terms
    assert exp.M_shift((1, 0, 0), order, symbols, M_dict,
                       source_order=source) == M[1] + M[0] * x
    assert exp.M_shift((0, 1, 0), order, symbols, M_dict,
                       source_order=source) == M[2] + M[0] * y
    assert exp.M_shift((0, 0, 1), order, symbols, M_dict,
                       source_order=source) == M[3] + M[0] * z
    # Check quadrupole terms
    assert exp.M_shift((2, 0, 0), order, symbols, M_dict, source_order=source) ==  \
        x**2/2 * M[0] + x * M[1] + M[4]

    assert sp.expand(
        exp.M_shift(
            (1, 1, 0), order, symbols, M_dict,
            source_order=source)) == x * y * M[0] + y * M[1] + x * M[2] + M[5]

    assert sp.expand(
        exp.M_shift(
            (1, 0, 1), order, symbols, M_dict,
            source_order=source)) == x * z * M[0] + z * M[1] + x * M[3] + M[6]
Esempio n. 2
0
def test_M_dipole():
    order = 2
    source = 1
    M_dict, _ = gen.generate_mappings(order,
                                      symbols,
                                      key='grevlex',
                                      source_order=source)

    array_length = Nterms(order) - Nterms(source - 1)
    M = sp.MatrixSymbol('M', array_length, 1)

    mux, muy, muz = sp.symbols('mux muy muz')

    # Check dipole terms
    assert exp.M_dipole((1, 0, 0), symbols, M_dict) == mux
    assert exp.M_dipole((0, 1, 0), symbols, M_dict) == muy
    assert exp.M_dipole((0, 0, 1), symbols, M_dict) == muz

    # Check quadrupole terms
    assert exp.M_dipole((2, 0, 0), symbols, M_dict) == -mux * x
    assert exp.M_dipole((1, 1, 0), symbols, M_dict) == -mux * y - muy * x
    assert exp.M_dipole((1, 0, 1), symbols, M_dict) == -mux * z - muz * x
    assert exp.M_dipole((0, 2, 0), symbols, M_dict) == -muy * y
    assert exp.M_dipole((0, 1, 1), symbols, M_dict) == -muy * z - muz * y
    assert exp.M_dipole((0, 0, 2), symbols, M_dict) == -muz * z
Esempio n. 3
0
def test_M_monopole():
    order = 2
    source = 0
    array_length = Nterms(order) - Nterms(source - 1)

    M = sp.MatrixSymbol('M', array_length, 1)
    M_dict, _ = gen.generate_mappings(order,
                                      symbols,
                                      key='grevlex',
                                      source_order=source)
    # Check monopole term
    assert exp.M((0, 0, 0), symbols) == q
    # Check dipole terms
    assert exp.M((1, 0, 0), symbols) == -q * x
    assert exp.M((0, 1, 0), symbols) == -q * y
    assert exp.M((0, 0, 1), symbols) == -q * z
Esempio n. 4
0
def test_M_shift_dipole():
    order = 2
    source = 1
    M = sp.MatrixSymbol('M', Nterms(order), 1)
    M_dict, _ = gen.generate_mappings(order,
                                      symbols,
                                      key='grevlex',
                                      source_order=source)

    # With a source order of 1, the monopole term is no longer produced. We should check
    # therefore, that M_shift returns an error.

    print(M_dict[(1, 0, 1)])

    try:
        exp.M_shift((0, 0, 0), order, symbols, M_dict, source_order=source)
        raise AssertionError("No exception raised!")
    except ValueError:
        pass

    assert exp.M_shift((1, 0, 0), order, symbols, M_dict,
                       source_order=source) == M[0]
    assert exp.M_shift((0, 1, 0), order, symbols, M_dict,
                       source_order=source) == M[1]
    assert exp.M_shift((0, 0, 1), order, symbols, M_dict,
                       source_order=source) == M[2]

    assert sp.expand(
        exp.M_shift((2, 0, 0), order, symbols, M_dict,
                    source_order=source)) == M[3] + M[0] * x

    assert sp.expand(
        exp.M_shift((1, 1, 0), order, symbols, M_dict,
                    source_order=source)) == y * M[0] + x * M[1] + M[4]

    assert sp.expand(
        exp.M_shift((1, 0, 1), order, symbols, M_dict,
                    source_order=source)) == z * M[0] + x * M[2] + M[5]
Esempio n. 5
0
def generate_code(order,
                  name,
                  precision='double',
                  cython=False,
                  CSE=False,
                  harmonic_derivs=False,
                  include_dir=None,
                  src_dir=None,
                  potential=True,
                  field=True,
                  source_order=0,
                  atomic=False,
                  gpu=False,
                  minpow=0,
                  language='c',
                  save_opscounts=None):
    """
    Inputs:

    order, int:
        Expansion order of the FMM operators.

    name, str:
        Name of generated files.

        Note: Cython compiles *.pyx files to the *.c files with the same name,
        which means that we have to use a different name for the cython file.
        We therefore append "wrap" to the end of the name for the cython file,
        and this is therefore the name of the Python module which must be
        imported if using pyximport.

    cython_wrapper, bool:
        Enable generation of a Cython wrapper for the C files.

    CSE, bool:
        Enable common subexpression elimination, to reduce the op count in
        the generated code.

    harmonic_derivs, bool:
        The harmonicity of the Laplace means that at order p, there are only
        2p - 1 independent derivatives. Enabling this option therefore computes
        some derivatives as combinations of others.

    source_order, int:
        If source_order > 0 then we set certain multipole terms to zero
        in the local expansion, and hence they are not used. This is useful if,
        for example, we only have pure dipoles or quadrupoles in the system.

    minpow, int:
        If minpow is set, pow(x, n) expressions are expanded such that
        if n < minpow, the expression in code is printed as multiplications.

        e.g. if a sympy expression is pow(x, 2) + pow(y, 6) and minpow is 5,
        the printed version will be x*x + pow(y, 6)

    save_opcounts, string:
        Filename to save opcounts in
    """
    if save_opscounts:
        f = open(save_opscounts, 'w')

    assert language in ['c', 'c++'], "Language must be 'c' or 'c++'"
    if language == 'c':
        fext = 'c'
        hext = 'h'
    if language == 'c++':
        fext = 'cpp'
        hext = 'h'

    logger.info(f"Generating FMM operators to order {order}")
    assert precision in ['double',
                         'float'], "Precision must be float or double"
    logger.info(f"Precision = {precision}")
    if CSE:
        logger.info(f"CSE Enabled")
        p = FunctionPrinter(precision=precision, debug=False, minpow=minpow)
    else:
        logger.info(f"CSE Disabled")
        p = FunctionPrinter(precision=precision, debug=True, minpow=minpow)

    header = ""
    body = ""

    x, y, z, q = sp.symbols('x y z q')
    symbols = (x, y, z)
    coords = [x, y, z]

    start = source_order
    if field:
        # No point starting at source_order
        # because no field calculation can be done
        # at this multipole order - the L2P derivative
        # is 0. This is true whether or not we
        # calculate potential, so we always increment
        # by 1 here the start order, such that the
        # field is always calculated.
        #
        # It may be preferable to instead compute
        # the field using a finite-difference approximation,
        # or to enable this as a general option.
        # However, this would be fairly sensitive, would
        # require another user parameter (lengths over which
        # to take the f.d. approximation), a decision on
        # whether to use forward/backward/central differencing,
        # and the order of the approximation. For now, we
        # leave it as a simple symbolic derivative.
        start += 1

    for i in range(start, order):
        print(f"Generating Order {i} operators")
        M_dict, _ = generate_mappings(i,
                                      symbols,
                                      'grevlex',
                                      source_order=source_order)
        L_dict, _ = generate_mappings(i - source_order,
                                      symbols,
                                      'grevlex',
                                      source_order=0)

        M = sp.Matrix(generate_M_operators(i, symbols, M_dict))

        head, code, P2M_opscount = p.generate(f'P2M_{i}',
                                              'M',
                                              M,
                                              coords + [q],
                                              operator='+=')
        print(f"P2M_{i} opscount = {P2M_opscount}")
        header += head
        body += code
        Ms = sp.Matrix(
            generate_M_shift_operators(i,
                                       symbols,
                                       M_dict,
                                       source_order=source_order))

        head, code, M2M_opscount = p.generate(f'M2M_{i}', 'Ms', Ms,
                                list(symbols) + \
                                [sp.MatrixSymbol('M', Nterms(i), 1)],
                                operator="+=", atomic=atomic)
        header += head
        body += code + '\n'
        print(f"M2M_{i} opscount = {M2M_opscount}")
        # Two stages here; generate derivs and then the L matrix. Both
        # must be passed to the function printer.
        derivs = sp.Matrix(
            generate_derivs(i,
                            symbols,
                            M_dict,
                            source_order,
                            harmonic_derivs=harmonic_derivs))
        L = sp.Matrix(
            generate_L_operators(i,
                                 symbols,
                                 M_dict,
                                 L_dict,
                                 source_order=source_order))

        head, code, M2L_opscount = p.generate(f'M2L_{i}', 'L', L,
                               list(symbols) +  \
                               [sp.MatrixSymbol('M', Nterms(i), 1)],
                                operator="+=", atomic=atomic, internal=[('D', derivs)])
        header += head
        body += code + '\n'
        print(f"M2L_{i} opscount = {M2L_opscount}")

        Ls = sp.Matrix(
            generate_L_shift_operators(i,
                                       symbols,
                                       L_dict,
                                       source_order=source_order))
        head, code, L2L_opscount = p.generate(f'L2L_{i}', 'Ls', Ls,
                               list(symbols) + \
                               [sp.MatrixSymbol('L', Nterms(i), 1)],
                                operator="+=", atomic=atomic)

        header += head
        body += code + '\n'
        print(f"L2L_{i} opscount = {L2L_opscount}")
        L2P = generate_L2P_operators(i,
                                     symbols,
                                     L_dict,
                                     potential=potential,
                                     field=field)

        Fs = sp.Matrix(L2P)
        head, code, L2P_opscount = p.generate(f'L2P_{i}', 'F', Fs,
                               list(symbols) + \
                               [sp.MatrixSymbol('L', Nterms(i), 1)],
                                operator="+=", atomic=atomic)

        header += head
        body += code + '\n'
        print(f"L2P_{i} opscount = {L2P_opscount}")
        M2P = generate_M2P_operators(i,
                                     symbols,
                                     M_dict,
                                     potential=potential,
                                     field=field,
                                     source_order=source_order,
                                     harmonic_derivs=harmonic_derivs)
        Fs = sp.Matrix(M2P)
        head, code, M2P_opscount = p.generate(f'M2P_{i}', 'F', Fs,
                                list(symbols) + \
                                [sp.MatrixSymbol('M', Nterms(i), 1)],
                                operator="+=", atomic=atomic)
        header += head
        body += code + '\n'
        print(f"M2P_{i} opscount = {M2P_opscount}")
        if i == start:
            P2P = sp.Matrix(
                generate_P2P_operators(symbols,
                                       M_dict,
                                       potential=potential,
                                       field=field,
                                       source_order=source_order))

            head, code, P2P_opscount = p.generate(f'P2P', 'F', P2P,
                                    list(symbols) + \
                                    [sp.MatrixSymbol('S', Nterms(i), 1)],
                                    operator="+=", atomic=atomic
                                    )
            print(f"P2P opscount = {P2P_opscount}")
            header += head
            body += code + '\n'

        if save_opscounts:
            if i == start:
                f.write(f'P2P,{P2P_opscount}\n')
            f.write(f'P2M_{i},{P2M_opscount}\n')
            f.write(f'M2M_{i},{M2M_opscount}\n')
            f.write(f'M2L_{i},{M2L_opscount}\n')
            f.write(f'L2P_{i},{L2P_opscount}\n')
            f.write(f'L2L_{i},{L2L_opscount}\n')
            f.write(f'M2P_{i},{M2P_opscount}\n')

# We now generate wrapper functions that cover all orders generated.
    unique_funcs = []
    func_definitions = header.split(';\n')
    # print(f"func_defs = {func_definitions}")
    for func in func_definitions:
        # Must do it this way in order to avoid breaking
        # for expansions > 10.
        function_name = func.split('(')[0]
        # print(f"Function_name = {function_name}")
        end_string = f'_{start}'
        if end_string == function_name[-len(end_string):]:
            # print("Unique!")
            unique_funcs.append(func)
        else:
            pass
            # print(f"{func} not unique")
            # print(f"  {end_string}  {function_name[-len(end_string)-1:]}")

    wrapper_funcs = [
        f.replace(')', ', int order)').replace(f'_{start}', '')
        for f in unique_funcs
    ]

    #  print(wrapper_funcs)

    func_definitions += wrapper_funcs
    # print('\n'.join(func_definitions))

    for wfunc, func in zip(wrapper_funcs, unique_funcs):
        # Add to header file
        header += wfunc + ';\n'
        # Create a switch statement that covers all functions:
        code = wfunc + " {\n"
        code += 'switch (order) {\n'
        for i in range(start, order):
            code += '  case {}:\n'.format(i)
            # print(func)
            replaced_code = func.replace(f'_{start}', f'_{i}').replace(
                '* ', '').replace('double ',
                                  '').replace('float ',
                                              '').replace('void ', '')
            # print(f"replaced_code: {replaced_code}")
            code += '    ' + replaced_code + ';\n    break;\n'
        code += "  }\n}\n"
        # print(code)
        body += code

    if not include_dir:
        f = open(f"{name}.{hext}", 'w')
    else:
        f = open(f"{include_dir.rstrip('/')}/{name}.{hext}", 'w')
    f.write(f"#pragma once\n")
    f.write(f"#define FMMGEN_MINORDER {start}\n")
    f.write(f"#define FMMGEN_MAXORDER {order}\n")
    f.write(f"#define FMMGEN_SOURCEORDER {source_order}\n")
    f.write(
        f"#define FMMGEN_SOURCESIZE {Nterms(source_order) - Nterms(source_order - 1)}\n"
    )
    if potential and not field:
        osize = 1
    elif field and not potential:
        osize = 3
    elif field and potential:
        osize = 4
    f.write(f"#define FMMGEN_OUTPUTSIZE {osize}\n")
    f.write(header)
    f.close()

    if not src_dir:
        f = open(f"{name}.{fext}", 'w')
    else:
        f = open(f"{src_dir.rstrip('/')}/{name}.{fext}", 'w')

    f.write(f'#include "{name}.{hext}"\n')
    if language == 'c':
        f.write(f'#include "math.h"\n')
    elif language == 'c++':
        f.write(f'#include<cmath>\n')

    f.write(body)
    f.close()

    if cython and gpu:
        raise Warning("Cannot write a Cython wrapper for GPU code; skipping")

    elif cython:
        logger.info(f"Generating Cython wrapper: {name}_wrap.pyx")
        library = f"{name}"

        f = open(f"{name}_decl.pxd", "w")
        pxdcode = textwrap.dedent("""\
        cdef extern from "{}.h":
            cdef int FMMGEN_MINORDER
            cdef int FMMGEN_MAXORDER
            cdef int FMMGEN_SOURCEORDER
            cdef int FMMGEN_SOURCESIZE
            cdef int FMMGEN_OUTPUTSIZE
            {}
        """)
        f.write(pxdcode.format(name, '\n    '.join(func_definitions)))

        f.close()

        f = open(f"{name}_wrap.pyx", "w")
        # expose the C functions from the header file.
        pyxcode = textwrap.dedent("""\
        # cython: language_level=3
        cimport numpy as np
        cimport {}

        FMMGEN_MINORDER = {}.FMMGEN_MINORDER
        FMMGEN_MAXORDER = {}.FMMGEN_MAXORDER
        FMMGEN_SOURCEORDER = {}.FMMGEN_SOURCEORDER
        FMMGEN_SOURCESIZE = {}.FMMGEN_SOURCESIZE
        FMMGEN_OUTPUTSIZE = {}.FMMGEN_OUTPUTSIZE
        """).format(*[name + '_decl'] * 6)

        subsdict = {" *": "[:]", "void": "cpdef", "_": ""}

        # Generate the actual wrapper code
        for funcname in func_definitions:
            # print(funcname)
            if not funcname:
                continue
            pyfuncname = funcname
            for key, value in subsdict.items():
                pyfuncname = pyfuncname.replace(key, value)
            pyxcode += pyfuncname + ':\n'

            function_name = funcname.split("(")[0].split(" ")[1]
            args = funcname.split("(")[1][:-1].split(",")
            processed_args = []

            for arg in args:
                if "*" in arg:
                    arg = arg.replace("* ", "&") + "[0]"
                processed_args.append(arg.split(" ")[-1])

            pyxcode += '    ' + \
                       name + '_decl.' + function_name + \
                       "(" + ', '.join(processed_args) + ')\n\n'

        f.write(pyxcode)
        f.close()

        f = open(f"{name}_wrap.pyxbld", "w")

        # print(library)

        logger.info(f"Generating Cython buildfile: {name}_wrap.pyxbld")
        bldcode = textwrap.dedent("""\
        import numpy as np

        def make_ext(modname, pyxfilename):
            from distutils.extension import Extension
            return Extension(name = modname,
                             sources=[pyxfilename, '{}'],
                             include_dirs=[np.get_include(), '.'],
                             library_dirs=['.'],
                             extra_link_args=[],
                             extra_compile_args=['-O3', '-fopenmp'])
        """).format(library + '.c', library)

        f.write(bldcode)
        f.close()