import os
import sys
import numpy as np
import logging
import os
import matplotlib.pyplot as plt
import seaborn as sns
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
from ..dynamics import Regressor
from ..utils import plotArray
[docs]
class FourierGenerator:
"""
Base class for peroidic trajectories generation.
Ref:
Fourier-based optimal excitation trajectories for the dynamic identification of robots
Kyung.Jo Park - Robotica - 2006.
"""
def __init__(self,trajectory_params:dict) -> None:
self.trajectory_params = trajectory_params
[docs]
def computeTrajectoryState(self,t:float=0,q0=None, qp0=None, qpp0=None):
""" Computes the trajectory states at time date t """
pulsation = 2*np.pi*self.trajectory_params['frequancy']
nbterms = self.trajectory_params['nbfourierterms']
ndof = self.trajectory_params['ndof']
q = np.zeros(ndof) if q0 is None else np.array(q0)
qp = np.zeros(ndof) if qp0 is None else np.array(qp0)
qpp = np.zeros(ndof) if qpp0 is None else np.array(qpp0)
if ndof != len(q):
logger.error('Trajectory Generation Engine :inconsistency in ndof!')
for i in range(ndof):
for j in range(1, nbterms + 1):
Aij = self.trajectory_params['Aij'][i][j - 1]
Bij = self.trajectory_params['Bij'][i][j - 1]
Cojt = np.cos(pulsation * j * t)
Sojt = np.sin(pulsation * j * t)
q[i] += Aij / (pulsation * j) * Sojt - Bij / (pulsation * j) * Cojt
qp[i] += Aij * Cojt + Bij * Sojt
qpp[i] += Bij * j * Cojt - Aij * j * Sojt
qpp[i] = pulsation * qpp[i]
return q, qp, qpp
[docs]
def computeFullTrajectory(self, ti: float, tf: float,q0=None, qp0=None, qpp0=None):
"""Computes the full trajectory data between ti and tf """
ndof = self.trajectory_params['ndof']
nb_traj_samples = self.trajectory_params['samples']
time = np.linspace(ti, tf, nb_traj_samples)
q = np.zeros((nb_traj_samples,ndof))
qp = np.zeros((nb_traj_samples,ndof))
qpp = np.zeros((nb_traj_samples,ndof))
for i in range(nb_traj_samples):
q[i,:], qp[i,:], qpp[i,:] = self.computeTrajectoryState(time[i],q0,qp0,qpp0)
return q, qp, qpp
[docs]
def computeDifferentiationError(self,ti:float,tf:float,x,q0=None,qp0=None,qpp0=None):
reg = Regressor()
q, qp, qpp = self.computeFullTrajectory(ti, tf, q0, qp0, qpp0)
err = reg.computeDifferentiationError(q,qp,qpp,x)
return err
[docs]
def computeConstraintedDifferentiationError(self,ti:float,tf:float,x,q0=None,qp0=None,qpp0=None):
reg = Regressor()
q, qp, qpp = self._computeTrajectoryConstraints(ti,tf,1.57,-1.57,1.2,-1.2,5.2,-5.2, q0, qp0, qpp0)
err = reg.computeDifferentiationError(q,qp,qpp,x)
return err
[docs]
def computeTrajectoryError(self,x,tspan,new_traj_params=None,q0=None,qp0=None,\
qpp0=None,verbose=False):
if not(new_traj_params is None):
k = self.trajectory_params['ndof'] * self.trajectory_params['nbfourierterms']
self.trajectory_params['Aij'] = np.reshape(new_traj_params[0:k],(-1,5))
self.trajectory_params['Bij'] = np.reshape(new_traj_params[k:2*k],(-1,5))
err = self.computeDifferentiationError(0,tspan,x,q0,qp0,qpp0)
if verbose:
print( f"RMSE = {err:.5f}")
return err
def _computeTrajectoryConstraints(self,ti,tf,qmax,qmin,qpmax,qpmin,qppmin,qppmax,\
q0=None, qp0= None, qpp0=None):
""" Computes the trajectory with taking constraintes into account """
ndof = self.trajectory_params['ndof']
nb_traj_samples = self.trajectory_params['samples']
time = np.linspace(ti, tf, nb_traj_samples)
q = np.zeros((nb_traj_samples,ndof))
qp = np.zeros((nb_traj_samples,ndof))
qpp = np.zeros((nb_traj_samples,ndof))
for i in range(nb_traj_samples):
q[i,:], qp[i,:], qpp[i,:] = self.computeTrajectoryState(time[i],q0,qp0,qpp0)
q[i,:] = np.clip(q[i,:],qmin,qmax)
qp[i,:] = np.clip(qp[i,:],qpmin,qpmax)
qpp[i,:] = np.clip(qpp[i,:],qppmin,qppmax)
return q, qp ,qpp
[docs]
def visualizeTrajectory(self, ti,tf, q0=None, qp0=None, qpp0=None, savePath=None):
"""Compute and plot a given trajectory, each variable in a single plot"""
q, qp, qpp = self.computeFullTrajectory(ti,tf,q0,qp0,qpp0)
plotArray(q,'Computed Trajectory Joints Positions')
plt.savefig(os.path.join(savePath,'computed_trajectory_positions'))
plotArray(qp,'Computed Trajectory Joints Velocity')
plt.savefig(os.path.join(savePath,'computed_trajectory_velocity'))
plotArray(qpp,'Computed Trajectory Joints Accelerations')
if not(savePath is None):
plt.savefig(os.path.join(savePath,'computed_trajectory_accelaertions'))
[docs]
def save2csv(self,ti:float,tf:float,file_path,q0=None,qp0=None,qpp0=None):
""" compute a given trajectory and save it to csv file."""
q, qp, qpp = self.computeFullTrajectory(ti,tf,q0,qp0,qpp0)
format = {'fmt': '%.2f', 'delimiter': ', ', 'newline': ',\n'}
traj_array = np.concatenate([q,qp,qpp])
np.savetxt(file_path, traj_array, **format)
[docs]
def computeTrajectoryCriterion(self,ti:float,tf:float,x,q0=None,qp0=None,qpp0=None)->float:
"""
Compute the trajectory identification criteria.
The criteria mesures how the target x parmters changes du to a variation in W or
in mesured torques
for a linear or /linearized system τ ≈ W(q,qp,qpp,x0)x
we use the Gramian matrix in calculations WTW wich is sysmetric square the cond number
expressed as lamdamax/lamdmin.
"""
reg = Regressor()
q, qp, qpp = self.computeFullTrajectory(ti, tf, q0, qp0, qpp0)
W = reg.computeDifferentialRegressor(q, qp, qpp,x)
WTW = W.T @ W
eig_max = np.max(np.linalg.eigvals(WTW))
C = np.abs(np.abs(eig_max))
return C
[docs]
def computeTrajectoryIdentifiability(self,ti,tf,torque,q,qp,qpp,x):
"""
Evaluate the regression criteria ε(qi, qpi, qppi, x) based on a given trajectory.
This function computes the regression criteria for a fixed system parameter vector `x`
and a trajectory `C(qi, qpi, qppi)` computed previously. The trajectory is considered
identifiable if the criteria is minimal for most of the time steps.
Args:
qi: Generalized position coordinates of the trajectory.
qpi: Generalized velocity coordinates of the trajectory.
qppi: Generalized acceleration coordinates of the trajectory.
x: Fixed system parameter vector.
Returns:
Regression criteria value ε for the given trajectory and system parameters.
Notes:
- The trajectory is identifiable if ε remains minimal over most time steps.
- TODO:
Investigate the relationship between the regression criteria evolution and
the trajectory `C` over time `t`.
"""
reg = Regressor()
N = len(q)
J = np.empty(N)
eps = np.empty(N)
for i in range(N):
J[i] = self.computeTrajectoryCriterion(ti,tf,q[i,:],qp[i,:],qpp[i,:])
eps[i] = reg.computeRegressionCriterion(torque[i,:],q[i,:],qp[i,:],qpp[i,:],x)
return J, eps
[docs]
def computeProba(self,eps_n,J_n, bound):
indices = np.where(J_n <= bound)[0]
eps_n_= np.sum(np.abs(eps_n[indices]) <= 0.5)
P = eps_n_ / len(eps_n)
return P
[docs]
def visualizeTrajectoryIdentifiability(self, ti, tf, torque, q, qp, qpp, x):
"""
Visualize the trajectory identifiability criteria
modeled by the probobilty function:
f(x) = P( abs((ε - εm)/σε) < 0.05 || abs((J - Jm)/σJ) < x) where x > 0
"""
J, eps = self.computeTrajectoryIdentifiability(ti, tf, torque, q, qp, qpp, x)
J_n = np.abs((J-np.mean(J))/np.std(J))
eps_n =np.abs((eps-np.mean(eps))/np.std(eps))
thresh = np.linspace(0,3,40)
P = np.zeros_like(thresh)
for i in range(len(thresh)):
P[i] = self.computeProba(eps_n,J_n,thresh[i])
plt.figure(figsize=(12, 6))
sns.lineplot(x=thresh, y= P,label='Propab vs thresh',linewidth=0.7)
plt.title('Trajectory Identifiability')
plt.xlabel('δ')
plt.ylabel('P')
plt.legend()