Skip to content

Commit

Permalink
cleaned up package
Browse files Browse the repository at this point in the history
  • Loading branch information
will-roscoe committed Dec 14, 2023
1 parent bb63bec commit b04b82d
Show file tree
Hide file tree
Showing 16 changed files with 144 additions and 126 deletions.
4 changes: 2 additions & 2 deletions .github/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ ignore = [
]

[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["E402"] # ignore module imports
"**/tests/*" = ["E402"]
"**__init__.py" = ["F401", "F402"] # ignore module imports
"**/tests/*" = ["F401", "F402"]
11 changes: 5 additions & 6 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@


import cProfile
import pstats
import nbody as nb
from line_profiler import profile

LINE_PROFILE=1

bodies = nb.horizons_batch(('10','199','299','399','499','599','699','799','899'))

for i,color in enumerate(['#F28322','#BFBEBD', '#D9B391', '#63BAA6', '#F27A5E', '#BFAE99', '#D9B779', '#95BBBF', '#789EBF']):
for i,color in enumerate(['#F28322','#BFBEBD', '#D9B391', '#63BAA6', '#F27A5E',
'#BFAE99', '#D9B779', '#95BBBF', '#789EBF']):
bodies[i].color = color
phys = nb.Engine(dt=1000)
phys.attach_bodies(bodies)
Expand All @@ -23,7 +21,8 @@

sim = nb.mplVisual(engine=phys,
name='SS',
focus_body=phys.bodies[0], show_info=True, autoscale=False, step_skip_frames=100, step_skip_points=100, max_period=3, cache=False, do_picking=True)
focus_body=phys.bodies[0], show_info=True, autoscale=False,
step_skip_frames=100, step_skip_points=100, max_period=3, cache=False, do_picking=True)


'''
Expand Down
7 changes: 3 additions & 4 deletions nbody/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# NBody __init__.py
'''
"""
File Structure:
⸺ nbody/
├── core/
Expand Down Expand Up @@ -43,10 +43,9 @@
.core.visual
.tools.io
.tools.horizons
'''
"""
from .core.body import Body
from .core.engine import Engine
from .core.visual import mplVisual
from .core.visual import MPLVisual
from .tools.horizons import horizons_batch, horizons_query
from .tools.io import obj_from, export_obj

3 changes: 0 additions & 3 deletions nbody/build/config.py

This file was deleted.

Binary file removed nbody/build/gmpy-gmpy2-2.2.0a1.tar.gz
Binary file not shown.
Binary file removed nbody/build/gmpy2-2.0.8-cp27-cp27m-win32.whl
Binary file not shown.
Binary file removed nbody/build/gmpy2-2.0.8-cp27-cp27m-win_amd64.whl
Binary file not shown.
Binary file removed nbody/build/gmpy2-2.1.5-cp311-cp311-win_amd64.whl
Binary file not shown.
20 changes: 11 additions & 9 deletions nbody/core/base.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from __future__ import annotations
# Python Builtins
from numpy import ndarray

from numbers import Number
from collections.abc import Iterable
from mpmath import mp, fp
import gmpy2 as g

# Local error definitions.
from ..tools import errors as e

Expand All @@ -16,13 +16,13 @@
mp.pretty, fp.pretty = True, True


def _O(obj):
def _O(obj): # noqa: N802
if isinstance(obj,(Vector, Variable, HistoricVariable, HistoricVector)):
return obj.c()
else:
return obj

def _V(obj):
def _V(obj): # noqa: N802
if isinstance(obj, Iterable):
if len(obj) == 3:
return Vector(li=obj)
Expand Down Expand Up @@ -60,13 +60,14 @@ def typecheck(argtype):

class Variable:
def __init__(self,init_var,identity='Variable',units=None):
(self.record,self.identity,self.units) = typecheck(((_ntype(init_var)(init_var),NumType),(identity,str),(units,(str,NoneType))))
(self.record,self.identity,self.units) = typecheck(((_ntype(init_var)(init_var),NumType),
(identity,str),(units,(str,NoneType))))
self.units = (units if units else '')

