diff --git a/examples/dem.py b/examples/dem.py
index 33a4e366756b7419884a95cd834b856b8c9b3900..5e7a6a02852183d450243db2381c6d6105b2d583 100644
--- a/examples/dem.py
+++ b/examples/dem.py
@@ -146,6 +146,7 @@ psim.add_contact_property('tangential_spring_displacement', pairs.vector(), [0.0
 psim.add_contact_property('impact_velocity_magnitude', pairs.double(), 0.0)
 
 psim.set_domain([0.0, 0.0, 0.0, domainSize_SI[0], domainSize_SI[1], domainSize_SI[2]])
+psim.pbc([True, True, False])
 psim.read_particle_data(
     "data/spheres.input",
     ['type', 'mass', 'radius', 'position', 'linear_velocity', 'flags'],
@@ -163,12 +164,12 @@ psim.read_particle_data(
 
 psim.build_neighbor_lists(linkedCellWidth + skin)
 psim.vtk_output(f"output/dem_{target}", frequency=visSpacing)
+#psim.compute(gravity, symbols={'densityParticle_SI': densityParticle_SI,
+#                               'densityFluid_SI': densityFluid_SI,
+#                               'gravity_SI': gravity_SI,
+#                               'pi': math.pi })
 psim.compute(linear_spring_dashpot, linkedCellWidth + skin, symbols={'dt': dt_SI})
 psim.compute(euler, symbols={'dt': dt_SI})
-psim.compute(gravity, symbols={'densityParticle_SI': densityParticle_SI,
-                               'densityFluid_SI': densityFluid_SI,
-                               'gravity_SI': gravity_SI,
-                               'pi': math.pi })
 
 if target == 'gpu':
     psim.target(pairs.target_gpu())
diff --git a/examples/lj.py b/examples/lj.py
index c5989fe8b7b37b73912b7f8b0d331aef02335e48..fafdfd4e7edd8331f0b2e53a42606ae8edf6f936 100644
--- a/examples/lj.py
+++ b/examples/lj.py
@@ -37,6 +37,7 @@ psim.add_feature_property('type', 'sigma6', pairs.double(), [epsilon for i in ra
 #psim.set_domain(0.0, 0.0, 0.0, 6.7184, 6.7184, 6.7184) # for 4x4x4 setup
 psim.set_domain(0.0, 0.0, 0.0, 53.747078, 53.747078, 53.747078)
 psim.read_particle_data("data/minimd_setup_32x32x32.input", ['type', 'mass', 'position', 'linear_velocity', 'flags'], pairs.sphere())
+psim.reneighbor_every(20)
 psim.build_neighbor_lists(cutoff_radius + skin)
 psim.vtk_output(f"output/lj_{target}")
 psim.compute(lj, cutoff_radius)
diff --git a/src/pairs/mapping/funcs.py b/src/pairs/mapping/funcs.py
index 2e753d70dfbca91805a4c4e9afd4eec292f7e443..c57a209bbe70718a9bb7115060662363476748da 100644
--- a/src/pairs/mapping/funcs.py
+++ b/src/pairs/mapping/funcs.py
@@ -101,11 +101,13 @@ class BuildParticleIR(ast.NodeVisitor):
     def visit_AugAssign(self, node):
         lhs = self.visit(node.target)
         rhs = self.visit(node.value)
+        op_class = VectorOp if Types.Vector in [lhs.type(), rhs.type()] else ScalarOp
+        bin_op = op_class(self.sim, lhs, rhs, BuildParticleIR.get_binary_op(node.op))
 
         if isinstance(lhs, UndefinedSymbol):
-            self.add_symbols({lhs.symbol_id: rhs})
+            self.add_symbols({lhs.symbol_id: bin_op})
         else:
-            Assign(self.sim, lhs, lhs + rhs)
+            Assign(self.sim, lhs, bin_op)
 
     def visit_BinOp(self, node):
         #print(ast.dump(node))
diff --git a/src/pairs/sim/domain_partitioning.py b/src/pairs/sim/domain_partitioning.py
index 86aa15b7f5dbb77726f3c3c4367746c852b5555b..08ce9a48d41f25cf8524f1a5665c3ef638b416ab 100644
--- a/src/pairs/sim/domain_partitioning.py
+++ b/src/pairs/sim/domain_partitioning.py
@@ -23,6 +23,9 @@ class DimensionRanges:
         return [step * 2 + 0, step * 2 + 1]
 
     def ghost_particles(self, step, position, offset=0.0):
+        if self.sim._pbc[step] is False:
+            return
+
         # Particles with one of the following flags are ignored
         flags_to_exclude = (Flags.Infinite | Flags.Fixed | Flags.Global)
 
diff --git a/src/pairs/sim/pbc.py b/src/pairs/sim/pbc.py
index 277c18f3f373e65861e282c7979d87fe894c25ac..af5fec1516b8aa485adbbcd8f578cd93bbb6ccea 100644
--- a/src/pairs/sim/pbc.py
+++ b/src/pairs/sim/pbc.py
@@ -23,8 +23,9 @@ class EnforcePBC(Lowerable):
         for i in ParticleFor(sim):
             # TODO: VecFilter?
             for d in range(0, ndims):
-                for _ in Filter(sim, positions[i][d] < grid.min(d)):
-                    Assign(sim, positions[i][d], positions[i][d] + grid.length(d)) 
+                if sim._pbc[d] is True:
+                    for _ in Filter(sim, positions[i][d] < grid.min(d)):
+                        Assign(sim, positions[i][d], positions[i][d] + grid.length(d)) 
 
-                for _ in Filter(sim, positions[i][d] > grid.max(d)):
-                    Assign(sim, positions[i][d], positions[i][d] - grid.length(d)) 
+                    for _ in Filter(sim, positions[i][d] > grid.max(d)):
+                        Assign(sim, positions[i][d], positions[i][d] - grid.length(d)) 
diff --git a/src/pairs/sim/shapes.py b/src/pairs/sim/shapes.py
index 8359da7f2b6a1f7fd138d100a3eb77035d70e4ed..dbd37db4107e59a53577413901c1a33a33467b14 100644
--- a/src/pairs/sim/shapes.py
+++ b/src/pairs/sim/shapes.py
@@ -1,3 +1,4 @@
 class Shapes:
     Sphere      =   0
     Halfspace   =   1
+    PointMasses =   2
diff --git a/src/pairs/sim/simulation.py b/src/pairs/sim/simulation.py
index 6315036dd311555cbb5ddcc00f44ebc3059c7fa5..8fd8b85e87cef5a8882773f46a29ec5f7e697eb3 100644
--- a/src/pairs/sim/simulation.py
+++ b/src/pairs/sim/simulation.py
@@ -68,10 +68,12 @@ class Simulation:
         self.ntimesteps = timesteps
         self.expr_id = 0
         self.iter_id = 0
+        self.reneighbor_frequency = 1
         self.vtk_file = None
         self.vtk_frequency = 0
         self._target = None
         self._dom_part = DimensionRanges(self)
+        self._pbc = [True for _ in range(dims)]
 
     def max_shapes(self):
         return 2
@@ -107,6 +109,10 @@ class Simulation:
     def ndims(self):
         return self.dims
 
+    def pbc(self, pbc_config):
+        assert len(pbc_config) == self.dims, "PBC must be specified for each dimension."
+        self._pbc = pbc_config
+
     def add_property(self, prop_name, prop_type, value=0.0, volatile=False):
         assert self.property(prop_name) is None, f"Property already defined: {prop_name}"
         return self.properties.add(prop_name, prop_type, value, volatile)
@@ -173,6 +179,9 @@ class Simulation:
         self.grid = Grid3D(self, grid[0], grid[1], grid[2], grid[3], grid[4], grid[5])
         self.setups.add_statement(InitializeDomain(self))
 
+    def reneighbor_every(self, frequency):
+        self.reneighbor_frequency = frequency
+
     def create_particle_lattice(self, grid, spacing, props={}):
         self.setups.add_statement(ParticleLattice(self, grid, spacing, props, self.position()))
 
@@ -267,16 +276,16 @@ class Simulation:
         comm = Comm(self, self._dom_part)
 
         timestep_procedures = [
-            (comm.exchange(), 20),
-            (comm.borders(), comm.synchronize(), 20),
-            (BuildCellLists(self, self.cell_lists), 20),
-            (PartitionCellLists(self, self.cell_lists), 20),
-            (BuildNeighborLists(self, self.neighbor_lists), 20),
+            (comm.exchange(), self.reneighbor_frequency),
+            (comm.borders(), comm.synchronize(), self.reneighbor_frequency),
+            (BuildCellLists(self, self.cell_lists), self.reneighbor_frequency),
+            (PartitionCellLists(self, self.cell_lists), self.reneighbor_frequency),
+            (BuildNeighborLists(self, self.neighbor_lists), self.reneighbor_frequency),
         ]
 
         if not self.contact_properties.empty():
             contact_history = ContactHistory(self.cell_lists)
-            timestep_procedures.append((BuildContactHistory(self, contact_history), 20))
+            timestep_procedures.append((BuildContactHistory(self, contact_history), self.reneighbor_frequency))
 
         timestep_procedures += [
             ResetVolatileProperties(self),