Rosetta 3.5
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
MembraneEnvSmoothEnergy.cc
Go to the documentation of this file.
1 // -*- mode:c++;tab-width:2;indent-tabs-mode:t;show-trailing-whitespace:t;rm-trailing-spaces:t -*-
2 // vi: set ts=2 noet:
3 //
4 // (c) Copyright Rosetta Commons Member Institutions.
5 // (c) This file is part of the Rosetta software suite and is made available under license.
6 // (c) The Rosetta software is developed by the contributing members of the Rosetta Commons.
7 // (c) For more information, see http://www.rosettacommons.org. Questions about this can be
8 // (c) addressed to University of Washington UW TechTransfer, email: license@u.washington.edu.
9 
10 /// @file core/scoring/methods/MembraneEnvSmoothEnergy.cc
11 /// @brief Statistically derived smooth membrane environment potential class implementation
12 /// @brief and based on EnvSmoothEnergy.cc developed by Andrew Leaver-Fay, Mike Tyka and Phil Bradley
13 /// @author Vladimir Yarov-Yarovoy
14 
15 // Unit headers
18 
19 // Package headers
20 #include <core/chemical/AA.hh>
22 #include <core/scoring/Energies.hh>
23 // AUTO-REMOVED #include <core/scoring/EnergyGraph.hh>
27 
28 // Project headers
29 #include <core/pose/Pose.hh>
31 
32 #include <core/id/AtomID.hh>
33 #include <utility/vector1.hh>
34 #include <utility/io/izstream.hh>
35 #include <basic/Tracer.hh>
36 #include <basic/database/open.hh>
37 
38 //Auto using namespaces
39 namespace ObjexxFCL { namespace fmt { } } using namespace ObjexxFCL::fmt; // AUTO USING NS
40 //Auto using namespaces end
41 
42 // C++
43 static basic::Tracer TR("core.scoring.methods.MembraneEnvSmoothEnergy",basic::t_info);
44 
45 namespace core {
46 namespace scoring {
47 namespace methods {
48 
49 
50 /// @details This must return a fresh instance of the MembraneEnvSmoothEnergy class,
51 /// never an instance already in use
53 MembraneEnvSmoothEnergyCreator::create_energy_method(
55 ) const {
56  return new MembraneEnvSmoothEnergy;
57 }
58 
60 MembraneEnvSmoothEnergyCreator::score_types_for_method() const {
61  ScoreTypes sts;
62  sts.push_back( Menv_smooth );
63  return sts;
64 }
65 
66 Distance const start_sig = 9.8;
67 Distance const end_sig = 10.2;
68 
71 
72 ////////////////////////////////////////////////////////////////////////////
73 
74 MembraneEnvSmoothEnergy::MembraneEnvSmoothEnergy() :
76 {
77  Size const max_aa( 20 );
78  Size const env_log_table_cen10_bins( 40 );
79  Size const max_mem_layers( 3 );
80  std::string tag,line;
81  chemical::AA aa;
82  mem_env_log10_.dimension( max_aa, max_mem_layers, env_log_table_cen10_bins );
83  utility::io::izstream stream;
84  basic::database::open( stream, "scoring/score_functions/MembranePotential/CEN10_Menv_smooth_log.txt" );
85  for ( Size i=1; i<= max_aa; ++i ){
86  getline( stream, line );
87  std::istringstream l(line);
88  l >> tag >> aa;
89  if ( l.fail() || tag != "MENV_SMOOTH_LOG_CEN10:" ) utility_exit_with_message("bad format for scoring/score_functions/MembranePotential/CEN10_Menv_smooth_log.txt (Menv_smooth)");
90  for( Size j=1;j<=max_mem_layers;++j) {
91  for( Size k=1;k<=env_log_table_cen10_bins;++k) {
92  l >> mem_env_log10_(aa,j,k);
93  }
94  }
95  }
96 }
97 
98 /// clone
101 {
102  return new MembraneEnvSmoothEnergy( *this );
103 }
104 
105 /////////////////////////////////////////////////////////////////////////////
106 // scoring
107 /////////////////////////////////////////////////////////////////////////////
108 
109 inline Real sqr ( Real x ) {
110  return x*x;
111 }
112 
113 
114 /// @details stores dScore/dNumNeighbors so that when neighbor atoms on adjacent
115 /// residues move, their influence on the score of the surrounding residues is
116 /// rapidly computed.
117 void
119  pose::Pose & pose,
120  ScoreFunction const &
121 ) const
122 {
124  Size const nres( pose.total_residue() );
125 
126  residue_N_.clear();
127  residue_E_.clear();
128  residue_dEdN_.clear();
129 
130 
131  // iterate over all the residues in the protein and count their neighbours
132  // and save values of E, N, and dEdN
133  for ( Size i = 1; i <= nres; ++i ) {
134 
135  // get the appropriate residue from the pose.
136  conformation::Residue const & rsd( pose.residue(i) );
137  // currently this is only for protein residues
138  if(! rsd.is_protein() ) continue; //return;
139 
140  Size const atomindex_i = rsd.atom_index( representative_atom_name( rsd.aa() ));
141 
142  core::conformation::Atom const & atom_i = rsd.atom(atomindex_i);
143 
144  const Energies & energies( pose.energies() );
145  const TwelveANeighborGraph & graph ( energies.twelveA_neighbor_graph() );
146 
147  Real countN = 0.0;
148 
149  // iterate across neighbors within 12 angstroms
151  ir = graph.get_node(i)->const_edge_list_begin(),
152  ire = graph.get_node(i)->const_edge_list_end();
153  ir != ire; ++ir ) {
154  Size const j( (*ir)->get_other_ind( i ) );
155  conformation::Residue const & rsd_j( pose.residue(j) );
156  Size atomindex_j( rsd_j.type().nbr_atom() );
157 
158  core::conformation::Atom const & atom_j = rsd_j.atom(atomindex_j);
159 
160  Real sqdist = atom_i.xyz().distance_squared(atom_j.xyz());
161  countN += sigmoidish_neighbor( sqdist );
162  }
163 
164  Real score = 0;
165  Real dscoredN = 0;
166 
167  calc_energy( rsd, pose, countN, rsd.aa(), score, dscoredN );
168 
169  residue_N_.push_back( countN );
170  residue_E_.push_back( score );
171  residue_dEdN_.push_back( dscoredN );
172 
173  //std::cout << "ENV: " << i << " " << score << std::endl;
174  }
175 
176 }
177 
178 void
180  pose::Pose & pose,
181  ScoreFunction const &
182 ) const
183 {
185 }
186 
187 /// @details counts the number of nbr atoms within a given radius of the for the input
188 /// residue. Because the representative atom on the input residue may be in a different
189 /// location than the representative atom on the same residue when scoring_begin() is called,
190 /// these neighbor counts cannot be reused; therefore, scoring_begin does not keep
191 /// neighbor counts.
192 void
194  conformation::Residue const & rsd,
195  pose::Pose const & pose,
196  EnergyMap & emap
197 ) const
198 {
199  // currently this is only for protein residues
200  if ( ! rsd.is_protein() ) return;
201 
202  TwelveANeighborGraph const & graph ( pose.energies().twelveA_neighbor_graph() );
203  Size const atomindex_i = rsd.atom_index( representative_atom_name( rsd.aa() ));
204 
205  core::conformation::Atom const & atom_i = rsd.atom(atomindex_i);
206 
207  Real countN = 0.0;
208  // iterate across neighbors within 12 angstroms
209  using namespace ObjexxFCL::fmt;
210  //TR << I(4,rsd.seqpos()) << std::endl;
212  ir = graph.get_node( rsd.seqpos() )->const_edge_list_begin(),
213  ire = graph.get_node( rsd.seqpos() )->const_edge_list_end();
214  ir != ire; ++ir ) {
215  Size const j( (*ir)->get_other_ind( rsd.seqpos() ) );
216  conformation::Residue const & rsd_j( pose.residue(j) );
217 
218  // if virtual residue, don't score
219  if (rsd_j.aa() == core::chemical::aa_vrt) continue;
220 
221  Size atomindex_j( rsd_j.nbr_atom() );
222 
223  core::conformation::Atom const & atom_j = rsd_j.atom(atomindex_j);
224 
225  Real sqdist = atom_i.xyz().distance_squared( atom_j.xyz() );
226  countN += sigmoidish_neighbor( sqdist );
227  //TR << I(4,j) << F(8,3,sqdist) << F(8,3,countN) << std::endl;
228  }
229 
230  Real score = 0;
231  Real dscoredN = 0;
232 
233  calc_energy( rsd, pose, countN, rsd.aa(), score, dscoredN );
234 
235  emap[ Menv_smooth ] += score;
236 }
237 
238 
239 /// @details Special cases handled for when an atom is both the representative
240 /// atom for an amino acid, and its nbr_atom.
241 void
243  id::AtomID const & atom_id,
244  pose::Pose const & pose,
245  kinematics::DomainMap const & ,
246  ScoreFunction const &,
247  EnergyMap const & weights,
248  Vector & F1,
249  Vector & F2
250 ) const
251 {
252  conformation::Residue const & rsd = pose.residue( atom_id.rsd() );
253 
254  if (! rsd.is_protein() ) return;
255 
256  Size const i = rsd.seqpos();
257  Size const i_nbr_atom = rsd.type().nbr_atom();
258  Size const i_rep_atom = rsd.atom_index( representative_atom_name( rsd.aa() ));
259 
260  // forces act only on the nbr atom (CB or CA) or the representative atom
261  if( i_nbr_atom != (Size) atom_id.atomno() && i_rep_atom != (Size) atom_id.atomno() ) return;
262 
263  core::conformation::Atom const & atom_i = rsd.atom( atom_id.atomno() );
264 
265  TwelveANeighborGraph const & graph ( pose.energies().twelveA_neighbor_graph() );
266 
267  // its possible both of these are true
268  bool const input_atom_is_nbr( i_nbr_atom == Size (atom_id.atomno()) );
269  bool const input_atom_is_rep( i_rep_atom == Size ( atom_id.atomno() ));
270 
271  Vector f1(0.0), f2(0.0);
272 
274  ir = graph.get_node(i)->const_edge_list_begin(),
275  ire = graph.get_node(i)->const_edge_list_end();
276  ir != ire; ++ir ) {
277  Size const j( (*ir)->get_other_ind( i ) );
278  conformation::Residue const & rsd_j( pose.residue(j) );
279 
280  // if virtual residue, don't score
281  if (rsd_j.aa() == core::chemical::aa_vrt ) continue;
282 
283  if ( input_atom_is_nbr && input_atom_is_rep && rsd_j.is_protein() ) {
284  Size const resj_rep_atom = rsd_j.atom_index( representative_atom_name( rsd_j.aa() ));
285  Size const resj_nbr_atom = rsd_j.nbr_atom();
286  if ( resj_rep_atom == resj_nbr_atom ) {
287  /// two birds, one stone
289  atom_i, rsd_j.atom( resj_rep_atom ),
290  weights[ Menv_smooth ] * ( residue_dEdN_[ j ] + residue_dEdN_[ i ] ),
291  F1, F2 );
292  } else {
294  atom_i, rsd_j.atom( resj_rep_atom ),
295  weights[ Menv_smooth ] * ( residue_dEdN_[ j ] ),
296  F1, F2 );
297 
299  atom_i, rsd_j.atom( resj_nbr_atom ),
300  weights[ Menv_smooth ] * ( residue_dEdN_[ i ] ),
301  F1, F2 );
302  }
303  } else if ( input_atom_is_nbr && rsd_j.is_protein() ) {
304  Size const resj_rep_atom = rsd_j.atom_index( representative_atom_name( rsd_j.aa() ));
305 
307  atom_i, rsd_j.atom( resj_rep_atom ),
308  weights[ Menv_smooth ] * ( residue_dEdN_[ j ] ),
309  F1, F2 );
310 
311  } else {
312  Size const resj_nbr_atom = rsd_j.nbr_atom();
314  atom_i, rsd_j.atom( resj_nbr_atom ),
315  weights[ Menv_smooth ] * ( residue_dEdN_[ i ] ),
316  F1, F2 );
317 
318  }
319 
320  }
321 
322 }
323 
324 /// @details returns const & to static data members to avoid expense
325 /// of string allocation and destruction. Do not call this function
326 /// on non-canonical aas
327 std::string const &
329 {
330  assert( aa >= 1 && aa <= chemical::num_canonical_aas );
331 
332  static std::string const cbeta_string( "CB" );
333  static std::string const sgamma_string( "SG" );
334  static std::string const cgamma_string( "CG" );
335  static std::string const cdelta_string( "CD" );
336  static std::string const czeta_string( "CZ" );
337  static std::string const calpha_string( "CA" );
338  static std::string const ceps_1_string( "CE1" );
339  static std::string const cdel_1_string( "CD1" );
340  static std::string const ceps_2_string( "CE2" );
341  static std::string const sdelta_string( "SD" );
342 
343  switch ( aa ) {
344  case ( chemical::aa_ala ) : return cbeta_string; break;
345  case ( chemical::aa_cys ) : return sgamma_string; break;
346  case ( chemical::aa_asp ) : return cgamma_string; break;
347  case ( chemical::aa_glu ) : return cdelta_string; break;
348  case ( chemical::aa_phe ) : return czeta_string; break;
349  case ( chemical::aa_gly ) : return calpha_string; break;
350  case ( chemical::aa_his ) : return ceps_1_string; break;
351  case ( chemical::aa_ile ) : return cdel_1_string; break;
352  case ( chemical::aa_lys ) : return cdelta_string; break;
353  case ( chemical::aa_leu ) : return cgamma_string; break;
354  case ( chemical::aa_met ) : return sdelta_string; break;
355  case ( chemical::aa_asn ) : return cgamma_string; break;
356  case ( chemical::aa_pro ) : return cgamma_string; break;
357  case ( chemical::aa_gln ) : return cdelta_string; break;
358  case ( chemical::aa_arg ) : return czeta_string; break;
359  case ( chemical::aa_ser ) : return cbeta_string; break;
360  case ( chemical::aa_thr ) : return cbeta_string; break;
361  case ( chemical::aa_val ) : return cbeta_string; break;
362  case ( chemical::aa_trp ) : return ceps_2_string; break;
363  case ( chemical::aa_tyr ) : return czeta_string; break;
364  default :
365  utility_exit_with_message( "ERROR: Failed to find amino acid " + chemical::name_from_aa( aa ) + " in EnvSmooth::representative_atom_name" );
366  break;
367  }
368 
369  // unreachable
370  return calpha_string;
371 }
372 
373 /// @brief MembraneEnvSmoothEnergy distance cutoff
374 Distance
376 {
377  return 0.0;
378 }
379 
380 /// @brief MembraneEnvSmoothEnergy
381 void
383 {
384  context_graphs_required[ twelve_A_neighbor_graph ] = true;
385 }
386 
387 void
389  conformation::Residue const & rsd,
390  pose::Pose const & pose,
391  Real const neighbor_count,
392  chemical::AA const aa,
393  Real & score,
394  Real & dscore_dneighbor_count
395 ) const
396 {
397  Size low_bin = static_cast< Size > ( floor(neighbor_count));
398  Size high_bin = static_cast< Size > ( ceil(neighbor_count));
399  Real inter = neighbor_count - low_bin;
400  if (neighbor_count <= 1) {
401  low_bin = high_bin = 1; inter = 0;
402  }
403 
404  Real const MembraneDepth (MembraneEmbed_from_pose( pose ).depth( rsd.seqpos() ) );
405 
406  Real thickness = 2.0;
407  int slope = 14;
408  int layer1,layer2,layer;
409  Real f,z,zn,layer_edge;
410 
411  Real const env10_weight=1.0;
412 
413  if (high_bin < 41 ){
414  if ( ( MembraneDepth < 11.0 ) || ( MembraneDepth > 49.0 ) ) {
415  //pure water layer
416  layer = 3;
417 
418  score = env10_weight * mem_env_log10_( aa, layer, low_bin ) * (1.0-inter) +
419  env10_weight * mem_env_log10_( aa, layer, high_bin ) * (inter);
420 
421  dscore_dneighbor_count = env10_weight * mem_env_log10_( aa, layer, high_bin ) -
422  env10_weight * mem_env_log10_( aa, layer, low_bin );
423 
424  } else if ( ( MembraneDepth >= 11.0 && MembraneDepth <= 13.0 ) || ( MembraneDepth >= 47.0 && MembraneDepth <= 49.0 ) ) {
425  //interpolate between water and interface phases
426  layer1 = 2; //interface layer
427  layer2 = 3; //water layer
428 
429  if ( MembraneDepth <= 13.0 ) {
430  layer_edge = 13.0;
431  } else {
432  layer_edge = 47.0;
433  }
434  z = 2*std::abs( (MembraneDepth - layer_edge ) ) / thickness;
435  zn = std::pow( z, slope );
436  f = zn/(1 + zn);
437 
438  score = f * ( env10_weight * mem_env_log10_( aa, layer2, low_bin ) * (1.0-inter) +
439  env10_weight * mem_env_log10_( aa, layer2, high_bin ) * (inter) ) +
440  ( 1 - f ) * ( env10_weight * mem_env_log10_( aa, layer1, low_bin ) * (1.0-inter) +
441  env10_weight * mem_env_log10_( aa, layer1, high_bin ) * (inter) );
442 
443  dscore_dneighbor_count = f * ( env10_weight * mem_env_log10_( aa, layer2, high_bin ) -
444  env10_weight * mem_env_log10_( aa, layer2, low_bin ) ) +
445  ( 1 - f ) * ( env10_weight * mem_env_log10_( aa, layer1, high_bin ) -
446  env10_weight * mem_env_log10_( aa, layer1, low_bin ) );
447 
448  if ( MembraneDepth <= 12.0 || MembraneDepth >= 48.0 ) {
449  layer = 2;
450  } else {
451  layer = 3;
452  }
453 
454  } else if ( ( MembraneDepth > 13.0 && MembraneDepth < 17.0 ) || ( MembraneDepth > 43.0 && MembraneDepth < 47.0 ) ) {
455  //pure interface phase
456  layer = 2;
457 
458  score = env10_weight * mem_env_log10_( aa, layer, low_bin ) * (1.0-inter) +
459  env10_weight * mem_env_log10_( aa, layer, high_bin ) * (inter);
460 
461  dscore_dneighbor_count = env10_weight * mem_env_log10_( aa, layer, high_bin ) -
462  env10_weight * mem_env_log10_( aa, layer, low_bin );
463 
464  } else if ( ( MembraneDepth >= 17.0 && MembraneDepth <= 19.0 ) || ( MembraneDepth >= 41.0 && MembraneDepth <= 43.0 ) ) {
465  //interpolate between interface and hydrophobic phases
466  layer1 = 1; //hydrophobic layer
467  layer2 = 2; //interface layer
468 
469  if ( MembraneDepth <= 19.0 ) {
470  layer_edge = 19.0;
471  } else {
472  layer_edge = 41.0;
473  }
474  z = 2*std::abs( (MembraneDepth - layer_edge ) ) / thickness;
475  zn = std::pow( z, slope );
476  f = zn/(1 + zn);
477 
478  score = f * ( env10_weight * mem_env_log10_( aa, layer2, low_bin ) * (1.0-inter) +
479  env10_weight * mem_env_log10_( aa, layer2, high_bin ) * (inter) ) +
480  ( 1 - f ) * ( env10_weight * mem_env_log10_( aa, layer1, low_bin ) * (1.0-inter) +
481  env10_weight * mem_env_log10_( aa, layer1, high_bin ) * (inter) );
482 
483  dscore_dneighbor_count = f * ( env10_weight * mem_env_log10_( aa, layer2, high_bin ) -
484  env10_weight * mem_env_log10_( aa, layer2, low_bin ) ) +
485  ( 1 - f ) * ( env10_weight * mem_env_log10_( aa, layer1, high_bin ) -
486  env10_weight * mem_env_log10_( aa, layer1, low_bin ) );
487 
488  } else {
489  //pure hydrophobic phase
490  layer = 1;
491 
492  score = env10_weight * mem_env_log10_( aa, layer, low_bin ) * (1.0-inter) +
493  env10_weight * mem_env_log10_( aa, layer, high_bin ) * (inter);
494 
495  dscore_dneighbor_count = env10_weight * mem_env_log10_( aa, layer, high_bin ) -
496  env10_weight * mem_env_log10_( aa, layer, low_bin );
497  }
498 
499  }else{ //high_bin >= 40 neighbors - does it ever happen?
500 
501  dscore_dneighbor_count = 0;
502 
503  if ( ( MembraneDepth < 11.0 ) || ( MembraneDepth > 49.0 ) ) {
504  //pure water layer
505  layer = 3;
506 
507  score = env10_weight * mem_env_log10_( aa, layer, 40 );
508 
509  } else if ( ( MembraneDepth >= 11.0 && MembraneDepth <= 13.0 ) || ( MembraneDepth >= 47.0 && MembraneDepth <= 49.0 ) ) {
510  //interpolate between water and interface phases
511  layer1 = 2; //interface layer
512  layer2 = 3; //water layer
513 
514  if ( MembraneDepth <= 13.0 ) {
515  layer_edge = 13.0;
516  } else {
517  layer_edge = 47.0;
518  }
519  z = 2*std::abs( (MembraneDepth - layer_edge ) ) / thickness;
520  zn = std::pow( z, slope );
521  f = zn/(1 + zn);
522 
523  score = f * ( env10_weight * mem_env_log10_( aa, layer2, 40 ) ) +
524  ( 1 - f ) * ( env10_weight * mem_env_log10_( aa, layer1, 40 ) );
525 
526  if ( MembraneDepth <= 12.0 || MembraneDepth >= 48.0 ) {
527  layer = 2;
528  } else {
529  layer = 3;
530  }
531 
532  } else if ( ( MembraneDepth > 13.0 && MembraneDepth < 17.0 ) || ( MembraneDepth > 43.0 && MembraneDepth < 47.0 ) ) {
533  //pure interface phase
534  layer = 2;
535 
536  score = env10_weight * mem_env_log10_( aa, layer, 40 );
537 
538  } else if ( ( MembraneDepth >= 17.0 && MembraneDepth <= 19.0 ) || ( MembraneDepth >= 41.0 && MembraneDepth <= 43.0 ) ) {
539  //interpolate between interface and hydrophobic phases
540  layer1 = 1; //hydrophobic layer
541  layer2 = 2; //interface layer
542 
543  if ( MembraneDepth <= 19.0 ) {
544  layer_edge = 19.0;
545  } else {
546  layer_edge = 41.0;
547  }
548  z = 2*std::abs( (MembraneDepth - layer_edge ) ) / thickness;
549  zn = std::pow( z, slope );
550  f = zn/(1 + zn);
551 
552  score = f * ( env10_weight * mem_env_log10_( aa, layer2, 40 ) ) +
553  ( 1 - f ) * ( env10_weight * mem_env_log10_( aa, layer1, 40 ) );
554 
555  } else {
556  //pure hydrophobic phase
557  layer = 1;
558 
559  score = env10_weight * mem_env_log10_( aa, layer, 40 );
560 
561  }
562  }
563 
564  score *= 2.019; // this factor is from rosetta++
565  dscore_dneighbor_count *= 2.019;
566 }
567 
568 
569 Real
571 {
572  if( sqdist > end_sig2 ) {
573  return 0.0;
574  } else if( sqdist < start_sig2 ) {
575  return 1.0;
576  } else {
577  Real dist = sqrt( sqdist );
578  return sqr(1.0 - sqr( (dist - start_sig) / (end_sig - start_sig) ) );
579  }
580 }
581 
582 
583 void
585  conformation::Atom const & atom1,
586  conformation::Atom const & atom2,
587  Real weighted_dScore_dN,
588  Vector & F1,
589  Vector & F2
590 ) const
591 {
592  DistanceSquared dist2 = atom1.xyz().distance_squared(atom2.xyz());
593  Distance dist( 0.0 ); // only used if start_sig2 <= dist2 <= end_sig2
594 
595  Real dNdd = 0;
596  if ( dist2 > end_sig2 ) {
597  dNdd = 0;
598  } else if ( dist2 < start_sig2 ) {
599  dNdd = 0.0;
600  } else {
601  dist = sqrt( dist2 );
602  Real x = (dist - start_sig)/ (end_sig - start_sig);
603  dNdd = 4*x*(-1 + x*x) / (end_sig - start_sig);
604  }
605 
606  Real dscoredd = ( weighted_dScore_dN ) * dNdd;
607  if ( dscoredd != 0 ) {
608 
609  Vector const f1( cross( atom1.xyz(), atom2.xyz() ));
610  Vector const f2( atom1.xyz() - atom2.xyz() );
611 
612  dscoredd /= dist;
613  F1 += dscoredd * f1;
614  F2 += dscoredd * f2;
615  }
616 }
619 {
620  return 1; // Initial versioning
621 }
622 }
623 }
624 }