def c(self):
return self.record

def next(self, val):
def next(self, val): # noqa: A003
self.record = _O(val)

def __len__(self):
Expand Down Expand Up @@ -184,7 +185,7 @@ def c(self):
return self.record[-1]


def next(self,next_val):
def next(self,next_val): # noqa: A003
temp = _O(next_val)
if isinstance(temp,NumType):
self.record.append(self.type(temp))
Expand Down Expand Up @@ -339,7 +340,8 @@ def __init__(self,x=None,y=None,z=None,li=None,identity=None,units_v=None):
li = ((x,y,z) if (isinstance(x,NumType) and isinstance(y,NumType) and isinstance(z,NumType)) else _O(li))
nt = _ntype(*li)
if isinstance(li,(tuple,list)) and len(li) == 3:
(self.X,self.Y,self.Z) = list(HistoricVariable(vl,f'{self.identity}_{i}',self.units) for (vl,i) in ((nt(li[0]),'x'),(nt(li[1]),'y'),(nt(li[2]),'z')))
(self.X,self.Y,self.Z) = list(HistoricVariable(vl,f'{self.identity}_{i}',self.units) for (vl,i) in
((nt(li[0]),'x'),(nt(li[1]),'y'),(nt(li[2]),'z')))
else:
e.raise_type_error('l,x,y,z',(Iterable,*NumType),(li,x,y,z))
self.type = nt
Expand All @@ -359,7 +361,7 @@ def c(self, usage=None):
except KeyError:
e.raise_out_of_range('c()',usage)

def next(self,next_vals):
def next(self,next_vals): # noqa: A003
temp = _O(next_vals)
if isinstance(temp,Iterable):

Expand Down
22 changes: 11 additions & 11 deletions nbody/core/body.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import math
from ..tools import errors as e
from .base import NullVector, typecheck, _O, _V, Iterable, NumType, NoneType, HistoricVector, Variable, Vector, VectorType
from .base import (typecheck, _O, _V,
Iterable, NumType, NoneType, VectorType,
HistoricVector, Variable, Vector, NullVector)

try:
from scipy.constants import G
except:
except ModuleNotFoundError:
G = 6.6743*10**(-11)

class Body:
Expand Down Expand Up @@ -51,17 +53,15 @@ def __getitem__(self, ind):
return (self.pos[ind], self.vel[ind], self.acc[ind])
elif isinstance(ind, str):
return {'pos':self.pos['all'], 'vel': self.vel['all'], 'acc':self.acc['all'],
'current':list(d.c() for d in (self.pos, self.vel, self.acc)),'info':{'identity':self.identity, 'mass':self.mass,
'radius':self.radius, 'color':self.color, 'bounce':self.bounce},'x':list(d.X for d in (self.pos, self.vel, self.acc)),
'y':list(d.Y for d in (self.pos, self.vel, self.acc)),'z':list(d.Z for d in (self.pos, self.vel, self.acc)),
'_data_':{'pos':self.pos['all'], 'vel': self.vel['all'], 'acc':self.acc['all'],'identity':self.identity, 'mass':self.mass,
'radius':self.radius, 'color':self.color, 'bounce':self.bounce}}[ind]
'current':list(d.c() for d in (self.pos, self.vel, self.acc)),'info':{'identity':self.identity, 'mass':self.mass,
'radius':self.radius, 'color':self.color, 'bounce':self.bounce},'x':list(d.X for d in
(self.pos, self.vel, self.acc)),
'y':list(d.Y for d in (self.pos, self.vel, self.acc)),'z':list(d.Z for d in (self.pos, self.vel, self.acc)),
'_data_':{'pos':self.pos['all'], 'vel': self.vel['all'], 'acc':self.acc['all'],'identity':self.identity,
'mass':self.mass, 'radius':self.radius, 'color':self.color, 'bounce':self.bounce}}[ind]

