From 65004c9ca6435ebbce8eb28a7b503f9f2e824e34 Mon Sep 17 00:00:00 2001
From: Behzad Safaei <iwia103h@alex1.nhr.fau.de>
Date: Mon, 10 Feb 2025 15:45:38 +0100
Subject: [PATCH] Add whole-program-generation sample

---
 data/sd_planes.input                          |   6 +
 examples/modular/spring_dashpot.py            |   4 +-
 .../spring_dashpot.py                         | 153 ++++++++++++++++++
 3 files changed, 162 insertions(+), 1 deletion(-)
 create mode 100644 data/sd_planes.input
 create mode 100644 examples/whole-program-generation/spring_dashpot.py

diff --git a/data/sd_planes.input b/data/sd_planes.input
new file mode 100644
index 0000000..e47832e
--- /dev/null
+++ b/data/sd_planes.input
@@ -0,0 +1,6 @@
+0, 1, 0, 0, 0, 1, 0, 0, 13
+0, 1, 0, 0, 0, 0, 1, 0, 13
+0, 1, 0, 0, 0, 0, 0, 1, 13
+0, 1, 1, 1, 1, -1, 0, 0, 13
+0, 1, 1, 1, 1, 0, -1, 0, 13
+0, 1, 1, 1, 1, 0, 0, -1, 13
diff --git a/examples/modular/spring_dashpot.py b/examples/modular/spring_dashpot.py
index 3109fa5..4374e78 100644
--- a/examples/modular/spring_dashpot.py
+++ b/examples/modular/spring_dashpot.py
@@ -46,7 +46,7 @@ def euler(i):
     angular_velocity[i] += wdot * dt
 
 def gravity(i):
-    force[i][2] = force[i][2] - mass[i] * gravity_SI
+    force[i][2] -= mass[i] * gravity_SI
 
 
 file_name = os.path.basename(__file__)
@@ -99,6 +99,8 @@ psim.pbc([False, False, False])
 psim.build_cell_lists(linkedCellWidth)
 
 psim.setup(update_mass_and_inertia, symbols={'infinity': math.inf })
+
+# The order of user-defined functions is not important here since they are only callable individually
 psim.compute(spring_dashpot, linkedCellWidth)
 psim.compute(euler, parameters={'dt': pairs.real()})
 psim.compute(gravity, symbols={'gravity_SI': gravity_SI })
