'''
Created on 2-June-2024

@author: b thakur

'''
import inspect
import traceback

import numpy as np
import sympy as sp

from sympy import (Symbol, symbols, latex, 
                   Derivative, Integral,
                   
                   )

from sympy.vector import (CoordSys3D, BaseVector, BaseScalar,
                          VectorAdd, VectorMul, VectorZero,
                          )


import random

from xv.util import (UtilClass, get_arrow_symbols, get_arrow_symbol, 
                     is_list_tuple_set,
                     )
# _newtonian_particle
# NewtonianParticle

class NewtonianParticle(UtilClass):
    '''
        particle = NewtonianParticle(verbose = self.verbose)
        
        terms = [random.randint(-9, 9),
                 random.randint(-9, 9),
                 random.randint(-9, 9),
                ]
        particle = NewtonianParticle(r_vec = terms, verbose = self.verbose)
        
        
        terms = [random.randint(-9, 9),
                 random.randint(-9, 9),
                 random.randint(-9, 9),
                ]
        particle = NewtonianParticle(auto_init = False, verbose = self.verbose)
        r_vec = particle.get_vector(*terms)
        particle.init_values(r_vec = r_vec)
    '''
    
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.auto_init = kwargs.get('auto_init', True)
        self._init_default_values(**kwargs)
        pass

    def init_values(self, **kwargs):
        return self._init_default_values(**kwargs)
    
    
    def _init_default_values(self, **kwargs):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())

        try:
            self.coord_sys_3d = kwargs.get('coord_sys_3d', None)
            if self.coord_sys_3d is None:
                self.coord_sys_3d = CoordSys3D('N')
            
            self.t = kwargs.get('t', Symbol('t'))
            self.dt = kwargs.get('dt', Symbol(f'd{latex(self.t)}'))
            self.t0 = kwargs.get('t0', Symbol(f'{latex(self.t)}_0'))
            self.t1 = kwargs.get('t1', Symbol(f'{latex(self.t)}_1'))
            
            self.m = kwargs.get('m', Symbol('m'))
    
            
            self.r = kwargs.get('r', get_arrow_symbol('r'))
            self.dr = kwargs.get('dr', get_arrow_symbol(f'd{latex(self.r)}'))
            self.r0 = kwargs.get('r0', get_arrow_symbol(f'{latex(self.r)}_0'))
            self.r1 = kwargs.get('r1', get_arrow_symbol(f'{latex(self.r)}_1'))
            
            self.v = kwargs.get('v', get_arrow_symbol('v'))
            self.dv = kwargs.get('dv', Symbol(f'd{latex(self.v)}'))
            self.v0 = kwargs.get('v0', Symbol(f'{latex(self.v)}_0'))
            self.v1 = kwargs.get('v1', Symbol(f'{latex(self.v)}_1'))
            
            
            self.a = kwargs.get('a', get_arrow_symbol('a'))
    
            self.E = kwargs.get('E', get_arrow_symbol('E'))
            self.dE = kwargs.get('dE', get_arrow_symbol(f'd{latex(self.E)}'))
    
            self.F = kwargs.get('E', get_arrow_symbol('F'))
            
            self.V = kwargs.get('V', Symbol('V'))
            self.dV = kwargs.get('dV', Symbol(f'd{latex(self.V)}'))
            self.V0 = kwargs.get('V0', Symbol(f'{latex(self.V)}_0'))
            self.V1 = kwargs.get('V1', Symbol(f'{latex(self.V)}_1'))
            
            self.W = kwargs.get('W', Symbol('W'))
            self.dW = kwargs.get('dW', Symbol(f'd{latex(self.W)}'))
            
            
            
            self.t_label = kwargs.get('t_label', Symbol(f"\\text{{time}}"))
            self.m_label = kwargs.get('m_label', Symbol(f"\\text{{mass}}"))
            self.r_label = kwargs.get('r_label', Symbol(f"\\text{{position vector}}"))
            self.v_label = kwargs.get('v_label', Symbol(f"\\text{{velocity}}"))
            self.a_label = kwargs.get('a_label', Symbol(f"\\text{{acceleration}}"))
            self.E_label = kwargs.get('E_label', Symbol(f"\\text{{field}}"))
            self.V_label = kwargs.get('V_label', Symbol(f"\\text{{potential}}"))
            self.F_label = kwargs.get('F_label', Symbol(f"\\text{{force}}"))
            self.W_label = kwargs.get('W_label', Symbol(f"\\text{{work}}"))
            
            self._r_vec = None
            self._init_r_vec(**kwargs)
            
            self._v_vec = kwargs.get('v_vec', None)
            self._v_ueval_symb = None
            self._a_vec = kwargs.get('a_vec', None)
            self._E_vec = kwargs.get('E_vec', None)
            self._V_val = kwargs.get('V_val', None)
            self._V_ueval_symb = None
            self._F_vec = kwargs.get('F_vec', None)
            self._F_ueval_symb = None
            self._W_val = kwargs.get('W_val', None)
            self._W_ueval_symb = None
            
            self._kwargs = kwargs
            
            self._init_derived_variables()
        except Exception as e:
            sError = f"""Error {self.__class__.__name__}.{inspect.stack()[0][3]}: {e}
            kwargs = {kwargs}
            """
            self._log(sError)
            self._log(traceback.format_exc())
            pass
        pass
    
    def _init_derived_variables(self):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())

        if not self._r_vec is None:
            return self._init_derived_variables_from_r() 
        
        if not self._E_vec is None:
            return self._init_derived_variables_from_E() 

        #if nothing is provided, init r_vec with default values 
        if self.auto_init:       
            self._r_vec = self._init_random_r_vec()
            if not self._r_vec is None:
                return self._init_derived_variables_from_r() 
        pass
    
    def _init_derived_variables_from_r(self):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())

        self._init_v_vec(**self._kwargs)
        self._init_a_vec(**self._kwargs)
        self._init_E_vec(**self._kwargs)
        self._init_F_vec(**self._kwargs)
        self._init_F_ueval_symb()
        self._init_V_val(**self._kwargs)
        self._init_V_ueval_symb()
        self._init_W_val(**self._kwargs)
        self._init_W_ueval_symb()
        pass

    def _init_derived_variables_from_E(self):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())

        self._init_E_vec(**self._kwargs)
        self._init_a_vec(**self._kwargs)
        self._init_F_ueval_symb()
        self._init_F_vec(**self._kwargs)
        self._init_v_vec(**self._kwargs)

        self._init_r_vec(**self._kwargs)
        
        self._init_V_val(**self._kwargs)
        self._init_V_ueval_symb()
        self._init_W_val(**self._kwargs)
        self._init_W_ueval_symb()
        pass


    def get_r_vec(self):
        return self._r_vec


    def _init_random_r_vec(self):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())

        try:
            t = self.t
            
            terms = []
            while len(terms) < 3:
                coeffs = [random.randint(-2, 2),
                             random.randint(-2, 2),
                             random.randint(-2, 2) 
                            ]
                exponents = [random.randint(-2, 2),
                             random.randint(-2, 2),
                             random.randint(-2, 2) 
                            ]
                term = [coeff * t**exponent for coeff, exponent in zip(coeffs, exponents)]
                terms.append(sp.Add(*term))
                pass
            
            r_vec = self.get_vector(*terms)
            return r_vec
        except Exception as e:
            sError = f"""Error {self.__class__.__name__}.{inspect.stack()[0][3]}: {e}
            """
            self._log(sError)
            self._log(traceback.format_exc())
            return None
        
        
    def _init_r_vec(self, **kwargs):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())

        try:
            if not self._r_vec is None:
                return True
            
            r_vec = kwargs.get('r_vec', None)
            
            if not r_vec is None:
                if is_list_tuple_set(r_vec):
                    r_vec = self.get_vector(*r_vec, N = self.coord_sys_3d)
                    
                self._r_vec = r_vec
                return True

            t = self.t
            r0 = self.r0
            t0 = self.t0

            v_vec = self.get_v_vec()

            if not v_vec is None:
                r_vec = r0 + Integral(v_vec, (t, t0, t) ).doit().expand()
                self._r_vec = r_vec
                return True

            return None
        except Exception as e:
            sError = f"""Error {self.__class__.__name__}.{inspect.stack()[0][3]}: {e}
            kwargs = {kwargs}
            """
            self._log(sError)
            self._log(traceback.format_exc())
            return False
        pass

    def get_stmt_r_from_v(self):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())

        try:
            r = self.r
            r0 = self.r0
            dr = self.dr
            r_label = self.r_label

            v = self.v
            v_label = self.v_label

            v_vec = self.get_v_ueval_symb()
            
            if v_vec is None:
                return None 

            t = self.t
            t0 = self.t0
            dt = self.dt

            t_label = self.t_label
            
            soln_stmt_dr = f"""
             
            Infinitesimal ${latex(r_label)}$
            <br/>
            <br/>
             
            $\\displaystyle 
            \\begin{{aligned}}
            
            {latex(dr)} 
            & = {latex(v_label)} \\cdot \\text{{ infinitesimal change in }} {latex(t_label)}
            \\\\ \\\\
            
            & =  {latex( v )}  {latex( dt )} 
            \\\\ \\\\
            
            & =  \\left( {latex( v_vec )} \\right) {latex( dt )} 
            
            \\end{{aligned}}
            $
            """
            
            soln_stmt_r = f"""
    
            Integrating both sides
            <br/>
            <br/>
             
            $\\displaystyle 
            \\begin{{aligned}}
            
            & {latex( Integral(1, (r, r0, r) ) )} 
            =  {latex( Integral(v_vec, (t, t0, t) ) )} 
            \\\\ \\\\
            
            \\implies
            & {latex(r)} - {latex(r0)} 
            = \\left[ {latex( v_vec )} \\right]_{{{t0}}}^{{{t}}}
            \\\\ \\\\
            
            \\implies
            & {latex(r)} 
            = {latex(r0)} + \\left[ {latex( v_vec )} \\right]_{{{t0}}}^{{{t}}}
            \\\\ \\\\
            
            \\end{{aligned}}
            $
            
            """
            
            soln_stmt = f"""
            {soln_stmt_dr.strip()}
            <br/>
            <br/>
            {soln_stmt_r.strip()}
            """
            
            return soln_stmt.replace("\n", " ").strip() 
        except Exception as e:
            sError = f"""Error {self.__class__.__name__}.{inspect.stack()[0][3]}: {e}
            """
            self._log(sError)
            self._log(traceback.format_exc())
            return None
        pass

    def get_v_vec(self):
        return self._v_vec

    def _init_v_vec(self, **kwargs):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())

        try:
            if not self._v_vec is None:
                return True

            
            v_vec = kwargs.get('v_vec', None)
            if not v_vec is None:
                self._v_vec = v_vec
                return True
            
            r_vec = self.get_r_vec()
            t = self.t
            v0 = self.v0
            t0 = self.t0
    
            if not r_vec is None:
                v_vec = Derivative(r_vec, t).doit()
                if v_vec == 0:
                    v_vec = self.get_vector(0, 0, 0, self.coord_sys_3d)
                self._v_vec = v_vec
                return True

            a_vec = self.get_a_vec()
    
            if not a_vec is None:
                v_vec = v0 + Integral(a_vec, (t, t0, t) ).doit().expand()
                self._v_vec = v_vec
                return True
            
            return False
        except Exception as e:
            sError = f"""Error {self.__class__.__name__}.{inspect.stack()[0][3]}: {e}
            kwargs = {kwargs}
            """
            self._log(sError)
            self._log(traceback.format_exc())
            return False
        pass
    
    def get_v_ueval_symb(self):
        if self._v_ueval_symb is None:
            self._init_v_ueval_symb()
        return self._v_ueval_symb

    def _init_v_ueval_symb(self):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())

        try:
            
            v_vec = self.get_v_vec()
            if v_vec is None:
                return False
            
            a_vec = self.get_a_vec()
            if a_vec is None:
                return False
            t = self.t
            t0 = self.t0
            
            v_ueval_symb = Symbol(f"""
            \\left[ {latex( Integral(a_vec, t ).doit() )} \\right] _{{{latex(t0)}}}^{{{latex(t)}}}
            """)
            self._v_ueval_symb = v_ueval_symb
            return True
        except Exception as e:
            sError = f"""Error {self.__class__.__name__}.{inspect.stack()[0][3]}: {e}
            """
            self._log(sError)
            self._log(traceback.format_exc())
            return False

        pass


    def get_stmt_v_from_r(self):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())

        try:
            t = self.t
            t_label = self.t_label
            r = self.r
            r_label = self.r_label
            v = self.v
            v_label = self.v_label
            
            r_vec = self.get_r_vec()
            if r_vec is None:
                return None
    
            v_vec = self.get_v_vec()
            if v_vec is None:
                return None 
            
            
            soln_stmt = f"""
             
            $\\displaystyle 
            \\begin{{aligned}}
            
            {latex(v_label)} \\; {latex(v)}
            & = \\text{{Rate of change in ${latex(r_label)}$ with respect to ${latex(t_label)}$ }}
            \\\\ \\\\
            
            & = {latex( Derivative(r, t) )}
            \\\\ \\\\
            
            & = {latex( Derivative(r_vec, t) )}
            \\\\ \\\\
            
            & = {latex( v_vec )}
            
            \\end{{aligned}}
            $
            """
            return soln_stmt.strip() 
        except Exception as e:
            sError = f"""Error {self.__class__.__name__}.{inspect.stack()[0][3]}: {e}
            """
            self._log(sError)
            self._log(traceback.format_exc())
            return None
        pass


    def get_stmt_v_from_a(self):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())

        try:
            v = self.v
            v0 = self.v0
            dv = self.dv
            v_label = self.v_label

            a = self.a
            a_label = self.a_label

            a_vec = self.get_a_vec()
            
            if a_vec is None:
                return None 

            t = self.t
            t0 = self.t0
            dt = self.dt

            t_label = self.t_label
            
            soln_stmt_dv = f"""
             
            Infinitesimal ${latex(v_label)}$
            <br/>
            <br/>
             
            $\\displaystyle 
            \\begin{{aligned}}
            
            {latex(dv)} 
            & = {latex(a_label)} \\cdot \\text{{ infinitesimal change in }} {latex(t_label)}
            \\\\ \\\\
            
            & =  {latex( a )}  {latex( dt )} 
            \\\\ \\\\
            
            & =  \\left( {latex( a_vec )} \\right) {latex( dt )} 
            
            \\end{{aligned}}
            $
            """
            
            soln_stmt_v = f"""
    
            Integrating both sides
            <br/>
            <br/>
             
            $\\displaystyle 
            \\begin{{aligned}}
            
            & {latex( Integral(1, (v, v0, v) ) )} 
            =  {latex( Integral(a_vec, (t, t0, t) ) )} 
            \\\\ \\\\
            
            \\implies
            & {latex(v)} - {latex(v0)} 
            = \\left[ {latex( a_vec )} \\right]_{{{t0}}}^{{{t}}}
            \\\\ \\\\
            
            \\implies
            & {latex(v)} 
            = {latex(v0)} + \\left[ {latex( a_vec )} \\right]_{{{t0}}}^{{{t}}}
            \\\\ \\\\
            
            \\end{{aligned}}
            $
            
            """
            
            soln_stmt = f"""
            {soln_stmt_dv.strip()}
            <br/>
            <br/>
            {soln_stmt_v.strip()}
            """
            
            return soln_stmt.replace("\n", " ").strip() 
        except Exception as e:
            sError = f"""Error {self.__class__.__name__}.{inspect.stack()[0][3]}: {e}
            """
            self._log(sError)
            self._log(traceback.format_exc())
            return None
        pass


    def get_a_vec(self):
        return self._a_vec

    def _init_a_vec(self, **kwargs):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())

        try:
            if not self._a_vec is None:
                return True
            
            a_vec = kwargs.get('a_vec', None)
            if not a_vec is None:
                self._a_vec = a_vec
                return True
            
            v_vec = self.get_v_vec()
    
            if not v_vec is None:
                t = self.t
                a_vec = Derivative(v_vec, t).doit()
                if a_vec == 0:
                    a_vec = self.get_vector(0, 0, 0, self.coord_sys_3d)
                self._a_vec = a_vec
                return True
            
            E_vec = self.get_E_vec()
            if not E_vec is None:
                self._a_vec = E_vec
                return True
            
            
            return False
        except Exception as e:
            sError = f"""Error {self.__class__.__name__}.{inspect.stack()[0][3]}: {e}
            kwargs = {kwargs}
            """
            self._log(sError)
            self._log(traceback.format_exc())
            return False
        pass


    def get_stmt_a_from_v(self):
        try:
            t = self.t
            t_label = self.t_label
            a = self.a
            a_label = self.a_label
            v = self.v
            v_label = self.v_label
            
            v_vec = self.get_v_vec()
            if v_vec is None:
                return None
    
            a_vec = self.get_a_vec()
            if a_vec is None:
                return None 
            
            soln_stmt = f"""
             
            $\\displaystyle 
            \\begin{{aligned}}
            
            {latex(a_label)} \\; {latex(a)}
            & = \\text{{Rate of change in }} {latex(v_label)} 
                \\text{{ with respect to }} {latex(t_label)}
            \\\\ \\\\
            
            & = {latex( Derivative(v, t) )}
            \\\\ \\\\
            
            & = {latex( Derivative(v_vec, t) )}
            \\\\ \\\\
            
            & = {latex( a_vec )}
            
            \\end{{aligned}}
            $
            """
            return soln_stmt.strip() 
        except Exception as e:
            sError = f"""Error {self.__class__.__name__}.{inspect.stack()[0][3]}: {e}
            """
            self._log(sError)
            self._log(traceback.format_exc())
            return None
        pass


    def get_stmt_a_from_E(self):
        try:
            E = self.E
            E_label = self.E_label
            
            a = self.a
            a_label = self.a_label
            
            E_vec = self.get_E_vec()
            if E_vec is None:
                return None 
            
            soln_stmt = f"""
             
            $\\displaystyle 
            \\begin{{aligned}}
            
            {latex(a_label)} \\; {latex(a)}
            & =  {latex( E_label )}
            \\\\ \\\\
            
            & =  {latex( E )}
            \\\\ \\\\
            
            & =  {latex( E_vec )}
            
            \\end{{aligned}}
            $
            """
            return soln_stmt.strip() 
        except Exception as e:
            sError = f"""Error {self.__class__.__name__}.{inspect.stack()[0][3]}: {e}
            """
            self._log(sError)
            self._log(traceback.format_exc())
            return None
        pass


    def get_E_vec(self):
        return self._E_vec

    def _init_E_vec(self, **kwargs):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())

        try:
            if not self._E_vec is None:
                return True
            
            E_vec = kwargs.get('E_vec', None)
            if not E_vec is None:
                self._E_vec = E_vec
                return True
            
            a_vec = self.get_a_vec()
            E_vec = a_vec
    
            if not E_vec is None:
                self._E_vec = E_vec
                return True
            
            return False
        except Exception as e:
            sError = f"""Error {self.__class__.__name__}.{inspect.stack()[0][3]}: {e}
            kwargs = {kwargs}
            """
            self._log(sError)
            self._log(traceback.format_exc())
            return False
        pass

    def get_stmt_E_from_a(self):
        try:
            E = self.E
            E_label = self.E_label
            
            a = self.a
            a_label = self.a_label
            
            v_vec = self.get_v_vec()
            if v_vec is None:
                return None
    
            a_vec = self.get_a_vec()
            if a_vec is None:
                return None 
            
            soln_stmt = f"""
             
            $\\displaystyle 
            \\begin{{aligned}}
            
            {latex(E_label)} \\; {latex(E)}
            & =  {latex( a_label )}
            \\\\ \\\\
            
            & =  {latex( a )}
            \\\\ \\\\
            
            & =  {latex( a_vec )}
            
            \\end{{aligned}}
            $
            """
            return soln_stmt.strip() 
        except Exception as e:
            sError = f"""Error {self.__class__.__name__}.{inspect.stack()[0][3]}: {e}
            """
            self._log(sError)
            self._log(traceback.format_exc())
            return None
        pass


    def get_F_vec(self):
        return self._F_vec

    def _init_F_vec(self, **kwargs):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())

        try:
            if not self._F_vec is None:
                return True
            
            F_vec = kwargs.get('F_vec', None)
            if not F_vec is None:
                self._F_vec = F_vec
                return True
            
            m = self.m
            E_vec = self.get_E_vec()
            F_vec = m * E_vec
    
            if not F_vec is None:
                self._F_vec = F_vec
                return True
            
            return False
        except Exception as e:
            sError = f"""Error {self.__class__.__name__}.{inspect.stack()[0][3]}: {e}
            kwargs = {kwargs}
            """
            self._log(sError)
            self._log(traceback.format_exc())
            return False
        pass

    def get_F_ueval_symb(self):
        if self._F_ueval_symb is None:
            self._init_F_ueval_symb()
        return self._F_ueval_symb

    def _init_F_ueval_symb(self):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())

        try:
            F_vec = self.get_F_vec()
            if F_vec is None:
                return None 
            
            m = self.m
    
            E_vec = self.get_E_vec()
            if E_vec is None:
                F_ueval_symb = Symbol(f"{latex( F_vec )}")
            elif isinstance(E_vec, VectorZero):
                F_ueval_symb = Symbol(f"{E_vec.get_latex()}")
            else:
                F_ueval_symb = Symbol(f"{latex( m )} \\left( {latex( E_vec )} \\right) ")
    
            self._F_ueval_symb = F_ueval_symb
            return True 
        except Exception as e:
            sError = f"""Error {self.__class__.__name__}.{inspect.stack()[0][3]}: {e}
            """
            self._log(sError)
            self._log(traceback.format_exc())
            return False
        pass


    def get_stmt_F_from_E(self):
        try:
            F = self.F
            F_label = self.F_label
    
            E = self.E
            m = self.m
            
            F_vec = self.get_F_vec()
            if F_vec is None:
                return None 
    
            E_vec = self.get_E_vec()
            if not E_vec is None:
                F_ueval_symb = Symbol(f"{latex( m )} \\left( {latex( E_vec )} \\right) ")
            else:  
                F_ueval_symb = Symbol(f"{latex( F_vec )}")
            
            F_ueval_symb = self.get_F_ueval_symb()
            
            soln_stmt = f"""
             
            $\\displaystyle 
            \\begin{{aligned}}
            
            {latex(F_label)} \\; {latex(F)}
            & =  {latex( m )} {latex( E )}
            \\\\ \\\\
            
            & =  {latex( m )} \\left( {latex( E_vec )} \\right)
            \\\\ \\\\
            
            & =  {latex( F_ueval_symb )}
            
            \\end{{aligned}}
            $
            """
    
            return soln_stmt.strip() 
        except Exception as e:
            sError = f"""Error {self.__class__.__name__}.{inspect.stack()[0][3]}: {e}
            """
            self._log(sError)
            self._log(traceback.format_exc())
            return None
        pass



    def get_V_val(self):
        return self._V_val
    
    def _init_V_val(self, **kwargs):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())

        try:
            if not self._V_val is None:
                return True
            
            V_val = kwargs.get('V_val', None)
            if not V_val is None:
                self._V_val = sp.sympify(V_val)
                return True
    
            E_vec = self.get_E_vec()
            if E_vec is None:
                return False
            
            v_vec = self.get_v_vec()
            if v_vec is None:
                return False
    
            t = self.t
            t0 = self.t0
            V0 = self.V0
            
    
            E_dot_v = E_vec.dot(v_vec).expand()
                
            V_val = V0 + Integral(E_dot_v, (t, t0, t) ).doit().expand()
                
    
            if not V_val is None:
                self._V_val = sp.sympify(V_val)
                return True
            
            return False
        except Exception as e:
            sError = f"""Error {self.__class__.__name__}.{inspect.stack()[0][3]}: {e}
            kwargs = {kwargs}
            """
            self._log(sError)
            self._log(traceback.format_exc())
            return False
        pass
    
    def get_V_ueval_symb(self):
        if self._V_ueval_symb is None:
            self._init_V_ueval_symb()
        return self._V_ueval_symb

    def _init_V_ueval_symb(self):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())

        try:
            V_val = self.get_V_val()
            if V_val is None:
                return None 
    
            t = self.t
            t0 = self.t0
            V0 = self.V0
    
            E_vec = self.get_E_vec()
            if E_vec is None:
                self._V_ueval_symb = Symbol(f"{latex(V_val)}")
                return True
            
            v_vec = self.get_v_vec()
            if v_vec is None:
                self._V_ueval_symb = Symbol(f"{latex(V_val)}")
                return True 
            
            E_dot_v = E_vec.dot(v_vec).expand()
    
            Itgl_a_dot_v_doit = Integral(E_dot_v, (t) ).doit().expand()
            
            if Itgl_a_dot_v_doit == 0:
                V_ueval_symb = V0
            else:
                V_ueval_symb = Symbol(f"""
                    {latex(V0)} + \\left[ {latex( Itgl_a_dot_v_doit )} \\right]_{{{t0}}}^{{{t}}}
                    """)
                pass
            
            self._V_ueval_symb = V_ueval_symb
            return True 
        except Exception as e:
            sError = f"""Error {self.__class__.__name__}.{inspect.stack()[0][3]}: {e}
            """
            self._log(sError)
            self._log(traceback.format_exc())
            return False
        pass

    def get_stmt_V_from_E(self):
        try:
            V = self.V
            V0 = self.V0
            dV = self.dV
            V_label = self.V_label
    
            t = self.t
            t0 = self.t0
            dt = self.dt
    
            r = self.r
            dr = self.dr
            r_label = self.r_label
    
            v = self.v
            
            E = self.E
            E_label = self.E_label
    
            E_vec = self.get_E_vec()
            if E_vec is None:
                return None 
            
            v_vec = self.get_v_vec()
            if v_vec is None:
                return None 
            
            print(f"""
            E_vec = {type(E_vec)} = {E_vec} 
            v_vec = {type(v_vec)} = {v_vec} 
            """)
            
            E_dot_v = E_vec.dot(v_vec).expand()
     
            E_dot_dr = Symbol(f"{latex( E )} \\cdot {latex( dr )}")
            Itgl_a_dot_v_doit = Integral(E_dot_v, (t) ).doit().expand()
            
            
            V_ueval_symb = self.get_V_ueval_symb()
            
            soln_stmt_dV = f"""
             
            Infinitesimal ${latex(V_label)}$
            <br/>
            <br/>
             
            $\\displaystyle 
            \\begin{{aligned}}
            
            {latex(dV)} 
            & = {latex(E_label)} \\cdot \\text{{ infinitesimal change in }} {latex(r_label)}
            \\\\ \\\\
            
            & =  {latex( E_dot_dr )}
            \\\\ \\\\
            
            & =  {latex( E )} \\cdot \\left( {latex( Derivative(r, t) )} {latex( dt )} \\right)
            \\\\ \\\\
    
            & =  {latex( E )} \\cdot \\left( {latex(v)} {latex( dt )} \\right)
            \\\\ \\\\
            
            & =  \\left( {latex( E_vec )} \\right) 
                 \\cdot \\left( {latex( v_vec )} \\right) {latex( dt )} 
            \\\\ \\\\
            
            & =  \\left( {latex(E_dot_v)} \\right) {latex( dt )}
            \\\\ \\\\
            
            
            \\end{{aligned}}
            $
            """
            
            soln_stmt_V = f"""
    
            Integrating both sides
            <br/>
            <br/>
             
            $\\displaystyle 
            \\begin{{aligned}}
            
            & {latex( Integral(1, (V, V0, V) ) )} 
            =  {latex( Integral(E_dot_v, (t, t0, t) ) )} 
            \\\\ \\\\
            
            \\implies
            & {latex(V)} - {latex(V0)} 
            = \\left[ {latex( Itgl_a_dot_v_doit )} \\right]_{{{t0}}}^{{{t}}}
            \\\\ \\\\
            
            \\implies
            & {latex(V)} 
            = {latex(V_ueval_symb)}
            \\\\ \\\\
            
            \\end{{aligned}}
            $
            
            """
            
            soln_stmt = f"""
            {soln_stmt_dV.strip()}
            <br/>
            <br/>
            {soln_stmt_V.strip()}
            """
            
            return soln_stmt.replace("\n", " ").strip() 
        except Exception as e:
            sError = f"""Error {self.__class__.__name__}.{inspect.stack()[0][3]}: {e}
            """
            self._log(sError)
            self._log(traceback.format_exc())
            return None
        pass


    def get_W_val(self):
        return self._W_val

    def _init_W_val(self, **kwargs):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())

        try:
            if not self._W_val is None:
                return True
            
            W_val = kwargs.get('W_val', None)
            if not W_val is None:
                self._W_val = W_val
                return True
            
            m = self.m
            V_val = self.get_V_val()
    
            if not V_val is None:
                self._W_val = m * V_val
                return None
            
            return False
        except Exception as e:
            sError = f"""Error {self.__class__.__name__}.{inspect.stack()[0][3]}: {e}
            kwargs = {kwargs}
            """
            self._log(sError)
            self._log(traceback.format_exc())
            return False
            
        
        pass


    def get_W_ueval_symb(self):
        if self._W_ueval_symb is None:
            self._init_W_ueval_symb()
        return self._W_ueval_symb

    def _init_W_ueval_symb(self):
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())

        try:
            
            W_val = self.get_W_val()
            if W_val is None:
                return False
            
            m = self.m
            V0 = self.V0
            V_ueval_symb = self.get_V_ueval_symb()
    
            if V_ueval_symb is None:
                self._W_ueval_symb = Symbol(f"{latex(W_val)}")
                return True

            if V_ueval_symb == V0:
                W_ueval_symb = Symbol(f"{latex(m)} {latex(V_ueval_symb)}")
            else:
                W_ueval_symb = Symbol(f"{latex(m)} \\left( {latex(V_ueval_symb)} \\right)")
                pass
            
            self._W_ueval_symb = W_ueval_symb
            return True
        except Exception as e:
            sError = f"""Error {self.__class__.__name__}.{inspect.stack()[0][3]}: {e}
            """
            self._log(sError)
            self._log(traceback.format_exc())
            return False

        pass


    def get_stmt_W_from_V(self):
        try:
            W = self.W
            W_label = self.W_label
    
            m = self.m
            V = self.V
            
            V_val = self.get_V_val()
    
            W_ueval_symb = self.get_W_ueval_symb()
            if W_ueval_symb is None:
                return None
            
            soln_stmt = f"""
             
            $\\displaystyle 
            \\begin{{aligned}}
            
            {latex(W_label)} \\; {latex(W)}
            & =  {latex( m )} {latex( V )}
            \\\\ \\\\
            
            & =  {latex( m )} \\left( {latex( V_val )} \\right)
            \\\\ \\\\
            
            & =  {latex( W_ueval_symb )}
            
            \\end{{aligned}}
            $
            """
    
            return soln_stmt.strip() 
        except Exception as e:
            sError = f"""Error {self.__class__.__name__}.{inspect.stack()[0][3]}: {e}
            """
            self._log(sError)
            self._log(traceback.format_exc())
            return None
        
        pass


    def get_vector(self, x = 0, y = 0, z = 0, N = None):
        if N is None:
            N = self.coord_sys_3d
        v = x * N.i + y * N.j + z * N.k
        return v
        pass
    
    
    
    pass
    
