diff --git a/arrays.py b/arrays.py new file mode 100644 index 0000000000000000000000000000000000000000..8d816c99b7fd24bf6afc742f810d9fed5e3f758e --- /dev/null +++ b/arrays.py @@ -0,0 +1,28 @@ +class Array: + def __init__(self, sim, arr_name, arr_size, arr_type): + self.sim = sim + self.arr_name = arr_name + self.arr_size = arr_size + self.arr_type = arr_type + + def __str__(self): + return f"Array<name: {self.arr_name}, size: {self.arr_size}, type: {self.arr_type}>" + + def name(self): + return self.arr_name + + def size(self): + return self.arr_size + + def type(self): + return self.arr_type + + def __getitem__(self, expr_ast): + from expr import ExprAST + return ExprAST(self.sim, self, expr_ast, '[]', True) + + def generate(self, mem=False): + return self.arr_name + + def transform(self, fn): + return fn(self) diff --git a/assign.py b/assign.py index 8c4dbe359b771f99520ca2f226167296b02ea544..9ecf404f8f3e1310727bea5c236bc1c4df1296a5 100644 --- a/assign.py +++ b/assign.py @@ -19,7 +19,7 @@ class AssignAST: self.assignments = [(dest, src)] def __str__(self): - return f"Assign<a: {dest}, b: {src}>" + return f"Assign<{self.assignments}>" def generate(self): if self.generated is False: diff --git a/block.py b/block.py index a76f92e01e1d310c693a5aa7315f684e931bf754..b76c77ebacef7d2b03d618613e7ab553d0330f18 100644 --- a/block.py +++ b/block.py @@ -2,7 +2,19 @@ from printer import printer class BlockAST: def __init__(self, stmts): - self.stmts = stmts + if isinstance(stmts, BlockAST): + self.stmts = stmts.statements() + else: + self.stmts = stmts + + def add_statement(self, stmt): + if isinstance(stmt, list): + self.stmts = self.stmts + stmt + else: + self.stmts.append(stmt) + + def statements(self): + return self.stmts def generate(self): printer.add_ind(4) @@ -15,3 +27,8 @@ class BlockAST: self.stmts[i] = self.stmts[i].transform(fn) return fn(self) + + def merge_blocks(block1, block2): + assert isinstance(block1, BlockAST), "First block type is not BlockAST!" + assert isinstance(block2, BlockAST), "Second block type is not BlockAST!" + return BlockAST(block1.statements() + block2.statements()) diff --git a/branches.py b/branches.py index 6f5a0777ebc31e99889e523ac0081c38b038056a..f7e54f1d54fd4d7c00c29ab64894d3aa8cc9fcdd 100644 --- a/branches.py +++ b/branches.py @@ -1,11 +1,22 @@ +from block import BlockAST +from lit import is_literal, LitAST from printer import printer class BranchAST: def __init__(self, cond, block_if, block_else): - self.cond = cond + self.cond = LitAST(cond) if is_literal(cond) else cond self.block_if = block_if self.block_else = block_else + def if_stmt(cond, body): + return BranchAST(cond, body if isinstance(body, BlockAST) else BlockAST(body), None) + + def if_else_stmt(cond, body_if, body_else): + return BranchAST(cond, + body_if if isinstance(body_if, BlockAST) else BlockAST(body_if), + body_else if isinstance(body_else, BlockAST) else BlockAST(body_else) + ) + def generate(self): cvname = self.cond.generate() printer.print(f"if({cvname}) {{") diff --git a/cell_lists.py b/cell_lists.py new file mode 100644 index 0000000000000000000000000000000000000000..195b96ddc02cfb619ceacbe9fb108491d8442a1d --- /dev/null +++ b/cell_lists.py @@ -0,0 +1,35 @@ +from assign import AssignAST +from block import BlockAST +from branches import BranchAST +from data_types import Type_Int +from loops import ForAST, ParticleForAST + +class CellLists: + def __init__(self, sim, spacing): + self.sim = sim + self.spacing = spacing + self.ncells = self.sim.add_array('ncells', self.sim.dimensions, Type_Int) + self.ncells_total = self.sim.add_var('ncells_total', Type_Int) + self.cell_capacity = self.sim.add_var('cell_capacity', Type_Int) + self.cell_particles = self.sim.add_array('cell_particles', self.ncells_total * self.cell_capacity, Type_Int) + self.cell_sizes = self.sim.add_array('cell_sizes', self.ncells_total, Type_Int) + + def build(self): + positions = self.sim.property('position') + reset_loop = ForAST(self.sim, 0, self.ncells_total) + reset_loop.set_body(BlockAST([self.cell_sizes[reset_loop.iter()].set(0)])) + + fill_loop = ParticleForAST(self.sim) + cell_index = [(positions[fill_loop.iter()][d] - self.sim.grid_config[d][0]) / self.spacing for d in range(0, self.sim.dimensions)] + flat_index = None + for d in range(0, self.sim.dimensions): + flat_index = cell_index[d] if flat_index is None else flat_index * self.ncells[d] + cell_index[d] + + fill_loop.set_body(BlockAST([ + BranchAST.if_stmt(flat_index >= 0 and flat_index <= self.ncells_total, [ + self.cell_particles[flat_index * self.cell_capacity + self.cell_sizes[flat_index]].set(fill_loop.iter()), + self.cell_sizes[flat_index].set(self.cell_sizes[flat_index] + 1) + ]) + ])) + + return BlockAST([reset_loop, fill_loop]) diff --git a/data_types.py b/data_types.py index 5d8c09f590a345aeb1d3fc538d6e38d9b39692ba..c918accfc69bc1cc5d0b717736fe1c0f926ce39e 100644 --- a/data_types.py +++ b/data_types.py @@ -1,4 +1,5 @@ Type_Invalid = -1 Type_Int = 0 Type_Float = 1 -Type_Vector = 2 +Type_Bool = 2 +Type_Vector = 3 diff --git a/expr.py b/expr.py index 214a77c053354c73e8c6045123f2cb5ad5dc6f73..d259f24d75087ed71057eecb5c1bae7d4e0492eb 100644 --- a/expr.py +++ b/expr.py @@ -6,7 +6,8 @@ from printer import printer from properties import Property def is_expr(e): - return isinstance(e, ExprAST) or isinstance(e, IterAST) or isinstance(e, LitAST) + from variables import Var + return isinstance(e, ExprAST) or isinstance(e, IterAST) or isinstance(e, LitAST) or isinstance(e, VarAST) class ExprAST: def __init__(self, sim, lhs, rhs, op, mem=False): @@ -47,6 +48,18 @@ class ExprAST: def __lt__(self, other): return ExprAST(self.sim, self, other, '<') + def __le__(self, other): + return ExprAST(self.sim, self, other, '<=') + + def __gt__(self, other): + return ExprAST(self.sim, self, other, '>') + + def __ge__(self, other): + return ExprAST(self.sim, self, other, '>=') + + def cmp(lhs, rhs): + return ExprAST(lhs.sim, lhs, rhs, '==') + def inv(self): return ExprAST(self.sim, 1.0, self, '/') @@ -60,11 +73,11 @@ class ExprAST: def set(self, other): assert self.mem is True, "Invalid assignment: lvalue expected!" - self.sim.produced_stmts.append(AssignAST(self.sim, self, other)) + return self.sim.capture_statement(AssignAST(self.sim, self, other)) def add(self, other): assert self.mem is True, "Invalid assignment: lvalue expected!" - self.sim.produced_stmts.append(AssignAST(self.sim, self, self + other)) + return self.sim.capture_statement(AssignAST(self.sim, self, self + other)) def infer_type(lhs, rhs, op): lhs_type = lhs.type() @@ -124,6 +137,9 @@ class ExprVecAST(): def __str__(self): return f"ExprVecAST<a: {self.lhs}, b: {self.rhs}, op: {self.expr.op}, i: {self.index}>" + def __sub__(self, other): + return ExprAST(self.sim, self, other, '-') + def __mul__(self, other): return ExprAST(self.sim, self, other, '*') diff --git a/lit.py b/lit.py index 2d45a69f194fbc0dcbe66efb70c7b30c7c29a3c4..af17aac890159e484b54dc66a7f08141400276b0 100644 --- a/lit.py +++ b/lit.py @@ -1,7 +1,7 @@ -from data_types import Type_Invalid, Type_Int, Type_Float, Type_Vector +from data_types import Type_Invalid, Type_Int, Type_Float, Type_Bool, Type_Vector def is_literal(a): - return isinstance(a, int) or isinstance(a, float) or isinstance(a, list) + return isinstance(a, int) or isinstance(a, float) or isinstance(a, bool) or isinstance(a, list) class LitAST: def __init__(self, value): @@ -14,6 +14,9 @@ class LitAST: if isinstance(value, float): self.lit_type = Type_Float + if isinstance(value, bool): + self.lit_type = Type_Bool + if isinstance(value, list): self.lit_type = Type_Vector diff --git a/loops.py b/loops.py index 24e7a593c3435c1d5cba05bc51a8e38e086cfa8f..2b679bc471968a1aadba3528d53034f950a36217 100644 --- a/loops.py +++ b/loops.py @@ -1,4 +1,5 @@ from data_types import Type_Int +from lit import is_literal, LitAST from printer import printer class IterAST(): @@ -26,6 +27,10 @@ class IterAST(): def __req__(self, other): return self.__cmp__(other) + def __mod__(self, other): + from expr import ExprAST + return ExprAST(self.sim, self, other, '%') + def __str__(self): return f"Iter<{self.iter_id}>" @@ -39,8 +44,8 @@ class IterAST(): class ForAST(): def __init__(self, sim, range_min, range_max, body=None): self.iterator = IterAST(sim) - self.min = range_min; - self.max = range_max; + self.min = LitAST(range_min) if is_literal(range_min) else range_min; + self.max = LitAST(range_max) if is_literal(range_max) else range_max; self.body = body; def iter(self): @@ -51,7 +56,9 @@ class ForAST(): def generate(self): it_id = self.iterator.generate() - printer.print(f"for(int {it_id} = {self.min}; {it_id} < {self.max}; {it_id}++) {{") + rmin = self.min.generate() + rmax = self.max.generate() + printer.print(f"for(int {it_id} = {rmin}; {it_id} < {rmax}; {it_id}++) {{") self.body.generate(); printer.print("}") diff --git a/particle.py b/particle.py index 05294ac763b602f06744be2bd4f4bc05d9e8971c..69f0217073b61bff75674b6079faa19244056531 100644 --- a/particle.py +++ b/particle.py @@ -16,7 +16,6 @@ force = psim.add_vector_property('force', volatile=True) grid_config = [[0.0, 4.0], [0.0, 4.0], [0.0, 4.0]] psim.setup_grid(grid_config) psim.create_particle_lattice(grid_config, spacing=[1.0, 1.0, 1.0]) -psim.setup_cell_lists(cutoff_radius + skin) for i, j, delta, rsq in psim.particle_pairs(cutoff_radius, position): sr2 = 1.0 / rsq diff --git a/particle_simulation.py b/particle_simulation.py index e223ab7018e79f65aa027ac6af389daeb97c35c6..3f151f4401db39fbcb3b598e435db8af444eff4a 100644 --- a/particle_simulation.py +++ b/particle_simulation.py @@ -1,22 +1,29 @@ +from arrays import Array from assign import AssignAST from block import BlockAST from branches import BranchAST +from cell_lists import CellLists from data_types import Type_Int, Type_Float, Type_Vector from expr import ExprAST from loops import ForAST, ParticleForAST, NeighborForAST from properties import Property from printer import printer +from timestep import Timestep from transform import Transform +from variables import Var class ParticleSimulation: def __init__(self, dims=3, timesteps=100): self.properties = [] + self.vars = [] + self.arrays = [] self.defaults = {} self.setup = [] self.grid_config = [] self.setup_stmts = [] - self.timestep_stmts = [] - self.produced_stmts = [] + self.captured_stmts = [] + self.capture_buffer = [] + self.capture = False self.dimensions = dims self.ntimesteps = timesteps self.expr_id = 0 @@ -35,6 +42,25 @@ class ParticleSimulation: def add_vector_property(self, prop_name, value=[0.0, 0.0, 0.0], volatile=False): return self.add_property(prop_name, Type_Vector, value, volatile) + def property(self, prop_name): + return [p for p in self.properties if p.name() == prop_name][0] + + def add_array(self, array_name, array_size, array_type): + arr = Array(self, array_name, array_size, array_type) + self.arrays.append(arr) + return arr + + def array(self, array_name): + return [a for a in self.arrays if a.name() == array_name][0] + + def add_var(self, var_name, var_type): + var = Var(self, var_name, var_type) + self.vars.append(var) + return var + + def var(self, var_name): + return [v for v in self.vars if v.name() == var_name][0] + def new_expr(self): self.expr_id += 1 return self.expr_id - 1 @@ -47,7 +73,7 @@ class ParticleSimulation: self.grid_config = config def create_particle_lattice(self, config, spacing, props={}): - positions = [p for p in self.properties if p.name() == 'position'][0] + positions = self.property('position') assignments = [] loops = [] index = None @@ -74,16 +100,6 @@ class ParticleSimulation: self.setup_stmts.append(loops[0]) self.nparticles += nparticles - def setup_cell_lists(self, cutoff_radius): - ncells = [ - (self.grid_config[0][1] - self.grid_config[0][0]) / cutoff_radius, - (self.grid_config[1][1] - self.grid_config[1][0]) / cutoff_radius, - (self.grid_config[2][1] - self.grid_config[2][0]) / cutoff_radius - ] - - def set_timesteps(self, ts): - self.ntimesteps = ts - def particle_pairs(self, cutoff_radius=None, position=None): i = ParticleForAST(self) j = NeighborForAST(self, i.iter()) @@ -92,22 +108,37 @@ class ParticleSimulation: if cutoff_radius is not None and position is not None: delta = position[i.iter()] - position[j.iter()] rsq = delta[0] * delta[0] + delta[1] * delta[1] + delta[2] * delta[2] + self.start_capture() yield i.iter(), j.iter(), delta, rsq - j.set_body(BlockAST([BranchAST(rsq < cutoff_radius, BlockAST(self.produced_stmts.copy()), None)])) + self.stop_capture() + j.set_body(BlockAST([BranchAST(rsq < cutoff_radius, BlockAST(self.capture_buffer.copy()), None)])) else: yield i.iter(), j.iter() - j.set_body(BlockAST(self.produced_stmts.copy())) + j.set_body(BlockAST(self.capture_buffer.copy())) - self.timestep_stmts.append(i) - self.produced_stmts = [] + self.captured_stmts.append(i) def particles(self): i = ParticleForAST(self) + self.start_capture() yield i.iter() - i.set_body(BlockAST(self.produced_stmts.copy())) - self.timestep_stmts.append(i) - self.produced_stmts = [] + self.stop_capture() + i.set_body(BlockAST(self.capture_buffer.copy())) + self.captured_stmts.append(i) + + def start_capture(self): + self.capture_buffer = [] + self.capture = True + + def stop_capture(self): + self.capture = False + + def capture_statement(self, stmt): + if self.capture is True: + self.capture_buffer.append(stmt) + + return stmt def generate_properties_decl(self): for p in self.properties: @@ -125,16 +156,16 @@ class ParticleSimulation: printer.print("int main() {") printer.print(f" const int nparticles = {self.nparticles};") setup_block = BlockAST(self.setup_stmts) - setup_block.transform(Transform.flatten) - setup_block.transform(Transform.simplify) reset_loop = ParticleForAST(self) reset_loop.set_body(BlockAST([AssignAST(self, p[reset_loop.iter()], 0.0) for p in self.properties if p.volatile is True])) - self.timestep_stmts.insert(0, reset_loop) - timestep_block = BlockAST([ForAST(self, 0, self.ntimesteps, BlockAST(self.timestep_stmts))]) - timestep_block.transform(Transform.flatten) - timestep_block.transform(Transform.simplify) + cell_lists = CellLists(self, 2.8) + timestep_loop = Timestep(self, self.ntimesteps) + timestep_loop.add(cell_lists.build(), 20) + timestep_loop.add(reset_loop) + timestep_loop.add(self.captured_stmts) + program = BlockAST.merge_blocks(setup_block, timestep_loop.as_block()) + program.transform(Transform.flatten) + program.transform(Transform.simplify) self.generate_properties_decl() - setup_block.generate() - timestep_block.generate() + program.generate() printer.print("}") - diff --git a/timestep.py b/timestep.py new file mode 100644 index 0000000000000000000000000000000000000000..c91b6a9087fa4e5c0917e36fea3288ed30721606 --- /dev/null +++ b/timestep.py @@ -0,0 +1,27 @@ +from block import BlockAST +from expr import ExprAST +from branches import BranchAST +from loops import ForAST + +class Timestep: + def __init__(self, sim, nsteps): + self.sim = sim + self.block = BlockAST([]) + self.timestep_loop = ForAST(sim, 0, nsteps, self.block) + + def add(self, item, exec_every=0): + assert exec_every >= 0, "Timestep frequency parameter must be higher or equal than zero!" + statements = item if not isinstance(item, BlockAST) else item.statements() + if exec_every > 0: + self.block.add_statement(BranchAST.if_stmt(ExprAST.cmp(self.timestep_loop.iter() % exec_every, 0), statements)) + else: + self.block.add_statement(statements) + + def as_block(self): + return BlockAST([self.timestep_loop]) + + def generate(self): + self.block.generate() + + def transform(self, fn): + self.block = self.block.transform(fn) diff --git a/variables.py b/variables.py new file mode 100644 index 0000000000000000000000000000000000000000..fa9bcaeb4139f8010717ee614b2df64a7607edf4 --- /dev/null +++ b/variables.py @@ -0,0 +1,49 @@ +from expr import ExprAST + +class Var: + def __init__(self, sim, var_name, var_type): + self.sim = sim + self.var_name = var_name + self.var_type = var_type + + def __str__(self): + return f"Var<name: {self.var_name}, type: {self.var_type}>" + + def __add__(self, other): + return ExprAST(self.sim, self, other, '+') + + def __radd__(self, other): + return ExprAST(self.sim, other, self, '+') + + def __sub__(self, other): + return ExprAST(self.sim, self, other, '-') + + def __mul__(self, other): + return ExprAST(self.sim, self, other, '*') + + def __rmul__(self, other): + return ExprAST(self.sim, other, self, '*') + + def __truediv__(self, other): + return ExprAST(self.sim, self, other, '/') + + def __rtruediv__(self, other): + return ExprAST(self.sim, other, self, '/') + + def __lt__(self, other): + return ExprAST(self.sim, self, other, '<') + + def inv(self): + return ExprAST(self.sim, 1.0, self, '/') + + def name(self): + return self.var_name + + def type(self): + return self.var_type + + def generate(self, mem=False): + return self.var_name + + def transform(self, fn): + return fn(self)