diff --git a/examples/whole-program-generation/spring_dashpot.py b/examples/whole-program-generation/spring_dashpot.py
new file mode 100644
index 0000000..f4796cf
--- /dev/null
+++ b/examples/whole-program-generation/spring_dashpot.py
@@ -0,0 +1,153 @@
+import math
+import pairs
+import sys
+import os
+
+def update_mass_and_inertia(i):
+    rotation_matrix[i] = diagonal_matrix(1.0)
+    rotation[i] = default_quaternion()
+
+    if is_sphere(i):
+        inv_inertia[i] = inversed(diagonal_matrix(0.4 * mass[i] * radius[i] * radius[i]))
+
+    else:
+        mass[i] = infinity
+        inv_inertia[i] = 0.0
+
+def spring_dashpot(i, j):
+    delta_ij = -penetration_depth(i, j)
+    skip_when(delta_ij < 0.0)
+    
+    velocity_wf_i = linear_velocity[i] + cross(angular_velocity[i], contact_point(i, j) - position[i])
+    velocity_wf_j = linear_velocity[j] + cross(angular_velocity[j], contact_point(i, j) - position[j])
+    
+    rel_vel = -(velocity_wf_i - velocity_wf_j)
+    rel_vel_n = dot(rel_vel, contact_normal(i, j))
+    rel_vel_t = rel_vel - rel_vel_n * contact_normal(i, j)
+
+    fNabs = stiffness[i,j] * delta_ij + damping_norm[i,j] * rel_vel_n
+    fN = fNabs * contact_normal(i, j)
+
+    fTabs = min(damping_tan[i,j] * length(rel_vel_t), friction[i, j] * fNabs)
+    fT = fTabs * normalized(rel_vel_t)
+
+    partial_force = fN + fT
+    apply(force, partial_force)
+    apply(torque, cross(contact_point(i, j) - position, partial_force))
+
+def euler(i):
+    inv_mass = 1.0 / mass[i]
+    position[i] +=  0.5 * inv_mass * force[i] * dt * dt + linear_velocity[i] * dt
+    linear_velocity[i] += inv_mass * force[i] * dt
+    wdot = rotation_matrix[i] * (inv_inertia[i] * torque[i]) * transposed(rotation_matrix[i])
+    phi = angular_velocity[i] * dt + 0.5 * wdot * dt * dt
+    rotation[i] = quaternion(phi, length(phi)) * rotation[i]
+    rotation_matrix[i] = quaternion_to_rotation_matrix(rotation[i])
+    angular_velocity[i] += wdot * dt
+
+def gravity(i):
+    force[i][2] -= mass[i] * gravity_SI
+
+
+# Number of 'type' features and their pair-wise properties
+ntypes = 2
+stiffness_SI = [5000 for i in range(ntypes * ntypes)]
+dampingNorm_SI = [20 for i in range(ntypes * ntypes)]
+dampingTan_SI = [20 for i in range(ntypes * ntypes)]
+friction_SI = [0.2 for i in range(ntypes * ntypes)]
+
+# Domain size
+domainSize_SI=[1, 1, 1]
+
+# Parameters required for generating the initial grid of particles 'dem_sc_grid'
+generationSpacing_SI = 0.1
+diameter_SI = 0.09
+minDiameter_SI = diameter_SI
+maxDiameter_SI = diameter_SI
+initialVelocity_SI = 0.5
+densityParticle_SI = 1000
+
+# Linked cell width 
+linkedCellWidth = 1.01 * maxDiameter_SI
+
+# Required symbol for the 'gravity' module
+gravity_SI = 9.81
+
+# Required symbol for the 'euler' module
+dt_SI = 1e-3
+
+# VTK frequency
+visSpacing = 60
+
+timeSteps = 6000
+
+file_name = os.path.basename(__file__)
+file_name_without_extension = os.path.splitext(file_name)[0]
+
+psim = pairs.simulation(
+    file_name_without_extension,
+    [pairs.sphere(), pairs.halfspace()],
+    timesteps=timeSteps,
+    double_prec=True,
+    particle_capacity=1000000,
+    neighbor_capacity=20,
+    debug=True, 
+    generate_whole_program=True)
+
+target = sys.argv[1] if len(sys.argv[1]) > 1 else "none"
+if target == 'gpu':
+    psim.target(pairs.target_gpu())
+elif target == 'cpu':
+    psim.target(pairs.target_cpu())
+else:
+    print(f"Invalid target, use {sys.argv[0]} <cpu/gpu>")
+
+
+psim.add_position('position')
+psim.add_property('mass', pairs.real())
+psim.add_property('linear_velocity', pairs.vector())
+psim.add_property('angular_velocity', pairs.vector())
+psim.add_property('force', pairs.vector(), volatile=True)
+psim.add_property('torque', pairs.vector(), volatile=True)
+psim.add_property('radius', pairs.real())
+psim.add_property('normal', pairs.vector())
+psim.add_property('inv_inertia', pairs.matrix())
+psim.add_property('rotation_matrix', pairs.matrix())
+psim.add_property('rotation', pairs.quaternion())
+
+psim.add_feature('type', ntypes)
+psim.add_feature_property('type', 'stiffness', pairs.real(), stiffness_SI)
+psim.add_feature_property('type', 'damping_norm', pairs.real(), dampingNorm_SI)
+psim.add_feature_property('type', 'damping_tan', pairs.real(), dampingTan_SI)
+psim.add_feature_property('type', 'friction', pairs.real(), friction_SI)
+
+psim.set_domain_partitioner(pairs.block_forest())
+psim.pbc([False, False, False])
+psim.build_cell_lists(linkedCellWidth)
+
+psim.set_domain([0.0, 0.0, 0.0, domainSize_SI[0], domainSize_SI[1], domainSize_SI[2]])
+
+# Generate particles
+psim.dem_sc_grid(domainSize_SI[0], domainSize_SI[1], domainSize_SI[2], 
+                 generationSpacing_SI,
+                 diameter_SI, 
+                 minDiameter_SI, 
+                 maxDiameter_SI, 
+                 initialVelocity_SI, 
+                 densityParticle_SI, 
+                 ntypes)
+
+# Read planes from file
+psim.read_particle_data( "data/sd_planes.input", ['type', 'mass', 'position', 'normal', 'flags'], pairs.halfspace())
+
+psim.vtk_output(f"output/dem_{target}", frequency=visSpacing)
+
+psim.setup(update_mass_and_inertia, symbols={'infinity': math.inf })
+
+# The user-defined functions are added to the timestep loop in the order they are given to 'compute'
+psim.compute(spring_dashpot, linkedCellWidth)
+psim.compute(gravity, symbols={'gravity_SI': gravity_SI })
+psim.compute(euler, symbols={'dt': dt_SI})
+
+psim.generate()
+
-- 
GitLab