Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.3.0] - 2022-09-23
### Added
- dsmc module

## [0.3.0] - 2022-09-22
### Added
- octree module
Expand Down
1 change: 1 addition & 0 deletions dsmc/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .dsmc import DSMC
143 changes: 140 additions & 3 deletions dsmc/dsmc.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,144 @@
import math
import numpy
import numpy as np
from numba import njit
from . import particles as prt
from . import octree as oc

@njit
def test():
return 1.0
def _push(velocities, positions, dt):
return positions + velocities*dt

@njit
def _boundary(velocities, positions, domain):
for p in range(len(positions)):
while not oc._is_inside(positions[p], domain):
for i in range(3):
if positions[p][i] < domain[i][0]:
positions[p][i] = 2.0 * domain[i][0] - positions[p][i]
velocities[p][i] *= -1.0
if positions[p][i] > domain[i][1]:
positions[p][i] = 2.0 * domain[i][1] - positions[p][i]
velocities[p][i] *= -1.0

return (velocities, positions)

@njit
def _calc_prob(vel1 : np.ndarray, vel2 : np.ndarray, sigma_T : float, Vc : float, dt : float, w : float, N : int) -> np.single:
"""
Parameters
----------
vel1 : velocity
vel2 : velocity
sigma_T : float
total cross section
Vc : float
cell volume
w : float
weight
N : int
number of particles

Returns
-------
collision proability : float
"""
return np.linalg.norm(vel1 - vel2) * sigma_T * dt * w * N / Vc;

@njit
def _calc_post_col_vels(velocity1 : np.ndarray, velocity2 : np.ndarray, mass1 : float, mass2 : float, rel_vel_module : float, rand_number1 : float, rand_number2 : float) -> tuple[np.ndarray, np.ndarray]:
mass12 = (mass1 + mass2)
mass1_12 = (mass1 / mass12)
mass2_12 = (mass2 / mass12)

cos_xi = (2.0 * rand_number1 - 1.0)
sin_xi = (np.sqrt(1.0 - cos_xi * cos_xi))
epsilon = (2.0 * np.pi * rand_number2)

centre_of_mass_velocity = (velocity1 * mass1 + velocity2 * mass2) * (1.0 / mass12)

rel_velocity_new = np.empty((3, ))

rel_velocity_new[0] = rel_vel_module * cos_xi
rel_velocity_new[1] = rel_vel_module * sin_xi * np.cos(epsilon)
rel_velocity_new[2] = rel_vel_module * sin_xi * np.sin(epsilon)

return (centre_of_mass_velocity + rel_velocity_new * mass2_12 , centre_of_mass_velocity - rel_velocity_new * mass1_12)

@njit
def _update_velocities(permutations : np.ndarray, velocities : np.ndarray, mass : float, sigma_T : float, Vc : float, dt : float, w : float, offset : int, N : int) -> np.ndarray:
i = 1
while i < N:
p1 = permutations[offset + i - 1]
p2 = permutations[offset + i]
P = _calc_prob(velocities[p1], velocities[p2], sigma_T, Vc, dt, w, N)
R = np.random.random(3)

if R[0] < P:
new_vels = _calc_post_col_vels(velocities[p1], velocities[p2], mass, mass, np.linalg.norm(velocities[p1] - velocities[p2]), R[1], R[2])
velocities[p1] = new_vels[0]
velocities[p2] = new_vels[1]

i += 2

return velocities

@njit
def _update_vels(permutations : np.ndarray, velocities : np.ndarray, mass : float, sigma_T : float, dt : float, w : float, elem_offsets : np.ndarray, number_elements : np.ndarray, number_children : np.ndarray, cell_boxes : np.ndarray, Nleafs : int) -> np.ndarray:
for i in range(Nleafs):
if not number_children[i]:
Vc = oc.get_V(cell_boxes[i])
velocities = _update_velocities(permutations, velocities, mass, sigma_T, Vc, dt, w, elem_offsets[i], number_elements[i])

return velocities

class DSMC:
def __init__(self):
self.clear()

def clear(self):
self.particles = prt.Particles()
self.octree = oc.Octree()
self.w = None
self.domain = None
self.sigma_T = 3.631681e-19
self.mass = None

