diff --git a/runtime/pairs.cpp b/runtime/pairs.cpp
index 352653f296a3d19cd21ccae5246f091895d9fd42..42a4d257cd0808bb1fe3f36eabd6d492b070b43f 100644
--- a/runtime/pairs.cpp
+++ b/runtime/pairs.cpp
@@ -109,98 +109,123 @@ FeatureProperty &PairsSimulation::getFeaturePropertyByName(std::string name) {
     return *fp;
 }
 
-void PairsSimulation::copyArrayToDevice(Array &array, bool write) {
+void PairsSimulation::copyArrayToDevice(Array &array, action_t action) {
     int array_id = array.getId();
 
-    if(!array_flags->isDeviceFlagSet(array_id)) {
-        if(array.isStatic()) {
-            PAIRS_DEBUG("Copying static array %s to device\n", array.getName().c_str());
-            pairs::copy_static_symbol_to_device(array.getHostPointer(), array.getDevicePointer(), array.getSize());
-        } else {
-            PAIRS_DEBUG("Copying array %s to device\n", array.getName().c_str());
-            pairs::copy_to_device(array.getHostPointer(), array.getDevicePointer(), array.getSize());
+    if(action == Ignore || action == WriteAfterRead || action == ReadOnly) {
+        if(action == Ignore || !array_flags->isDeviceFlagSet(array_id)) {
+            if(array.isStatic()) {
+                PAIRS_DEBUG("Copying static array %s to device\n", array.getName().c_str());
+                pairs::copy_static_symbol_to_device(
+                    array.getHostPointer(), array.getDevicePointer(), array.getSize());
+            } else {
+                PAIRS_DEBUG("Copying array %s to device\n", array.getName().c_str());
+                pairs::copy_to_device(array.getHostPointer(), array.getDevicePointer(), array.getSize());
+            }
         }
-
-        array_flags->setDeviceFlag(array_id);
     }
 
-    if(write) {
+    if(action != ReadOnly) {
         array_flags->clearHostFlag(array_id);
     }
+
+    array_flags->setDeviceFlag(array_id);
 }
 
-void PairsSimulation::copyArrayToHost(Array &array, bool write) {
+void PairsSimulation::copyArrayToHost(Array &array, action_t action) {
     int array_id = array.getId();
 
-    if(!array_flags->isHostFlagSet(array_id)) {
-        if(array.isStatic()) {
-            PAIRS_DEBUG("Copying static array %s to host\n", array.getName().c_str());
-            pairs::copy_static_symbol_to_host(array.getDevicePointer(), array.getHostPointer(), array.getSize());
-        } else {
-            PAIRS_DEBUG("Copying array %s to host\n", array.getName().c_str());
-            pairs::copy_to_host(array.getDevicePointer(), array.getHostPointer(), array.getSize());
-        }
+    if(action == Ignore || action == WriteAfterRead || action == ReadOnly) {
+        if(action == Ignore || !array_flags->isHostFlagSet(array_id)) {
+            if(array.isStatic()) {
+                PAIRS_DEBUG("Copying static array %s to host\n", array.getName().c_str());
+                pairs::copy_static_symbol_to_host(
+                    array.getDevicePointer(), array.getHostPointer(), array.getSize());
+            } else {
+                PAIRS_DEBUG("Copying array %s to host\n", array.getName().c_str());
+                pairs::copy_to_host(array.getDevicePointer(), array.getHostPointer(), array.getSize());
+            }
 
-        array_flags->setHostFlag(array_id);
+        }
     }
 
-    if(write) {
+    if(action != ReadOnly) {
         array_flags->clearDeviceFlag(array_id);
     }
+
+    array_flags->setHostFlag(array_id);
 }
 
-void PairsSimulation::copyPropertyToDevice(Property &prop, bool write) {
+void PairsSimulation::copyPropertyToDevice(Property &prop, action_t action) {
     int prop_id = prop.getId();
 
-    if(!prop_flags->isDeviceFlagSet(prop_id)) {
-        PAIRS_DEBUG("Copying property %s to device\n", prop.getName().c_str());
-        pairs::copy_to_device(prop.getHostPointer(), prop.getDevicePointer(), prop.getTotalSize());
-        prop_flags->setDeviceFlag(prop_id);
+    if(action == Ignore || action == WriteAfterRead || action == ReadOnly) {
+        if(action == Ignore || !prop_flags->isDeviceFlagSet(prop_id)) {
+            PAIRS_DEBUG("Copying property %s to device\n", prop.getName().c_str());
+            pairs::copy_to_device(prop.getHostPointer(), prop.getDevicePointer(), prop.getTotalSize());
+        }
     }
 
-    if(write) {
+    if(action != ReadOnly) {
         prop_flags->clearHostFlag(prop_id);
     }
+
+    prop_flags->setDeviceFlag(prop_id);
 }
 
-void PairsSimulation::copyPropertyToHost(Property &prop, bool write) {
+void PairsSimulation::copyPropertyToHost(Property &prop, action_t action) {
     int prop_id = prop.getId();
 
-    if(!prop_flags->isHostFlagSet(prop_id)) {
-        PAIRS_DEBUG("Copying property %s to host\n", prop.getName().c_str());
-        pairs::copy_to_host(prop.getDevicePointer(), prop.getHostPointer(), prop.getTotalSize());
-        prop_flags->setHostFlag(prop_id);
+    if(action == Ignore || action == WriteAfterRead || action == ReadOnly) {
+        if(action == Ignore || !prop_flags->isHostFlagSet(prop_id)) {
+            PAIRS_DEBUG("Copying property %s to host\n", prop.getName().c_str());
+            pairs::copy_to_host(prop.getDevicePointer(), prop.getHostPointer(), prop.getTotalSize());
+        }
     }
 
-    if(write) {
+    if(action != ReadOnly) {
         prop_flags->clearDeviceFlag(prop_id);
     }
+
+    prop_flags->setHostFlag(prop_id);
 }
 
-void PairsSimulation::copyContactPropertyToDevice(ContactProperty &contact_prop, bool write) {
+void PairsSimulation::copyContactPropertyToDevice(ContactProperty &contact_prop, action_t action) {
     int prop_id = contact_prop.getId();
 
-    if(!contact_prop_flags->isDeviceFlagSet(prop_id)) {
-        PAIRS_DEBUG("Copying contact property %s to device\n", contact_prop.getName().c_str());
-        pairs::copy_to_device(contact_prop.getHostPointer(), contact_prop.getDevicePointer(), contact_prop.getTotalSize());
-        contact_prop_flags->setDeviceFlag(prop_id);
+    if(action == Ignore || action == WriteAfterRead || action == ReadOnly) {
+        if(action == Ignore || !contact_prop_flags->isDeviceFlagSet(prop_id)) {
+            PAIRS_DEBUG("Copying contact property %s to device\n", contact_prop.getName().c_str());
+            pairs::copy_to_device(
+                contact_prop.getHostPointer(),
+                contact_prop.getDevicePointer(),
+                contact_prop.getTotalSize());
+
+            contact_prop_flags->setDeviceFlag(prop_id);
+        }
     }
 
-    if(write) {
+    if(action != ReadOnly) {
         contact_prop_flags->clearHostFlag(prop_id);
     }
 }
 
-void PairsSimulation::copyContactPropertyToHost(ContactProperty &contact_prop, bool write) {
+void PairsSimulation::copyContactPropertyToHost(ContactProperty &contact_prop, action_t action) {
     int prop_id = contact_prop.getId();
 
-    if(!contact_prop_flags->isHostFlagSet(contact_prop.getId())) {
-        PAIRS_DEBUG("Copying contact property %s to host\n", contact_prop.getName().c_str());
-        pairs::copy_to_host(contact_prop.getDevicePointer(), contact_prop.getHostPointer(), contact_prop.getTotalSize());
-        contact_prop_flags->setHostFlag(prop_id);
+    if(action == Ignore || action == WriteAfterRead || action == ReadOnly) {
+        if(!contact_prop_flags->isHostFlagSet(contact_prop.getId())) {
+            PAIRS_DEBUG("Copying contact property %s to host\n", contact_prop.getName().c_str());
+            pairs::copy_to_host(
+                contact_prop.getDevicePointer(),
+                contact_prop.getHostPointer(),
+                contact_prop.getTotalSize());
+
+            contact_prop_flags->setHostFlag(prop_id);
+        }
     }
 
-    if(write) {
+    if(action != ReadOnly) {
         contact_prop_flags->clearDeviceFlag(prop_id);
     }
 }
@@ -214,7 +239,7 @@ void PairsSimulation::communicateSizes(int dim, const int *send_sizes, int *recv
     auto nsend_id = getArrayByHostPointer(send_sizes).getId();
     auto nrecv_id = getArrayByHostPointer(recv_sizes).getId();
 
-    copyArrayToHost(nsend_id, false);
+    copyArrayToHost(nsend_id, ReadOnly);
     array_flags->setHostFlag(nrecv_id);
     array_flags->clearDeviceFlag(nrecv_id);
     this->getDomainPartitioner()->communicateSizes(dim, send_sizes, recv_sizes);
@@ -233,11 +258,11 @@ void PairsSimulation::communicateData(
     auto nsend_id = getArrayByHostPointer(nsend).getId();
     auto nrecv_id = getArrayByHostPointer(nrecv).getId();
 
-    copyArrayToHost(send_buf_id, false);
-    copyArrayToHost(send_offsets_id, false);
-    copyArrayToHost(recv_offsets_id, false);
-    copyArrayToHost(nsend_id, false);
-    copyArrayToHost(nrecv_id, false);
+    copyArrayToHost(send_buf_id, ReadOnly);
+    copyArrayToHost(send_offsets_id, ReadOnly);
+    copyArrayToHost(recv_offsets_id, ReadOnly);
+    copyArrayToHost(nsend_id, ReadOnly);
+    copyArrayToHost(nrecv_id, ReadOnly);
     array_flags->setHostFlag(recv_buf_id);
     array_flags->clearDeviceFlag(recv_buf_id);
     this->getDomainPartitioner()->communicateData(dim, elem_size, send_buf, send_offsets, nsend, recv_buf, recv_offsets, nrecv);
diff --git a/runtime/pairs.hpp b/runtime/pairs.hpp
index ec26c6a1547cf41e97151ed647398d0ead84d38d..6ad84e74adabc3dc84f753689b3f651a3cf686c5 100644
--- a/runtime/pairs.hpp
+++ b/runtime/pairs.hpp
@@ -122,29 +122,29 @@ public:
     void setArrayDeviceFlag(Array &array) { array_flags->setDeviceFlag(array.getId()); }
     void clearArrayDeviceFlag(array_t id) { clearArrayDeviceFlag(getArray(id)); }
     void clearArrayDeviceFlag(Array &array) { array_flags->clearDeviceFlag(array.getId()); }
-    void copyArrayToDevice(array_t id, bool write) { copyArrayToDevice(getArray(id), write); }
-    void copyArrayToDevice(Array &array, bool write);
+    void copyArrayToDevice(array_t id, action_t action) { copyArrayToDevice(getArray(id), action); }
+    void copyArrayToDevice(Array &array, action_t action);
 
     void setArrayHostFlag(array_t id) { setArrayHostFlag(getArray(id)); }
     void setArrayHostFlag(Array &array) { array_flags->setHostFlag(array.getId()); }
     void clearArrayHostFlag(array_t id) { clearArrayHostFlag(getArray(id)); }
     void clearArrayHostFlag(Array &array) { array_flags->clearHostFlag(array.getId()); }
-    void copyArrayToHost(array_t id, bool write) { copyArrayToHost(getArray(id), write); }
-    void copyArrayToHost(Array &array, bool write);
+    void copyArrayToHost(array_t id, action_t action) { copyArrayToHost(getArray(id), action); }
+    void copyArrayToHost(Array &array, action_t action);
 
     void setPropertyDeviceFlag(property_t id) { setPropertyDeviceFlag(getProperty(id)); }
     void setPropertyDeviceFlag(Property &prop) { prop_flags->setDeviceFlag(prop.getId()); }
     void clearPropertyDeviceFlag(property_t id) { clearPropertyDeviceFlag(getProperty(id)); }
     void clearPropertyDeviceFlag(Property &prop) { prop_flags->clearDeviceFlag(prop.getId()); }
-    void copyPropertyToDevice(property_t id, bool write) { copyPropertyToDevice(getProperty(id), write); }
-    void copyPropertyToDevice(Property &prop, bool write);
+    void copyPropertyToDevice(property_t id, action_t action) { copyPropertyToDevice(getProperty(id), action); }
+    void copyPropertyToDevice(Property &prop, action_t action);
 
     void setPropertyHostFlag(property_t id) { setPropertyHostFlag(getProperty(id)); }
     void setPropertyHostFlag(Property &prop) { prop_flags->setHostFlag(prop.getId()); }
     void clearPropertyHostFlag(property_t id) { clearPropertyHostFlag(getProperty(id)); }
     void clearPropertyHostFlag(Property &prop) { prop_flags->clearHostFlag(prop.getId()); }
-    void copyPropertyToHost(property_t id, bool write) { copyPropertyToHost(getProperty(id), write); }
-    void copyPropertyToHost(Property &prop, bool write);
+    void copyPropertyToHost(property_t id, action_t action) { copyPropertyToHost(getProperty(id), action); }
+    void copyPropertyToHost(Property &prop, action_t action);
 
     void setContactPropertyDeviceFlag(property_t id) {
         setContactPropertyDeviceFlag(getContactProperty(id));
@@ -162,11 +162,11 @@ public:
         contact_prop_flags->clearDeviceFlag(prop.getId());
     }
 
-    void copyContactPropertyToDevice(property_t id, bool write) {
-        copyContactPropertyToDevice(getContactProperty(id), write);
+    void copyContactPropertyToDevice(property_t id, action_t action) {
+        copyContactPropertyToDevice(getContactProperty(id), action);
     }
 
-    void copyContactPropertyToDevice(ContactProperty &prop, bool write);
+    void copyContactPropertyToDevice(ContactProperty &prop, action_t action);
 
     void setContactPropertyHostFlag(property_t id) {
         setContactPropertyHostFlag(getContactProperty(id));
@@ -184,11 +184,11 @@ public:
         contact_prop_flags->clearHostFlag(prop.getId());
     }
 
-    void copyContactPropertyToHost(property_t id, bool write) {
-        copyContactPropertyToHost(getContactProperty(id), write);
+    void copyContactPropertyToHost(property_t id, action_t action) {
+        copyContactPropertyToHost(getContactProperty(id), action);
     }
 
-    void copyContactPropertyToHost(ContactProperty &prop, bool write);
+    void copyContactPropertyToHost(ContactProperty &prop, action_t action);
 
     void copyFeaturePropertyToDevice(property_t id) {
         copyFeaturePropertyToDevice(getFeatureProperty(id));
diff --git a/runtime/pairs_common.hpp b/runtime/pairs_common.hpp
index 8559d9430ab82229e75974db900144ab8d048073..a209950f404175a9430c5857c284304ffa023f1a 100644
--- a/runtime/pairs_common.hpp
+++ b/runtime/pairs_common.hpp
@@ -11,6 +11,7 @@ typedef double real_t;
 typedef int array_t;
 typedef int property_t;
 typedef int layout_t;
+typedef int action_t;
 
 enum PropertyType {
     Prop_Invalid = -1,
@@ -27,6 +28,15 @@ enum DataLayout {
     SoA
 };
 
+enum Actions {
+    NoAction = 0,
+    ReadAfterWrite = 1,
+    WriteAfterRead = 2,
+    ReadOnly = 3,
+    WriteOnly = 4,
+    Ignore = 5
+};
+
 enum DomainPartitioning {
     DimRanges = 0,
     BoxList,
diff --git a/runtime/thermo.hpp b/runtime/thermo.hpp
index 993e9c8bc898cd502574de2a5d0df9621d16b250..b09693ab9ca47ea71c650205f68f30bbb44bcd2b 100644
--- a/runtime/thermo.hpp
+++ b/runtime/thermo.hpp
@@ -26,8 +26,8 @@ double compute_thermo(PairsSimulation *ps, int nlocal, double xprd, double yprd,
     //const double e_scale = 0.5;
     double t = 0.0, p;
 
-    ps->copyPropertyToHost(masses, false);
-    ps->copyPropertyToHost(velocities, false);
+    ps->copyPropertyToHost(masses, ReadOnly);
+    ps->copyPropertyToHost(velocities, ReadOnly);
 
     for(int i = 0; i < nlocal; i++) {
         t += masses(i) * (  velocities(i, 0) * velocities(i, 0) +
diff --git a/runtime/vtk.hpp b/runtime/vtk.hpp
index 527f074cf8659096c390e9a2cfd7292d0466b49e..2fed101ce630eef636610ac54c0489ea42177e81 100644
--- a/runtime/vtk.hpp
+++ b/runtime/vtk.hpp
@@ -29,8 +29,8 @@ void vtk_write_data(PairsSimulation *ps, const char *filename, int start, int en
     filename_oss << timestep << ".vtk";
     std::ofstream out_file(filename_oss.str());
 
-    ps->copyPropertyToHost(masses, false);
-    ps->copyPropertyToHost(positions, false);
+    ps->copyPropertyToHost(masses, ReadOnly);
+    ps->copyPropertyToHost(positions, ReadOnly);
 
     for(int i = start; i < end; i++) {
         if(flags(i) & FLAGS_INFINITE) {
diff --git a/src/pairs/code_gen/cgen.py b/src/pairs/code_gen/cgen.py
index cf163257fabe101d0ed723fa61302a65ccfecc2e..b8dcd293708b0b7dcd70cb2c2bc034db768e50c5 100644
--- a/src/pairs/code_gen/cgen.py
+++ b/src/pairs/code_gen/cgen.py
@@ -1,4 +1,5 @@
 import math
+from pairs.ir.actions import Actions
 from pairs.ir.assign import Assign
 from pairs.ir.atomic import AtomicAdd
 from pairs.ir.arrays import Array, ArrayAccess, DeclareStaticArray, RegisterArray, ReallocArray
@@ -454,28 +455,28 @@ class CGen:
             self.print(f"{call};")
 
         if isinstance(ast_node, CopyArray):
-            array_id = ast_node.array.id()
-            array_name = ast_node.array.name()
+            array_id = ast_node.array().id()
+            array_name = ast_node.array().name()
             ctx_suffix = "Device" if ast_node.context() == Contexts.Device else "Host"
-            write = "true" if ast_node.write else "false"
-            self.print(f"pairs->copyArrayTo{ctx_suffix}({array_id}, {write}); // {array_name}")
+            action = Actions.c_keyword(ast_node.action())
+            self.print(f"pairs->copyArrayTo{ctx_suffix}({array_id}, {action}); // {array_name}")
 
         if isinstance(ast_node, CopyContactProperty):
-            prop_id = ast_node.contact_prop.id()
-            prop_name = ast_node.contact_prop.name()
-            write = "true" if ast_node.write else "false"
+            prop_id = ast_node.contact_prop().id()
+            prop_name = ast_node.contact_prop().name()
+            action = Actions.c_keyword(ast_node.action())
             ctx_suffix = "Device" if ast_node.context() == Contexts.Device else "Host"
-            self.print(f"pairs->copyContactPropertyTo{ctx_suffix}({prop_id}, {write}); // {prop_name}")
+            self.print(f"pairs->copyContactPropertyTo{ctx_suffix}({prop_id}, {action}); // {prop_name}")
 
         if isinstance(ast_node, CopyProperty):
-            prop_id = ast_node.prop.id()
-            prop_name = ast_node.prop.name()
-            write = "true" if ast_node.write else "false"
+            prop_id = ast_node.prop().id()
+            prop_name = ast_node.prop().name()
+            action = Actions.c_keyword(ast_node.action())
             ctx_suffix = "Device" if ast_node.context() == Contexts.Device else "Host"
-            self.print(f"pairs->copyPropertyTo{ctx_suffix}({prop_id}, {write}); // {prop_name}")
+            self.print(f"pairs->copyPropertyTo{ctx_suffix}({prop_id}, {action}); // {prop_name}")
 
         if isinstance(ast_node, CopyVar):
-            var_name = ast_node.variable.name()
+            var_name = ast_node.variable().name()
             ctx_suffix = "Device" if ast_node.context() == Contexts.Device else "Host"
             self.print(f"rv_{var_name}.copyTo{ctx_suffix}();")
 
diff --git a/src/pairs/ir/actions.py b/src/pairs/ir/actions.py
new file mode 100644
index 0000000000000000000000000000000000000000..82176a674313e4817628f3922d94986068f58cb1
--- /dev/null
+++ b/src/pairs/ir/actions.py
@@ -0,0 +1,29 @@
+class Actions:
+    Invalid = -1
+    NoAction = 0
+    ReadAfterWrite = 1
+    WriteAfterRead = 2
+    ReadOnly = 3
+    WriteOnly = 4
+    Ignore = 5
+
+    def update_rule(action, new_op):
+        if action == Actions.NoAction:
+            return Actions.ReadOnly if new_op == 'r' else Actions.WriteOnly
+
+        if action == Actions.ReadOnly and new_op == 'w':
+            return Actions.WriteAfterRead
+
+        if action == Actions.WriteOnly and new_op == 'r':
+            return Actions.ReadAfterWrite
+
+        return action
+
+    def c_keyword(action):
+        return "NoAction"       if action == Actions.NoAction else       \
+               "ReadAfterWrite" if action == Actions.ReadAfterWrite else \
+               "WriteAfterRead" if action == Actions.WriteAfterRead else \
+               "ReadOnly"       if action == Actions.ReadOnly else       \
+               "WriteOnly"      if action == Actions.WriteOnly else      \
+               "Ignore"         if action == Actions.Ignore else         \
+               "Invalid"
diff --git a/src/pairs/ir/device.py b/src/pairs/ir/device.py
index 96af50536f03309aa396285f5b70660b74af4b7b..5082acf339636fb9cea405bb1172288492636e3a 100644
--- a/src/pairs/ir/device.py
+++ b/src/pairs/ir/device.py
@@ -30,59 +30,84 @@ class DeviceStaticRef(ASTNode):
 
 
 class CopyArray(ASTNode):
-    def __init__(self, sim, array, ctx, write):
+    def __init__(self, sim, array, ctx, action):
         super().__init__(sim)
-        self.array = array
-        self.ctx = ctx
-        self.write = write
+        self._array = array
+        self._ctx = ctx
+        self._action = action
         self.sim.add_statement(self)
 
+    def array(self):
+        return self._array
+
     def context(self):
-        return self.ctx
+        return self._ctx
+
+    def action(self):
+        return self._action
 
     def children(self):
-        return [self.array]
+        return [self._array]
 
 
 class CopyProperty(ASTNode):
-    def __init__(self, sim, prop, ctx, write):
+    def __init__(self, sim, prop, ctx, action):
         super().__init__(sim)
-        self.prop = prop
-        self.ctx = ctx
-        self.write = write
+        self._prop = prop
+        self._ctx = ctx
+        self._action = action
         self.sim.add_statement(self)
 
+    def prop(self):
+        return self._prop
+
     def context(self):
-        return self.ctx
+        return self._ctx
+
+    def action(self):
+        return self._action
 
     def children(self):
-        return [self.prop]
+        return [self._prop]
 
 
 class CopyContactProperty(ASTNode):
     def __init__(self, sim, prop, ctx, write):
         super().__init__(sim)
-        self.contact_prop = prop
-        self.ctx = ctx
-        self.write = write
+        self._contact_prop = prop
+        self._ctx = ctx
+        self._action = action
         self.sim.add_statement(self)
 
+    def contact_prop(self):
+        return self._contact_prop
+
     def context(self):
-        return self.ctx
+        return self._ctx
+
+    def action(self):
+        return self._action
 
     def children(self):
-        return [self.prop]
+        return [self._prop]
 
 
 class CopyVar(ASTNode):
-    def __init__(self, sim, variable, ctx):
+    def __init__(self, sim, variable, ctx, action):
         super().__init__(sim)
-        self.variable = variable
-        self.ctx = ctx
+        self._variable = variable
+        self._ctx = ctx
+        self._action = action
         self.sim.add_statement(self)
 
+    def variable(self):
+        return self._variable
+
     def context(self):
-        return self.ctx
+        return self._ctx
+
+    def action(self):
+        return self._action
 
     def children(self):
-        return [self.variable]
+        return [self._variable]
diff --git a/src/pairs/ir/kernel.py b/src/pairs/ir/kernel.py
index 12ed846d1dff080003435cae8e1b620067f953d2..e5e19c9d6cec2389a6ad76f7d1e606d28dd12f29 100644
--- a/src/pairs/ir/kernel.py
+++ b/src/pairs/ir/kernel.py
@@ -1,3 +1,4 @@
+from pairs.ir.actions import Actions
 from pairs.ir.arrays import Array, ArrayAccess
 from pairs.ir.ast_node import ASTNode
 from pairs.ir.scalars import ScalarOp
@@ -50,10 +51,10 @@ class Kernel(ASTNode):
         return self._variables
 
     def read_only_variables(self):
-        return [v for v in self._variables if 'w' not in self._variables[v]]
+        return [var for var in self._variables if self._variables[var] == Actions.ReadOnly]
 
     def write_variables(self):
-        return [v for v in self._variables if 'w' in self._variables[v]]
+        return [var for var in self._variables if self._variables[var] != Actions.ReadOnly]
 
     def arrays(self):
         return self._arrays
@@ -67,9 +68,6 @@ class Kernel(ASTNode):
     def feature_properties(self):
         return self._feature_properties
 
-    def properties_to_synchronize(self):
-        return {p for p in self._properties if self._properties[p][0] == 'r'}
-
     def array_accesses(self):
         return self._array_accesses
 
@@ -79,53 +77,52 @@ class Kernel(ASTNode):
     def vector_ops(self):
         return self._vector_ops
 
-    def write_properties(self):
-        return {p for p in self._properties if 'w' in self._properties[p]}
-
     def add_array(self, array, write=False):
         array_list = array if isinstance(array, list) else [array]
-        character = 'w' if write else 'r'
+        new_op = 'w' if write else 'r'
 
-        for a in array_list:
-            assert isinstance(a, Array), \
+        for array in array_list:
+            assert isinstance(array, Array), \
                 "Kernel.add_array(): Element is not of type Array."
 
-            self._arrays[a] = character if a not in self._arrays else \
-                              self._arrays[a] + character
+            action = Actions.NoAction if array not in self._arrays else self._arrays[array]
+            self._arrays[array] = Actions.update_rule(action, new_op)
 
     def add_variable(self, variable, write=False):
         variable_list = variable if isinstance(variable, list) else [variable]
-        character = 'w' if write else 'r'
+        new_op = 'w' if write else 'r'
 
-        for v in variable_list:
-            if not v.temporary():
-                assert isinstance(v, Var), \
+        for var in variable_list:
+            if not var.temporary():
+                assert isinstance(var, Var), \
                     "Kernel.add_variable(): Element is not of type Var."
 
-                self._variables[v] = character if v not in self._variables else \
-                                     self._variables[v] + character
+                action = Actions.NoAction if var not in self._variables else self._variables[var]
+                self._variables[var] = Actions.update_rule(action, new_op)
 
     def add_property(self, prop, write=False):
         prop_list = prop if isinstance(prop, list) else [prop]
-        character = 'w' if write else 'r'
+        new_op = 'w' if write else 'r'
 
-        for p in prop_list:
-            assert isinstance(p, Property), \
+        for prop in prop_list:
+            assert isinstance(prop, Property), \
                 "Kernel.add_property(): Element is not of type Property."
 
-            self._properties[p] = character if p not in self._properties else \
-                                  self._properties[p] + character
+            action = Actions.NoAction if prop not in self._properties else self._properties[prop]
+            self._properties[prop] = Actions.update_rule(action, new_op)
 
     def add_contact_property(self, contact_prop, write=False):
         contact_prop_list = contact_prop if isinstance(contact_prop, list) else [contact_prop]
-        character = 'w' if write else 'r'
+        new_op = 'w' if write else 'r'
 
-        for cp in contact_prop_list:
+        for contact_prop in contact_prop_list:
             assert isinstance(cp, ContactProperty), \
                 "Kernel.add_contact_property(): Element is not of type ContactProperty."
 
-            self._contact_properties[cp] = character if cp not in self._contact_properties else \
-                                           self._contact_properties[cp] + character
+            action = Actions.NoAction if contact_prop not in self._contact_properties else \
+                     self._contact_properties[contact_prop]
+
+            self._contact_properties[contact_prop] = Actions.update_rule(action, new_op)
 
     def add_feature_property(self, feature_prop):
         feature_prop_list = feature_prop if isinstance(feature_prop, list) else [feature_prop]
@@ -134,7 +131,8 @@ class Kernel(ASTNode):
             assert isinstance(fp, FeatureProperty), \
                 "Kernel.add_feature_property(): Element is not of type FeatureProperty."
 
-            self._feature_properties[fp] = 'r'
+            # Feature properties cannot be written into
+            self._feature_properties[fp] = Actions.ReadOnly
 
     def add_array_access(self, array_access):
         array_access_list = array_access if isinstance(array_access, list) else [array_access]
diff --git a/src/pairs/ir/module.py b/src/pairs/ir/module.py
index abbc401f9c098ecf8c0ae919c2d7806e3d246ec6..ab78942b2f43f946a714513fab4ad7d390442197 100644
--- a/src/pairs/ir/module.py
+++ b/src/pairs/ir/module.py
@@ -1,4 +1,5 @@
 from pairs.ir.arrays import Array
+from pairs.ir.actions import Actions
 from pairs.ir.ast_node import ASTNode
 from pairs.ir.features import FeatureProperty
 from pairs.ir.properties import Property, ContactProperty
@@ -55,14 +56,11 @@ class Module(ASTNode):
     def variables(self):
         return self._variables
 
-    def variables_to_synchronize(self):
-        return {v for v in self._variables if 'w' in self._variables[v] and v.device_flag}
-
     def read_only_variables(self):
-        return [v for v in self._variables if 'w' not in self._variables[v]]
+        return [var for var in self._variables if self._variables[var] == Actions.ReadOnly]
 
     def write_variables(self):
-        return [v for v in self._variables if 'w' in self._variables[v]]
+        return [var for var in self._variables if self._variables[var] != Actions.ReadOnly]
 
     def arrays(self):
         return self._arrays
@@ -79,79 +77,61 @@ class Module(ASTNode):
     def host_references(self):
         return self._host_references
 
-    def properties_to_synchronize(self):
-        #return {p for p in self._properties if self._properties[p][0] == 'r'}
-        return {p for p in self._properties}
-
-    def write_properties(self):
-        return {p for p in self._properties if 'w' in self._properties[p]}
-
-    def contact_properties_to_synchronize(self):
-        #return {cp for cp in self._contact_properties if self._contact_properties[cp][0] == 'r'}
-        return {cp for cp in self._contact_properties}
-
-    def write_contact_properties(self):
-        return {cp for cp in self._contact_properties if 'w' in self._contact_properties[cp]}
-
-    def arrays_to_synchronize(self):
-        #return {a for a in self._arrays if a.sync() and self._arrays[a][0] == 'r'}
-        return {a for a in self._arrays if a.sync()}
-
-    def write_arrays(self):
-        return {a for a in self._arrays if a.sync() and 'w' in self._arrays[a]}
-
     def add_array(self, array, write=False):
         array_list = array if isinstance(array, list) else [array]
-        character = 'w' if write else 'r'
+        new_op = 'w' if write else 'r'
 
-        for a in array_list:
-            assert isinstance(a, Array), \
-                "Module.add_array(): given element is not of type Array!"
+        for array in array_list:
+            assert isinstance(array, Array), \
+                "Module.add_array(): given element is not of type Array."
 
-            self._arrays[a] = character if a not in self._arrays else \
-                              self._arrays[a] + character
+            action = Actions.NoAction if array not in self._arrays else self._arrays[array]
+            self._arrays[array] = Actions.update_rule(action, new_op)
 
     def add_variable(self, variable, write=False):
         variable_list = variable if isinstance(variable, list) else [variable]
-        character = 'w' if write else 'r'
+        new_op = 'w' if write else 'r'
 
-        for v in variable_list:
-            assert isinstance(v, Var), \
+        for var in variable_list:
+            assert isinstance(var, Var), \
                 "Module.add_variable(): given element is not of type Var!"
 
-            self._variables[v] = character if v not in self._variables else \
-                                 self._variables[v] + character
+            action = Actions.NoAction if var not in self._variables else self._variables[var]
+            self._variables[var] = Actions.update_rule(action, new_op)
 
     def add_property(self, prop, write=False):
         prop_list = prop if isinstance(prop, list) else [prop]
-        character = 'w' if write else 'r'
+        new_op = 'w' if write else 'r'
 
-        for p in prop_list:
-            assert isinstance(p, Property), \
-                "Module.add_property(): given element is not of type Property!"
+        for prop in prop_list:
+            assert isinstance(prop, Property), \
+                "Module.add_property(): given element is not of type Property."
 
-            self._properties[p] = character if p not in self._properties else \
-                                  self._properties[p] + character
+            action = Actions.NoAction if prop not in self._properties else self._properties[prop]
+            self._properties[prop] = Actions.update_rule(action, new_op)
 
     def add_contact_property(self, contact_prop, write=False):
         contact_prop_list = contact_prop if isinstance(contact_prop, list) else [contact_prop]
-        character = 'w' if write else 'r'
+        new_op = 'w' if write else 'r'
+
+        for contact_prop in contact_prop_list:
+            assert isinstance(contact_prop, ContactProperty), \
+                "Module.add_contact_property(): given element is not of type ContactProperty."
 
-        for cp in contact_prop_list:
-            assert isinstance(cp, ContactProperty), \
-                "Module.add_contact_property(): given element is not of type ContactProperty!"
+            action = Actions.NoAction if contact_prop not in self._contact_properties else \
+                     self._contact_properties[contact_prop]
 
-            self._contact_properties[cp] = character if cp not in self._contact_properties else \
-                                           self._contact_properties[cp] + character
+            self._contact_properties[contact_prop] = Actions.update_rule(action, new_op)
 
     def add_feature_property(self, feature_prop):
         feature_prop_list = feature_prop if isinstance(feature_prop, list) else [feature_prop]
 
         for fp in feature_prop_list:
             assert isinstance(fp, FeatureProperty), \
-                "Module.add_feature_property(): given element is not of type FeatureProperty!"
+                "Module.add_feature_property(): given element is not of type FeatureProperty."
 
-            self._feature_properties[fp] = 'r'
+            # Feature properties cannot be written into
+            self._feature_properties[fp] = Actions.ReadOnly
 
     def add_host_reference(self, elem):
         self._host_references.add(elem)
diff --git a/src/pairs/transformations/devices.py b/src/pairs/transformations/devices.py
index 45764a5b7c1a6f0fe4b1bd52e66d98b520999bca..3e7c126f08e8df0a8fa914a359c4379c9ed85083 100644
--- a/src/pairs/transformations/devices.py
+++ b/src/pairs/transformations/devices.py
@@ -1,5 +1,5 @@
 import math
-from pairs.ir.scalars import ScalarOp
+from pairs.ir.actions import Actions
 from pairs.ir.block import Block
 from pairs.ir.branches import Filter
 from pairs.ir.cast import Cast
@@ -10,6 +10,7 @@ from pairs.ir.lit import Lit
 from pairs.ir.loops import For
 from pairs.ir.module import ModuleCall
 from pairs.ir.mutator import Mutator
+from pairs.ir.scalars import ScalarOp
 from pairs.ir.types import Types
 
 
@@ -34,34 +35,33 @@ class AddDeviceCopies(Mutator):
                     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
 
-                    for array in s.module.arrays_to_synchronize():
-                        write = array in s.module.write_arrays()
-                        new_stmts += [CopyArray(s.sim, array, copy_context, write)]
+                    for array, action in s.module.arrays().items():
+                        new_stmts += [CopyArray(s.sim, array, copy_context, action)]
 
-                    for prop in s.module.properties_to_synchronize():
-                        write = prop in s.module.write_properties()
-                        new_stmts += [CopyProperty(s.sim, prop, copy_context, write)]
+                    for prop, action in s.module.properties().items():
+                        new_stmts += [CopyProperty(s.sim, prop, copy_context, action)]
 
-                    for contact_prop in s.module.contact_properties_to_synchronize():
-                        write = prop in s.module.write_contact_properties()
-                        new_stmts += [CopyContactProperty(s.sim, contact_prop, copy_context, write)]
+                    for contact_prop, action in s.module.contact_properties().items():
+                        new_stmts += [CopyContactProperty(s.sim, contact_prop, copy_context, action)]
 
                     if self.module_resizes[s.module] and s.module.run_on_device:
-                        new_stmts += [CopyArray(s.sim, s.sim.resizes, Contexts.Device, False)]
+                        new_stmts += [CopyArray(s.sim, s.sim.resizes, Contexts.Device, Actions.Ignore)]
 
                     if s.module.run_on_device:
-                        for var in s.module.variables_to_synchronize():
-                            new_stmts += [CopyVar(s.sim, var, Contexts.Device)]
+                        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)]
 
                 new_stmts.append(s)
 
                 if isinstance(s, ModuleCall):
                     if s.module.run_on_device:
-                        for var in s.module.variables_to_synchronize():
-                            new_stmts += [CopyVar(s.sim, var, Contexts.Host)]
+                        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, False)]
+                            new_stmts += [CopyArray(s.sim, s.sim.resizes, Contexts.Host, Actions.Ignore)]
 
         ast_node.stmts = new_stmts
         return ast_node