static int ks_assign_hydrogens(const float* xyz, const int* nco_indices, const int n_residues, float *hcoords) /* Assign hydrogen atom coordinates */ { int ri, pc_index, po_index; __m128 pc, po, r_co, r_h, r_n, norm_r_co; __m128 tenth = _mm_set1_ps(0.1f); r_n = load_float3(xyz + 3*nco_indices[0]); store_float3(hcoords, r_n); hcoords += 3; for (ri = 1; ri < n_residues; ri++) { pc_index = nco_indices[3*(ri-1) + 1]; po_index = nco_indices[3*(ri-1) + 2]; pc = load_float3(xyz + 3*pc_index); po = load_float3(xyz + 3*po_index); r_co = _mm_sub_ps(pc, po); r_n = load_float3(xyz + 3*nco_indices[3*ri + 0]); norm_r_co = _mm_mul_ps(r_co, _mm_rsqrt_ps(_mm_dp_ps(r_co, r_co, 0xFF))); r_h = _mm_add_ps(r_n, _mm_mul_ps(tenth, norm_r_co)); store_float3(hcoords, r_h); hcoords += 3; } return 1; }
int drid_moments(float* coords, int32_t index, int32_t* partners, int32_t n_partners, double* moments) { int32_t i; float d; moments_t onlinemoments; __m128 x, y, r, r2, s; moments_clear(&onlinemoments); x = load_float3(&coords[3 * index]); for (i = 0; i < n_partners; i++) { y = load_float3(&coords[3 * partners[i]]); r = _mm_sub_ps(x, y); /* x - y */ r2 = _mm_mul_ps(r, r); /* (x - y)**2 */ /* horizontal add the components of d2 with */ /* two instructions. note: it's critical */ /* here that the last entry of x1 and x2 was 0 */ /* so that d2.w = 0 */ s = _mm_add_ps(r2, _mm_movehl_ps(r2, r2)); s = _mm_add_ss(s, _mm_shuffle_ps(s, s, 1)); /* store into a regular float. I tried using _mm_rsqrt_ps, but it's not accurate to pass the tests */ _mm_store_ss(&d, s); moments_push(&onlinemoments, 1.0 / sqrt((double) d)); } moments[0] = moments_mean(&onlinemoments); moments[1] = sqrt(moments_second(&onlinemoments)); moments[2] = cbrt(moments_third(&onlinemoments)); return 1; }
int dihedral(const float* xyz, const int* quartets, float* out, const int n_frames, const int n_atoms, const int n_quartets) { /* Compute the angle between sets of four atoms in every frame of xyz. Parameters ---------- xyz : array, shape=(n_frames, n_atoms, 3) Cartesian coordinates of the atoms in every frame, in contiguous C order. quartets : array, shape=(n_quartets, 3) The specific quartet of atoms whose angle you want to compute. The angle computed will be the torsion around the bound between the middle two elements (i.e aABCD). A 2d array of indices, in C order. out : array, shape=(n_frames, n_pairs) Array where the angles will be stored, in contiguous C order. All of the arrays are assumed to be contiguous. This code will segfault if they're not. */ int i, j; __m128 x0, x1, x2, x3, b1, b2, b3, c1, c2, p1, p2; for (i = 0; i < n_frames; i++) { for (j = 0; j < n_quartets; j++) { x0 = load_float3(xyz + 3*quartets[4*j + 0]); x1 = load_float3(xyz + 3*quartets[4*j + 1]); x2 = load_float3(xyz + 3*quartets[4*j + 2]); x3 = load_float3(xyz + 3*quartets[4*j + 3]); b1 = _mm_sub_ps(x1, x0); b2 = _mm_sub_ps(x2, x1); b3 = _mm_sub_ps(x3, x2); c1 = cross(b2, b3); c2 = cross(b1, b2); p1 = _mm_mul_ps(_mm_dp_ps(b1, c1, 0x71), _mm_sqrt_ps(_mm_dp_ps(b2, b2, 0x71))); p2 = _mm_dp_ps(c1, c2, 0x71); *(out++) = atan2(_mm_cvtss_f32(p1), _mm_cvtss_f32(p2)); }; xyz += n_atoms*3; } return 1; }
int angle(const float* xyz, const int* triplets, float* out, const int n_frames, const int n_atoms, const int n_angles) { /* Compute the angle between tripples of atoms in every frame of xyz. Parameters ---------- xyz : array, shape=(n_frames, n_atoms, 3) Cartesian coordinates of the atoms in every frame, in contiguous C order. triplets : array, shape=(n_angles, 3) The specific tripple of atoms whose angle you want to compute. The angle computed will be centered around the middle element (i.e aABC). A 2d array of indices, in C order. out : array, shape=(n_frames, n_pairs) Array where the angles will be stored, in contiguous C order. All of the arrays are assumed to be contiguous. This code will segfault if they're not. */ int i, j; __m128 r_m, r_n, r_o, u_prime, u, v_prime, v; for (i = 0; i < n_frames; i++) { for (j = 0; j < n_angles; j++) { r_m = load_float3(xyz + 3*triplets[3*j + 0]); r_o = load_float3(xyz + 3*triplets[3*j + 1]); r_n = load_float3(xyz + 3*triplets[3*j + 2]); u_prime = _mm_sub_ps(r_m, r_o); v_prime = _mm_sub_ps(r_n, r_o); // normalize the vectors u_prime and v_prime u = _mm_mul_ps(u_prime, _mm_rsqrt_ps(_mm_dp_ps(u_prime, u_prime, 0x7F))); v = _mm_mul_ps(v_prime, _mm_rsqrt_ps(_mm_dp_ps(v_prime, v_prime, 0x7F))); // compute the arccos of the dot product, and store the result. *(out++) = acos(_mm_cvtss_f32(_mm_dp_ps(u, v, 0x71))); } // advance to the next frame xyz += n_atoms*3; } return 1; }
/** * Identify bends in the chain, where the kappa angle (virtual bond angle from * c-alpha i-2, to i, to i+2) is greater than 70 degrees * dssp-2.2.0/structure.cpp:1729 */ static std::vector<int> calculate_bends(const float* xyz, const int* ca_indices, const int* chain_ids, const int n_residues, std::vector<int>& skip) { __m128 prev_ca, this_ca, next_ca, u_prime, v_prime, u, v; float kappa; std::vector<int> is_bend(n_residues, 0); for (int i = 2; i < n_residues-2; i++) { if (chain_ids[i-2] == chain_ids[i+2] && !skip[i-2] && !skip[i] && !skip[i+2]) { prev_ca = load_float3(xyz + 3*ca_indices[i-2]); this_ca = load_float3(xyz + 3*ca_indices[i]); next_ca = load_float3(xyz + 3*ca_indices[i+2]); u_prime = _mm_sub_ps(prev_ca, this_ca); v_prime = _mm_sub_ps(this_ca, next_ca); /* normalize the vectors u_prime and v_prime */ u = _mm_div_ps(u_prime, _mm_sqrt_ps(_mm_dp_ps2(u_prime, u_prime, 0x7F))); v = _mm_div_ps(v_prime, _mm_sqrt_ps(_mm_dp_ps2(v_prime, v_prime, 0x7F))); /* compute the arccos of the dot product. this gives the angle */ kappa = (float) acos(CLIP(_mm_cvtss_f32(_mm_dp_ps2(u, v, 0x71)), -1, 1)); is_bend[i] = kappa > (70 * (M_PI / 180.0)); } } return is_bend; }
int dist(const float* xyz, const int* pairs, float* distance_out, float* displacement_out, const int n_frames, const int n_atoms, const int n_pairs) { /* Compute the distance/displacement between pairs of atoms in every frame of xyz. Parameters ---------- xyz : array, shape=(n_frames, n_atoms, 3) Cartesian coordinates of the atoms in every frame, in contiguous C order. pairs : array, shape=(n_pairs, 2) The specific pairs of atoms whose distance you want to compute. A 2d array of pairs, in C order. distance_out : array, shape=(n_frames, n_pairs), optional Array where the distances between pairs will be stored, in contiguous C order. If NULL is passed in, this return value will not be saved displacement_out : array, shaoe=(n_frames, n_pairs, 3), optional An optional return value: if you'd also like to save the displacement vectors between the pairs, you can pass a pointer here. If displacement_out is NULL, then this variable will not be saved back to memory. All of the arrays are assumed to be contiguous. This code will segfault if they're not. */ int i, j; int store_displacement = displacement_out == NULL ? 0 : 1; int store_distance = distance_out == NULL ? 0 : 1; __m128 x1, x2, r12, r12_2, s; for (i = 0; i < n_frames; i++) { for (j = 0; j < n_pairs; j++) { // Load the two vectors whos distance we want to compute // x1 = xyz[i, pairs[j,0], 0:3] // x2 = xyz[i, pairs[j,1], 0:3] x1 = load_float3(xyz + 3*pairs[2*j + 0]); x2 = load_float3(xyz + 3*pairs[2*j + 1]); // r12 = x2 - x1 r12 = _mm_sub_ps(x2, x1); // r12_2 = r12*r12 r12_2 = _mm_mul_ps(r12, r12); if (store_displacement) { // store the two lower entries (x,y) in memory _mm_storel_pi((__m64*)(displacement_out), r12); displacement_out += 2; // swap high-low and then store the z entry in the memory _mm_store_ss(displacement_out++, _mm_movehl_ps(r12, r12)); } if (store_distance) { // horizontal add the components of d2 with // two instructions. note: it's critical // here that the last entry of x1 and x2 was 0 // so that d2.w = 0 s = _mm_hadd_ps(r12_2, r12_2); s = _mm_hadd_ps(s, s); // sqrt our final answer s = _mm_sqrt_ps(s); // s now contains our answer in all four elements, because // of the way the hadd works. we only want to store one // element. _mm_store_ss(distance_out++, s); } } // advance to the next frame xyz += n_atoms*3; } return 1; }
int kabsch_sander(const float* xyz, const int* nco_indices, const int* ca_indices, const int n_frames, const int n_atoms, const int n_residues, int* hbonds, float* henergies) { /* Find all of backbone hydrogen bonds between residues in each frame of a trajectory. Parameters ---------- xyz : array, shape=(n_frames, n_atoms, 3) The cartesian coordinates of all of the atoms in each frame. nco_indices : array, shape=(n_residues, 3) The indices of the backbone N, C, and O atoms for each residue. ca_indices : array, shape=(n_residues,) The index of the CA atom of each residue. If a residue does not contain a CA atom, or you want to skip the residue for another reason, the value should be -1 Returns ------- hbonds : array, shape=(n_frames, n_residues, 2) This is a little tricky, so bear with me. This array gives the indices of the residues that each backbone hbond *acceptor* is engaged in an hbond with. For instance, the equality `bonds[i, j, 0] == k` is interpreted as "in frame i, residue j is accepting its first hydrogen bond from residue k". `bonds[i, j, 1] == k` means that residue j is accepting its second hydrogen bond from residue k. A negative value indicates that no such hbond exists. henergies : array, shape=(n_frames, n_residues, 2) The semantics of this array run parallel to the hbonds array, but instead of giving the identity of the interaction partner, it gives the energy of the hbond. Only hbonds with energy below -0.5 kcal/mol are recorded. */ int i, ri, rj; static float HBOND_ENERGY_CUTOFF = -0.5; __m128 ri_ca, rj_ca, r12; __m128 MINIMAL_CA_DISTANCE2 = _mm_set1_ps(0.81); float* hcoords = (float*) malloc(n_residues*3 * sizeof(float)); if (hcoords == NULL) { fprintf(stderr, "Memory Error\n"); exit(1); } for (i = 0; i < n_frames; i++) { ks_assign_hydrogens(xyz, nco_indices, n_residues, hcoords); for (ri = 0; ri < n_residues; ri++) { // -1 is used to indicate that this residue lacks a this atom type // so just skip it if (ca_indices[ri] == -1) continue; ri_ca = load_float3(xyz + 3*ca_indices[ri]); for (rj = ri + 1; rj < n_residues; rj++) { if (ca_indices[rj] == -1) continue; rj_ca = load_float3(xyz + 3*ca_indices[rj]); // check the ca distance before proceding r12 = _mm_sub_ps(ri_ca, rj_ca); if(_mm_extract_epi16(CAST__M128I(_mm_cmplt_ps(_mm_dp_ps(r12, r12, 0x7F), MINIMAL_CA_DISTANCE2)), 0)) { float e = ks_donor_acceptor(xyz, hcoords, nco_indices, ri, rj); if (e < HBOND_ENERGY_CUTOFF) // hbond from donor=ri to acceptor=rj store_energies(hbonds, henergies, ri, rj, e); if (rj != ri + 1) { float e = ks_donor_acceptor(xyz, hcoords, nco_indices, rj, ri); if (e < HBOND_ENERGY_CUTOFF) // hbond from donor=rj to acceptor=ri store_energies(hbonds, henergies, rj, ri, e); } } } } xyz += n_atoms*3; // advance to the next frame hbonds += n_residues*2; henergies += n_residues*2; } free(hcoords); return 1; }
static float ks_donor_acceptor(const float* xyz, const float* hcoords, const int* nco_indices, int donor, int acceptor) { /* Conpute the Kabsch-Sander hydrogen bond energy between two residues in a single conformation. Parameters ---------- xyz : array, shape=(n_atoms, 3) All of the atoms in this frame nhco0 : array, shape=(4,) The indices of the backbone N, H, C, and O atoms in one residue. nhco1 : array, shape=(4,) The indices of the backbone N, H, C, and O atoms in the other residue. donor : int Boolean flag. If 0, then nhco0 is the hydrogen bond proton donor (i.e. we look at its N and H). If 1, then nhco1 is the hydrogen bond proton donor. Returns ------- energy : float The KS backbone hydrogen bond energy, in kcal/mol. A number under -0.5 is considered significant. */ float energy; __m128 r_n, r_h, r_c, r_o, r_ho, r_nc, r_hc, r_no, d2_honchcno; __m128 coupling; // 332 (kcal*A/mol) * 0.42 * 0.2 * (1nm / 10 A) coupling = _mm_setr_ps(-2.7888, -2.7888, 2.7888, 2.7888); r_n = load_float3(xyz + 3*nco_indices[3*donor]); r_h = load_float3(hcoords + 3*donor); r_c = load_float3(xyz + 3*nco_indices[3*acceptor + 1]); r_o = load_float3(xyz + 3*nco_indices[3*acceptor + 2]); //printf("Donor Index %d\n", donor); //printf("Acceptor Index %d\n", acceptor); /*printf("N index %d\n", 3*nco_indices[3*donor + 0]); printf("C index %d\n", 3*nco_indices[3*acceptor + 1]); printf("O index %d\n", 3*nco_indices[3*acceptor + 2]); printf("\nrN "); printf_m128(r_n); printf("rH "); printf_m128(r_h); printf("rC "); printf_m128(r_c); printf("rO "); printf_m128(r_o);*/ r_ho = _mm_sub_ps(r_h, r_o); r_hc = _mm_sub_ps(r_h, r_c); r_nc = _mm_sub_ps(r_n, r_c); r_no = _mm_sub_ps(r_n, r_o); // compute all four dot products (each of the squared distances), and then // pack them into a single float4 using three shuffles. d2_honchcno = _mm_shuffle_ps(_mm_shuffle_ps(_mm_dp_ps(r_ho, r_ho, 0xF3), _mm_dp_ps(r_nc, r_nc, 0xF3), _MM_SHUFFLE(0,1,0,1)), _mm_shuffle_ps(_mm_dp_ps(r_hc, r_hc, 0xF3), _mm_dp_ps(r_no, r_no, 0xF3), _MM_SHUFFLE(0,1,0,1)), _MM_SHUFFLE(2,0,2,0)); energy = _mm_cvtss_f32(_mm_dp_ps(coupling, _mm_rsqrt_ps(d2_honchcno), 0xFF)); //printf("Energy: %f\n\n", energy); return (energy < -9.9f ? -9.9f : energy); }
int dist_mic(const float* xyz, const int* pairs, const float* box_matrix, float* distance_out, float* displacement_out, const int n_frames, const int n_atoms, const int n_pairs) { /* Compute the distance/displacement between pairs of atoms in every frame of xyz following the minimum image convention in periodic boundary conditions. The computation follows scheme B.9 in Tukerman, M. "Statistical Mechanics: Theory and Molecular Simulation", 2010. Parameters ---------- xyz : array, shape=(n_frames, n_atoms, 3) Cartesian coordinates of the atoms in every frame, in contiguous C order. pairs : array, shape=(n_pairs, 2) The specific pairs of atoms whose distance you want to compute. A 2d array of pairs, in C order. box_matrix : array, shape=(3,3) The box matrix for a single frame. All of the frames are assumed to use this box vector. distance_out : array, shape=(n_frames, n_pairs) Array where the distances between pairs will be stored, in contiguous C order. displacement_out : array, shaoe=(n_frames, n_pairs, 3), optional An optional return value: if you'd also like to save the displacement vectors between the pairs, you can pass a pointer here. If displacement_out is NULL, then this variable will not be saved back to memory. All of the arrays are assumed to be contiguous. This code will segfault if they're not. */ #ifndef __SSE4_1__ _MM_SET_ROUNDING_MODE(_MM_ROUND_NEAREST); int rounding_mode = _MM_GET_ROUNDING_MODE(); #endif int i, j; int store_displacement = displacement_out == NULL ? 0 : 1; int store_distance = distance_out == NULL ? 0 : 1; __m128 r1, r2, s12, r12, s, r12_2; __m128 hinv[3]; __m128 h[3]; for (i = 0; i < n_frames; i++) { // Store the columns of the box matrix in three float4s. This format // is fast for matrix * vector product. See, for example, this S.O. question: // http://stackoverflow.com/questions/14967969/efficient-4x4-matrix-vector-multiplication-with-sse-horizontal-add-and-dot-prod h[0] = _mm_setr_ps(box_matrix[0], box_matrix[3], box_matrix[6], 0.0f); h[1] = _mm_setr_ps(box_matrix[1], box_matrix[4], box_matrix[7], 0.0f); h[2] = _mm_setr_ps(box_matrix[2], box_matrix[5], box_matrix[8], 0.0f); // Calculate the inverse of the box matrix, and also store it in the same // format. inverse33(box_matrix, hinv+0, hinv+1, hinv+2); for (j = 0; j < n_pairs; j++) { // Load the two vectors whos distance we want to compute r1 = load_float3(xyz + 3*pairs[2*j + 0]); r2 = load_float3(xyz + 3*pairs[2*j + 1]); r12 = _mm_sub_ps(r2, r1); // s12 = INVERSE(H) * r12 s12 = _mm_add_ps(_mm_add_ps( _mm_mul_ps(hinv[0], _mm_shuffle_ps(r12, r12, _MM_SHUFFLE(0,0,0,0))), _mm_mul_ps(hinv[1], _mm_shuffle_ps(r12, r12, _MM_SHUFFLE(1,1,1,1)))), _mm_mul_ps(hinv[2], _mm_shuffle_ps(r12, r12, _MM_SHUFFLE(2,2,2,2)))); // s12 = s12 - NEAREST_INTEGER(s12) #ifdef __SSE4_1__ s12 = _mm_sub_ps(s12, _mm_round_ps(s12, _MM_FROUND_TO_NEAREST_INT)); #else s12 = _mm_sub_ps(s12, _mm_cvtepi32_ps(_mm_cvtps_epi32(s12))); #endif r12 = _mm_add_ps(_mm_add_ps( _mm_mul_ps(h[0], _mm_shuffle_ps(s12, s12, _MM_SHUFFLE(0,0,0,0))), _mm_mul_ps(h[1], _mm_shuffle_ps(s12, s12, _MM_SHUFFLE(1,1,1,1)))), _mm_mul_ps(h[2], _mm_shuffle_ps(s12, s12, _MM_SHUFFLE(2,2,2,2)))); if (store_displacement) { // store the two lower entries (x,y) in memory _mm_storel_pi((__m64*)(displacement_out), r12); displacement_out += 2; // swap high-low and then store the z entry in the memory _mm_store_ss(displacement_out++, _mm_movehl_ps(r12, r12)); } if (store_distance) { // out = sqrt(sum(r12**2)) r12_2 = _mm_mul_ps(r12, r12); s = _mm_hadd_ps(r12_2, r12_2); s = _mm_hadd_ps(s, s); s = _mm_sqrt_ps(s); _mm_store_ss(distance_out++, s); } } // advance to the next frame xyz += n_atoms*3; box_matrix += 9; } #ifndef __SSE4_1__ _MM_SET_ROUNDING_MODE(rounding_mode); #endif return 1; }
static void asa_frame(const float* frame, const int n_atoms, const float* atom_radii, const float* sphere_points, const int n_sphere_points, int* neighbor_indices, float* centered_sphere_points, float* areas) { /*// Calculate the accessible surface area of each atom in a single snapshot // // Parameters // ---------- // frame : 2d array, shape=[n_atoms, 3] // The coordinates of the nuclei // n_atoms : int // the major axis length of frame // atom_radii : 1d array, shape=[n_atoms] // the van der waals radii of the atoms PLUS the probe radius // sphere_points : 2d array, shape=[n_sphere_points, 3] // a bunch of uniformly distributed points on a sphere // n_sphere_points : int // the number of sphere points // centered_sphere_points : WORK BUFFER 2d array, shape=[n_sphere_points, 3] // empty memory that intermediate calculations can be stored in // neighbor_indices : WORK BUFFER 2d array, shape=[n_atoms] // empty memory that intermediate calculations can be stored in // NOTE: the point of these work buffers is that if we want to call // this function repreatedly, its more efficient not to keep re-mallocing // these work buffers, but instead just reuse them. // areas : 1d array, shape=[n_atoms] // the output buffer to place the results in -- the surface area of each // atom */ int i, j, k, k_prime; __m128 r, r_i, r_j, r_ij, atom_radius_i, atom_radius_j, radius_cutoff; __m128 radius_cutoff2, sp, r_jk, r2; int n_neighbor_indices, is_accessible, k_closest_neighbor; float constant = 4.0 * M_PI / n_sphere_points; for (i = 0; i < n_atoms; i++) { atom_radius_i = _mm_set1_ps(atom_radii[i]); r_i = load_float3(frame+i*3); /* Get all the atoms close to atom `i` */ n_neighbor_indices = 0; for (j = 0; j < n_atoms; j++) { if (i == j) { continue; } r_j = load_float3(frame+j*3); r_ij = _mm_sub_ps(r_i, r_j); atom_radius_j = _mm_set1_ps(atom_radii[j]); /* Look for atoms `j` that are nearby atom `i` */ radius_cutoff = _mm_add_ps(atom_radius_i, atom_radius_j); radius_cutoff2 = _mm_mul_ps(radius_cutoff, radius_cutoff); r2 = _mm_dp_ps(r_ij, r_ij, 0x7F); if (_mm_extract_epi16(CAST__M128I(_mm_cmplt_ps(r2, radius_cutoff2)), 0)) { neighbor_indices[n_neighbor_indices] = j; n_neighbor_indices++; } if (_mm_extract_epi16(CAST__M128I(_mm_cmplt_ps(r2, _mm_set1_ps(1e-10))), 0)) { printf("ERROR: THIS CODE IS KNOWN TO FAIL WHEN ATOMS ARE VIRTUALLY"); printf("ON TOP OF ONE ANOTHER. YOU SUPPLIED TWO ATOMS %f", _mm_cvtss_f32(r)); printf("APART. QUITTING NOW"); exit(1); } } /* Center the sphere points on atom i */ for (j = 0; j < n_sphere_points; j++) { sp = _mm_add_ps(r_i, _mm_mul_ps(atom_radius_i, load_float3(sphere_points + 3*j))); store_float3(centered_sphere_points + 3*j, sp); } /* Check if each of these points is accessible */ k_closest_neighbor = 0; for (j = 0; j < n_sphere_points; j++) { is_accessible = 1; r_j = load_float3(centered_sphere_points + 3*j); /* iterate through the sphere points by cycling through them */ /* in a circle, starting with k_closest_neighbor and then wrapping */ /* around */ for (k = k_closest_neighbor; k < n_neighbor_indices + k_closest_neighbor; k++) { k_prime = k % n_neighbor_indices; r = _mm_set1_ps(atom_radii[neighbor_indices[k_prime]]); r_jk = _mm_sub_ps(r_j, load_float3(frame+3*neighbor_indices[k_prime])); if (_mm_extract_epi16(CAST__M128I(_mm_cmplt_ps(_mm_dp_ps(r_jk, r_jk, 0xFF), _mm_mul_ps(r, r))), 0)) { k_closest_neighbor = k; is_accessible = 0; break; } } if (is_accessible) { areas[i]++; } } areas[i] *= constant * (atom_radii[i])*(atom_radii[i]); } }