Peano
Loading...
Searching...
No Matches
Project.py
Go to the documentation of this file.
1# This file is part of the Peano project. For conditions of distribution and
2# use, please see the copyright notice at www.peano-framework.org
3
4import gc
5import os
6import re
7import sys
8import subprocess
9import multiprocessing
10
15
16# Bound directly (not via the peano4.api.output.* attribute chain) because this
17# name is used in a default parameter value below, which is evaluated while
18# peano4.api itself is still being imported - the peano4.api attribute chain
19# is not yet fully wired up at that point.
20from .output import Overwrite
21from .Logger import Logger
22
23class Project (object):
24 """!
25
26 Represents a Peano 4 project.
27
28 namespace Sequence of strings representing the (nested) namespace. Pass in
29 [ "examples", "algebraicmg" ] for example if you wanna write a solver that
30 is embedded into the namespace examples::algebraicmg.
31
32
33 ## Global project properties
34
35 If you want to alter some global constants, you should manipulate
36 the constants attribute. It will eventually feed into the generated
37 file Constants.h. See peano4.api.output.Constants for further info.
38
39 """
40
41 def __init__(self, namespace, project_name, directory = ".", subdirectory = "", executable = ""):
42 """!
43 project_name Simple string.
44 """
45 if sys.version_info.major < 3:
46 print( "Warning: should be invoked through python3, i.e. with newer Python version" )
47
48 if subdirectory and not os.path.exists(subdirectory):
49 os.mkdir(subdirectory)
50
51 self.rootnamespace = namespace.copy()
52 self.namespace = namespace
53 self.namespaces = []
55 self.project_name = project_name
56 self.directory = directory
57 self._subdirectory = subdirectory
58 if subdirectory:
59 self.namespace += [ subdirectory ]
60 if self.namespace:
61 if not self.namespace in self.namespaces:
62 self.namespaces.append(self.namespace)
63 if not self.subdirectorysubdirectory in self.subdirectories:
65
66 #
67 # Create default output model, i.e. those parts that have to be there
68 # always
69 #
71
72 #
73 # Empty model by default
74 #
76
78
80
81 self.is_generated = False
82 self.is_built = False
84
86 self.subconstants = []
87
88 if executable:
89 self.output.makefile.set_executable_name(executable)
90
91 self.is_extended = False
92
93
94 def __str__(self):
95 return "(#steps=" + str(self.solversteps) + ",model=" + str(self.datamodel) + ")"
96
97 def set_fenv_handler(self, args):
98 self.main.d[ "FENV_ARGS" ] = args
99
100 def cleanup(self):
101 """!
102 This routine has to be called after you've generated your code.
103 """
104 self.output.clear()
105 self.datamodel.clear()
106 self.solversteps.clear()
107
109
110 self.is_generated = False
111 self.is_built = False
112
114 self.subconstants = []
115
116 def generate(self,
117 overwrite=Overwrite.Default,
118 throw_away_data_after_generation=False):
119 """!
120 Generate all code. If you add stuff to your project after a
121 build, you have to (re-)generate the code. If you compile only
122 once, you don't have to invoke this routine explicitly. It is
123 lazily called by the other project routines - the latest before
124 you run the code.
125
126 It is important that I reset the output after each generate
127 call before you change parameter settings and call generate
128 again. To do so, invoke cleanup(). If you forget this, two
129 subsequent generate calls enrich the output twice.
130
131 throw_away_data_after_generation: Bool
132 The Peano 4 memory footprint can become quite substantial
133 effectively reducing the translation capabilities (as compilers
134 tend to require a lot of memory, too). So pass in True if you
135 want the script to throw away the data structures (and run a
136 garbage collection) after all files have been generated. Please
137 note that it invalidates both this object (and maybe another
138 object that you've used to generate the present one - such as
139 ExaHyPE). It really strips everything down to the stuff you
140 absolutely need to translate and run the code.
141 """
142 Logger().new_section("Generate code")
143 self.is_generated = True
144 self.is_built = False
145
146 self.output.readme.add_package_description( self.constants.readme_entry() )
147
148 self.datamodel.construct_output(self.output)
149 self.solversteps.construct_output(self.output)
150
151 self.main.construct_output(self.output,self.project_name + "-main")
152 if self.subconstants:
153 for i in range(len(self.subconstants)):
154 self.subconstants[i].generate(overwrite, self.directory, self.subdirectories[i])
155 else:
156 self.constants.generate(overwrite, self.directory)
157
158 # Generate the README last, so it is genuinely the final artefact: its
159 # "is ready" message (see peano4.api.output.Readme) is meant to be the
160 # last thing this routine reports before Logger's own atexit-triggered
161 # close() takes over.
162 self.output.generate(overwrite, self.directory, self.subdirectories)
163
164 if throw_away_data_after_generation:
165 self.datamodel = None
166 self.solversteps = None
167 self.output = None
168 gc.collect()
169 print("threw away all data and ran garbage collection")
170
171 def build(self,
172 make=True,
173 make_clean_first=True,
174 throw_away_data_after_build=False,
175 number_of_parallel_builds=-1):
176 """!
177 Invokes the underlying make build mechanism on the project.
178
179 number_of_parallel_builds: int
180 This is mapped onto make -jnumber_of_parallel_builds, i.e., it
181 determines how many parallel make instances the code spawns.
182 Usually, a lot of the generated code is quite lengthy. Therefore
183 compile time can be high. If you pass something smaller or equal
184 to zero, it will use the core count as guideline how to compile.
185
186 throw_away_data_after_build: Bool
187 see generate()
188 """
189 if number_of_parallel_builds <= 0:
190 number_of_parallel_builds = multiprocessing.cpu_count()
191
192 if not self.is_generated:
193 self.generate()
194
195 if make:
196 if make_clean_first:
197 print("clean up project ... in {}".format(self.directory))
198 try:
199 if make_clean_first:
200 subprocess.check_call(["make", "clean"], cwd=self.directory)
201 self.is_built = False
202 print("clean complete")
203 except Exception as e:
204 print("clean failed (" + str(e) + ") - continue anyway")
205
206 if not self.is_built:
207 print("start to compile with concurrency level of " + str(number_of_parallel_builds) + " ...")
208 try:
209 process = subprocess.run(["make", "-j"+str(number_of_parallel_builds)], cwd=self.directory, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
210 # Extract warnings and errors from error message
211 error_message = process.stderr.decode().strip()
212 warnings = re.findall(r"(?i)warning:.*", error_message)
213 errors = re.findall(r"(?i)error:.*", error_message)
214 # Print warnings into the terminal
215 if warnings:
216 print("\nWarnings ({}):".format(len(warnings)))
217 print("\n".join(warnings))
218 if errors:
219 print("\nErrors ({}):".format(len(errors)))
220 error_message = "\n".join(errors)
221 # Check the return code for linker errors
222 # Raise exception with errors
223 if process.returncode != 0:
224 linker_error_message = process.stderr.decode().strip() + "\n" + error_message
225 raise Exception(linker_error_message)
226 print("compile completed successfully")
227 self.is_built = True
228 self.build_was_successful = True
229 except Exception as e:
230 self.is_built = True
231 self.build_was_successful = False
232 print(str(e))
233 print("compile was not successful")
234 sys.exit(1)
235 else:
236 print("cannot build as code generation has not been successful")
237
238 if throw_away_data_after_build:
239 self.cleanup()
240 self.datamodel = None
241 self.solversteps = None
242 self.output = None
243 gc.collect()
244 print("threw away all data and ran garbage collection")
245
246 def run(self, executable, args=[], prefix=None, pipefile=None, rebuild_if_required=True):
247 """!
248 Runs the code. args should be a list of strings or the empty list.
249 prefix is an array, too. A typical invocation looks alike
250 project.run( ["16.0", "32.0"], ["/opt/mpi/mpirun", "-n", "1"] )
251 The operation returns True if the run had been successful
252
253 pipefile: string or None
254 """
255 success = False
256 if rebuild_if_required and not self.is_built and not self.build_was_successful:
257 self.build()
258
259 if not rebuild_if_required or (self.is_built and self.build_was_successful):
260 print( "run executable " + str(executable))
261
262 invocation = []
263 if prefix!=None:
264 invocation += prefix
265 invocation += ["./" + executable]
266 invocation += args
267
268 try:
269 result = None
270 if pipefile==None:
271 result = subprocess.run( invocation, stdout=subprocess.PIPE, stderr=subprocess.PIPE )
272 else:
273 result = subprocess.run( invocation, stdout=open( pipefile, "w" ), stderr=subprocess.PIPE )
274 if result.returncode==0:
275 if pipefile==None:
276 print( result.stdout.decode("utf-8") )
277 if result.stderr!=None:
278 print( result.stderr.decode("utf-8") )
279 print( "run completed without error code, but check for warnings and numerical assertions/errors" )
280 else:
281 print( result.stderr.decode("utf-8") )
282 print( "run failed" )
283 success = True
284 except Exception as e:
285 print( "run of application was not successful: " + str(e) )
286 print( "invocation: " + str(invocation) )
287 else:
288 print( "cannot run as code compilation has not been successful" )
289 return success
290
291
293 subproject: 'Project',
294 master = False): #, subnamespace: str #def merge
295 """!
296 Adds a new Peano4 project into this Peano 4 project
297 """
298 """
299 assert(
300 lhs.namespace == rhs.namespace
301 ), "namespaces of the projects being merged don't match"
302
303 assert(
304 lhs.directory == rhs.directory
305 ), "directories of the projects being merged don't match"
306
307 assert(
308 lhs.output.makefile.d["CXX"] == lhs.output.makefile.d["CXX"]
309 ), "CXX compilers of the projects being merged don't match"
310
311 assert(
312 lhs.output.makefile.d["FC"] == lhs.output.makefile.d["FC"]
313 ), "FC compilers of the projects being merged don't match"
314
315 assert(
316 lhs.output.makefile.d["DIM"] == lhs.output.makefile.d["DIM"]
317 ), "dimensions of the projects being merged don't match"
318
319 assert(
320 lhs.output.makefile.d["MODE"] == lhs.output.makefile.d["MODE"]
321 ), "compile modes of the projects being merged don't match"
322 """
323 # assert(
324 # lhs._domain_offset == rhs._domain_offset and
325 # lhs._domain_size == rhs._domain_size and
326 # lhs._dimensions == rhs._dimensions and
327 # lhs._plotter_precision == rhs._plotter_precision
328 # ), "global simulation parameters of the projects being merged don't match"
329
330 # an internal utility function to merge two dictionaries
331 # def _merge(lhs_dict: dict, rhs_dict: dict) -> dict:
332 # dict_merged = lhs_dict.copy()
333 # for key,value in rhs_dict.items():
334 # if key in dict_merged:
335 # dict_merged[key] += value
336 # else:
337 # dict_merged[key] = value
338 # return dict_merged
339
340 assert(subproject._subdirectory != ""), "subdirectory of a subproject mustn't be empty"
341
342 self.namespace = subproject.namespace
343 self.namespaces.append(subproject.namespace)
344 self.datamodel.namespace = subproject.datamodel.namespace
345 self.datamodel._subdirectory = subproject.datamodel._subdirectory
346 if not subproject.subdirectory in self.subdirectories:
347 self.subdirectories.append(subproject.subdirectory)
348 if not self.datamodel.namespace in self.datamodel.namespaces:
349 self.datamodel.namespaces.append(self.datamodel.namespace)
350 if not self.datamodel.subdirectory in self.datamodel.subdirectories:
351 self.datamodel.subdirectories.append(self.datamodel.subdirectory)
352
353
354 #
355 # merge all DaStGen2 and ParticleSet attributes of datamodel
356 #
357 for data in subproject.datamodel.cell_data:
358 self.datamodel.add_cell(data)
359 for data in subproject.datamodel.face_data:
360 self.datamodel.add_face(data)
361 for particle in subproject.datamodel.vertex_data:
362 self.datamodel.add_vertex(particle)
363 for particle in subproject.datamodel.global_data:
364 self.datamodel.add_global(particle)
365
366 #
367 # merge all steps of solversteps
368 #
369
370 # make the steps currently contained in the Peano4 project aware of the incoming subproject's data
371 for step in self.solversteps._steps:
372 added_step = subproject.solversteps._steps[0]
373 step.cell_data.extend(added_step.cell_data)
374 step.face_data.extend(added_step.face_data)
375 step.vertex_data.extend(added_step.vertex_data)
376
377 # make the incoming subproject's steps aware of the Peano4 project data
378 if len(self.solversteps._steps):
379 for added_step in subproject.solversteps._steps:
380 step = self.solversteps._steps[0]
381 added_step.cell_data.clear()
382 added_step.cell_data.extend(step.cell_data)
383 added_step.face_data.clear()
384 added_step.face_data.extend(step.face_data)
385 added_step.vertex_data.clear()
386 added_step.vertex_data.extend(step.vertex_data)
387
388 # finally append the incoming subproject's steps to the Peano4 project's steps
389 for step in subproject.solversteps._steps:
390 self.solversteps._steps.append(step)
391
392 #
393 # merge all keys and strings of output.makefile
394 #
395
396 # ... for compilers
397 if (self.output.makefile.d["CXX"]):
398 assert (
399 self.output.makefile.d["CXX"] == subproject.output.makefile.d["CXX"]
400 ), "the CXX compiler of the subproject being added doesn't match the one of the main project"
401 else:
402 self.output.makefile.d["CXX"] = subproject.output.makefile.d["CXX"]
403
404 if (self.output.makefile.d["FC"]):
405 assert (
406 self.output.makefile.d["FC"] == subproject.output.makefile.d["FC"]
407 ), "the FC compiler of the subproject being added doesn't match the one of the main project"
408 else:
409 self.output.makefile.d["FC"] = subproject.output.makefile.d["FC"]
410
411 for flag in subproject.output.makefile.d["CXXFLAGS"].split():
412 self.output.makefile.add_CXX_flag(flag)
413
414 for flag in subproject.output.makefile.d["FCFLAGS"].split():
415 self.output.makefile.add_Fortran_flag(flag)
416
417 for flag in subproject.output.makefile.d["LDFLAGS"].split():
418 self.output.makefile.add_linker_flag(flag)
419
420 self.output.makefile.d["LIBS"] += subproject.output.makefile.d["LIBS"]
421 self.output.makefile.d["GENERATED_INCLUDE_DIRS"] |= subproject.output.makefile.d["GENERATED_INCLUDE_DIRS"]
422 self.output.makefile.d["MODE"] = subproject.output.makefile.d["MODE"]
423
424 for module in subproject.output.makefile.d["FORTRAN_MODULES"]:
425 self.output.makefile.add_Fortran_module(module)
426
427 assert (
428 self.output.makefile.d["EXECUTABLENAME"]
429 ), "the name of the main project is empty"
430 if not self.output.makefile.d["DIM"]:
431 self.output.makefile.set_dimension(subproject.output.makefile.d["DIM"])
432 else:
433 assert (
434 self.output.makefile.d["DIM"] == subproject.output.makefile.d["DIM"]
435 ), "the dimensions of the added subproject doesn't match the current one of the main project"
436
437 # ... for filepaths
438 for filename in subproject.output.makefile.hfiles:
439 self.output.makefile.add_h_file(filename, False)
440 for filename in subproject.output.makefile.cppfiles:
441 self.output.makefile.add_cpp_file(filename, False)
442 for filename in subproject.output.makefile.fortranfiles:
443 self.output.makefile.add_Fortran_file(filename, False)
444
445 for filename in subproject.output.makefile.generated_hfiles:
446 self.output.makefile.add_h_file(filename, True)
447 for filename in subproject.output.makefile.generated_cppfiles:
448 self.output.makefile.add_cpp_file(filename, True)
449 for filename in subproject.output.makefile.generated_fortranfiles:
450 self.output.makefile.add_Fortran_file(filename, True)
451
452 #
453 # merge all strings and artefacts of readme
454 #
455 for i in subproject.output.readme._entries:
456 self.output.readme.add_entry(i)
457
458 for i in subproject.output.readme._package_descriptions:
459 self.output.readme.add_package_description(i)
460
461 for i in subproject.output.readme._entries:
462 self.output.readme.add_entry(i)
463
464 for i in subproject.output.artefacts:
465 self.output.add(i)
466
467 #
468 # the last added subproject becomes the master project (which will provide the template for the main file)
469 #
470 if not self.is_extended:
471 self.main = subproject.main
472 self.rootnamespace = subproject.rootnamespace
473 self._subdirectory = subproject._subdirectory
474 self.is_extended = True
475 self.main.merge(subproject.main)
476
477 # self.main.project = self
478 # self.main.overwrite = subproject.main.overwrite
479 # self.main.d.update(subproject.main.d)
480
481 #
482 # merge all constants
483 #
484 self.subconstants.append(subproject.constants)
485
486
487 @property
488 def subdirectory(self):
489 subdirectory = self._subdirectory
490 if subdirectory:
491 subdirectory += "/"
492 return subdirectory
#define assert(...)
Definition LinuxAMD.h:28
Singleton logger for the Peano 4 Python API.
Definition Logger.py:37
Represents a Peano 4 project.
Definition Project.py:23
run(self, executable, args=[], prefix=None, pipefile=None, rebuild_if_required=True)
Runs the code.
Definition Project.py:246
build(self, make=True, make_clean_first=True, throw_away_data_after_build=False, number_of_parallel_builds=-1)
Invokes the underlying make build mechanism on the project.
Definition Project.py:175
set_fenv_handler(self, args)
Definition Project.py:97
generate(self, overwrite=Overwrite.Default, throw_away_data_after_generation=False)
Generate all code.
Definition Project.py:118
__init__(self, namespace, project_name, directory=".", subdirectory="", executable="")
project_name Simple string.
Definition Project.py:41
cleanup(self)
This routine has to be called after you've generated your code.
Definition Project.py:100
add_subproject(self, 'Project' subproject, master=False)
Adds a new Peano4 project into this Peano 4 project.
Definition Project.py:294
Representative of generated Constants.h holding all user-defined constants.
Definition Constants.py:10
Represents the total output generated from the Peano4 data model plus all the operations on it.
Definition Output.py:7
The default sequence sketches what Peano does if there's no main.
All operations within Peano 4 model.
Definition Steps.py:6
The Python-side model of "what happens during one mesh sweep".
Definition __init__.py:1