![]() |
ESPResSo 3.2.0-159-gf5c8922-git
Extensible Simulation Package for Soft Matter Research
|
00001 /* 00002 Copyright (C) 2010,2011,2012,2013 The ESPResSo project 00003 Copyright (C) 2002,2003,2004,2005,2006,2007,2008,2009,2010 00004 Max-Planck-Institute for Polymer Research, Theory Group 00005 00006 This file is part of ESPResSo. 00007 00008 ESPResSo is free software: you can redistribute it and/or modify 00009 it under the terms of the GNU General Public License as published by 00010 the Free Software Foundation, either version 3 of the License, or 00011 (at your option) any later version. 00012 00013 ESPResSo is distributed in the hope that it will be useful, 00014 but WITHOUT ANY WARRANTY; without even the implied warranty of 00015 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00016 GNU General Public License for more details. 00017 00018 You should have received a copy of the GNU General Public License 00019 along with this program. If not, see <http://www.gnu.org/licenses/>. 00020 */ 00021 #ifndef _COMMUNICATION_H 00022 #define _COMMUNICATION_H 00023 /** \file communication.h 00024 This file contains the asynchronous MPI communication. 00025 00026 It is the header file for \ref communication.c "communication.c". 00027 00028 The asynchronous MPI communication is used during the script 00029 evaluation. Except for the master node that interpretes the Tcl 00030 script, all other nodes wait in mpi_loop() for the master node to 00031 issue an action using mpi_call(). \ref mpi_loop immediately 00032 executes an MPI_Bcast and therefore waits for the master node to 00033 broadcast a command, which is done by mpi_call(). The request 00034 consists of three integers, the first one describing the action 00035 issued, the second and third an arbitrary parameter depending on 00036 the action issued. If applicable, the second parameter is the node 00037 number of the slave this request is dedicated to. 00038 00039 To add new actions (e. g. to implement new Tcl commands), do the 00040 following: 00041 - write the mpi_* function that is executed on the master 00042 - write the mpi_*_slave function 00043 - Add your slave function to CALLBACK_LIST in communication.c 00044 00045 After this your procedure is free to do anything. However, it has 00046 to be in (MPI) sync with what your new mpi_*_slave does. This 00047 procedure is called immediately after the broadcast with the 00048 arbitrary integer as parameter. To this aim it has also to be 00049 added to \ref CALLBACK_LIST "callbacks". Last but not least for 00050 debugging purposes you can add a nice name to \ref #names in the 00051 same way. */ 00052 00053 /* from here we borrow the enumeration of 00054 the global variables */ 00055 #include "global.h" 00056 #include "particle_data.h" 00057 #include "random.h" 00058 #include "topology.h" 00059 #include <mpi.h> 00060 00061 /************************************************** 00062 * exported variables 00063 **************************************************/ 00064 00065 /** \name Exported Variables */ 00066 /*@{*/ 00067 /** The number of this node. */ 00068 extern int this_node; 00069 /** The total number of nodes. */ 00070 extern int n_nodes; 00071 /*@}*/ 00072 extern MPI_Comm comm_cart; 00073 00074 /************************************************** 00075 * for every procedure requesting a MPI negotiation 00076 * a slave exists which processes this request on 00077 * the slave nodes. It is denoted by *_slave. 00078 **************************************************/ 00079 00080 /** \name Exported Functions */ 00081 /*@{*/ 00082 /** Initialize MPI and determine \ref n_nodes and \ref this_node. */ 00083 void mpi_init(int *argc, char ***argv); 00084 00085 /** Process requests from master node. Slave nodes main loop. */ 00086 void mpi_loop(); 00087 00088 /** Issue REQ_TERM: stop Espresso, all slave nodes exit. */ 00089 void mpi_stop(); 00090 00091 /** Finalize MPI. Called by all nodes upon exit */ 00092 void mpi_finalize(); 00093 00094 /** Issue REQ_BCAST_PAR: broadcast a parameter from datafield. 00095 @param i the number from \ref global.h "global.h" referencing the datafield. 00096 @return nonzero on error 00097 */ 00098 int mpi_bcast_parameter(int i); 00099 00100 /** Issue REQ_WHO_HAS: ask nodes for their attached particles. */ 00101 void mpi_who_has(); 00102 00103 /** Issue REQ_EVENT: tells all clients of some system change. 00104 The events are: 00105 <ul> 00106 <li> PARTICLE_CHANGED 00107 <li> INTERACTION_CHANGED 00108 </ul> 00109 Then all nodes execute the respective on_* procedure from \ref initialize.c 00110 Note that not all of these codes are used. Since some actions (like placing a 00111 particle) include communication anyways, this is handled by the way. 00112 */ 00113 void mpi_bcast_event(int event); 00114 00115 /** Issue REQ_PLACE: move particle to a position on a node. 00116 Also calls \ref on_particle_change. 00117 \param id the particle to move. 00118 \param node the node to attach it to. 00119 \param pos the particles position. 00120 */ 00121 void mpi_place_particle(int node, int id, double pos[3]); 00122 00123 /** Issue REQ_PLACE: create particle at a position on a node. 00124 Also calls \ref on_particle_change. 00125 \param id the particle to create. 00126 \param node the node to attach it to. 00127 \param pos the particles position. 00128 */ 00129 void mpi_place_new_particle(int node, int id, double pos[3]); 00130 00131 /** Issue REQ_SET_V: send particle velocity. 00132 Also calls \ref on_particle_change. 00133 \param part the particle. 00134 \param node the node it is attached to. 00135 \param v its new velocity. 00136 */ 00137 void mpi_send_v(int node, int part, double v[3]); 00138 00139 /** Issue REQ_SET_F: send particle force. 00140 Also calls \ref on_particle_change. 00141 \param part the particle. 00142 \param node the node it is attached to. 00143 \param F its new force. 00144 */ 00145 void mpi_send_f(int node, int part, double F[3]); 00146 00147 /** Issue REQ_SET_SOLV: send particle solvation free energy 00148 Also calls \ref on_particle_change. 00149 \param part the particle. 00150 \param node the node it is attached to. 00151 \param solvation its new solvation free energy. 00152 */ 00153 void mpi_send_solvation(int node, int part, double *solvation); 00154 00155 00156 00157 /** Issue REQ_SET_M: send particle mass. 00158 Also calls \ref on_particle_change. 00159 \param part the particle. 00160 \param node the node it is attached to. 00161 \param mass its new mass. 00162 */ 00163 void mpi_send_mass(int node, int part, double mass); 00164 00165 /** Issue REQ_SET_Q: send particle charge. 00166 Also calls \ref on_particle_change. 00167 \param part the particle. 00168 \param node the node it is attached to. 00169 \param q its new charge. 00170 */ 00171 void mpi_send_q(int node, int part, double q); 00172 00173 /** Issue REQ_SET_MU_E: send particle electrophoretic mobility. 00174 Also calls \ref on_particle_change. 00175 \param part the particle. 00176 \param node the node it is attached to. 00177 \param mu_E its new mobility. 00178 */ 00179 void mpi_send_mu_E(int node, int part, double mu_E[3]); 00180 00181 #ifdef ROTATIONAL_INERTIA 00182 /** Issue REQ_SET_ROTATIONAL_INERTIA: send particle rotational inertia. 00183 Also calls \ref on_particle_change. 00184 \param part the particle. 00185 \param node the node it is attached to. 00186 \param rinertia its new rotational inertia. 00187 */ 00188 void mpi_send_rotational_inertia(int node, int part, double rinertia[3]); 00189 #endif 00190 00191 #ifdef ROTATION 00192 /** Issue REQ_SET_QUAT: send particle orientation. 00193 Also calls \ref on_particle_change. 00194 \param part the particle. 00195 \param node the node it is attached to. 00196 \param quat its new quaternions. 00197 */ 00198 void mpi_send_quat(int node, int part, double quat[4]); 00199 00200 00201 /** Issue REQ_SET_ROTATION: send particle rotation flag 00202 Also calls \ref on_particle_change. 00203 \param part the particle. 00204 \param pnode the node it is attached to. 00205 \param rot the rotation flag 00206 */ 00207 void mpi_send_rotation(int pnode, int part, int rot); 00208 00209 00210 /* Issue REQ_SET_LAMBDA: send particle angular velocity. 00211 Also calls \ref on_particle_change. 00212 \param part the particle. 00213 \param node the node it is attached to. 00214 \param omega its new angular velocity. 00215 */ 00216 void mpi_send_omega(int node, int part, double omega[3]); 00217 00218 /** Issue REQ_SET_TORQUE: send particle torque. 00219 Also calls \ref on_particle_change. 00220 \param part the particle. 00221 \param node the node it is attached to. 00222 \param torque its new torque. 00223 */ 00224 void mpi_send_torque(int node, int part, double torque[3]); 00225 #endif 00226 00227 00228 #ifdef DIPOLES 00229 /** Issue REQ_SET_DIP: send particle dipole orientation. 00230 Also calls \ref on_particle_change. 00231 \param part the particle. 00232 \param node the node it is attached to. 00233 \param dip its new dipole orientation. 00234 */ 00235 void mpi_send_dip(int node, int part, double dip[3]); 00236 /** Issue REQ_SET_DIPM: send particle dipole moment. 00237 Also calls \ref on_particle_change. 00238 \param part the particle. 00239 \param node the node it is attached to. 00240 \param dipm its new dipole moment (absolut value). 00241 */ 00242 void mpi_send_dipm(int node, int part, double dipm); 00243 #endif 00244 00245 #ifdef VIRTUAL_SITES 00246 /** Issue REQ_SET_DIPM: send particle dipole moment. 00247 Also calls \ref on_particle_change. 00248 \param part the particle. 00249 \param node the node it is attached to. 00250 \param isVirtual its new isVirtual. 00251 */ 00252 void mpi_send_virtual(int node, int part, int isVirtual); 00253 #endif 00254 00255 #ifdef VIRTUAL_SITES_RELATIVE 00256 void mpi_send_vs_relative(int node, int part, int vs_relative_to, double vs_distance); 00257 #endif 00258 00259 /** Issue REQ_SET_TYPE: send particle type. 00260 Also calls \ref on_particle_change. 00261 \param part the particle. 00262 \param node the node it is attached to. 00263 \param type its new type. 00264 */ 00265 void mpi_send_type(int node, int part, int type); 00266 00267 /** Issue REQ_SET_MOL_ID: send molecule id. 00268 Also calls \ref on_particle_change. 00269 \param part the particle. 00270 \param node the node it is attached to. 00271 \param mid its new mol_id. 00272 */ 00273 void mpi_send_mol_id(int node, int part, int mid); 00274 00275 /** Issue REQ_SET_BOND: send bond. 00276 Also calls \ref on_particle_change. 00277 \param pnode node it is attached to. 00278 \param part identity of principal atom of the bond. 00279 \param bond field containing the bond type number and the identity of all bond partners (secundary atoms of the bond). 00280 \param _delete if true, do not add the bond, rather delete it if found 00281 \return 1 on success or 0 if not (e. g. bond to delete does not exist) 00282 */ 00283 int mpi_send_bond(int pnode, int part, int *bond, int _delete); 00284 00285 /** Issue REQ_SET_EXCLUSION: send exclusions. 00286 Also calls \ref on_particle_change. 00287 \param part identity of first particle of the exclusion. 00288 \param part2 identity of secnd particle of the exclusion. 00289 \param _delete if true, do not add the exclusion, rather delete it if found 00290 */ 00291 void mpi_send_exclusion(int part, int part2, int _delete); 00292 00293 00294 /** Issue REQ_REM_PART: remove a particle. 00295 Also calls \ref on_particle_change. 00296 \param id the particle to remove. 00297 \param node the node it is attached to. 00298 */ 00299 void mpi_remove_particle(int node, int id); 00300 00301 /** Issue REQ_GET_PART: recv particle data. The data has to be freed later 00302 using \ref free_particle, otherwise the dynamically allocated parts, bonds 00303 and exclusions are left over. 00304 \param part the particle. 00305 \param node the node it is attached to. 00306 \param part_data where to store the received data. 00307 \note Gets a copy of the particle data not a pointer to the actual particle 00308 used in integration 00309 */ 00310 void mpi_recv_part(int node, int part, Particle *part_data); 00311 00312 /** Issue REQ_INTEGRATE: start integrator. 00313 @param n_steps how many steps to do. 00314 @return nonzero on error 00315 */ 00316 int mpi_integrate(int n_steps); 00317 00318 /** Issue REQ_BCAST_IA: send new ia params. 00319 Also calls \ref on_short_range_ia_change. 00320 00321 mpi_bcast_ia_params is used for both, bonded and non-bonded 00322 interaction parameters. Therefor i and j are used depending on 00323 their value: 00324 00325 \param i particle type for non bonded interaction parameters / 00326 bonded interaction type number. 00327 \param j if not negative: particle type for non bonded interaction parameters / 00328 if negative: flag for bonded interaction */ 00329 void mpi_bcast_ia_params(int i, int j); 00330 00331 #ifdef ADRESS 00332 /* #ifdef THERMODYNAMIC_FORCE */ 00333 void mpi_bcast_tf_params(int i); 00334 /* #endif */ 00335 #endif 00336 00337 00338 /** Issue REQ_BCAST_IA_SIZE: send new size of \ref ia_params. 00339 \param s the new size for \ref ia_params. 00340 */ 00341 void mpi_bcast_n_particle_types(int s); 00342 00343 /** Issue REQ_GATHER: gather data for analysis in analyze. 00344 \param job what to do: 00345 <ul> 00346 <li> 1 calculate and reduce (sum up) energies, using \ref energy_calc. 00347 <li> 2 calculate and reduce (sum up) pressure, stress tensor, using \ref pressure_calc. 00348 <li> 3 calculate and reduce (sum up) instantaneous pressure, using \ref pressure_calc. 00349 </ul> 00350 \param result where to store the gathered value(s): 00351 <ul><li> job=1 unused (the results are stored in a global 00352 energy array of type \ref Observable_stat) 00353 <li> job=2 unused (the results are stored in a global 00354 virials array of type \ref Observable_stat) 00355 <li> job=3 unused (the results are stored in a global 00356 virials array of type \ref Observable_stat) 00357 \param result_t where to store the gathered value(s): 00358 <ul><li> job=1 unused (the results are stored in a global 00359 energy array of type \ref Observable_stat) 00360 <li> job=2 unused (the results are stored in a global 00361 p_tensor tensor of type \ref Observable_stat) 00362 <li> job=3 unused (the results are stored in a global 00363 p_tensor tensor of type \ref Observable_stat) 00364 \param result_nb where to store the gathered value(s): 00365 <ul><li> job=1 unused (the results are stored in a global 00366 energy array of type \ref Observable_stat_non_bonded) 00367 <li> job=2 unused (the results are stored in a global 00368 virials_non_bonded array of type \ref Observable_stat_non_bonded) 00369 <li> job=3 unused (the results are stored in a global 00370 virials_non_bonded array of type \ref Observable_stat_non_bonded) 00371 \param result_t_nb where to store the gathered value(s): 00372 <ul><li> job=1 unused (the results are stored in a global 00373 energy array of type \ref Observable_stat_non_bonded) 00374 <li> job=2 unused (the results are stored in a global 00375 p_tensor_non_bonded tensor of type \ref Observable_stat_non_bonded) 00376 <li> job=3 unused (the results are stored in a global 00377 p_tensor_non_bonded tensor of type \ref Observable_stat_non_bonded) 00378 </ul> 00379 */ 00380 void mpi_gather_stats(int job, void *result, void *result_t, void *result_nb, void *result_t_nb); 00381 00382 /** Issue GET_LOCAL_STRESS_TENSOR: gather the contribution to the local stress tensors from 00383 each node. 00384 */ 00385 00386 void mpi_local_stress_tensor(DoubleList *TensorInBin, int bins[3], int periodic[3], double range_start[3], double range[3]); 00387 00388 /** Issue REQ_GETPARTS: gather all particle informations (except bonds). 00389 This is slow and may use huge amounts of memory. If il is non-NULL, also 00390 the bonding information is also fetched and stored in a single intlist 00391 pointed to by il. The particles bonding information references this array, 00392 which is the only data you have to free later (besides the result array 00393 you allocated). YOU MUST NOT CALL \ref free_particle on any of these particles! 00394 00395 \param result where to store the gathered particles 00396 \param il if non-NULL, the integerlist where to store the bonding info 00397 */ 00398 void mpi_get_particles(Particle *result, IntList *il); 00399 00400 /** Issue REQ_SET_TIME_STEP: send new \ref time_step and rescale the 00401 velocities accordingly. 00402 */ 00403 void mpi_set_time_step(double time_step); 00404 00405 /** Issue REQ_BCAST_COULOMB: send new coulomb parameters. */ 00406 void mpi_bcast_coulomb_params(); 00407 00408 /** Issue REQ_SEND_EXT_FORCE: send nex external flag and external force. */ 00409 void mpi_send_ext_force(int pnode, int part, int flag, int mask, double force[3]); 00410 00411 /** Issue REQ_SEND_EXT_TORQUE: send nex external flag and external torque. */ 00412 void mpi_send_ext_torque(int pnode, int part, int flag, int mask, double torque[3]); 00413 00414 #ifdef LANGEVIN_PER_PARTICLE 00415 /** Issue REQ_SEND_PARTICLE_T: send particle type specific temperature. */ 00416 void mpi_set_particle_temperature(int pnode, int part, double _T); 00417 00418 /** Issue REQ_SEND_PARTICLE_T: send particle type specific frictional coefficient. */ 00419 void mpi_set_particle_gamma(int pnode, int part, double gamma); 00420 #endif 00421 00422 /** Issue REQ_BCAST_COULOMB: send new coulomb parameters. */ 00423 void mpi_bcast_constraint(int del_num); 00424 00425 #if defined(LB_BOUNDARIES) || defined(LB_BOUNDARIES_GPU) 00426 /** Issue REQ_LB_BOUNDARY: set up walls for lb fluid */ 00427 void mpi_bcast_lbboundary(int del_num); 00428 #endif 00429 00430 /** Issue REQ_RANDOM_SEED: read/set seed of random number generators on each node. */ 00431 void mpi_random_seed(int cnt, long *seed); 00432 00433 /** Issue REQ_RANDOM_STAT: read/set status of random number generators on each node. */ 00434 void mpi_random_stat(int cnt, RandomStatus *stat); 00435 00436 /** Issue REQ_BCAST_LJFORCECAP: initialize LJ force capping. */ 00437 void mpi_cap_forces(double force_cap); 00438 00439 /** Issue REQ_BCAST_MORSEFORCECAP: initialize Morse force capping. */ 00440 //void mpi_morse_cap_forces(double force_cap); 00441 00442 /** Issue REQ_BCAST_BUCKFORCECAP: initialize Buckingham force capping. */ 00443 //void mpi_buck_cap_forces(double force_cap); 00444 00445 /** Issue REQ_BCAST_TABFORCECAP: initialize tabulated force capping. */ 00446 //void mpi_tab_cap_forces(double force_cap); 00447 00448 /** Issue REQ_GET_CONSFOR: get force acting on constraint */ 00449 void mpi_get_constraint_force(int constraint, double force[3]); 00450 00451 /** Issue REQ_BIT_RANDOM_SEED: read/set seed of the bit random number generators on each node. */ 00452 void mpi_bit_random_seed(int cnt, int *seed); 00453 00454 /** Issue REQ_BIT_RANDOM_STAT: read/set status of the bit random number generators on each node. */ 00455 void mpi_bit_random_stat(int cnt, BitRandomStatus *stat); 00456 00457 /** Issue REQ_RESCALE_PART: rescales all particle positions in direction 'dir' by a factor 'scale'. */ 00458 void mpi_rescale_particles(int dir, double scale); 00459 00460 /** Issue REQ_BCAST_CS: change the cell structure on all nodes. */ 00461 void mpi_bcast_cell_structure(int cs); 00462 00463 /** Issue REQ_BCAST_NPTISO_GEOM: broadcast nptiso geometry parameter to all nodes. */ 00464 void mpi_bcast_nptiso_geom(void); 00465 00466 /** Issue REQ_BCAST_LJANGLEFORCECAP: initialize LJANGLE force capping. */ 00467 //void mpi_ljangle_cap_forces(double force_cap); 00468 00469 00470 /** Issue REQ_UPDATE_MOL_IDS: Update the molecule ids so that they are 00471 in sync with the topology. Note that this only makes sense if you 00472 have a simple topology such that each particle can only belong to 00473 a single molecule */ 00474 void mpi_update_mol_ids(void); 00475 00476 /** Issue REQ_SYNC_TOPO: Update the molecules ids to that they correspond to the topology */ 00477 int mpi_sync_topo_part_info(void); 00478 00479 /** Issue REQ_BCAST_LBPAR: Broadcast a parameter for Lattice Boltzmann. 00480 * @param field References the parameter field to be broadcasted. The references are defined in \ref lb.h "lb.h" 00481 */ 00482 void mpi_bcast_lb_params(int field); 00483 00484 /** Issue REQ_BCAST_cuda_global_part_vars: Broadcast a parameter for CUDA 00485 */ 00486 void mpi_bcast_cuda_global_part_vars(); 00487 00488 /** Issue REQ_SEND_FLUID: Send a single lattice site to a processor. 00489 * @param node processor to send to 00490 * @param index index of the lattice site 00491 * @param rho local fluid density 00492 * @param j local fluid velocity 00493 * @param pi local fluid pressure 00494 */ 00495 void mpi_send_fluid(int node, int index, double rho, double *j, double *pi); 00496 00497 /** Issue REQ_GET_FLUID: Receive a single lattice site from a processor. 00498 * @param node processor to send to 00499 * @param index index of the lattice site 00500 * @param rho local fluid density 00501 * @param j local fluid velocity 00502 * @param pi local fluid pressure 00503 */ 00504 void mpi_recv_fluid(int node, int index, double *rho, double *j, double *pi); 00505 00506 /** Issue REQ_LB_GET_BOUNDARY_FLAG: Receive a single lattice sites boundary flag from a processor. 00507 * @param node processor to send to 00508 * @param index index of the lattice site 00509 * @param boundary local boundary flag 00510 */ 00511 void mpi_recv_fluid_boundary_flag(int node, int index, int *boundary); 00512 00513 /** Issue REQ_ICCP3M_ITERATION: performs iccp3m iteration. 00514 @return nonzero on error 00515 */ 00516 int mpi_iccp3m_iteration(int dummy); 00517 00518 /** Issue REQ_ICCP3M_INIT: performs iccp3m initialization 00519 @return nonzero on error 00520 */ 00521 int mpi_iccp3m_init(int dummy); 00522 00523 /** Issue REQ_RECV_FLUID_POPULATIONS: Send a single lattice site to a processor. 00524 * @param node processor to send to 00525 * @param index index of the lattice site 00526 * @param pop local fluid population 00527 */ 00528 void mpi_recv_fluid_populations(int node, int index, double *pop); 00529 00530 /** Issue REQ_SEND_FLUID_POPULATIONS: Send a single lattice site to a processor. 00531 * @param node processor to send to 00532 * @param index index of the lattice site 00533 * @param pop local fluid population 00534 */ 00535 void mpi_send_fluid_populations(int node, int index, double *pop); 00536 00537 /** Part of MDLC 00538 */ 00539 void mpi_bcast_max_mu(); 00540 00541 /** Issue REQ_GET_ERRS: gather all error messages from all nodes and return them 00542 00543 @param errors contains the errors from all nodes. This has to point to an array 00544 of character pointers, one for each node. 00545 @return \ref ES_OK if no error occured, otherwise \ref ES_ERROR 00546 */ 00547 int mpi_gather_runtime_errors(char **errors); 00548 00549 /** Galilei and other: set all particle velocities and rotational inertias to zero. 00550 set all forces and torques on the particles to zero 00551 calculate the centre of mass (CMS) 00552 calculate the velocity of the CMS 00553 remove the CMS velocity from the system 00554 */ 00555 void mpi_kill_particle_motion( int rotation ); 00556 void mpi_kill_particle_forces( int torque ); 00557 void mpi_system_CMS(); 00558 void mpi_system_CMS_velocity(); 00559 void mpi_galilei_transform(); 00560 00561 /** Issue REQ_CATALYTIC_REACTIONS: notify the system of changes to the reaction parameters 00562 */ 00563 void mpi_setup_reaction(); 00564 00565 /*@}*/ 00566 00567 /** \name Event codes for \ref mpi_bcast_event 00568 These codes are used by \ref mpi_bcast_event to notify certain changes 00569 of doing something now. 00570 */ 00571 /*@{*/ 00572 #define P3M_COUNT_CHARGES 0 00573 #define INVALIDATE_SYSTEM 1 00574 #define CHECK_PARTICLES 2 00575 #define MAGGS_COUNT_CHARGES 3 00576 #define P3M_COUNT_DIPOLES 5 00577 /*@}*/ 00578 00579 #endif
1.7.5.1