source: src/Graph/BondGraph.hpp@ 08a0f52

Action_Thermostats Add_AtomRandomPerturbation Add_FitFragmentPartialChargesAction Add_RotateAroundBondAction Add_SelectAtomByNameAction Added_ParseSaveFragmentResults AddingActions_SaveParseParticleParameters Adding_Graph_to_ChangeBondActions Adding_MD_integration_tests Adding_ParticleName_to_Atom Adding_StructOpt_integration_tests AtomFragments Automaking_mpqc_open AutomationFragmentation_failures Candidate_v1.5.4 Candidate_v1.6.0 Candidate_v1.6.1 ChangeBugEmailaddress ChangingTestPorts ChemicalSpaceEvaluator CombiningParticlePotentialParsing Combining_Subpackages Debian_Package_split Debian_package_split_molecuildergui_only Disabling_MemDebug Docu_Python_wait EmpiricalPotential_contain_HomologyGraph EmpiricalPotential_contain_HomologyGraph_documentation Enable_parallel_make_install Enhance_userguide Enhanced_StructuralOptimization Enhanced_StructuralOptimization_continued Example_ManyWaysToTranslateAtom Exclude_Hydrogens_annealWithBondGraph FitPartialCharges_GlobalError Fix_BoundInBox_CenterInBox_MoleculeActions Fix_ChargeSampling_PBC Fix_ChronosMutex Fix_FitPartialCharges Fix_FitPotential_needs_atomicnumbers Fix_ForceAnnealing Fix_IndependentFragmentGrids Fix_ParseParticles Fix_ParseParticles_split_forward_backward_Actions Fix_PopActions Fix_QtFragmentList_sorted_selection Fix_Restrictedkeyset_FragmentMolecule Fix_StatusMsg Fix_StepWorldTime_single_argument Fix_Verbose_Codepatterns Fix_fitting_potentials Fixes ForceAnnealing_goodresults ForceAnnealing_oldresults ForceAnnealing_tocheck ForceAnnealing_with_BondGraph ForceAnnealing_with_BondGraph_continued ForceAnnealing_with_BondGraph_continued_betteresults ForceAnnealing_with_BondGraph_contraction-expansion FragmentAction_writes_AtomFragments FragmentMolecule_checks_bonddegrees GeometryObjects Gui_Fixes Gui_displays_atomic_force_velocity ImplicitCharges IndependentFragmentGrids IndependentFragmentGrids_IndividualZeroInstances IndependentFragmentGrids_IntegrationTest IndependentFragmentGrids_Sole_NN_Calculation JobMarket_RobustOnKillsSegFaults JobMarket_StableWorkerPool JobMarket_unresolvable_hostname_fix MoreRobust_FragmentAutomation ODR_violation_mpqc_open PartialCharges_OrthogonalSummation PdbParser_setsAtomName PythonUI_with_named_parameters QtGui_reactivate_TimeChanged_changes Recreated_GuiChecks Rewrite_FitPartialCharges RotateToPrincipalAxisSystem_UndoRedo SaturateAtoms_findBestMatching SaturateAtoms_singleDegree StoppableMakroAction Subpackage_CodePatterns Subpackage_JobMarket Subpackage_LinearAlgebra Subpackage_levmar Subpackage_mpqc_open Subpackage_vmg Switchable_LogView ThirdParty_MPQC_rebuilt_buildsystem TrajectoryDependenant_MaxOrder TremoloParser_IncreasedPrecision TremoloParser_MultipleTimesteps TremoloParser_setsAtomName Ubuntu_1604_changes stable
Last change on this file since 08a0f52 was 826e8c, checked in by Frederik Heber <heber@…>, 13 years ago

Introduced new LinkedCell into BondGraph.

  • new convenience functions BondGraph::getDomain(), ::getLinkedCell(), ::getTime() to have World dependency not in header file (circular include).
  • new function BondGraph::getMaxPossibleBondDistance() where one element is given.
  • dropped BondGraph::CreateAdjacency(LC_deprecated) as functionality is better placed directly into templated version directly.
  • rewrote templated BondGraph::CreateAdjacency that now makes use of LinkedCell_View::getAllNeighbours().
  • FIX: BondGraph::CreateAdjacency() created bonds for atom's fathers, not for atoms.
  • TESTFIX: New LinkedCell construct in BondGraph::CreateAdjacency changes order of ids in adjacency file:
    • ids are now in order (unlike to before), hence we simply use the new file in the regression test.
    • this is actually a fault of the test, not of the code as order of ids is not guaranteed in the file anyway.
  • TESTFIX: Removed XFAILs from regression test Graph/SubgraphDissection- BoundaryCondition.
  • TESTFIX: CountBondsUnitTest use two molecules that are on top of each other: This causes mess with changed LinkedCell BondGraph recognition, and we even actually need just one molecule for the testing.
  • Property mode set to 100644
