import numpy as np # Dyadic Solution Path Finder def find_path(A, motor = [0,1], fixed_nodes=[0, 1]): ''' This function finds the solution path of a dyadic mechanism. Parameters: A (np.array): Adjacency matrix of the mechanism. motor (list): motor nodes. fixed_nodes (list): List of fixed nodes. Returns: path (np.array): Solution path of the mechanism. status (bool): True if the mechanism is dyadic and has a solution path, False otherwise. ''' path = [] A,fixed_nodes,motor = np.array(A),np.array(fixed_nodes),np.array(motor) unkowns = np.array(list(range(A.shape[0]))) knowns = np.concatenate([fixed_nodes,[motor[-1]]]) unkowns = unkowns[np.logical_not(np.isin(unkowns,knowns))] counter = 0 while unkowns.shape[0] != 0: if counter == unkowns.shape[0]: # Non dyadic or DOF larger than 1 return [], False n = unkowns[counter] ne = np.where(A[n])[0] kne = knowns[np.isin(knowns,ne)] # print(kne.shape[0]) if kne.shape[0] == 2: path.append([n,kne[0],kne[1]]) counter = 0 knowns = np.concatenate([knowns,[n]]) unkowns = unkowns[unkowns!=n] elif kne.shape[0] > 2: #redundant or overconstraint return [], False else: counter += 1 return np.array(path), True # Dyadic Mechanism Sorting def get_order(A, motor = [0,1], fixed_nodes=[0, 1]): ''' This function sorts the mechanism based on the solution path. Parameters: A (np.array): Adjacency matrix of the mechanism. motor (list): motor nodes. fixed_nodes (list): List of fixed nodes. Returns: joint order (np.array): Sorted order of the joints in a mechanism. ''' path, status = find_path(A, motor, fixed_nodes) fixed_nodes = np.array(fixed_nodes) if status: return np.concatenate([motor,fixed_nodes[fixed_nodes!=motor[0]],path[:,0]]) else: raise Exception("Non Dyadic or Dof larger than 1") def sort_mechanism(A, x0, motor = [0,1], fixed_nodes=[0, 1]): ''' This function sorts the mechanism based on the solution path. Parameters: A (np.array): Adjacency matrix of the mechanism. x0 (np.array): Initial positions of the joints. motor (list): motor nodes. fixed_nodes (list): List of fixed nodes. Returns: A_s (np.array): Sorted adjacency matrix of the mechanism. x0 (np.array): Sorted initial positions of the joints. motor (np.array): Motor nodes. fixed_nodes (np.array): Fixed nodes. ord (np.array): Sorted order of the joints in a mechanism. ''' ord = get_order(A, motor, fixed_nodes) n_t = np.zeros(A.shape[0]) n_t[fixed_nodes] = 1 A_s = A[ord,:][:,ord] n_t_s = n_t[ord] return A_s, x0[ord], np.array([0,1]), np.where(n_t_s)[0], ord # Vectorized Dyadic Solver def solve_rev_vectorized_batch_CPU(As,x0s,node_types,thetas): Gs = np.square((np.expand_dims(x0s,1) - np.expand_dims(x0s,2))).sum(-1) x = np.zeros([x0s.shape[0],x0s.shape[1],thetas.shape[0],2]) x = x + np.expand_dims(node_types * x0s,2) m = x[:,0] + np.tile(np.expand_dims(np.swapaxes(np.concatenate([np.expand_dims(np.cos(thetas),0),np.expand_dims(np.sin(thetas),0)],0),0,1),0),[x0s.shape[0],1,1]) * np.expand_dims(np.expand_dims(np.sqrt(Gs[:,0,1]),-1),-1) m = np.expand_dims(m,1) m = np.pad(m,[[0,0],[1,x0s.shape[1]-2],[0,0],[0,0]],mode='constant') x += m for k in range(3,x0s.shape[1]): inds = np.argsort(As[:,k,0:k])[:,-2:] l_ijs = np.linalg.norm(x[np.arange(x0s.shape[0]),inds[:,0]] - x[np.arange(x0s.shape[0]),inds[:,1]], axis=-1) gik = np.sqrt(np.expand_dims(Gs[np.arange(x0s.shape[0]),inds[:,0],np.ones(shape=[x0s.shape[0]],dtype=int)*k],-1)) gjk = np.sqrt(np.expand_dims(Gs[np.arange(x0s.shape[0]),inds[:,1],np.ones(shape=[x0s.shape[0]],dtype=int)*k],-1)) cosphis = (np.square(l_ijs) + np.square(gik) - np.square(gjk))/(2 * l_ijs * gik) cosphis = np.where(np.tile(node_types[:,k],[1,thetas.shape[0]])==0.0,cosphis,np.zeros_like(cosphis)) x0i1 = x0s[np.arange(x0s.shape[0]),inds[:,0],np.ones(shape=[x0s.shape[0]]).astype(np.int32)] x0i0 = x0s[np.arange(x0s.shape[0]),inds[:,0],np.zeros(shape=[x0s.shape[0]]).astype(np.int32)] x0j1 = x0s[np.arange(x0s.shape[0]),inds[:,1],np.ones(shape=[x0s.shape[0]]).astype(np.int32)] x0j0 = x0s[np.arange(x0s.shape[0]),inds[:,1],np.zeros(shape=[x0s.shape[0]]).astype(np.int32)] x0k1 = x0s[:,k,1] x0k0 = x0s[:,k,0] s = np.expand_dims(np.sign((x0i1-x0k1)*(x0i0-x0j0) - (x0i1-x0j1)*(x0i0-x0k0)),-1) phi = s * np.arccos(cosphis) a = np.transpose(np.concatenate([np.expand_dims(np.cos(phi),0),np.expand_dims(-np.sin(phi),0)],0),axes=[1,2,0]) b = np.transpose(np.concatenate([np.expand_dims(np.sin(phi),0),np.expand_dims(np.cos(phi),0)],0),axes=[1,2,0]) R = np.einsum("ijk...->jki...", np.concatenate([np.expand_dims(a,0),np.expand_dims(b,0)],0)) xi = x[np.arange(x0s.shape[0]),inds[:,0]] xj = x[np.arange(x0s.shape[0]),inds[:,1]] scaled_ij = (xj-xi)/np.expand_dims(l_ijs,-1) * np.expand_dims(gik,-1) x_k = np.squeeze(np.matmul(R, np.expand_dims(scaled_ij,-1))) + xi x_k = np.where(np.tile(np.expand_dims(node_types[:,k],-1),[1,thetas.shape[0],2])==0.0,x_k,np.zeros_like(x_k)) x_k = np.expand_dims(x_k,1) x_k = np.pad(x_k,[[0,0],[k,x0s.shape[1]-k-1],[0,0],[0,0]],mode='constant') x += x_k return x # Solve a single mechanism def solve_mechanism(A, x0 , motor = [0,1], fixed_nodes=[0, 1], thetas = np.linspace(0,2*np.pi,200)): A,x0,motor,fixed_nodes,ord = sort_mechanism(A, x0, motor, fixed_nodes) n_t = np.zeros([A.shape[0],1]) n_t[fixed_nodes] = 1 A = np.expand_dims(A,0) x0 = np.expand_dims(x0,0) n_t = np.expand_dims(n_t,0) sol = solve_rev_vectorized_batch_CPU(A,x0,n_t,thetas) return sol[0], ord