import torch, math

class TorchBackedStruct:
    def __init__(self, size=0, dtype=float, **named_shapes):
        offset = 0
        self._offset_size_by_name = {}
        for name, shape in named_shapes.items():
            if type(shape) is int:
                shape = [shape]
            size = math.prod(shape)
            self._slices_shapes_by_name[name] = [tuple(slice(offset,offset+size),slice(None)), [-1, size], [-1] + list(shape)]
            offset += size
        self._shape = [size, offset]
        self._storage = torch.zeros(self._shape, dtype=dtype)
    def __len__(self):
        return self._shape[0]
    def tensor(self):
        return self._storage
    def resize(self, size):
        old_size = self._shape[0]
        self._shape[0] = size
        if size <= old_size:
            self._storage = self._storage[:size,:]
        else:
            storage = torch.empty(self._shape, dtype=self.storage.dtype)
            storage[:old_size,:] = self.storage
            storage[old_size:,:] = 0
            self.storage = storage
    def __getattr__(self, name):
        try:
            [slices, storage_shape, user_shape] = self._slices_shapes_by_name[name]
            return self.storage[slices].view(user_shape)
        except IndexError:
            return getattr(self.storage, name)
    def __setattr__(self, name, values):
        [slices, storage_shape, user_shape] = self._slices_shapes_by_name[name]
        self.storage[slices] = values.view(storage_shape)
    def __dir__(self):
        return self._slices_shape_by_name.keys()

if __name__ == '__main__':
    dims = 2
    nobjs = 32
    dsquared = 1
    min_start = -16 + dsquared
    max_start = 16 - dsquared
    objs = TorchBackedStruct(pos=dims, bfdpos=[nobjs,dims], vel=dims, invmass=1, dsquared=1, dtype=torch.int64)
    objs.resize(nobjs)
    objs.dsquared = dsquared
    objs.pos = torch.randint(min_start, max_start, objs.pos.shape)
    objs.invmass = 1

    t = time.time()
    while True:
        old_t = t
        t = time.time()
        dt = t - old_t

        dpos = objs.vel * dt
        # dpos will cause intersections.
        # we could keep checking for intersections; however this will create a lot of bouncing

        # consider confined particles treated elastically.
        # if there is no space for them to move, and they have velocity (from gravity), then they bounce off each other forever with no motion infinitely quickly constantly
        # this is pressure. they are pressing on each other as if they are one object.
        # when one object is on another with gravity, gravity is considered a force acting on the object rather than an acceleration of it
        # gravity acts constantly everywhere

            # particles have velocity and force acting on them
            # if a particle bounces off a neighbor, and the resulting velocity has it bounce off another neighbor, this is kind of a different situation
                    # how can we possible have accurate diagonal motion with integers
                    # it makes no sense
                        # ummmm
                        # a particle falling onto another particle has some high-integer v
                        # it strikes the other at some point (somehow this point is considered an integer)
                        # i am just really surprised if this is possible. it could be but it's really surprising if so

                # the collision point is the earliest intersection of the swept-sphere paths the two spheres travel on
                # selfdot(pos1+vel1*t) = rsquared
                # selfdot(pos2+vel2*t) = rsquared
                # selfdot(touchpt - (pos1 + vel1*t)) = rsquared
                # selfdot(touchpt - (pos2 + vel2*t)) = rsquared
                # simpler is to compare their distances to the sum of their radii (inhibition forced us to look up on the internet >( )
                # selfdot(pos1 + vel1*t, pos2 + vel2*t) = 4rsquared
                # even simpler is to consider one the reference frame for the other
                # selfdot(pos12+vel12*t) = 4rsquared
                # sum(pos12)**2 + sum(pos12*vel12)*t + sum(vel12)**2*t
                # it's a quadratic equation where A = sum(vel12)**2, B = sum(pos12*vel12), and C = sum(pos12)
                # the quadratic equation is (-B +- sqrt(B^2-4AC))/(2A)
                # so it's immediately rational from A, meanwhile you'd need B^2-4AC to be a square number or a rational of square numbers
                #

                    # sqrt(sum(pos12*vel12)**2 - 4*sum(vel12)**2*sum(pos12)**2)
                    # the numbers involved are all formed from the initial system state, velocities and positions
                    # there is a constraint in that it is the difference of two squares, the second of which is a product of 3, one of which is 4.
                    # there are certain situations where this square root is an integer, and the simplest is where the first term is twice the second.
                    # i'm not sure that ever happens if all the values inside the radical are integers, though, as the first term is a square and 2 is not
                    # it certainly happens when the radical is 0, although that may not be a useful value.
                    # B*B - 4*A*C = D * D
                    # when is the difference between squares itself a square i dunno
                    # 2*4*A*C = B*B
                    # B = sqrt(2*4*A*C)
                    # this can work if there is a 2 factor in A or C !
                    # sum(pos12*vel12)**2 = 8*sum(vel12)**2*sum(pos12)**2
                    # all the 2s in A and C are squared.


                        # another approach could be to consider t to have an unincluded analytical component (although it does kind of 'expand' it may stay numerically small)
                        # the particles could be considered at integral or rational t that is below the real t. the extra could be held analytically if needed.
                        # then the distances between them would involve squaring that square root, and the rational value that isn't rooted could be included in the terms.
                        # it would be very very accurate

        objs.pos += objs.vel * dt

        objs.vel -= 0.5 * dt * objs.invmass

        # doing without spatial partitioning first
        bf_collides.dpos = objs.pos[:,None,:].expand([-1,len(objs),-1]) - objs.pos[None,:,:]

        # 
        
        # so, this is a challenge i've encountered before.
        # right now the cognition isn't oriented to reasonable solve it.
        # there are N objects with various velocities and forces

        # when objects strike, their velocities exchange via consistent rules
        # however when there are multiple objects, many many in a small space, it can be more complicated
        # and the real underlying physics are not quite that simple object striking situation.

        # notably we have floating point numbers here with inaccuracy. there's inherent inaccuracy.
        # so if objects are all squished together, some will be overlapping or spaced, inherently, from the inaccuracy.
        #           one could try to do it with rational math, keeping the equations such that the magnitudes of the rationals don't expand too much
        #           then objects would not intersect.
        #           let's consider 1 dimension.
        #           it might work
         