def update(self,dt=1,vel_change=None,acc_change=None,vel_next=None):
if vel_next:
vel = _V(vel_next)
else:
vel = self.vel
vel = _V(vel_next) if vel_next else self.vel

if acc_change is not None:
acc_change = _V(acc_change)
Expand Down
25 changes: 15 additions & 10 deletions nbody/core/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def attach_bodies(self, new_bodies):

def _loadeng(self,eng):
# used for loading an engine from an npz file.
self = eng
self = eng #noqa: F841


def save_as(self,dump='engine',file_name='nbody_data'):
Expand All @@ -40,11 +40,11 @@ def save_as(self,dump='engine',file_name='nbody_data'):


def load_as(self,objects='engine',file_name='nbody_data'):
_loadobjs = {'bodies':("self.attach_bodies(objs['bodies'])",),
'engine':("self._loadeng(objs['engine'])",)}
_loadobjs = {'bodies':("self.attach_bodies(_objs['bodies'])",),
'engine':("self._loadeng(_objs['engine'])",)}

with open(f'{file_name}.npz','rb') as file:
objs = np.load(file,allow_pickle=True)
_objs = np.load(file,allow_pickle=True)
for func in _loadobjs[objects]:
try:
# usage limited to values in dict above.
Expand Down Expand Up @@ -93,17 +93,19 @@ def _check_collision(self,body,co_restitution=0):
# check if distance is less than radii
if body_dist < (bod.radius + body.radius).c():
(returned_coll,n,meff) = (True,(bod.pos-body.pos)/body_dist,1/((1/bod.mass)+(1/body.mass)))
#calc vel change: dv = n^ * [n^*[v2 -v1]*[1+e]*m"]/m1, n^ is normal unit vector, m" is effective mass as above.
# calc vel change: dv = n^ * [n^*[v2 -v1]*[1+e]*m"]/m1, n^ is normal unit vector,
# m" is effective mass as above.
dv = n*(-1*((n*(body.vel - bod.vel))*(1+co_restitution)*meff)/body.mass)
return (dv+body.vel), False

unitcomps = {'x':Vector((1,0,0)),'y':Vector((0,1,0)),'z':Vector((0,0,1))}
for pl in self.planes:
# check for plane collision. we estimate a range of times as the plane has no thickness, and time interval would have to be extremely miniscule compared to velocity to always catch.
# check for plane collision. we estimate a range of times as the plane has no thickness,
# and time interval would have to be extremely miniscule compared to velocity to always catch.
body_dists = list(abs(((body.pos + body.vel*m*self.dt)[pl[0]] - pl[1])) for m in range(self._rangechk))
if any([(body_dists[i] < body.radius) for i in range(self._rangechk)]) or\
body_dists[0] < body.radius:
on_plane = (True if body_dists[0]/body.radius <= 1.01 and body_dists[-1]/body.radius <= 1.01 else False)
on_plane = (body_dists[0]/body.radius <= 1.01 and body_dists[-1]/body.radius <= 1.01)
returned_coll = True
# similar to above, but normals are precalculated.
return (unitcomps[pl[0]]*(-2*(body.vel*unitcomps[pl[0]])) + body.vel)*co_restitution, on_plane
Expand Down Expand Up @@ -136,7 +138,8 @@ def _gen_sim(self):

def simulate(self,intervals):
if isinstance(intervals, int) and len(self.bodies) > 0:
for _ in trange(intervals, desc=f'«Engine» → Evaluating motion for each interval of {self.dt} seconds', unit='ints'):
for _ in trange(intervals,
desc=f'«Engine» → Evaluating motion for each interval of {self.dt} seconds', unit='ints'):


for body, col_vel, on_plane, acc_g in self._gen_sim():
Expand All @@ -147,7 +150,9 @@ def simulate(self,intervals):
body.update(dt=self.dt,vel_next=(col_vel+fieldvel).c(),acc_change=acc_g)

