diff --git a/MATHTYPE b/MATHTYPE new file mode 100644 index 0000000..691afc3 --- /dev/null +++ b/MATHTYPE @@ -0,0 +1 @@ +mp \ No newline at end of file diff --git a/main.py b/main.py index 2fa7db5..eaa5345 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,6 @@ import nbody as nb - +''' LINE_PROFILE=1 bodies = nb.horizons_batch(('10','199','299','399','499','599','699','799','899')) @@ -16,15 +16,23 @@ phys.simulate(20000) #phys.save_as('bodies','solarsystem_bodies') - +''' #phys.load_as('bodies','solarsystem_bodies') - -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) - - +eng= nb.Engine(dt=0.05) +bodies = (nb.Body(mass=5, init_pos=(-10.0,0,0), init_vel=(2.0,0,0), bounce=1, radius=2), + nb.Body(mass=10, init_pos=(10.0,0,0), init_vel=(-2.0,0,0), bounce=1, radius=2), + #Body(mass=1, init_pos=(15,1.,0), init_vel=(-0.5,0,0), bounce=1, radius=1) + ) +eng.do_bodygravity = False +eng.do_collisions = True +eng.do_fieldgravity = False +eng.attach_bodies(bodies) +eng.simulate(1000) +sim = nb.MPLVisual(engine=eng, + name='SS', show_info=True, + step_skip_frames=1, step_skip_points=1, max_pts=1, cache=False, body_model='surface') + +sim.start() ''' import cProfile, pstats profiler = cProfile.Profile() diff --git a/main.py.lprof b/main.py.lprof deleted file mode 100644 index 99875e6..0000000 Binary files a/main.py.lprof and /dev/null differ diff --git a/main.py.prof b/main.py.prof deleted file mode 100644 index 049c525..0000000 Binary files a/main.py.prof and /dev/null differ diff --git a/nbody/__init__.py b/nbody/__init__.py index 37417e6..fe37a75 100644 --- a/nbody/__init__.py +++ b/nbody/__init__.py @@ -44,6 +44,7 @@ .tools.io .tools.horizons """ +from .core.base import math_conf as CONFIG #noqa from .core.body import Body from .core.engine import Engine from .core.visual import MPLVisual diff --git a/nbody/core/base.py b/nbody/core/base.py index 9d0db39..94846e4 100644 --- a/nbody/core/base.py +++ b/nbody/core/base.py @@ -1,21 +1,25 @@ from __future__ import annotations # Python Builtins + from numbers import Number -from collections.abc import Iterable from mpmath import mp, fp # Local error definitions. from ..tools import errors as e +# CONFIGURE GLOBAL MATH FUNCTIONS +from ..tools import _config as conf +math_conf = conf.MathContext(use=mp) - +Iterable = (list, tuple) Any = object NoneType = type(None) -NumType = (Number,type(mp.mpf(1)), type(fp.mpf(1))) +NumType = (Number, int, float ,type(mp.mpf(1)),type(fp.mpf(1))) mp.pretty, fp.pretty = True, True + def _O(obj): # noqa: N802 if isinstance(obj,(Vector, Variable, HistoricVariable, HistoricVector)): return obj.c() @@ -34,13 +38,13 @@ def _V(obj): # noqa: N802 def _ntype(*objs): types = [type(_O(obj)) for obj in objs] - for ntype in types: - if ntype not in NumType: - return ntype + for i,obj in enumerate(objs): + if not isinstance(obj, NumType): + return types[i] if all(ntype == int for ntype in types): return int else: - return fp.mpf + return math_conf.type def typecheck(argtype): @@ -94,49 +98,49 @@ def __repr__(self): return f'VarObj({self.c()} {self.units}, len={len(self)}, rec={self.record}, id={self.identity})' def __add__(self,other): - return Variable(fp.fadd(self.c(),_O(other))) + return Variable(math_conf.add(self.c(),_O(other))) __radd__ = __add__ def __sub__(self,other): - return Variable(fp.fsub(self.c(),_O(other))) + return Variable(math_conf.chop(math_conf.sub(self.c(),_O(other)))) def __mul__(self,other): - return Variable(fp.fmul(self.c(),_O(other))) + return Variable(math_conf.mul(self.c(),_O(other))) __rmul__ = __mul__ def __truediv__(self,other): - return Variable(fp.fdiv(self.c(),_O(other))) + return Variable(math_conf.div(self.c(),_O(other))) def __iadd__(self,other): - self.next(fp.fadd(self.c(),_O(other))) + self.next(math_conf.add(self.c(),_O(other))) return self def __isub__(self,other): - self.next(fp.fsub(self.c(),_O(other))) + self.next(math_conf.chop(math_conf.sub(self.c(),_O(other)))) return self def __imul__(self,other): - self.next(fp.fmul(self.c(),_O(other))) + self.next(math_conf.mul(self.c(),_O(other))) return self def __itruediv__(self,other): - self.next(fp.fdiv(self.c(),_O(other))) + self.next(math_conf.div(self.c(),_O(other))) return self def __rsub__(self,other): - return Variable(fp.chop(mp.fsub(_O(other), self.c()))) + return Variable(math_conf.chop(mp.fsub(_O(other), self.c()))) def __rtruediv__(self,other): - return Variable(fp.fdiv(_O(other), self.c())) + return Variable(math_conf.div(_O(other), self.c())) def __pow__(self, other): - return Variable(fp.power(self.c(),_O(other))) + return Variable(math_conf.pow(self.c(),_O(other))) def __rpow__(self, other): - return Variable(fp.power(_O(other), self.c())) + return Variable(math_conf.pow(_O(other), self.c())) def __eq__(self, other): temp = _O(other) @@ -150,7 +154,7 @@ def __lt__(self, other): def __le__(self, other): - return self.__eq__(other) or self.__le__(other) + return (self.__eq__(other) or self.__lt__(other)) def __ne__(self, other): @@ -164,7 +168,7 @@ def __gt__(self, other): def __ge__(self, other): - return self.__eq__(other) or self.__gt__(other) + return (self.__eq__(other) or self.__gt__(other)) class HistoricVariable(Variable): def __init__(self,init_var,identity='HistoricVariable',units=''): @@ -250,7 +254,7 @@ def c(self,usage=None): e.raise_out_of_range('c()',usage) def magnitude(self): - return fp.norm(fp.matrix(self.c())) + return math_conf.norm(math_conf.matrix(self.c())) def unit(self): if float(self.magnitude()) == 0.: @@ -281,7 +285,7 @@ def __iter__(self): def __add__(self,other): temp = _O(other) if len(temp) == 3: - return Vector([fp.fadd(self.c(i),temp[i]) for i in range(3)]) + return Vector([math_conf.add(self.c(i),temp[i]) for i in range(3)]) else: e.raise_component_error('other or temp',temp) @@ -290,16 +294,16 @@ def __add__(self,other): def __sub__(self,other): temp = _O(other) if len(temp) == 3: - return Vector([fp.fsub(self.c(i),temp[i]) for i in range(3)]) + return Vector([math_conf.sub(self.c(i),temp[i]) for i in range(3)]) else: e.raise_component_error('other or temp',temp) def __mul__(self,other): temp = _O(other) if isinstance(temp,Iterable) and len(temp) == 3: - return Variable(mp.fsum([fp.fmul(val, temp[i]) for (i, val) in enumerate(self.c())])) + return Variable(math_conf.sum([math_conf.mul(val, temp[i]) for (i, val) in enumerate(self.c())])) elif isinstance(temp,NumType): - return Vector([fp.fmul(val,temp) for val in self.c()]) + return Vector([math_conf.mul(val,temp) for val in self.c()]) else: e.raise_component_error('other or temp',temp) @@ -308,7 +312,7 @@ def __mul__(self,other): def __truediv__(self,other): temp = _O(other) if isinstance(temp,NumType): - return Vector([fp.fdiv(val,temp) for val in self.c()]) + return Vector([math_conf.div(val,temp) for val in self.c()]) else: e.raise_type_error('other',NumType,other) @@ -316,7 +320,7 @@ def __truediv__(self,other): def __rsub__(self,other): temp = _O(other) if len(temp) == 3: - return Vector([fp.fsub(temp[i], self.c(i)) for i in range(3)]) + return Vector([math_conf.sub(temp[i], self.c(i)) for i in range(3)]) else: e.raise_component_error('other or temp',temp) diff --git a/nbody/core/body.py b/nbody/core/body.py index d8091eb..43a43f8 100644 --- a/nbody/core/body.py +++ b/nbody/core/body.py @@ -74,10 +74,7 @@ def update(self,dt=1,vel_change=None,acc_change=None,vel_next=None): if vel_change is not None: vel_change = _V(vel_change) - if isinstance(vel_change,Iterable) and len(vel_change.c()) == 3: - self.pos.next(self.pos + (vel + vel_change)*dt) - else: - raise RuntimeError + self.pos.next(self.pos + (vel + vel_change)*dt) else: self.pos.next(self.pos + vel*dt) @@ -114,7 +111,7 @@ def per(): else: return a def ke(): - return (Vector(self.vel[ind])*(self.vel[ind])*(1/2)*self.mass).c() + return ((Vector(self.vel[ind]).magnitude()**2)*(1/2)*self.mass).c() _get_lookup = {**dict.fromkeys(['sma', 'semi_major_axis'],sma), **dict.fromkeys(['per', 'period'],per), diff --git a/nbody/core/engine.py b/nbody/core/engine.py index 915cc6e..545f90f 100644 --- a/nbody/core/engine.py +++ b/nbody/core/engine.py @@ -86,16 +86,21 @@ def _check_collision(self,body,co_restitution=0): if self.do_collisions == True: # has it found a collision? returned_coll = False - for bod in self.bodies: - if body != bod: + for body2 in self.bodies: + if body != body2: # get distance between bodies - body_dist = (bod.pos - body.pos).magnitude() + body_dist = (body2.pos - body.pos).magnitude() # 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))) + if body_dist < (body2.radius + body.radius).c(): + returned_coll = True + n = (body2.pos-body.pos).unit() + meff = 1/((1/body2.mass)+(1/body.mass)) + rel_v = body2.vel - body.vel + impulse = n *(n*rel_v)*(1 + co_restitution) * meff + dv = impulse/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. - dv = n*(-1*((n*(body.vel - bod.vel))*(1+co_restitution)*meff)/body.mass) + #dv = n * -1*(( ( n *(body.vel-body2.vel) )*( 1+co_restitution)*meff )/body.mass.c()) return (dv+body.vel), False unitcomps = {'x':Vector((1,0,0)),'y':Vector((0,1,0)),'z':Vector((0,0,1))} @@ -103,9 +108,9 @@ def _check_collision(self,body,co_restitution=0): # 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 = (body_dists[0]/body.radius <= 1.01 and body_dists[-1]/body.radius <= 1.01) + if any([(body_dists[i] < body.radius) for i in range(self._rangechk)]): + on_plane = ((body_dists[0]/body.radius <= 1.01) if len(body_dists) == 1 + else (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 @@ -132,17 +137,19 @@ def _find_gravity(self,body): return res/body.mass - def _gen_sim(self): - for i,body in enumerate(self.bodies): - yield [body, *self._check_collision(body, body.bounce),self._find_gravity(body)] + + 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'): + _temp = [[0,0,0] for _ in self.bodies] + for i,body in enumerate(self.bodies): + _temp[i] = [*self._check_collision(body, body.bounce),self._find_gravity(body)] - - for body, col_vel, on_plane, acc_g in self._gen_sim(): + for i,body in enumerate(self.bodies): + col_vel, on_plane, acc_g = _temp[i] if not on_plane and self.do_fieldgravity: fieldvel = list(sum(v.c(i) for v in self.fields) for i in range(3)) else: diff --git a/nbody/core/visual.py b/nbody/core/visual.py index 55a42b3..7d797a5 100644 --- a/nbody/core/visual.py +++ b/nbody/core/visual.py @@ -49,6 +49,7 @@ def __init__(self, 'vect_params': dict(vel=False, acc=False, size=1), 'start_index':0, 'info_calc':False, + 'prebuild':False, 'focus_range':None, 'anim_cache':False, 'max_pts':None, @@ -59,11 +60,11 @@ def __init__(self, '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, + 'file':None, } self.files = dict() self.engine = engine @@ -78,7 +79,7 @@ def __init__(self, self.args['color_dict']['line'] = (0,0,0,0) self.args['color_dict']['face'] = (0,0,0,0) - + # plotting values @@ -94,31 +95,18 @@ def __init__(self, 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: - 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)) - if tau > 0: - lower = math.ceil(tau) - 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)) - 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: - i.pop(0) - body_data[f] = body_data_f - tbar.update(1) - self.trail_data[b] = body_data + if self.args['prebuild']: + self.trail_data = dict() + 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: + body_data[f] = self.gen_trail(f,b) + tbar.update(1) + self.trail_data[b] = body_data + else: + self.trail_data = None # init a formatter to manage the info readout if show_info == True: self.fmt = Formatter(engine=self.engine,plotskip=self.plt['ptstep'], @@ -208,7 +196,25 @@ def _draw_legend(self): def _draw_vectors(self,pos,other,c): self.ax.quiver(*pos,*other,length=self.args['vect_params']['size'],color=c,zorder=8, clip_on=False) - + + def gen_trail(self, f, b): + try: + 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 + 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)) + 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: + i.pop(0) + return body_data_f def _animate(self,ind): @@ -261,7 +267,7 @@ def _animate(self,ind): # plot bodies for i,b in enumerate(self.engine.bodies): - _poshist = self.trail_data[b][ind] + _poshist = (self.gen_trail(ind,b) if self.args['prebuild'] is False else self.trail_data[b][ind]) _pos = [float(m) for m in b.pos[ind]] _color = (b.color if all((b.color == n for n in (None,'None'))) else f'C{i}') # draw vectors diff --git a/nbody/tests/test_base.py b/nbody/tests/test_base.py index f7f293e..3e2b068 100644 --- a/nbody/tests/test_base.py +++ b/nbody/tests/test_base.py @@ -11,6 +11,8 @@ for i in range(1,10): _hvect.next((i,i,i)) + + class TestBaseFuncs: '''Testing class independent functions ''' @@ -42,11 +44,11 @@ def test_histvector(self): _hvect = nb.HistoricVector(0,0,0) _hvect2 = nb.HistoricVector(li=(0,0,0)) def test_len(self): - var_l = len(nb.Variable(0)) - hvar_l = len(_hvar) - vect_l = len(nb.Vector((0,0,0))) - hvect_l = len(_hvect) - assert var_l == vect_l and hvar_l == hvect_l + varl = len(nb.Variable(0)) == 1 + vectl = len(_hvar) == 10 + hvarl = len(nb.Vector((0,0,0))) == 3 + hvectl = len(_hvect) == 10 + assert varl and vectl and hvarl and hvectl def test_types(self): assert (isinstance(nb.Variable(0), nb.VarType) and isinstance(nb.HistoricVariable(0), nb.VarType) and diff --git a/nbody/tests/test_core.py b/nbody/tests/test_core.py index d2e3895..cf20031 100644 --- a/nbody/tests/test_core.py +++ b/nbody/tests/test_core.py @@ -1,4 +1,53 @@ import pytest -from .. import Engine, Body, mplVisual +from .. import Engine, Body +import mpmath as mp +class TestBody: + def test_init_body(self): + _body = Body(mass=1, init_pos=(0,0,0), init_vel=(1,0,0), radius=1) + assert True + def test_vel(self): + body = Body(mass=1, init_pos=(0,0,0), init_vel=(1,0,0), radius=1) + body.update(1) + c_pos = body.pos.c() + body.update(1, (1,0,0)) + nc_vel = body.vel.c() + nc_pos = body.pos.c() + assert c_pos == (1,0,0) and nc_pos == (3,0,0) and nc_vel == (1,0,0) + +class TestEngine: + def test_init_eng(self): + _eng = Engine() + assert True + def test_eng_grav_f(self): + eng = Engine() + eng.attach_bodies((Body(mass=1, init_pos=(0,0,0), init_vel=(1,0,0), radius=1),Body(1000, (100,0,0), radius=10))) + eng.do_bodygravity = True + eng.do_collisions, eng.do_fieldgravity = False, False + fg = eng._find_gravity(eng.bodies[0]) + assert mp.almosteq(fg.c(0), 6.674e-12) + # fg almost equal to 6.674e-12 0, 0 + def test_eng_grav_f2(self): + eng2=Engine() + eng2.attach_bodies((Body(mass=1, init_pos=(0,0,0), init_vel=(1,0,0), radius=1), + Body(1000, (100,0,0), radius=10), + Body(mass=1000, init_pos=(-50, 0, 100), radius=10))) + eng2.do_bodygravity = True + eng2.do_collisions, eng2.do_fieldgravity = False, False + fg2 = eng2._find_gravity(eng2.bodies[0]) + print(fg2) + assert mp.almosteq(fg2.c(0), 4.2864e-12) and mp.almosteq(fg2.c(2), 4.7757e-12) + def test_body_collisions(self): + eng3 = Engine() + bodies = (Body(mass=5, init_pos=(-10.0,0,0), init_vel=(2.0,0,0), bounce=1, radius=2), + Body(mass=10, init_pos=(10.0,0,0), init_vel=(-2.0,0,0), bounce=1, radius=2), + Body(mass=1, init_pos=(15,1.,0), init_vel=(-0.5,0,0), bounce=1, radius=1), + ) + eng3.do_bodygravity = False + eng3.do_collisions = True + eng3.do_fieldgravity = True + eng3.attach_bodies(bodies) + eng3.simulate(1000) + _ke = list(sum(b.get_('ke',ind=i) for b in eng3.bodies) for i in (0,-1)) + assert mp.almosteq(_ke[0], _ke[1]) \ No newline at end of file diff --git a/nbody/tests/test_tools.py b/nbody/tests/test_tools.py index 729dc6e..fb63189 100644 --- a/nbody/tests/test_tools.py +++ b/nbody/tests/test_tools.py @@ -3,7 +3,7 @@ import shutil import os -from .. import export_obj, obj_from, Engine, Body, mplVisual +from .. import export_obj, obj_from, Engine, Body, MPLVisual from ..tools.horizons import horizons_batch, horizons_query def cleanup(folder): diff --git a/nbody/tools/_config.py b/nbody/tools/_config.py new file mode 100644 index 0000000..a5a7022 --- /dev/null +++ b/nbody/tools/_config.py @@ -0,0 +1,52 @@ +from mpmath import fp, mp +from math import sqrt +def fltmat(obj): + return obj +def fltnorm(*obj): + return sqrt(sum(x**2 for x in obj)) +class MathContext: + def __init__(self,use=None): + with open('MATHTYPE', 'r') as file: + lines = file.readlines() + use = eval(lines[0]) + print(f'{use}') + if use == float: + self.type = use + self.add = float.__add__ + self.sub = float.__sub__ + self.sum = sum + self.div = float.__truediv__ + self.mul = float.__mul__ + self.pow = float.__pow__ + self.norm = fltnorm + self.matrix = fltmat + self.chop = float + elif use in (mp, fp): + self.type = use.mpf + self.add = use.fadd + self.sum = use.fsum + self.sub = use.fsub + self.div = use.fdiv + self.mul = use.fmul + self.pow = use.power + self.norm = use.norm + self.matrix = use.matrix + self.chop = use.chop + elif isinstance(use,dict): + self.type = use['type'] + self.add = use['add'] + self.sum = use['sum'] + self.sub = use['sub'] + self.div = use['div'] + self.mul = use['mul'] + self.pow = use['pow'] + self.norm = use['norm'] + self.matrix = use['matrix'] + self.chop = use['chop'] + else: + raise TypeError + def change(self, use=float): + with open('MATHTYPE', 'x') as file: + file.write(str(use)) + self.__init__(use) + \ No newline at end of file diff --git a/nbody/tools/formatter.py b/nbody/tools/formatter.py index 0431d30..eb21ccf 100644 --- a/nbody/tools/formatter.py +++ b/nbody/tools/formatter.py @@ -120,7 +120,7 @@ def convert(self, arg=None): args = arg if arg else self._quantities() # get best units for each item we want for key,value in args.items(): - if not isinstance(value, Iterable): + if not isinstance(value, (list, tuple)): # scalars conv[key] = self._get_best_unit(value, _units[key]) else: @@ -150,10 +150,12 @@ def _quantities_to_strings(self, arg=None): return strings def __str__(self): - if self.par['raw'] == False: - # if called as a string, return _basetemplate with the correct items inserted. - return self._basetemplate().format(**self._quantities_to_strings(self.convert())) + if self.target[0] is not None: + if self.par['raw'] == False: + # if called as a string, return _basetemplate with the correct items inserted. + return self._basetemplate().format(**self._quantities_to_strings(self.convert())) + else: + return self._basetemplate().format(*self._quantities_to_strings()) else: - return self._basetemplate().format(*self._quantities_to_strings()) - + return '' diff --git a/nbody/tools/io.py b/nbody/tools/io.py index 1cccdc0..bf2982d 100644 --- a/nbody/tools/io.py +++ b/nbody/tools/io.py @@ -92,7 +92,7 @@ def obj_from(object, obj='engine'): # noqa: A002 for val in func_strings: if val[0] == 'plane' and len(val) == 3: engine.create_plane(str(val[1]), float(val[2])) - elif val[0] == 'field' and len(parts) == 2: + elif val[0] == 'field' and len(val) == 2: vect = [mpf(v) for v in val[1].strip("'").replace('(', '').replace(')', '').split(',')] engine.create_acceleration(vect) elif val[0] == 'rel' and len(val) == 2: @@ -101,7 +101,7 @@ def obj_from(object, obj='engine'): # noqa: A002 engine.simulate(int(val[1])) else: tqdm.write( - f'«obj_from()» → [!] Couldn\'t parse function "{' '.join(val)}" so it has been skipped.', + f'«obj_from()» → [!] Couldn\'t parse function "{'|'.join(val)}" so it has been skipped.', ) return engine elif obj == 'body': diff --git a/temp.dat b/temp.dat deleted file mode 100644 index 3526384..0000000 Binary files a/temp.dat and /dev/null differ diff --git a/temp.json b/temp.json deleted file mode 100644 index e69de29..0000000