Skip to content
Snippets Groups Projects

Draft: Jt/bht

Closed jakut77 requested to merge jt/bht into main
6 unresolved threads
Files
14
+ 316
0
import numpy as np
class MainApp:
def __init__(self):
"""Initialize the MainApp with a root node."""
self.rootNode = TreeNode(x=0, y=0, width=100, height=100)
def BuildTree(self, particles):
"""Build the Quadtree by inserting particles.
Args:
particles (list): A list of Particle objects to be inserted into the Quadtree.
"""
self.ResetTree() # Empty the tree
for particle in particles:
self.rootNode.insert(particle)
def ResetTree(self):
"""Reset the Quadtree by reinitializing the root node."""
self.rootNode = TreeNode(x=0, y=0, width=100, height=100)
class Particle:
def __init__(self, x, y, mass):
"""Initialize a Particle with x and y coordinates and mass."""
self.x = x
self.y = y
self.mass = mass
self.vx = 0.0 # Velocity component in x direction
self.vy = 0.0 # Velocity component in y direction
self.fx = 0.0 # Force component in x direction
self.fy = 0.0 # Force component in y direction
class TreeNode:
def __init__(self, x, y, width, height):
"""Initialize a TreeNode representing a quadrant in the Quadtree.
Args:
x (float): x-coordinate of the node.
y (float): y-coordinate of the node.
width (float): Width of the node.
height (float): Height of the node.
"""
self.x = x
self.y = y
self.width = width
self.height = height
self.particle = None # Particle contained in this node
self.center_of_mass = np.array([(x + width) / 2, (y + width) / 2])
self.total_mass = 0
self.children = np.empty(4, dtype=object) # Children nodes (SW, SE, NW, NE)
def contains(self, particle):
"""Check if the particle is within the bounds of this node.
Args:
particle (Particle): The particle to be checked.
Returns:
bool: True if the particle is within the node's bounds, False otherwise.
"""
return (self.x <= particle.x < self.x + self.width and self.y <= particle.y < self.y + self.height)
def insert(self, particle):
"""Insert a particle into the Quadtree.
Args:
particle (Particle): The particle to be inserted.
Returns:
bool: True if the particle is inserted, False otherwise.
"""
if not self.contains(particle):
return False # Particle doesn't belong in this node
if self.particle is None and all(child is None for child in self.children):
# If the node is empty and has no children, insert particle here.
self.particle = particle
#print(f'particle inserted: x={round(self.particle.x,2)}, y={round(self.particle.y,2)}')
return True # Particle inserted in an empty node
if all(child is None for child in self.children):
# If no children exist, create and insert both particles
self.subdivide()
self.insert(self.particle) # Reinsert existing particle
self.insert(particle) # Insert new particle
self.particle = None # Clear particle from this node
else:
# If the node has children, insert particle in the child node.
quad_index = self.get_quadrant(particle)
if self.children[quad_index] is None:
# Create a child node if it doesn't exist
self.children[quad_index] = TreeNode(self.x + (quad_index % 2) * (self.width / 2),
self.y + (quad_index // 2) * (self.height / 2), self.width / 2,
self.height / 2)
self.children[quad_index].insert(particle)
def subdivide(self):
"""Subdivide the node into four quadrants."""
sub_width = self.width / 2
sub_height = self.height / 2
self.children[0] = TreeNode(self.x, self.y, sub_width, sub_height) # SW
self.children[1] = TreeNode(self.x + sub_width, self.y, sub_width, sub_height) # SE
self.children[2] = TreeNode(self.x, self.y + sub_height, sub_width, sub_height) # NW
self.children[3] = TreeNode(self.x + sub_width, self.y + sub_height, sub_width, sub_height) # NE
def get_quadrant(self, particle):
"""Determine the quadrant index for a particle based on its position.
Args:
particle (Particle): The particle to determine the quadrant index for.
Returns:
int: Quadrant index (0 for SW, 1 for SE, 2 for NW, 3 for NE).
"""
mid_x = self.x + self.width / 2
mid_y = self.y + self.height / 2
quad_index = (particle.x >= mid_x) + 2 * (particle.y >= mid_y)
return quad_index
def print_tree(self, depth=0):
"""Print the structure of the Quadtree.
Args:
depth (int): Current depth in the tree (for indentation in print).
"""
if self.particle:
print(
f"{' ' * depth}Particle at ({round(self.particle.x,2)}, {round(self.particle.y,2)}) in Node ({self.x}, {self.y}), size={self.width}"
)
else:
print(f"{' ' * depth}Node ({self.x}, {self.y}) - Width: {self.width}, Height: {self.height}")
for child in self.children:
if child:
child.print_tree(depth + 2)
def ComputeMassDistribution(self):
"""Compute the mass distribution for the tree nodes.
This function calculates the total mass and the center of mass
for each node in the Quadtree. It's a recursive function that
computes the mass distribution starting from the current node.
Note:
This method modifies the 'mass' and 'center_of_mass' attributes
for each node in the Quadtree.
Returns:
None
"""
if self.particle is not None:
# Node contains only one particle
self.center_of_mass = np.array([self.particle.x, self.particle.y])
self.total_mass = self.particle.mass
else:
# Multiple particles in node
total_mass = 0
center_of_mass_accumulator = np.array([0.0, 0.0])
for child in self.children:
if child is not None:
# Recursively compute mass distribution for child nodes
child.ComputeMassDistribution()
total_mass += child.total_mass
center_of_mass_accumulator += child.total_mass * child.center_of_mass
if total_mass > 0:
self.center_of_mass = center_of_mass_accumulator / total_mass
self.total_mass = total_mass
else:
# If total mass is 0 or no child nodes have mass, leave values as default
pass
#self.center_of_mass = np.array([(x+width)/2, (y+width)/2])
#self.total_mass = 0
def CalculateForceFromTree(self, target_particle, theta=1.0):
"""Calculate the force on a target particle using the Barnes-Hut algorithm.
Args:
target_particle (Particle): The particle for which the force is calculated.
theta (float): The Barnes-Hut criterion for force approximation.
Returns:
np.ndarray: The total force acting on the target particle.
"""
total_force = np.array([0.0, 0.0])
if self.particle is not None:
# Node contains only one particle
if self.particle != target_particle:
# Calculate gravitational force between target_particle and node's particle
force = self.GravitationalForce(target_particle, self.particle)
total_force += force
else:
if self.total_mass == 0:
return total_force
r = np.linalg.norm(np.array([target_particle.x, target_particle.y]) - self.center_of_mass)
d = max(self.width, self.height)
if d / r < theta:
# Calculate gravitational force between target_particle and "node particle" representing cluster
node_particle = Particle(self.center_of_mass[0], self.center_of_mass[1], self.total_mass)
force = self.GravitationalForce(target_particle, node_particle)
total_force += force
else:
for child in self.children:
if child is not None:
# Recursively calculate force from child nodes
if target_particle is not None: # Check if the target_particle is not None
force = child.CalculateForceFromTree(target_particle)
total_force += force
return total_force
def CalculateForce(self, target_particle, particle, theta=1.0):
"""Calculate the gravitational force between two particles.
Args:
target_particle (Particle): The particle for which the force is calculated.
particle (Particle): The particle exerting the force.
Returns:
np.ndarray: The force vector acting on the target particle due to 'particle'.
"""
force = np.array([0.0, 0.0])
print('function CalculateForce is called')
if self.particle is not None:
# Node contains only one particle
if self.particle != target_particle:
# Calculate gravitational force between target_particle and node's particle
force = self.GravitationalForce(target_particle, self.particle)
else:
if target_particle is not None and particle is not None: # Check if both particles are not None
r = np.linalg.norm(
np.array([target_particle.x, target_particle.y]) - np.array([particle.x, particle.y]))
d = max(self.width, self.height)
if d / r < theta:
# Calculate gravitational force between target_particle and particle
force = self.GravitationalForce(target_particle, particle)
else:
for child in self.children:
if child is not None:
# Recursively calculate force from child nodes
force += child.CalculateForce(target_particle, particle)
return force
def GravitationalForce(self, particle1, particle2):
"""Calculate the gravitational force between two particles.
Args:
particle1 (Particle): First particle.
particle2 (Particle): Second particle.
Returns:
np.ndarray: The gravitational force vector between particle1 and particle2.
"""
#G = 6.674 * (10 ** -11) # Gravitational constant
G = 1
dx = particle2.x - particle1.x
dy = particle2.y - particle1.y
cutoff_radius = 5
r = max(np.sqrt(dx**2 + dy**2), cutoff_radius)
force_magnitude = G * particle1.mass * particle2.mass / (r**2)
force_x = force_magnitude * (dx / r)
force_y = force_magnitude * (dy / r)
return np.array([force_x, force_y])
# Helper method to retrieve all particles in the subtree
def particles_in_subtree(self):
"""Retrieve all particles in the subtree rooted at this node.
Returns:
list: A list of particles in the subtree rooted at this node.
"""
particles = []
if self.particle is not None:
particles.append(self.particle)
else:
for child in self.children:
if child is not None:
particles.extend(child.particles_in_subtree())
return particles
def compute_center_of_mass(self):
"""Compute the center of mass for the node."""
print('Function compute_center_of_mass is called')
if self.particle is not None:
self.center_of_mass = np.array([self.particle.x, self.particle.y])
self.mass = self.particle.mass
else:
total_mass = 0
center_of_mass_accumulator = np.array([0.0, 0.0])
for child in self.children:
if child is not None:
child.compute_center_of_mass()
total_mass += child.mass
center_of_mass_accumulator += child.mass * child.center_of_mass
if total_mass > 0:
self.center_of_mass = center_of_mass_accumulator / total_mass
self.mass = total_mass
else:
self.center_of_mass = np.array([0.0, 0.0])
self.mass = 0
Loading