if intervals+1 == len(self.bodies[0].pos):
tqdm.write(f'«Engine» → Finished Evaluating {intervals} intervals, ~{len(self.bodies[0].pos)} total intervals.')
tqdm.write(
f'«Engine» → Finished Evaluating {intervals} intervals, ~{len(self.bodies[0].pos)} total intervals.',
)

def barycenter(self, index):
mass_dist = NullVector()
Expand All @@ -166,7 +171,7 @@ def __getitem__(self, ind):
'dofg':self.do_fieldgravity,
'planes':self.planes,
'fields':self.fields,
'bodies':self.bodies
'bodies':self.bodies,
}


49 changes: 33 additions & 16 deletions nbody/core/visual.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,18 @@

PICKRADIUS = 10

def sphere(pos,radius,N=20):
def sphere(pos,radius,n=20):
(c,r) = (pos,radius)
# get a cubic mesh of points
u,v = np.mgrid[0:2*np.pi:N*1j, 0:np.pi:N*1j]
u,v = np.mgrid[0:2*np.pi:n*1j, 0:np.pi:n*1j]
# compute x,y,z values from spherical mesh
x = r*np.cos(u)*np.sin(v)+c[0]
y = r*np.sin(u)*np.sin(v)+c[1]
z = r*np.cos(v)+c[2]
return x,y,z


