Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
No results found
Show changes
from pairs.ir.block import pairs_inline
from pairs.ir.functions import Call_Void
from pairs.ir.timers import Timers
from pairs.ir.types import Types
from pairs.sim.lowerable import FinalLowerable
class RegisterTimers(FinalLowerable):
......@@ -12,9 +13,14 @@ class RegisterTimers(FinalLowerable):
for t in range(Timers.Offset):
Call_Void(self.sim, "pairs::register_timer", [t, Timers.name(t)])
for m in self.sim.module_list:
if m.name != 'main' and m.name != 'initialize':
Call_Void(self.sim, "pairs::register_timer", [m.module_id + Timers.Offset, m.name])
# Interface modules
for m in self.sim.interface_modules():
if m.name != 'initialize' and m.name != 'end' and m.return_type==Types.Void:
Call_Void(self.sim, "pairs::register_timer", [m.module_id + Timers.Offset, "INTERFACE_MODULES::" + m.name])
# Internal modules
for m in self.sim.modules():
Call_Void(self.sim, "pairs::register_timer", [m.module_id + Timers.Offset, "INTERNAL_MODULES::" + m.name])
class RegisterMarkers(FinalLowerable):
......@@ -24,6 +30,7 @@ class RegisterMarkers(FinalLowerable):
@pairs_inline
def lower(self):
if self.sim._enable_profiler:
for m in self.sim.module_list:
if m.name != 'main' and m.name != 'initialize' and m.must_profile():
# Only internal modules are profiled
for m in self.sim.modules():
if m.must_profile():
Call_Void(self.sim, "LIKWID_MARKER_REGISTER", [m.name])
This diff is collapsed.
class LoadBalancingAlgorithms:
Morton = 0
Hilbert = 1
Diffusive = 3
Metis = 2
def c_keyword(algorithm):
return "Hilbert" if algorithm == LoadBalancingAlgorithms.Hilbert else \
"Morton" if algorithm == LoadBalancingAlgorithms.Morton else \
"Diffusive" if algorithm == LoadBalancingAlgorithms.Diffusive else \
"Metis" if algorithm == LoadBalancingAlgorithms.Metis else \
"Invalid"
\ No newline at end of file
......@@ -2,3 +2,4 @@ class Shapes:
Sphere = 0
Halfspace = 1
PointMass = 2
Box = 3
\ No newline at end of file
from pairs.ir.arrays import Arrays
from pairs.ir.block import Block
from pairs.ir.branches import Filter
from pairs.ir.features import Features, FeatureProperties
from pairs.ir.kernel import Kernel
from pairs.ir.layouts import Layouts
from pairs.ir.module import Module, ModuleCall
from pairs.ir.module import Module
from pairs.ir.properties import Properties, ContactProperties
from pairs.ir.symbols import Symbol
from pairs.ir.types import Types
from pairs.ir.variables import Variables
#from pairs.graph.graphviz import ASTGraph
from pairs.mapping.funcs import compute, setup
from pairs.sim.arrays import DeclareArrays
from pairs.sim.cell_lists import CellLists, BuildCellLists, BuildCellListsStencil, PartitionCellLists, BuildCellNeighborLists
from pairs.sim.comm import Comm, Synchronize, Borders, Exchange, ReverseComm
from pairs.sim.contact_history import ContactHistory, BuildContactHistory, ClearUnusedContactHistory, ResetContactHistoryUsageStatus
from pairs.sim.copper_fcc_lattice import CopperFCCLattice
from pairs.sim.dem_sc_grid import DEMSCGrid
from pairs.sim.domain import InitializeDomain, UpdateDomain
from pairs.mapping.funcs import compute
from pairs.sim.cell_lists import CellLists, BuildCellLists, PartitionCellLists, BuildCellNeighborLists
from pairs.sim.comm import Comm
from pairs.sim.contact_history import ContactHistory
from pairs.sim.domain_partitioners import DomainPartitioners
from pairs.sim.domain_partitioning import BlockForest, DimensionRanges
from pairs.sim.load_balancing_algorithms import LoadBalancingAlgorithms
from pairs.sim.features import AllocateFeatureProperties
from pairs.sim.grid import Grid2D, Grid3D, MutableGrid
from pairs.sim.instrumentation import RegisterMarkers, RegisterTimers
from pairs.sim.lattice import ParticleLattice
from pairs.sim.grid import Grid3D
from pairs.sim.neighbor_lists import NeighborLists, BuildNeighborLists
from pairs.sim.properties import AllocateProperties, AllocateContactProperties, ResetVolatileProperties
from pairs.sim.read_from_file import ReadParticleData
from pairs.sim.thermo import ComputeThermo
from pairs.sim.timestep import Timestep
from pairs.sim.variables import DeclareVariables
from pairs.sim.vtk import VTKWrite
from pairs.transformations import Transformations
from pairs.code_gen.interface import InterfaceModules
......@@ -44,17 +29,14 @@ class Simulation:
code_gen,
shapes,
dims=3,
timesteps=100,
double_prec=False,
use_contact_history=False,
particle_capacity=800000,
neighbor_capacity=100,
generate_whole_program=False):
neighbor_capacity=100):
# Code generator for the simulation
self.code_gen = code_gen
self.code_gen.assign_simulation(self)
self._generate_whole_program = generate_whole_program
# Data structures to be generated
self.position_prop = None
......@@ -66,7 +48,6 @@ class Simulation:
self.contact_properties = ContactProperties(self)
# General capacities, sizes and particle properties
self.sim_timestep = self.add_var('sim_timestep', Types.Int32, runtime=True)
self.particle_capacity = \
self.add_var('particle_capacity', Types.Int32, particle_capacity, runtime=True)
self.neighbor_capacity = self.add_var('neighbor_capacity', Types.Int32, neighbor_capacity)
......@@ -93,23 +74,13 @@ class Simulation:
self._capture_statements = True
self._block = Block(self, [])
# Different segments of particle code/functions
self.create_domain = Block(self, [])
self.create_domain_at_initialization = False
self.setup_particles = Block(self, [])
# Interface modules
self.interface_module_list = []
# Internal modules and kernels
self.module_list = []
self.kernel_list = []
# Individual user-defined and interface modules are created only when generate_whole_program is False
self.udf_module_list = []
self.interface_module_list = []
# User-defined functions to be called by other subroutines (used only when generate_whole_program is True)
self.setup_functions = []
self.pre_step_functions = []
self.functions = []
# Structures to generated resize code for capacities
self._check_properties_resize = False
self._resizes_to_check = {}
......@@ -131,7 +102,6 @@ class Simulation:
self._module_name = None # Current module name
self._double_prec = double_prec # Use double-precision FP arithmetic
self.dims = dims # Number of dimensions
self.ntimesteps = timesteps # Number of time-steps
self.reneighbor_frequency = 1 # Re-neighbor frequency
self.rebalance_frequency = 0 # Re-balance frequency for dynamic load balancing
self._target = None # Hardware target info
......@@ -155,14 +125,6 @@ class Simulation:
else:
raise Exception("Invalid domain partitioner.")
def set_workload_balancer(self, algorithm=LoadBalancingAlgorithms.Morton,
regrid_min=100, regrid_max=1000, rebalance_frequency=0):
assert self._partitioner == DomainPartitioners.BlockForest, "Load balancing is only supported by BlockForest."
self.rebalance_frequency = rebalance_frequency
self._dom_part.load_balancer = algorithm
self._dom_part.regrid_min = regrid_min
self._dom_part.regrid_max = regrid_max
def partitioner(self):
return self._partitioner
......@@ -181,45 +143,25 @@ class Simulation:
def max_shapes(self):
return len(self._shapes)
def add_udf_module(self, module):
assert isinstance(module, Module), "add_udf_module(): Given parameter is not of type Module!"
assert module.user_defined and not module.interface
if module.name not in [m.name for m in self.udf_module_list]:
self.udf_module_list.append(module)
def add_interface_module(self, module):
assert isinstance(module, Module), "add_interface_module(): Given parameter is not of type Module!"
assert module.interface and not module.user_defined
assert module.interface
if module.name not in [m.name for m in self.interface_module_list]:
self.interface_module_list.append(module)
def add_module(self, module):
assert isinstance(module, Module), "add_module(): Given parameter is not of type Module!"
assert not module.interface and not module.user_defined
assert not module.interface
if module.name not in [m.name for m in self.module_list]:
self.module_list.append(module)
def interface_modules(self):
"""List interface modules"""
return self.interface_module_list
def udf_modules(self):
return self.udf_module_list
def modules(self):
"""List simulation modules, with main always in the last position"""
sorted_mods = []
main_mod = None
for m in self.module_list:
if m.name != 'main':
sorted_mods.append(m)
else:
main_mod = m
if main_mod is not None:
sorted_mods += [main_mod]
return sorted_mods
"""List internal modules"""
return self.module_list
def add_kernel(self, kernel):
assert isinstance(kernel, Kernel), "add_kernel(): Given parameter is not of type Kernel!"
......@@ -301,45 +243,22 @@ class Simulation:
assert self.var(var_name) is None, f"Variable already defined: {var_name}"
return self.vars.add(var_name, var_type, init_value, runtime)
def add_temp_var(self, init_value):
return self.vars.add_temp(init_value)
def add_temp_var(self, init_value, type=None):
return self.vars.add_temp(init_value, type)
def add_symbol(self, sym_type):
return Symbol(self, sym_type)
def add_symbol(self, sym_type, name=None):
return Symbol(self, sym_type, name)
def var(self, var_name):
return self.vars.find(var_name)
def set_domain(self, grid):
"""Set domain bounds.
If the domain is set through this function, the 'set_domain' module won't be generated in the modular version.
Use this function only if you do not need to set domain at runtime.
This function is required only for whole-program generation."""
self.create_domain_at_initialization = True
"""Set domain bounds if they are known at P4IRS compile time"""
self.grid = Grid3D(self, grid[0], grid[1], grid[2], grid[3], grid[4], grid[5])
self.create_domain.add_statement(InitializeDomain(self))
def reneighbor_every(self, frequency):
self.reneighbor_frequency = frequency
def create_particle_lattice(self, grid, spacing, props={}):
self.setup_particles.add_statement(ParticleLattice(self, grid, spacing, props, self.position()))
def read_particle_data(self, filename, prop_names, shape_id):
"""Generate statement to read particle data from file"""
props = [self.property(prop_name) for prop_name in prop_names]
self.setup_particles.add_statement(ReadParticleData(self, filename, props, shape_id))
def copper_fcc_lattice(self, nx, ny, nz, rho, temperature, ntypes):
"""Specific initialization for MD Copper FCC lattice case"""
self.setup_particles.add_statement(CopperFCCLattice(self, nx, ny, nz, rho, temperature, ntypes))
def dem_sc_grid(self, xmax, ymax, zmax, spacing, diameter, min_diameter, max_diameter, initial_velocity, particle_density, ntypes):
"""Specific initialization for DEM grid"""
self.setup_particles.add_statement(
DEMSCGrid(self, xmax, ymax, zmax, spacing, diameter, min_diameter, max_diameter,
initial_velocity, particle_density, ntypes))
def build_cell_lists(self, spacing=None, store_neighbors_per_cell=False):
"""Add routines to build the linked-cells acceleration structure.
Leave spacing as None so it can be set at runtime."""
......@@ -358,11 +277,8 @@ class Simulation:
self.neighbor_lists = NeighborLists(self, self.cell_lists)
return self.neighbor_lists
def compute(self, func, cutoff_radius=None, symbols={}, parameters={}, pre_step=False, skip_first=False):
return compute(self, func, cutoff_radius, symbols, parameters, pre_step, skip_first)
def setup(self, func, symbols={}):
return setup(self, func, symbols)
def compute(self, func, cutoff_radius=None, symbols={}, parameters={}, compute_globals=False, run_on_device=True, profile=False):
return compute(self, func, cutoff_radius, symbols, parameters, compute_globals, run_on_device, profile)
def init_block(self):
"""Initialize new block in this simulation instance"""
......@@ -386,60 +302,15 @@ class Simulation:
else:
raise Exception("Two sizes assigned to same capacity!")
def build_setup_module_with_statements(self):
"""Build a Module in the setup part of the program using the last initialized block"""
self.setup_functions.append(
Module(self,
name=self._module_name,
block=Block(self, self._block),
resizes_to_check=self._resizes_to_check,
check_properties_resize=self._check_properties_resize,
run_on_device=True))
def build_pre_step_module_with_statements(self, run_on_device=True, skip_first=False, profile=False):
"""Build a Module in the pre-step part of the program using the last initialized block"""
module = Module(self, name=self._module_name,
block=Block(self, self._block),
resizes_to_check=self._resizes_to_check,
check_properties_resize=self._check_properties_resize,
run_on_device=run_on_device)
if profile:
module.profile()
if skip_first:
self.pre_step_functions.append((module, {'skip_first': True}))
else:
self.pre_step_functions.append(module)
def build_module_with_statements(self, run_on_device=True, skip_first=False, profile=False):
"""Build a Module in the compute part of the program using the last initialized block"""
module = Module(self, name=self._module_name,
block=Block(self, self._block),
resizes_to_check=self._resizes_to_check,
check_properties_resize=self._check_properties_resize,
run_on_device=run_on_device)
if profile:
module.profile()
if skip_first:
self.functions.append((module, {'skip_first': True}))
else:
self.functions.append(module)
def build_user_defined_function(self, run_on_device=True):
def build_interface_module_with_statements(self, run_on_device=False):
"""Build a user-defined Module that will be callable seperately as part of the interface"""
Module(self, name=self._module_name,
block=Block(self, self._block),
resizes_to_check=self._resizes_to_check,
check_properties_resize=self._check_properties_resize,
run_on_device=run_on_device,
user_defined=True)
interface=True)
def capture_statements(self, capture=True):
"""When toggled, all constructed statements are captured and automatically added to the last initialized block"""
self._capture_statements = capture
......@@ -524,130 +395,11 @@ class Simulation:
self._comm = Comm(self, self._dom_part)
self.create_update_cells_block()
if self._generate_whole_program:
self.generate_program()
else:
self.generate_library()
def generate_library(self):
InterfaceModules(self).create_all()
# User defined functions are wrapped inside seperate interface modules here.
# The udf's have the same name as their interface module but they get implemented in the pairs::internal scope.
for m in self.udf_module_list:
module = Module(self, name=m.name, block=Block(self, m), interface=True)
module._id = m._id
Transformations(self.interface_modules(), self._target).apply_all()
# Generate library
self.code_gen.generate_library()
# Generate getters for the runtime functions
self.code_gen.generate_interfaces()
def generate_program(self):
assert self.grid, "No domain is created. Set domain bounds with 'set_domain'."
reverse_comm_module = ReverseComm(self._comm, reduce=True)
# Params that determine when a method must be called only when reneighboring
every_reneighbor_params = {'every': self.reneighbor_frequency}
timestep_procedures = []
# First steps executed during each time-step in the simulation
timestep_procedures += self.pre_step_functions
# Rebalancing routines
if self.rebalance_frequency:
update_domain_procedures = Block.from_list(self, [
Exchange(self._comm),
UpdateDomain(self),
Borders(self._comm),
ResetVolatileProperties(self),
BuildCellListsStencil(self, self.cell_lists),
self.update_cells_procedures
])
timestep_procedures.append((update_domain_procedures, {'every': self.rebalance_frequency}))
# Communication routines
timestep_procedures += [(Exchange(self._comm), every_reneighbor_params),
(Borders(self._comm), Synchronize(self._comm), every_reneighbor_params)]
# Update acceleration data structures
timestep_procedures += [(self.update_cells_procedures, every_reneighbor_params)]
# Add routines for contact history management
if self._use_contact_history:
if self.neighbor_lists is not None:
timestep_procedures.append(
(BuildContactHistory(self, self._contact_history, self.cell_lists),
every_reneighbor_params))
timestep_procedures.append(ResetContactHistoryUsageStatus(self, self._contact_history))
# Reset volatile properties
timestep_procedures += [ResetVolatileProperties(self)]
# Add computational kernels
timestep_procedures += self.functions
# For whole-program-generation, add reverse_comm wherever needed in the timestep loop (eg: after computational kernels) like this:
timestep_procedures += [reverse_comm_module]
# Clear unused contact history
if self._use_contact_history:
timestep_procedures.append(ClearUnusedContactHistory(self, self._contact_history))
# Add routine to calculate thermal data
if self._compute_thermo != 0:
timestep_procedures.append(
(ComputeThermo(self), {'every': self._compute_thermo}))
# Data structures and timer/markers initialization
inits = Block.from_list(self, [
DeclareVariables(self),
DeclareArrays(self),
AllocateProperties(self),
AllocateContactProperties(self),
AllocateFeatureProperties(self),
RegisterTimers(self),
RegisterMarkers(self)
])
# Construct the time-step loop
timestep = Timestep(self, self.ntimesteps, timestep_procedures)
self.enter(timestep.block)
# Add routine to write VTK data when set
if self.vtk_file is not None:
timestep.add(VTKWrite(self, self.vtk_file, timestep.timestep(), self.vtk_frequency))
self.leave()
# Combine everything into a whole program
# Initialization and setup functions, together with time-step loop
# UpdateDomain is added after setup_particles because particles must be already present in the simulation
body = Block.from_list(self, [
self.create_domain,
self.setup_particles,
UpdateDomain(self),
self.setup_functions,
BuildCellListsStencil(self, self.cell_lists),
timestep.as_block()
])
program = Module(self, name='main', block=Block.merge_blocks(inits, body))
# Apply transformations
transformations = Transformations(program, self._target)
transformations.apply_all()
# Generate whole program
self.code_gen.generate_program(program)
# Generate getters for the runtime functions
self.code_gen.generate_interfaces()
self.code_gen.generate_interfaces()
\ No newline at end of file
from pairs.ir.assign import Assign
from pairs.ir.block import pairs_inline
from pairs.ir.functions import Call_Int, Call_Void
from pairs.ir.particle_attributes import ParticleAttributeList
from pairs.ir.types import Types
from pairs.sim.grid import Grid3D
from pairs.sim.lowerable import Lowerable
class ComputeThermo(Lowerable):
def __init__(self, sim):
super().__init__(sim)
@pairs_inline
def lower(self):
xprd = self.sim.grid.length(0)
yprd = self.sim.grid.length(1)
zprd = self.sim.grid.length(2)
Call_Void(self.sim, "pairs::compute_thermo", [self.sim.nlocal, xprd, yprd, zprd, 1])
from pairs.ir.scalars import ScalarOp
from pairs.ir.block import Block
from pairs.ir.branches import Branch, Filter
from pairs.ir.functions import Call_Void
from pairs.ir.loops import For
from pairs.ir.timers import Timers
class Timestep:
def __init__(self, sim, nsteps, item_list=None):
self.sim = sim
self.block = Block(sim, [])
self.timestep_loop = For(sim, 0, nsteps + 1, self.block) if self.sim._generate_whole_program else None
if item_list is not None:
for item in item_list:
if isinstance(item, tuple):
stmt_else = None
if len(item) == 2:
stmt, params = item
if len(item) == 3:
stmt, stmt_else, params = item
exec_every = 0 if 'every' not in params else params['every']
skip_first = False if 'skip_first' not in params else params['skip_first']
self.add(stmt, exec_every, stmt_else, skip_first)
else:
self.add(item)
def timestep(self):
return self.timestep_loop.iter() if self.sim._generate_whole_program else self.sim.sim_timestep
def add(self, item, exec_every=0, item_else=None, skip_first=False):
assert exec_every >= 0, "exec_every parameter must be higher or equal than zero!"
stmts = item if not isinstance(item, Block) else item.statements()
stmts_else = None
ts = self.timestep()
self.sim.enter(self.block)
if item_else is not None:
stmts_else = item_else if not isinstance(item_else, Block) else item_else.statements()
if exec_every > 0:
cond = ScalarOp.or_op(ScalarOp.cmp((ts + 1) % exec_every, 0), ScalarOp.cmp(ts, 0))
one_way = True if stmts_else is None else False
self.block.add_statement(
Branch(self.sim, ScalarOp.inline(cond), one_way,
Block(self.sim, stmts),
Block(self.sim, stmts_else) if not one_way else None))
elif skip_first:
self.block.add_statement(Filter(self.sim, ScalarOp.inline(ts > 0), Block(self.sim, stmts)))
else:
self.block.add_statement(stmts)
self.sim.leave()
def as_block(self):
_capture = self.sim._capture_statements
self.sim.capture_statements(False)
block = Block(self.sim, [Call_Void(self.sim, "pairs::start_timer", [Timers.All]),
self.timestep_loop if self.sim._generate_whole_program else self.block,
Call_Void(self.sim, "pairs::stop_timer", [Timers.All])])
self.sim.capture_statements(_capture)
return block
......@@ -73,6 +73,7 @@ class Transformations:
def add_device_copies(self):
if self._target.is_gpu():
self.analysis().fetch_device_copies()
self.apply(AddDeviceCopies(), [self._module_resizes])
self.analysis().fetch_modules_references()
......@@ -104,14 +105,11 @@ class Transformations:
self.licm()
self.modularize()
self.add_device_kernels()
self.add_host_references_to_modules()
self.add_device_copies()
self.lower(True)
self.add_expression_declarations()
self.add_host_references_to_modules()
# self.add_host_references_to_modules()
self.add_device_references_to_modules()
# TODO: Place stop timers before the function returns
# or simply don't instrument modules that have a non-void return type
# to avoid having to deal with returns within conditional blocks
# self.add_instrumentation()
self.add_instrumentation()
......@@ -36,15 +36,19 @@ class AddDeviceCopies(Mutator):
if isinstance(s, ModuleCall):
copy_context = Contexts.Device if s.module.run_on_device else Contexts.Host
clear_context = Contexts.Host if s.module.run_on_device else Contexts.Device
new_stmts += [
Call_Void(ast_node.sim, "pairs::start_timer", [Timers.DeviceTransfers])
]
for array, action in s.module.arrays().items():
new_stmts += [CopyArray(s.sim, array, copy_context, action)]
# TODO: Add device copies only if they are not mannualy taken care of inside the module
# if array not in s.module.device_copies():
new_stmts += [CopyArray(s.sim, array, copy_context, action)]
# TODO: Add copyToHost for host references in device modules
# if array in s.module.host_references():
# new_stmts += [CopyArray(s.sim, array, Contexts.Host, action)]
for prop, action in s.module.properties().items():
new_stmts += [CopyProperty(s.sim, prop, copy_context, action)]
# if prop in s.module.host_references():
# new_stmts += [CopyProperty(s.sim, prop, Contexts.Host, action)]
for fp, action in s.module.feature_properties().items():
new_stmts += [CopyFeatureProperty(s.sim, fp, copy_context, action)]
......@@ -59,28 +63,20 @@ class AddDeviceCopies(Mutator):
for var, action in s.module.variables().items():
if action != Actions.ReadOnly and var.device_flag:
new_stmts += [CopyVar(s.sim, var, Contexts.Device, action)]
# if var not in s.module.device_copies() and var in s.module.host_references():
# new_stmts += [CopyVar(s.sim, var, Contexts.Host, action)]
new_stmts += [
Call_Void(ast_node.sim, "pairs::stop_timer", [Timers.DeviceTransfers])
]
new_stmts.append(s)
if isinstance(s, ModuleCall):
if s.module.run_on_device:
new_stmts += [
Call_Void(ast_node.sim, "pairs::start_timer", [Timers.DeviceTransfers])
]
for var, action in s.module.variables().items():
if action != Actions.ReadOnly and var.device_flag:
new_stmts += [CopyVar(s.sim, var, Contexts.Host, action)]
if self.module_resizes[s.module]:
new_stmts += [CopyArray(s.sim, s.sim.resizes, Contexts.Host, Actions.Ignore)]
new_stmts += [
Call_Void(ast_node.sim, "pairs::stop_timer", [Timers.DeviceTransfers])
]
ast_node.stmts = new_stmts
return ast_node
......
from pairs.ir.block import Block
from pairs.ir.functions import Call_Void
from pairs.ir.module import ModuleCall
from pairs.ir.mutator import Mutator
from pairs.ir.timers import Timers
from pairs.ir.types import Types
class AddModulesInstrumentation(Mutator):
......@@ -12,7 +12,8 @@ class AddModulesInstrumentation(Mutator):
def mutate_ModuleCall(self, ast_node):
ast_node._module = self.mutate(ast_node._module)
module = ast_node._module
if module.name == 'main' or module.name == 'initialize':
if module.name == 'initialize' or module.name == 'end' or module.return_type!=Types.Void:
return ast_node
if module.must_profile():
......