Start here
Getting started
This documentation describes the CUDA implementation on the repository’s main branch. YASPS is research software: expect a source build, an NVIDIA development environment, and JIT compilation the first time a new symbolic computation is used.
Requirements
The current code assumes:
- Linux or a Linux-like CUDA development environment;
- an NVIDIA GPU, driver, and CUDA Toolkit with
nvcc; - Python, Cython, NumPy, and PyCUDA;
- a C++17 compiler;
- Eigen 3 headers available at
/usr/include/eigen3; - support for the
sm_89CUDA target used by the generated build commands.
The
sm_89target is currently hard-coded in several JIT paths. On a different GPU architecture, update the-arch=sm_89flags in the kernel generators before expecting compilation to work.
Install from the repository
Clone the project, enter its Python package directory, and install it:
git clone https://github.com/txstc55/yasps.git
cd yasps/yasps
python -m pip install --upgrade pip setuptools wheel Cython numpy pycuda
python -m pip install .
On Debian or Ubuntu, Eigen and the host build tools are normally installed with:
sudo apt-get install build-essential python3-dev libeigen3-dev
The repository also contains yasps/install.sh, but its final step invokes a maintainer-specific notification command and expects a local fcm_token. For a normal installation, use the direct Python commands above.
Verify CUDA and the package
python - <<'PY'
import pycuda.autoinit
import pycuda.driver as cuda
from yasps import scene
print("CUDA device:", cuda.Device(0).name())
print("YASPS import: OK")
PY
Importing YASPS initializes PyCUDA, so this check must run where a CUDA device is visible.
A complete quadratic example
This model creates four scalar degrees of freedom and minimizes
E(x) = ½ Σᵢ (xᵢ − xᵢ*)²
import numpy as np
from yasps import scene
model = scene("quadratic_demo")
mesh = model.addMesh("model")
dofs = mesh.addPrimitive("dofs", numInstances=4)
x = dofs.addAttribute("x", rows=1, cols=1)
x.updateValue(np.array([4.0, -2.0, 7.0, 1.0], dtype=np.float64))
target = dofs.addConstant("target", rows=1, cols=1)
target.updateValue(np.array([1.0, 1.0, 1.0, 1.0], dtype=np.float64))
dx = x - target
quadratic_expression = 0.5 * dx * dx
quadratic = dofs.addAttribute(
"quadratic",
computed_attribute=quadratic_expression,
)
model.addEnergy(quadratic, projection_method=2)
model.addMinimizeTarget([x])
# YASPS solves H * direction = gradient.
direction = model.minimizeEnergy(tolerance=1e-8)[0]
x.updateValue(x.value - direction, deepCopy=True)
print(x.value.get())
The important sequence is:
- Build a scene hierarchy.
- Add data attributes and constants.
- Build an unnamed symbolic expression.
- Bind the expression to a name with
addAttribute. - Register the scalar per-instance energy.
- Register the data attributes to minimize against.
- Ask for a solve and apply the returned direction.
The first solve generates and compiles derivative, assembly, and solver kernels. Later solves reuse the generated code as long as the symbolic structure and relevant sparse layout remain compatible.
Working with GPU values
Attribute values are flattened pycuda.gpuarray.GPUArray objects:
gpu_values = x.value
host_values = x.value.get()
updateValue accepts NumPy arrays, Python values convertible to NumPy, or PyCUDA arrays:
x.updateValue(host_array)
x.updateValue(other_gpu_array) # may reuse the supplied GPU storage
x.updateValue(other_gpu_array, deepCopy=True) # copy into owned storage
Call expression.compute() to materialize an otherwise symbolic expression:
values = quadratic_expression.compute().value.get()
Where to go next
- Read the mental model before combining attributes from different primitives.
- Learn the attribute expression syntax.
- Use Connectivity and JOIN for mesh topology.
- Use Primitive unions for mixed parameterizations.
- Follow Energies and minimization for Newton and contact loops.
- Put the entire frontend together in the five-bunny mixed-separation walkthrough.