if __name__ == '__main__':
    dims = 2
    objs = TorchBackedStruct(pos=dims, vel=dims, invmass=1, rsquared=1, dtype=torch.float32)
    objs.resize(32)
    bf_collides = TorchBackedStruct(dpos=[len(objs),dims], dtype=objs.dtype)
    bf_collides.resize(len(objs))
    objs.pos = torch.rand(objs.pos.shape)
    t = time.time()
    while True:
        old_t = t
        t = time.time()
        dt = t - old_t

        objs.pos += objs.vel * dt

        objs.vel -= 0.5 * dt * objs.invmass

        # doing without spatial partitioning first
        bf_collides.dpos = objs.pos[:,None,:].expand([-1,len(objs),-1]) - objs.pos[None,:,:]

        # 
        
        # so, this is a challenge i've encountered before.
        # right now the cognition isn't oriented to reasonable solve it.
        # there are N objects with various velocities and forces

        # when objects strike, their velocities exchange via consistent rules
        # however when there are multiple objects, many many in a small space, it can be more complicated
        # and the real underlying physics are not quite that simple object striking situation.

        # notably we have floating point numbers here with inaccuracy. there's inherent inaccuracy.
        # so if objects are all squished together, some will be overlapping or spaced, inherently, from the inaccuracy.
        #           one could try to do it with rational math, keeping the equations such that the magnitudes of the rationals don't expand too much
        #           then objects would not intersect.
        #           let's consider 1 dimension.
        #           it might work
         

###
# 2025-05-04 2000
        # i'm getting into experimentation.
        # subtraction amplifies the size of the rationals
        # but within the lowest common multiple of all the denominators
        # would integer math work here? or are there increasing accuracy parts?
            # modifications:
            # - scaling by time, could use integer milliseconds maybe
            # - 

                                # i think the idea of shaping them as spheres -- oh buttttt
                                # uhhhh there's a cosine in there. cosines aren't rational.
                                # you'll be probably multiplying velocities by cosines.
                             # so the velocity terms start getting filled with sinusoidal constants
                             # ummm i suppose a dot product is equal to a cosine ...
                        # i'm not there yet i'm still looking
                                    # in elastic collision of spheres, momentum reflects along the line between their centres
                                    # so we'd subtract their centers ( a subtraction )
                                    # then project their velocities onto that subtraction -- ummm so a dot product divided by a distance, maybe
                                    # it's possible the division could be removed or simplified by using the right radii.
                                            # the reason it is unlikely is because both that the division concern is unconsidered, and the remaining math
                                            #   is unconsidered ... combined with it not being a normal approach
                                            # it may be quite possible! but with the amnesia, repetition-harming, and mistake-causing problems,
                                            # we don't have information on it yet




# the integer approach seems not the appropriate pursuit
# the particles seemed reasonable
# given this does seem to be somewhat repeat, there could be more gain from the course-thing
# of course, tomorrow is a quite different day