File size: 19.3 KB
Line 
1/*
2 * bondgraph.hpp
3 *
4 * Created on: Oct 29, 2009
5 * Author: heber
6 */
7
8#ifndef BONDGRAPH_HPP_
9#define BONDGRAPH_HPP_
10
11using namespace std;
12
13/*********************************************** includes ***********************************/
14
15// include config.h
16#ifdef HAVE_CONFIG_H
17#include <config.h>
18#endif
19
20#include <iosfwd>
21
22#include <boost/serialization/array.hpp>
23
24#include "Atom/AtomSet.hpp"
25#include "Bond/bond.hpp"
26#include "Box.hpp"
27#include "CodePatterns/Assert.hpp"
28#include "CodePatterns/Log.hpp"
29#include "CodePatterns/Range.hpp"
30#include "Element/element.hpp"
31#include "Fragmentation/MatrixContainer.hpp"
32#include "Helpers/defs.hpp"
33#include "LinkedCell/LinkedCell_View.hpp"
34#include "LinkedCell/IPointCloud.hpp"
35#include "LinkedCell/PointCloudAdaptor.hpp"
36#include "WorldTime.hpp"
37
38/****************************************** forward declarations *****************************/
39
40class molecule;
41class BondedParticle;
42class MatrixContainer;
43
44/********************************************** definitions *********************************/
45
46/********************************************** declarations *******************************/
47
48
49class BondGraph {
50 //!> analysis bonds unit test should be friend to access private parts.
51 friend class AnalysisBondsTest;
52 //!> own bond graph unit test should be friend to access private parts.
53 friend class BondGraphTest;
54public:
55 /** Constructor of class BondGraph.
56 * This classes contains typical bond lengths and thus may be used to construct a bond graph for a given molecule.
57 */
58 BondGraph(bool IsA);
59
60 /** Destructor of class BondGraph.
61 */
62 ~BondGraph();
63
64 /** Parses the bond lengths in a given file and puts them int a matrix form.
65 * Allocates \a MatrixContainer for BondGraph::BondLengthMatrix, using MatrixContainer::ParseMatrix(),
66 * but only if parsing is successful. Otherwise variable is left as NULL.
67 * \param &input input stream to parse table from
68 * \return true - success in parsing file, false - failed to parse the file
69 */
70 bool LoadBondLengthTable(std::istream &input);
71
72 /** Removes allocated bond length table.
73 *
74 */
75 void CleanupBondLengthTable();
76
77 /** Determines the maximum of all element::CovalentRadius for elements present in \a &Set.
78 *
79 * I.e. the function returns a sensible cutoff criteria for bond recognition,
80 * e.g. to be used for LinkedCell_deprecated or others.
81 *
82 * \param &Set AtomSetMixin with all particles to consider
83 */
84 template <class container_type,
85 class iterator_type,
86 class const_iterator_type>
87 double getMaxPossibleBondDistance(
88 const AtomSetMixin<container_type,iterator_type,const_iterator_type> &Set) const
89 {
90 double max_distance = 0.;
91 // get all elements
92 std::set< const element *> PresentElements;
93 for(const_iterator_type AtomRunner = Set.begin(); AtomRunner != Set.end(); ++AtomRunner) {
94 PresentElements.insert( (*AtomRunner)->getType() );
95 }
96 // create all element combinations
97 for (std::set< const element *>::const_iterator iter = PresentElements.begin();
98 iter != PresentElements.end();
99 ++iter) {
100 for (std::set< const element *>::const_iterator otheriter = iter;
101 otheriter != PresentElements.end();
102 ++otheriter) {
103 const range<double> MinMaxDistance(getMinMaxDistance((*iter),(*otheriter)));
104 if (MinMaxDistance.last > max_distance)
105 max_distance = MinMaxDistance.last;
106 }
107 }
108 return max_distance;
109 }
110
111 /** Determines the maximum of all element::CovalentRadius for elements present in \a &Set.
112 *
113 * I.e. the function returns a sensible cutoff criteria for bond recognition,
114 * e.g. to be used for LinkedCell_deprecated or others.
115 *
116 * \param &Set AtomSetMixin with all particles to consider
117 */
118 template <class container_type,
119 class iterator_type,
120 class const_iterator_type>
121 double getMaxPossibleBondDistance(
122 const element * const Walker,
123 const AtomSetMixin<container_type,iterator_type,const_iterator_type> &Set) const
124 {
125 double max_distance = 0.;
126 // get all elements
127 std::set< const element *> PresentElements;
128 for(const_iterator_type AtomRunner = Set.begin(); AtomRunner != Set.end(); ++AtomRunner) {
129 PresentElements.insert( (*AtomRunner)->getType() );
130 }
131 // create all element combinations
132 for (std::set< const element *>::const_iterator iter = PresentElements.begin();
133 iter != PresentElements.end();
134 ++iter) {
135 const range<double> MinMaxDistance(getMinMaxDistance((*iter),Walker));
136 if (MinMaxDistance.last > max_distance)
137 max_distance = MinMaxDistance.last;
138 }
139 return max_distance;
140 }
141
142 /** Returns bond criterion for given pair based on a bond length matrix.
143 * This calls element-version of getMinMaxDistance().
144 * \param *Walker first BondedParticle
145 * \param *OtherWalker second BondedParticle
146 * \return Range with bond interval
147 */
148 range<double> getMinMaxDistance(
149 const BondedParticle * const Walker,
150 const BondedParticle * const OtherWalker) const;
151
152 /** Returns SQUARED bond criterion for given pair based on a bond length matrix.
153 * This calls element-version of getMinMaxDistance() and squares the values
154 * of either interval end.
155 * \param *Walker first BondedParticle
156 * \param *OtherWalker second BondedParticle
157 * \return Range with bond interval
158 */
159 range<double> getMinMaxDistanceSquared(
160 const BondedParticle * const Walker,
161 const BondedParticle * const OtherWalker) const;
162
163 /** Creates an adjacency list of the molecule.
164 * Generally, we use the CSD approach to bond recognition, that is the the distance
165 * between two atoms A and B must be within [Rcov(A)+Rcov(B)-t,Rcov(A)+Rcov(B)+t] with
166 * a threshold t = 0.4 Angstroem.
167 * To make it O(N log N) the function uses the linked-cell technique as follows:
168 * The procedure is step-wise:
169 * -# Remove every bond in list
170 * -# go through every atom in given \a set, check the atoms therein against all possible bond partners, add bond if true
171 * -# correct the bond degree iteratively (single->double->triple bond)
172 * -# finally print the bond list to \a *out if desired
173 * \param &set Container with all atoms to create adjacency for
174 */
175 template <class container_type,
176 class iterator_type,
177 class const_iterator_type>
178 void CreateAdjacency(
179 AtomSetMixin<container_type,iterator_type,const_iterator_type> &Set) const
180 {
181 LOG(1, "STATUS: Removing all present bonds.");
182 cleanAdjacencyList(Set);
183
184 // count atoms in molecule = dimension of matrix (also give each unique name and continuous numbering)
185 const unsigned int counter = Set.size();
186 if (counter > 1) {
187 LOG(1, "STATUS: Setting max bond distance.");
188 LinkedCell::LinkedCell_View LC = getLinkedCell(getMaxPossibleBondDistance(Set));
189
190 LOG(1, "STATUS: Creating LinkedCell structure for given " << counter << " atoms.");
191
192 Box &domain = getDomain();
193 unsigned int CurrentTime = getTime();
194
195 unsigned int BondCount = 0;
196 // go through every atom in the set (observed cause we change its bonds)
197 for(typename AtomSetMixin<container_type,iterator_type,const_iterator_type>::iterator iter = Set.begin();
198 iter != Set.end(); ++iter) {
199 const atom * const Walker = dynamic_cast<const atom *>(*iter);
200 ASSERT(Walker != NULL,
201 "BondGraph::CreateAdjacency() - TesselPoint "
202 +(*iter)->getName()+" that was not an atom retrieved from given set");
203 LOG(2, "INFO: Current Atom is " << *Walker << ".");
204
205 // obtain all possible neighbors
206 LinkedCell::LinkedList ListOfNeighbors = LC.getAllNeighbors(
207 getMaxPossibleBondDistance(Walker->getType(), Set),
208 Walker->getPosition());
209 if (!ListOfNeighbors.empty()) {
210 // we have some possible candidates, go through each
211 for (LinkedCell::LinkedList::const_iterator neighboriter = ListOfNeighbors.begin();
212 neighboriter != ListOfNeighbors.end();
213 ++neighboriter) {
214 if ((*neighboriter) > Walker) { // just to not add bonds from both sides
215 const atom * const OtherWalker = dynamic_cast<const atom *>(*neighboriter);
216 ASSERT(OtherWalker != NULL,
217 "BondGraph::CreateAdjacency() - TesselPoint "
218 +(*neighboriter)->getName()+" that was not an atom retrieved from LinkedList");
219 LOG(3, "INFO: Current other atom is " << *OtherWalker << ".");
220
221 const range<double> MinMaxDistanceSquared(
222 getMinMaxDistanceSquared(Walker, OtherWalker));
223 const double distance = domain.periodicDistanceSquared(OtherWalker->getPosition(),Walker->getPosition());
224 LOG(3, "INFO: Checking squared distance " << distance << " against typical bond length of " << MinMaxDistanceSquared << ".");
225 const bool status = MinMaxDistanceSquared.isInRange(distance);
226 if (status) { // create bond if distance is smaller
227 LOG(1, "ACCEPT: Adding Bond between " << *Walker << " and " << *OtherWalker << " in distance " << sqrt(distance) << ".");
228 // directly use iter to avoid const_cast'ing Walker, too
229 //const bond * Binder =
230 const_cast<atom *>(Walker)->addBond(CurrentTime, const_cast<atom *>(OtherWalker));
231 ++BondCount;
232 } else {
233 LOG(2, "REJECT: Squared distance "
234 << distance << " is out of squared covalent bounds "
235 << MinMaxDistanceSquared << ".");
236 }
237
238 } else {
239 LOG(5, "REJECT: Not Adding: Wrong order.");
240 }
241 }
242 }
243 }
244 LOG(1, "I detected " << BondCount << " bonds in the molecule.");
245
246 // correct bond degree by comparing valence and bond degree
247 LOG(1, "STATUS: Correcting bond degree.");
248 CorrectBondDegree(Set);
249
250 // output bonds for debugging (if bond chain list was correctly installed)
251 LOG(2, "STATUS: Printing list of created bonds.");
252 std::stringstream output;
253 for(const_iterator_type AtomRunner = Set.begin(); AtomRunner != Set.end(); ++AtomRunner) {
254 (*AtomRunner)->OutputBondOfAtom(output);
255 output << std::endl << "\t\t";
256 }
257 LOG(2, output.str());
258 } else {
259 LOG(1, "REJECT: AtomCount is " << counter << ", thus no bonds, no connections.");
260 }
261 }
262
263 /** Creates an adjacency list of the given \a Set of atoms.
264 *
265 * Note that the input stream is required to refer to the same number of
266 * atoms also contained in \a Set.
267 *
268 * \param &Set container with atoms
269 * \param *input input stream to parse
270 * \param skiplines how many header lines to skip
271 * \param id_offset is base id compared to World startin at 0
272 */
273 template <class container_type,
274 class iterator_type,
275 class const_iterator_type>
276 void CreateAdjacencyListFromDbondFile(
277 AtomSetMixin<container_type,iterator_type,const_iterator_type> &Set,
278 ifstream *input,
279 unsigned int skiplines,
280 int id_offset) const
281 {
282 char line[MAXSTRINGSIZE];
283
284 // check input stream
285 if (input->fail()) {
286 ELOG(0, "Opening of bond file failed \n");
287 return;
288 };
289 // skip headers
290 for (unsigned int i=0;i<skiplines;i++)
291 input->getline(line,MAXSTRINGSIZE);
292
293 // create lookup map
294 LOG(1, "STATUS: Creating lookup map.");
295 std::map< unsigned int, atom *> AtomLookup;
296 unsigned int counter = id_offset; // if ids do not start at 0
297 for (iterator_type iter = Set.begin(); iter != Set.end(); ++iter) {
298 AtomLookup.insert( make_pair( counter++, *iter) );
299 }
300 LOG(2, "INFO: There are " << counter << " atoms in the given set.");
301
302 LOG(1, "STATUS: Scanning file.");
303 unsigned int atom1, atom2;
304 unsigned int bondcounter = 0;
305 while (!input->eof()) // Check whether we read everything already
306 {
307 input->getline(line,MAXSTRINGSIZE);
308 stringstream zeile(line);
309 if (zeile.str().empty())
310 continue;
311 zeile >> atom1;
312 zeile >> atom2;
313
314 LOG(4, "INFO: Looking for atoms " << atom1 << " and " << atom2 << ".");
315 if (atom2 < atom1) //Sort indices of atoms in order
316 std::swap(atom1, atom2);
317 ASSERT(atom2 < counter,
318 "BondGraph::CreateAdjacencyListFromDbondFile() - ID "
319 +toString(atom2)+" exceeds number of present atoms "+toString(counter)+".");
320 ASSERT(AtomLookup.count(atom1),
321 "BondGraph::CreateAdjacencyListFromDbondFile() - Could not find an atom with the ID given in dbond file");
322 ASSERT(AtomLookup.count(atom2),
323 "BondGraph::CreateAdjacencyListFromDbondFile() - Could not find an atom with the ID given in dbond file");
324 atom * const Walker = AtomLookup[atom1];
325 atom * const OtherWalker = AtomLookup[atom2];
326
327 LOG(3, "INFO: Creating bond between atoms " << atom1 << " and " << atom2 << ".");
328 //const bond * Binder =
329 Walker->addBond(WorldTime::getTime(), OtherWalker);
330 bondcounter++;
331 }
332 LOG(1, "STATUS: "<< bondcounter << " bonds have been parsed.");
333 }
334
335 /** Removes all bonds within the given set of iterable atoms.
336 *
337 * @param Set Range with atoms
338 */
339 template <class container_type,
340 class iterator_type,
341 class const_iterator_type>
342 void cleanAdjacencyList(
343 AtomSetMixin<container_type,iterator_type,const_iterator_type> &Set) const
344 {
345 // remove every bond from the list
346 for(iterator_type AtomRunner = Set.begin(); AtomRunner != Set.end(); ++AtomRunner) {
347 (*AtomRunner)->removeAllBonds();
348// BondList& ListOfBonds = (*AtomRunner)->getListOfBonds();
349// for(BondList::iterator BondRunner = ListOfBonds.begin();
350// !ListOfBonds.empty();
351// BondRunner = ListOfBonds.begin()) {
352// ASSERT((*BondRunner)->Contains(*AtomRunner),
353// "BondGraph::cleanAdjacencyList() - "+
354// toString(*BondRunner)+" does not contain "+
355// toString(*AtomRunner)+".");
356// delete((*BondRunner));
357// }
358 }
359 }
360
361 /** correct bond degree by comparing valence and bond degree.
362 * correct Bond degree of each bond by checking both bond partners for a mismatch between valence and current sum of bond degrees,
363 * iteratively increase the one first where the other bond partner has the fewest number of bonds (i.e. in general bonds oxygene
364 * preferred over carbon bonds). Beforehand, we had picked the first mismatching partner, which lead to oxygenes with single instead of
365 * double bonds as was expected.
366 * @param Set Range with atoms
367 * \return number of bonds that could not be corrected
368 */
369 template <class container_type,
370 class iterator_type,
371 class const_iterator_type>
372 int CorrectBondDegree(
373 AtomSetMixin<container_type,iterator_type,const_iterator_type> &Set) const
374 {
375 // reset
376 resetBondDegree(Set);
377 // re-calculate
378 return calculateBondDegree(Set);
379 }
380
381 /** Equality comparator for class BondGraph.
382 *
383 * @param other other instance to compare to
384 * @return true - if equal in every member variable, except static
385 * \a BondGraph::BondThreshold.
386 */
387 bool operator==(const BondGraph &other) const;
388
389 /** Unequality comparator for class BondGraph.
390 *
391 * @param other other instance to compare to
392 * @return false - if equal in every member variable, except static
393 * \a BondGraph::BondThreshold.
394 */
395 bool operator!=(const BondGraph &other) const {
396 return !(*this == other);
397 }
398
399private:
400 /** Convenience function to place access to World::getLinkedCell() into source module.
401 *
402 * @return ref to LinkedCell_View
403 */
404 LinkedCell::LinkedCell_View getLinkedCell(const double max_distance) const;
405
406 /** Convenience function to place access to World::getDomain() into source module.
407 *
408 * @return ref to Box
409 */
410 Box &getDomain() const;
411
412 /** Convenience function to place access to WorldTime::getTime() into source module.
413 *
414 * @return current time step
415 */
416 unsigned int getTime() const;
417
418 /** Returns the BondLengthMatrix entry for a given index pair.
419 * \param firstelement index/atom number of first element (row index)
420 * \param secondelement index/atom number of second element (column index)
421 * \note matrix is of course symmetric.
422 */
423 double GetBondLength(
424 int firstelement,
425 int secondelement) const;
426
427 /** Returns bond criterion for given pair based on a bond length matrix.
428 * This calls either the covalent or the bond matrix criterion.
429 * \param *Walker first BondedParticle
430 * \param *OtherWalker second BondedParticle
431 * \return Range with bond interval
432 */
433 range<double> getMinMaxDistance(
434 const element * const Walker,
435 const element * const OtherWalker) const;
436
437 /** Returns bond criterion for given pair of elements based on a bond length matrix.
438 * The matrix should be contained in \a this BondGraph and contain an element-
439 * to-element length.
440 * \param *Walker first element
441 * \param *OtherWalker second element
442 * \return Range with bond interval
443 */
444 range<double> BondLengthMatrixMinMaxDistance(
445 const element * const Walker,
446 const element * const OtherWalker) const;
447
448 /** Returns bond criterion for given pair of elements based on covalent radius.
449 * \param *Walker first element
450 * \param *OtherWalker second element
451 * \return Range with bond interval
452 */
453 range<double> CovalentMinMaxDistance(
454 const element * const Walker,
455 const element * const OtherWalker) const;
456
457
458 /** Resets the bond::BondDegree of all atoms in the set to 1.
459 *
460 * @param Set Range with atoms
461 */
462 template <class container_type,
463 class iterator_type,
464 class const_iterator_type>
465 void resetBondDegree(
466 AtomSetMixin<container_type,iterator_type,const_iterator_type> &Set) const
467 {
468 // reset bond degrees
469 for(iterator_type AtomRunner = Set.begin(); AtomRunner != Set.end(); ++AtomRunner) {
470 (*AtomRunner)->resetBondDegree();
471 }
472 }
473
474 /** Calculates the bond degree for each atom on the set.
475 *
476 * @param Set Range with atoms
477 * @return number of non-matching bonds
478 */
479 template <class container_type,
480 class iterator_type,
481 class const_iterator_type>
482 int calculateBondDegree(
483 AtomSetMixin<container_type,iterator_type,const_iterator_type> &Set) const
484 {
485 //LOG(1, "Correcting Bond degree of each bond ... ");
486 int No = 0, OldNo = -1;
487 do {
488 OldNo = No;
489 No=0;
490 for(iterator_type AtomRunner = Set.begin(); AtomRunner != Set.end(); ++AtomRunner) {
491 No+=(*AtomRunner)->CorrectBondDegree();
492 }
493 } while (OldNo != No);
494 //LOG(0, " done.");
495 return No;
496 }
497
498 bool operator==(const periodentafel &other) const;
499
500 bool operator!=(const periodentafel &other) const {
501 return !(*this == other);
502 }
503
504private:
505 // default constructor for serialization
506 BondGraph();
507
508 friend class boost::serialization::access;
509 // serialization
510 template<class Archive>
511 void serialize(Archive & ar, const unsigned int version)
512 {
513 //ar & const_cast<double &>(BondThreshold);
514 ar & BondLengthMatrix;
515 ar & IsAngstroem;
516 }
517
518 //!> half width of the interval for allowed bond distances
519 static const double BondThreshold;
520 //!> Matrix with bond lenth per two elements
521 MatrixContainer *BondLengthMatrix;
522 //!> distance units are angstroem (true), bohr radii (false)
523 bool IsAngstroem;
524};
525
526#endif /* BONDGRAPH_HPP_ */
Note: See TracBrowser for help on using the repository browser.