File: codeobject.py

package info (click to toggle)
brian 2.9.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 6,872 kB
  • sloc: python: 51,820; cpp: 2,033; makefile: 108; sh: 72
file content (172 lines) | stat: -rw-r--r-- 6,025 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
"""
Module implementing the C++ "standalone" `CodeObject`
"""

from brian2.codegen.codeobject import CodeObject, constant_or_scalar
from brian2.codegen.generators.cpp_generator import CPPCodeGenerator, c_data_type
from brian2.codegen.targets import codegen_targets
from brian2.codegen.templates import Templater
from brian2.core.functions import DEFAULT_FUNCTIONS
from brian2.core.preferences import prefs
from brian2.devices.device import get_device
from brian2.utils.stringtools import replace

__all__ = ["CPPStandaloneCodeObject"]


def openmp_pragma(pragma_type):
    nb_threads = prefs.devices.cpp_standalone.openmp_threads
    openmp_on = not (nb_threads == 0)

    ## First we need to deal with some special cases that have to be handle in case
    ## openmp is not activated
    if pragma_type == "set_num_threads":
        if not openmp_on:
            return ""
        elif nb_threads > 0:
            # We have to fix the exact number of threads in all parallel sections
            return f"omp_set_dynamic(0);\nomp_set_num_threads({int(nb_threads)});"
    elif pragma_type == "get_thread_num":
        if not openmp_on:
            return "0"
        else:
            return "omp_get_thread_num()"
    elif pragma_type == "get_num_threads":
        if not openmp_on:
            return "1"
        else:
            return f"{int(nb_threads)}"
    elif pragma_type == "with_openmp":
        # The returned value is a proper Python boolean, i.e. not something
        # that should be included in the generated code but rather for use
        # in {% if ... %} statements in the template
        return openmp_on

    ## Then by default, if openmp is off, we do not return any pragma statement in the templates
    elif not openmp_on:
        return ""
    ## Otherwise, we return the correct pragma statement
    elif pragma_type == "static":
        return "#pragma omp for schedule(static)"
    elif pragma_type == "single":
        return "#pragma omp single"
    elif pragma_type == "single-nowait":
        return "#pragma omp single nowait"
    elif pragma_type == "critical":
        return "#pragma omp critical"
    elif pragma_type == "atomic":
        return "#pragma omp atomic"
    elif pragma_type == "once":
        return "#pragma once"
    elif pragma_type == "parallel-static":
        return "#pragma omp parallel for schedule(static)"
    elif pragma_type == "static-ordered":
        return "#pragma omp for schedule(static) ordered"
    elif pragma_type == "ordered":
        return "#pragma omp ordered"
    elif pragma_type == "include":
        return "#include <omp.h>"
    elif pragma_type == "parallel":
        return "#pragma omp parallel"
    elif pragma_type == "master":
        return "#pragma omp master"
    elif pragma_type == "barrier":
        return "#pragma omp barrier"
    elif pragma_type == "compilation":
        return "-fopenmp"
    elif pragma_type == "sections":
        return "#pragma omp sections"
    elif pragma_type == "section":
        return "#pragma omp section"
    else:
        raise ValueError(f'Unknown OpenMP pragma "{pragma_type}"')


class CPPStandaloneCodeObject(CodeObject):
    """
    C++ standalone code object

    The ``code`` should be a `~brian2.codegen.templates.MultiTemplate`
    object with two macros defined, ``main`` (for the main loop code) and
    ``support_code`` for any support code (e.g. function definitions).
    """

    templater = Templater(
        "brian2.devices.cpp_standalone",
        ".cpp",
        env_globals={
            "c_data_type": c_data_type,
            "openmp_pragma": openmp_pragma,
            "constant_or_scalar": constant_or_scalar,
            "prefs": prefs,
            "zip": zip,
        },
    )
    generator_class = CPPCodeGenerator

    def __init__(self, *args, **kwds):
        super().__init__(*args, **kwds)
        #: Store whether this code object defines before/after blocks
        self.before_after_blocks = []

    def __call__(self, **kwds):
        return self.run()

    def compile_block(self, block):
        pass  # Compilation will be handled in device

    def run_block(self, block):
        if block == "run":
            get_device().main_queue.append((f"{block}_code_object", (self,)))
        else:
            # Check the C++ code whether there is anything to run
            cpp_code = getattr(self.code, f"{block}_cpp_file")
            if len(cpp_code) and "EMPTY_CODE_BLOCK" not in cpp_code:
                get_device().main_queue.append((f"{block}_code_object", (self,)))
                self.before_after_blocks.append(block)


codegen_targets.add(CPPStandaloneCodeObject)


# At module initialization time, we do not yet know whether the code will be
# run with OpenMP or not. We therefore use a "dynamic implementation" which
# generates the rand/randn implementation during code generation.
def generate_rand_code(rand_func, owner):
    nb_threads = prefs.devices.cpp_standalone.openmp_threads
    if nb_threads == 0:  # no OpenMP
        thread_number = "0"
    else:
        thread_number = "omp_get_thread_num()"
    if rand_func not in ["rand", "randn"]:
        raise AssertionError(rand_func)
    code = """
           inline double _%RAND_FUNC%(const int _vectorisation_idx) {
               return brian::_random_generators[%THREAD_NUMBER%].%RAND_FUNC%();
           }
           """
    code = replace(
        code,
        {
            "%THREAD_NUMBER%": thread_number,
            "%RAND_FUNC%": rand_func,
        },
    )
    return {"support_code": code}


rand_impls = DEFAULT_FUNCTIONS["rand"].implementations
rand_impls.add_dynamic_implementation(
    CPPStandaloneCodeObject,
    code=lambda owner: generate_rand_code("rand", owner),
    namespace=lambda owner: {},
    name="_rand",
)

randn_impls = DEFAULT_FUNCTIONS["randn"].implementations
randn_impls.add_dynamic_implementation(
    CPPStandaloneCodeObject,
    code=lambda owner: generate_rand_code("randn", owner),
    namespace=lambda owner: {},
    name="_randn",
)