def advance(self, dt):
if self.domain is None:
raise Exception("simulation domain not defined")
if self.particles.N == 0:
raise Exception("no particles created")
if self.w == None:
raise Exception("particle weight not set")

self.octree.build(self.particles.Pos)

self.particles.VelPos = (self._update_velocities(dt), self.particles.Pos)
positions = _push(self.particles.Vel, self.particles.Pos, dt)
self.particles.VelPos = _boundary(self.particles.Vel, positions, self.domain)

def _update_velocities(self, dt):
Nleafs : int = len(self.octree.leafs)
elem_offsets : np.ndarray = np.array([leaf.elem_offset for leaf in self.octree.leafs], dtype=int)
number_elements : np.ndarray = np.array([leaf.number_elements for leaf in self.octree.leafs], dtype=int)
number_children : np.ndarray = np.array([leaf.number_children for leaf in self.octree.leafs], dtype=int)
cell_boxes : np.ndarray = np.array([box for box in self.octree.cell_boxes])

return _update_vels(self.octree.permutations, self.particles.Vel, self.mass, self.sigma_T, dt, self.w, elem_offsets, number_elements, number_children, cell_boxes, Nleafs)

def create_particles(self, box, T, n):
N = int(round(n / self.w))
print("creating {} particles".format(N))
self.particles.create_particles(box, self.mass, T, N)

print("now containing {} particles, {} total".format(N, self.particles.N))

def set_domain(self, domain):
self.domain = np.array(domain)

def set_mass(self, mass):
self.mass = mass