class mplVisual:
class MPLVisual:
def __init__(self,
engine,
name='NBody Simulation (Matplotib)',
Expand All @@ -56,12 +56,14 @@ def __init__(self,
'labelling_type':'legend',
'body_model':'dots',
'info_body':focus_body,
'fmt_params':dict(),
'fmt_params':dict(output_raw=False,
items=['identity','mass','radius','energy','period','pos','vel','acc', 'time'],
vector_pos=False, vector_vel=False, vector_acc=False),
'file':None,
'step_skip_frames':1,
'step_skip_points':1,
'max_period':2,
'fps':30
'fps':30,
}
self.files = dict()
self.engine = engine
Expand Down Expand Up @@ -89,21 +91,27 @@ def __init__(self,
# list of data intervals to create frames for
flist = list(self.plt['frmstep']*x for x in range(int(len(self.engine)/self.plt['frmstep'])))
# organise args for animation function.
self.anim_args = dict(interval=(5 if self.args['speed_control'] is True else 1000/self.args['fps']), frames=flist, cache_frame_data=self.args['anim_cache'])
self.anim_args = dict(interval=(5 if self.args['speed_control'] is True else 1000/self.args['fps']),
frames=flist, cache_frame_data=self.args['anim_cache'])
# build data for trails
self.trail_data = dict()
with tqdm(total = len(self.engine.bodies)*len(flist), desc='«mplVisual» → Building Trails', unit='items') as tbar:
with tqdm(total = len(self.engine.bodies)*len(flist),
desc='«mplVisual» → Building Trails', unit='items') as tbar:
for b in self.engine.bodies:
body_data = dict()
for f in flist:
try:
tau = (f-(self.plt['frmstep']*b.get_('period', f, self.plt['ptstep'], self.plt['major_body'], engine=self.engine)/self.engine.dt))
tau = (f-(self.plt['frmstep']*b.get_('period', f, self.plt['ptstep'],
self.plt['major_body'],engine=self.engine)/self.engine.dt))
if tau > 0:
lower = math.ceil(tau)
else: raise TypeError
else:
raise TypeError
except TypeError:
lower = self.args['start_index']
body_data_f = list(list(float(m) for m in _b.record[lower:f:self.plt['ptstep']]) for _b in (b.pos.X,b.pos.Y,b.pos.Z))
body_data_f = list(list(float(m) for m in
_b.record[lower:f:self.plt['ptstep']])
for _b in (b.pos.X,b.pos.Y,b.pos.Z))
if self.plt['maxpts'] is not None:
while any(len(i) > self.plt['maxpts'] for i in body_data_f):
for i in body_data_f:
Expand All @@ -113,11 +121,13 @@ def __init__(self,
self.trail_data[b] = body_data
# init a formatter to manage the info readout
if show_info == True:
self.fmt = Formatter(output_raw=False,items=['identity','mass','radius','energy','period','pos','vel','acc', 'time'], vector_pos=False, vector_vel=False, vector_acc=False, engine=self.engine,plotskip=self.plt['ptstep'], c_mass=self.plt['major_body'])
self.fmt = Formatter(engine=self.engine,plotskip=self.plt['ptstep'],
c_mass=self.plt['major_body'], **self.args['fmt_params'])
# if true precalculate all info readouts for each frame and body.
if self.args['info_calc'] is True:
self.info_data = dict()
with tqdm(total = len(self.engine.bodies)*len(flist), desc='«mplVisual» → Precomputing All Descriptions', unit='items') as pbar:
with tqdm(total = len(self.engine.bodies)*len(flist),
desc='«mplVisual» → Precomputing All Descriptions', unit='items') as pbar:
for b in self.engine.bodies:
body_data = dict()
for f in flist:
Expand All @@ -142,7 +152,8 @@ def __init__(self,
# axes to contain speed control slider
if self.args['speed_control'] == True:
self.spd_ax = self.fig.add_axes((0.1,0.25,0.05,0.5))
self.speed_slider = Slider(self.spd_ax,valinit=1000/self.plt['interval'], valmax=1000/5,label='Target\nFPS',valmin=0.1,orientation='vertical')
self.speed_slider = Slider(self.spd_ax,valinit=1000/self.plt['interval'],
valmax=1000/5,label='Target\nFPS',valmin=0.1,orientation='vertical')
self.speed_slider.label.set_color(self.args['color_dict']['text'])
def _sp_ud(val):
self.plt['interval'] = 1000/val
Expand Down Expand Up @@ -175,7 +186,8 @@ def _draw_info(self, ind):
else:
# or read from precalculated.
inf_string = self.info_data[self.args['info_body']][ind]
self.ax.text2D(s=inf_string, transform=self.ax.transAxes, x=0.05, y=0.2, size='small', horizontalalignment='left',
self.ax.text2D(s=inf_string, transform=self.ax.transAxes,
x=0.05, y=0.2, size='small', horizontalalignment='left',
verticalalignment='bottom', color=self.args['color_dict']['text'])
def _draw_legend(self):
handles, labels = self.ax.get_legend_handles_labels()
Expand All @@ -185,7 +197,11 @@ def _draw_legend(self):
if label not in label_list:
handle_list.append(handle)
label_list.append(label)
self.leg = self.ax.legend(handle_list, label_list, draggable=True, facecolor=self.args['color_dict']['bkgd'], fancybox=True, loc=1, fontsize='small')
self.leg = self.ax.legend(handle_list,
label_list,
draggable=True,
facecolor=self.args['color_dict']['bkgd'],
fancybox=True, loc=1, fontsize='small')
for text in self.leg.get_texts():
text.set_picker(PICKRADIUS)
text.set_color(self.args['color_dict']['text'])
Expand Down Expand Up @@ -213,7 +229,8 @@ def _animate(self,ind):
# set plot range to max distances from focus body
(limx,limy,limz) = (float(m) for m in self.focus_body.pos[ind])
if self.args['focus_range'] is None:
self.args['focus_range'] = float(max((max(abs(x) for x in (self.focus_body.pos - bod.pos)) for bod in self.engine.bodies)))
self.args['focus_range'] = float(max(
(max(abs(x) for x in (self.focus_body.pos - bod.pos)) for bod in self.engine.bodies)))
else:
limx,limy,limz=0,0,0
if self.args['focus_range'] is None:
Expand Down
Loading

0 comments on commit b04b82d

Please sign in to comment.