def set_weight(self, w):
self.octree.w = w
self.w = w
5 changes: 5 additions & 0 deletions dsmc/octree.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ def _sort(permutations : npt.NDArray, box : npt.NDArray, positions : npt.NDArray
Nnew += 1

return new_permutations, Nnew

@njit
def get_V(box):
return (box[0][1] - box[0][0]) * (box[1][1] - box[1][0]) * (box[2][1] - box[2][0])

class Leaf:
def __init__(self):
Expand Down Expand Up @@ -137,6 +141,7 @@ def clear(self):
self.level = 0

def build(self, positions):
self.clear()
self._create_root(positions)
self.permutations = np.array([i for i in range(len(positions))])

Expand Down
20 changes: 12 additions & 8 deletions dsmc/particles.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,14 +121,18 @@ def VelPos(self):

@VelPos.setter
def VelPos(self, vel_pos):
assert vel_pos[0] == vel_pos[1]
assert isinstance(vel_pos[0], np.array)
assert isinstance(vel_pos[1], np.array)
assert len(vel_pos[0]) == len(vel_pos[1])

self._velocities = vel_pos[0]
self._positions = vel_pos[1]
self._N = len(self._positions)

def create_particles(self, X, mass, T, N):
self._velocities = get_velocities(T, mass, N)
self._positions = calc_positions(X[0], X[1], X[2], N)
self._N = N

def create_particles(self, X, mass, T, N):
if self._N == 0:
self._velocities = get_velocities(T, mass, N)
self._positions = calc_positions(X[0], X[1], X[2], N)
self._N = N
else:
self._velocities = np.concatenate((self._velocities, get_velocities(T, mass, N)))
self._positions = np.concatenate((self._positions, calc_positions(X[0], X[1], X[2], N)))
self._N += N
38 changes: 38 additions & 0 deletions examples/plot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import matplotlib.pyplot as plt
import csv
import numpy as np

if __name__ == '__main__':
res = []

with open('test.csv') as file:
reader = csv.reader(file, delimiter=',')
res = []

for line in reader:
l = [m for m in line if m]
data = (np.array(l)).astype(float)
data = np.sort(data)

N = 100
sor = np.zeros((N, ))
x = np.zeros((N, ))
dx = 0.05/N
x[0] = dx
q = 0

for i in range(len(data)):
while data[i] > x[q]:
q += 1
x[q] = x[q - 1] + dx

sor[q] += 1


x.resize(q)
sor.resize(q)

res.append((x, sor))

plt.plot(res[-1][0], res[-1][1])
plt.show()
40 changes: 40 additions & 0 deletions examples/shock_tube.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import dsmc

if __name__ == '__main__':
# general parameters
solver = dsmc.DSMC()
domain = ((-0.1e-3, 0.1e-3), (-0.1e-3, 0.1e-3), (0, 50e-3))
dt = 1e-7
w = 2.4134e+15
mass = 6.6422e-26
niter = 300

# low denisty particles
nhigh = 2.5e+20
Thigh = 300
Boxhigh = ((-0.1e-3, 0.1e-3), (-0.1e-3, 0.1e-3), (25e-3, 50e-3))

# high denisty particles
nlow = 2.5e+19
Tlow = 300
Boxlow = ((-0.1e-3, 0.1e-3), (-0.1e-3, 0.1e-3), (0, 25e-3))

solver.set_domain(domain)
solver.set_weight(w)
solver.set_mass(mass)

solver.create_particles(Boxlow, Tlow, nlow)
solver.create_particles(Boxhigh, Thigh, nhigh)

with open("test.csv", "w") as file:
for it in range(niter):
print("iteration {:4}/{}".format(it + 1, niter), end="\r", flush=True)
solver.advance(dt)

for pos in solver.particles.Pos:
file.write("{:.4e},".format(pos[2]))

file.write("\n")

print("")
print('done')
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from setuptools import setup
setup(
name='dsmc',
version='0.3.0',
version='0.4.0',
author='Leo Basov',
python_requires='>=3.6, <4',
packages=["dsmc"],
Expand Down
39 changes: 36 additions & 3 deletions tests/unit/test_dsmc/dsmc.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,39 @@
import unittest
from dsmc import dsmc
import numpy as np
from dsmc import dsmc as ds

class TestDSMC(unittest.TestCase):
def test_test(self):
self.assertEqual(1.0, dsmc.test())
def test_Constructor(self):
ds.DSMC()

def test__calc_prob(self):
vel1 : np.ndarray = np.array([1.0, 2.0, 3.0])
vel2 : np.ndarray = np.array([1.0, 2.0, 3.0])
sigma_T : float = 1.0
Vc : float = 1.0
dt : float = 1.0
w : float = 1.0
N : int = 1

res = ds._calc_prob(vel1, vel2, sigma_T, Vc, dt, w, N)

self.assertEqual(np.linalg.norm(vel1 - vel2), res)

def test__calc_post_col_vels(self):
velocity1 : np.ndarray = np.array([1.0, 2.0, 3.0])
velocity2 : np.ndarray = np.array([1.0, 2.0, 3.0])
mass1 : float = 1.0
mass2 : float = 1.0
rel_vel_module : float = 1.0
rand_number1 : float = 1.0
rand_number2 : float = 1.0

res = ds._calc_post_col_vels(velocity1, velocity2, mass1, mass2, rel_vel_module, rand_number1, rand_number2)

self.assertEqual(1.5, res[0][0])
self.assertEqual(2.0, res[0][1])
self.assertEqual(3.0, res[0][2])

self.assertEqual(0.5, res[1][0])
self.assertEqual(2.0, res[1][1])
self.assertEqual(3.0, res[1][2])
11 changes: 11 additions & 0 deletions tests/unit/test_dsmc/particles.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,14 @@ def test_create_particles(self):
self.assertTrue(particles.Pos[i][0] >= x[0] and particles.Pos[i][0] <= x[1])
self.assertTrue(particles.Pos[i][1] >= y[0] and particles.Pos[i][1] <= y[1])
self.assertTrue(particles.Pos[i][2] >= z[0] and particles.Pos[i][2] <= z[1])

particles.create_particles(X, mass, T, N)

self.assertEqual(2*N, len(particles.Pos))
self.assertEqual(2*N, len(particles.Vel))
self.assertEqual(2*N, particles.N)

for i in range(2*N):
self.assertTrue(particles.Pos[i][0] >= x[0] and particles.Pos[i][0] <= x[1])
self.assertTrue(particles.Pos[i][1] >= y[0] and particles.Pos[i][1] <= y[1])
self.assertTrue(particles.Pos[i][2] >= z[0] and particles.Pos[i][2] <= z[1])