PLearn 0.1
ScoreLayerVariable.cc
Go to the documentation of this file.
00001 // -*- C++ -*-
00002 
00003 // ScoreLayerVariable.cc
00004 //
00005 // Copyright (C) 2006 Olivier Delalleau
00006 //
00007 // Redistribution and use in source and binary forms, with or without
00008 // modification, are permitted provided that the following conditions are met:
00009 //
00010 //  1. Redistributions of source code must retain the above copyright
00011 //     notice, this list of conditions and the following disclaimer.
00012 //
00013 //  2. Redistributions in binary form must reproduce the above copyright
00014 //     notice, this list of conditions and the following disclaimer in the
00015 //     documentation and/or other materials provided with the distribution.
00016 //
00017 //  3. The name of the authors may not be used to endorse or promote
00018 //     products derived from this software without specific prior written
00019 //     permission.
00020 //
00021 // THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
00022 // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
00023 // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
00024 // NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
00025 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
00026 // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
00027 // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
00028 // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
00029 // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
00030 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00031 //
00032 // This file is part of the PLearn library. For more information on the PLearn
00033 // library, go to the PLearn Web site at www.plearn.org
00034 
00035 // Authors: Olivier Delalleau
00036 
00040 #include "ScoreLayerVariable.h"
00041 #include "MoleculeTemplate.h"
00042 #include <plearn/var/ColumnSumVariable.h>
00043 #include <plearn/var/ConcatRowsVariable.h>
00044 #include <plearn/var/LogVariable.h>
00045 #include <plearn/var/RowSumSquareVariable.h>
00046 #include <plearn/var/SigmoidVariable.h>
00047 #include <plearn/var/SoftmaxVariable.h>
00048 #include <plearn/var/SquareRootVariable.h>
00049 #include <plearn/var/SubMatVariable.h>
00050 #include <plearn/var/SumVariable.h>
00051 #include <plearn/var/Var_operators.h>
00052 #include <plearn/var/VarRowsVariable.h>
00053 
00054 namespace PLearn {
00055 using namespace std;
00056 
00059 PLEARN_IMPLEMENT_OBJECT(
00060     ScoreLayerVariable,
00061     "First layer (alignment scores) of a SurfaceTemplateLearner.",
00062     "In addition to the alignment scores, this variable also replicates\n"
00063     "additional input features, if provided in the source input variable\n"
00064     "(as indicated by the 'templates_source' VMat learnt option).\n"
00065 );
00066 
00068 // ScoreLayerVariable //
00070 ScoreLayerVariable::ScoreLayerVariable():
00071     n_active_templates(-1),
00072     n_inactive_templates(-1),
00073     normalize_by_n_features(false),
00074     seed_(-1),
00075     simple_mixture(false),
00076     random_gen(new PRandom())
00077 {}
00078 
00080 // declareOptions //
00082 void ScoreLayerVariable::declareOptions(OptionList& ol)
00083 {
00084     declareOption(ol, "icp_aligner_template",
00085                   &ScoreLayerVariable::icp_aligner_template,
00086                   OptionBase::buildoption,
00087         "The model of ICP aligner we want to use (will be replicated for\n"
00088         "each underlying score variable).");
00089 
00090     declareOption(ol, "n_active_templates",
00091                   &ScoreLayerVariable::n_active_templates,
00092                   OptionBase::buildoption,
00093         "Number of randomly chosen templates of active molecules (-1 means\n"
00094         "we take all of them and they are not shuffled).");
00095 
00096     declareOption(ol, "n_inactive_templates",
00097                   &ScoreLayerVariable::n_inactive_templates,
00098                   OptionBase::buildoption,
00099         "Number of randomly chosen templates of inactive molecules (-1 means\n"
00100         "we take all of them and they are not shuffled).");
00101 
00102     declareOption(ol, "normalize_by_n_features",
00103                   &ScoreLayerVariable::normalize_by_n_features,
00104                   OptionBase::buildoption,
00105         "If true, the score will be normalized by the number of features,\n"
00106         "i.e. scaled by one over (3 + number of common chemical features).");
00107 
00108     declareOption(ol, "seed", &ScoreLayerVariable::seed_,
00109                   OptionBase::buildoption,
00110         "Seed of the random number generator (similar to a PLearner's seed).");
00111 
00112     declareOption(ol, "templates_source",
00113                   &ScoreLayerVariable::templates_source,
00114                   OptionBase::learntoption, // To simplify help.
00115         "The VMat templates are obtained from. This VMat's first column must\n"
00116         "be the name of a molecule, there may be other input features, and\n"
00117         "there must be a binary target indicating whether a molecule is\n"
00118         "active or inactive. This VMat is also used to initialize standard\n"
00119         "deviations of chemical features.");
00120 
00121     // It is a learnt option as usually it will be set by the
00122     // SurfaceTemplateLearner above.
00123     declareOption(ol, "simple_mixture", &ScoreLayerVariable::simple_mixture,
00124                   OptionBase::learntoption,
00125         "If true, then instead of being a layer of scores, it will be a\n"
00126         "single-unit layer where scores have been processed to compute an\n"
00127         "activity probability, by adding a log coefficient to each template,\n"
00128         "performing a softmax on the resulting scores and summing over the\n"
00129         "weights obtained for active templates.");
00130 
00131     // Now call the parent class' declareOptions
00132     inherited::declareOptions(ol);
00133 
00134     // Redeclare some options as learnt options to make help simpler.
00135     redeclareOption(ol, "varray", &ScoreLayerVariable::varray,
00136                                   OptionBase::learntoption,
00137         "Now a learnt option to simplify help.");
00138 
00139     redeclareOption(ol, "varname", &ScoreLayerVariable::varname,
00140                                    OptionBase::learntoption,
00141         "Now a learnt option to simplify help.");
00142 }
00143 
00145 // bprop //
00147 void ScoreLayerVariable::bprop()
00148 {
00149     // The gradient is back-propagated only on the score variables, since the
00150     // other variables are inputs that do not need be updated.
00151     int n = simple_mixture ? 1
00152                            : getNActiveTemplates() + getNInactiveTemplates();
00153     PLASSERT( n <= length() );
00154     real* copy_grad_ptr = final_output->gradientdata;
00155     real* grad_ptr = gradientdata;
00156     for (int i = 0; i < n; i++, copy_grad_ptr++, grad_ptr++)
00157         *copy_grad_ptr += *grad_ptr;
00158 }
00159 
00161 // build //
00163 void ScoreLayerVariable::build()
00164 {
00165     inherited::build();
00166     build_();
00167 }
00168 
00170 // build_ //
00172 void ScoreLayerVariable::build_()
00173 {
00174     if (seed_ != 0)
00175         random_gen->manual_seed(seed_);
00176 
00177     if (!templates_source)
00178         return;
00179 
00180     // Verify that we have been given the input variable, and resize the input
00181     // array that is going to be constructed.
00182     if (varray.length() < 1)
00183         return;
00184     varray.resize(1);
00185 
00186     // Obtain mappings from the templates VMat in order to be able to load the
00187     // molecule templates.
00188     VMat mappings_source_backup = mappings_source;
00189     setMappingsSource(templates_source);
00190     PLASSERT( templates_source->targetsize() == 1 );
00191 
00192     // Randomly select active and inactive templates.
00193     TVec<int> list_of_active, list_of_inactive;
00194     int n = templates_source->length();
00195     Vec input, target;
00196     real weight;
00197     map<string, StatsCollector> chemical_stats;
00198     for (int i = 0; i < n; i++) {
00199         templates_source->getExample(i, input, target, weight);
00200         PLASSERT( fast_exact_is_equal(target[0], 0) ||
00201                 fast_exact_is_equal(target[0], 1) );
00202         if (fast_exact_is_equal(target[0], 0))
00203             list_of_inactive.append(i);
00204         else
00205             list_of_active.append(i);
00206         // Compute statistics on chemical features.
00207         PP<MoleculeTemplate> mol_template =
00208             getMoleculeTemplate(input[0], target[0]);
00209         Mat& features = mol_template->features;
00210         for (int j = 0; j < mol_template->feature_names.length(); j++) {
00211             string feature_name = mol_template->feature_names[j];
00212             StatsCollector& stats_col = chemical_stats[feature_name];
00213             for (int k = 0; k < features.length(); k++)
00214                 stats_col.update(features(k, j));
00215         }
00216     }
00217     n_active_in_source = list_of_active.length();
00218     n_inactive_in_source = list_of_inactive.length();
00219     if (n_active_templates != -1)
00220         random_gen->shuffleElements(list_of_active);
00221     if (n_inactive_templates != -1)
00222         random_gen->shuffleElements(list_of_inactive);
00223     PLASSERT( list_of_active.length() >= getNActiveTemplates() );
00224     PLASSERT( list_of_inactive.length() >= getNInactiveTemplates() );
00225     list_of_active.resize(getNActiveTemplates());
00226     list_of_inactive.resize(getNInactiveTemplates());
00227     // We do a copy of the list of active molecules instead of just appending
00228     // the inactive ones because this list may be reused later (in the case of
00229     // the simple mixture for instance).
00230     TVec<int> templates = list_of_active.copy();
00231     templates.append(list_of_inactive);
00232 
00233     // Create the Var that will run all ICPs.
00234     run_icp_var = new RunICPVariable(varray[0]);
00235     run_icp_var->setScoreLayer(this);
00236 
00237     // This VarArray will list additional parameters that must be optimized.
00238     VarArray optimized_params;
00239 
00240     // Create the corresponding score variables.
00241     outputs.resize(0);
00242     scaling_coeffs.resize(0);
00243     int index_in_run_icp_var = 0; // Current index.
00244     TVec<int> indices_of_active; // List of active templates.
00245     for (int i = 0; i < templates.length(); i++) {
00246         templates_source->getExample(templates[i], input, target, weight);
00247         if (is_equal(target[0], 1))
00248             indices_of_active.append(i);
00249         PP<MoleculeTemplate> mol_template =
00250             getMoleculeTemplate(input[0], target[0]);
00251         Var molecule_coordinates(mol_template->n_points(), 3);
00252         // Create the ICP aligner that will be used for this template.
00253         CopiesMap copies;
00254         PP<ChemicalICP> icp_aligner = icp_aligner_template->deepCopy(copies);
00255         icp_aligner->mol_template = mol_template;
00256         icp_aligner->build();
00257         // Set the current molecule of the ICP aligner to be the same template:
00258         // this will properly resize some important Vars.
00259         icp_aligner->setMolecule((MoleculeTemplate*) mol_template);
00260         // Initialize standard deviations of chemical features from the global
00261         // standard deviations.
00262         for (int j = 0; j < mol_template->feature_names.length(); j++) {
00263             string feature_name = mol_template->feature_names[j];
00264             icp_aligner->all_template_feat_dev->matValue.column(j).fill(
00265                     chemical_stats[feature_name].stddev());
00266         }
00267 // BC TODO ADD pout des stats
00268         // Declare this new template (with associated molecule coordinates) to
00269         // the RunICPVariable.
00270         run_icp_var->addTemplate(icp_aligner, (MoleculeTemplate*) mol_template,
00271                                  molecule_coordinates);
00272         // Declare the RunICPVariable as parent of the feature indices, in
00273         // order to ensure these variables are used after ICP has been run.
00274         // Similarly, the variable containing the indices of the matching
00275         // neighbors has to have the RunICPVariable as parent.
00276         PP<UnaryVariable> mol_feat_indices =
00277             (UnaryVariable*) ((Variable*) icp_aligner->mol_feat_indices);
00278         mol_feat_indices->setInput((RunICPVariable*) run_icp_var);
00279         PP<UnaryVariable> template_feat_indices =
00280             (UnaryVariable*) ((Variable*) icp_aligner->template_feat_indices);
00281         template_feat_indices->setInput((RunICPVariable*) run_icp_var);
00282         PP<UnaryVariable> matching_neighbors =
00283             (UnaryVariable*) ((Variable*) icp_aligner->matching_neighbors);
00284         matching_neighbors->setInput((RunICPVariable*) run_icp_var);
00285         // Build graph of Variables.
00286         // (1) Compute the distance in chemical features.
00287         Var template_features = icp_aligner->used_template_features;
00288         icp_aligner->all_template_features->setName(
00289                 "all_template_features_" + tostring(i));
00290         optimized_params.append(icp_aligner->all_template_features);
00291         Var molecule_features_all_points = icp_aligner->used_mol_features;
00292         Var molecule_features =
00293             new VarRowsVariable(molecule_features_all_points,
00294                                 matching_neighbors);
00295         Var diff_features = template_features - molecule_features;
00296         Var template_features_stddev = icp_aligner->used_template_feat_dev;
00297         icp_aligner->all_template_feat_dev->setName(
00298                 "all_template_feat_dev_" + tostring(i));
00299         optimized_params.append(icp_aligner->all_template_feat_dev);
00300         Var feature_distance_at_each_point =
00301             rowSumSquare(diff_features / template_features_stddev);
00302         feature_distance_at_each_point->setName(
00303                 "feature_distance_at_each_point_" + tostring(i));
00304         Var total_feature_distance = columnSum(feature_distance_at_each_point);
00305         total_feature_distance->setName("total_feature_distance_"+tostring(i));
00306         // (2) Compute the associated weights for the geometric distance.
00307         string wm = icp_aligner->weighting_method;
00308         Var weights;
00309         if (wm == "none") {
00310             weights = Var(mol_template->n_points(), 1);
00311             weights->value.fill(1);
00312         } else if (wm == "features_sigmoid") {
00313             Var shift = icp_aligner->weighting_params[0];
00314             Var slope = icp_aligner->weighting_params[1];
00315             // We add a small value to avoid zero distances, as the square root
00316             // is not differentiable at zero.
00317             Var regularized_distances = feature_distance_at_each_point + 1e-10;
00318             Var l1_dist = squareroot(regularized_distances);
00319             weights = - sigmoid(slope * (l1_dist - shift));
00320             shift->setName("shift_" + tostring(i));
00321             slope->setName("slope_" + tostring(i));
00322             weights->setName("weights_" + tostring(i));
00323             optimized_params.append(shift);
00324             optimized_params.append(slope);
00325         } else {
00326             PLERROR("In ScoreLayerVariable::build_ - Unsupported value for "
00327                     "'weighting_method'");
00328         }
00329         // (3) Compute the distance in geometric coordinates.
00330         Var template_coordinates =
00331             PLearn::subMat((RunICPVariable*) run_icp_var,
00332                            index_in_run_icp_var, 0,
00333                            mol_template->n_points(), 3);
00334         index_in_run_icp_var += mol_template->n_points();
00335         Var diff_coordinates = template_coordinates - molecule_coordinates;
00336         Var template_coordinates_stddev = icp_aligner->template_geom_dev;
00337         template_coordinates_stddev->setName(
00338                 "template_coordinates_stddev_" + tostring(i));
00339         optimized_params.append(template_coordinates_stddev);
00340         Var distance_at_each_point =
00341             rowSumSquare(diff_coordinates / template_coordinates_stddev);
00342         distance_at_each_point->setName("distance_at_each_point_"+tostring(i));
00343         Var weighted_total_geometric_distance =
00344             columnSum(distance_at_each_point * weights);
00345         weighted_total_geometric_distance->setName(
00346                 "weighted_total_geometric_distance_" + tostring(i));
00347         // (4) Sum to obtain the final score.
00348         Var total_cost =
00349             -0.5 * (total_feature_distance +
00350                     weighted_total_geometric_distance)
00351             - sum(log(template_coordinates_stddev))
00352             - sum(log(template_features_stddev));
00353         // Note that at build time, the scaling coefficient is always equal to
00354         // one over the number of points, regardless of the value of option
00355         // 'normalize_by_n_features': this is because the number of features
00356         // depends on the molecule being aligned, thus the scaling coefficient
00357         // will be modified at run time by the RunICPVariable.
00358         Var scaling_var = var(real(1.0 / mol_template->n_points()));
00359         scaling_coeffs.append(scaling_var);
00360         total_cost = scaling_var * total_cost;
00361 
00362         // (5) Compute path on which sizes will have to be updated after ICP.
00363         VarArray path_inputs = icp_aligner->used_template_features &
00364                                icp_aligner->used_mol_features      &
00365                                icp_aligner->used_template_feat_dev;
00366         VarArray path_outputs = total_cost;
00367         VarArray& path_to_resize = run_icp_var->getPathsToResize(i);
00368         path_to_resize = propagationPath(path_inputs, path_outputs);
00369 
00370         // (6) Add template coefficient in the case of the simple mixture mode.
00371         if (simple_mixture) {
00372             Var coeff = var(real(1.0 / templates.length()));
00373             coeff->min_value = 1e-10;
00374             coeff->max_value = 1;
00375             total_cost += log(coeff);
00376             optimized_params.append(coeff);
00377         }
00378 
00379         // (7) Build output array.
00380         outputs.append(total_cost);
00381     }
00382 
00383     // Append the additional input features if they are present.
00384     if (templates_source->inputsize() > 1) {
00385         if (simple_mixture)
00386             PLERROR("In ScoreLayerVariable::build_ - The simple mixture model "
00387                     "is not meant to be used with extra input information");
00388         Var input_var = varray[0];
00389         PLASSERT( input_var->width() == 1 );
00390         Var input_minus_molecule_id =
00391             PLearn::subMat(input_var, 1, 0, input_var->length() - 1, 1);
00392         outputs.append(input_minus_molecule_id);
00393     }
00394 
00395     // Concatenate all outputs in a single Variable.
00396     final_output = vconcat(outputs);
00397 
00398     // Build softmax output for activity probability in the case of the simple
00399     // mixture model.
00400     if (simple_mixture) {
00401         Var softmax_output = softmax(final_output);
00402         PLASSERT( list_of_active.length() > 0 );
00403         PLASSERT( softmax_output->width() == 1 );
00404         // Select only active templates (we assume - and verify - here that
00405         // they they are the first ones).
00406         if (min(list_of_active) != 0 ||
00407             max(list_of_active) != list_of_active.length() - 1)
00408         {
00409             PLERROR("In ScoreLayerVariable::build_ - The active templates must"
00410                     " be first");
00411         }
00412         final_output = PLearn::subMat(softmax_output, 0, 0,
00413                                       list_of_active.length(), 1);
00414         // Then we just sum over the resulting posteriors.
00415         final_output = sum(final_output);
00416     }
00417     
00418     // The final 'varray' will contain, in this order:
00419     // - the input variable to this layer (already here)
00420     // - the parameters (means, standard deviations, ...)
00421     // - the final output
00422     // The final output is not a parameter that will be updated during
00423     // initialization, but it needs to be in this Variable's parents so that
00424     // back-propagation is correctly performed.
00425     varray.append(optimized_params);
00426     varray.append(final_output);
00427     // We have changed a parent's option, we should re-build.
00428     inherited::build();
00429 
00430     // Restore the mappings source that was given prior to calling build().
00431     if (mappings_source_backup)
00432         setMappingsSource(mappings_source_backup);
00433 }
00434 
00436 // fprop //
00438 void ScoreLayerVariable::fprop()
00439 {
00440     // Just need to copy the data from 'final_output'.
00441     int n = nelems();
00442     PLASSERT( n == length() );
00443     real* copy_ptr = final_output->valuedata;
00444     real* ptr = valuedata;
00445     for (int i = 0; i < n; i++, ptr++, copy_ptr++)
00446         *ptr = *copy_ptr;
00447 }
00448 
00450 // getMolecule //
00452 PP<Molecule> ScoreLayerVariable::getMolecule(real molecule_id)
00453 {
00454     PLASSERT( mappings_source );
00455     PPath molecule_path = mappings_source->getValString(0, molecule_id);
00456     if (molecule_path.isEmpty())
00457         PLERROR("In ScoreLayerVariable::getMolecule - Could not find "
00458                 "associated mapping");
00459     string canonic_path = molecule_path.canonical();
00460     // Load the molecule if necessary.
00461     Molecule* molecule = 0;
00462     if (molecules.find(canonic_path) == molecules.end()) {
00463         molecule =
00464             new Molecule(molecule_path);
00465         molecules[canonic_path] = molecule;
00466     } else
00467         molecule = molecules[canonic_path];
00468     PLASSERT( molecule );
00469     return molecule;
00470 }
00471 
00473 // getMoleculeTemplate //
00475 PP<MoleculeTemplate> ScoreLayerVariable::getMoleculeTemplate(real molecule_id,
00476                                                              real activity)
00477 {
00478     PLASSERT( fast_exact_is_equal(activity, 0) ||
00479             fast_exact_is_equal(activity, 1) ||
00480             fast_exact_is_equal(activity, -1) );
00481     PLASSERT( mappings_source );
00482     PPath molecule_path = mappings_source->getValString(0, molecule_id);
00483     if (molecule_path.isEmpty())
00484         PLERROR("In ScoreLayerVariable::getMolecule - Could not find "
00485                 "associated mapping");
00486     string canonic_path = molecule_path.canonical();
00487     // Load the molecule if necessary.
00488     MoleculeTemplate* molecule = 0;
00489     if (molecule_templates.find(canonic_path) == molecule_templates.end()) {
00490         molecule =
00491             new MoleculeTemplate(molecule_path, int(activity));
00492         molecule_templates[canonic_path] = molecule;
00493     } else
00494         molecule = molecule_templates[canonic_path];
00495     PLASSERT( molecule );
00496     return molecule;
00497 }
00498 
00500 // getNActiveTemplates //
00502 int ScoreLayerVariable::getNActiveTemplates()
00503 {
00504     if (n_active_templates >= 0)
00505         return n_active_templates;
00506     else
00507         return n_active_in_source;
00508 }
00509 
00511 // getNInactiveTemplates //
00513 int ScoreLayerVariable::getNInactiveTemplates()
00514 {
00515     if (n_inactive_templates >= 0)
00516         return n_inactive_templates;
00517     else
00518         return n_inactive_in_source;
00519 }
00520 
00522 // makeDeepCopyFromShallowCopy //
00524 void ScoreLayerVariable::makeDeepCopyFromShallowCopy(CopiesMap& copies)
00525 {
00526     inherited::makeDeepCopyFromShallowCopy(copies);
00527 
00528     deepCopyField(run_icp_var,          copies);
00529     deepCopyField(icp_aligner_template, copies);
00530     deepCopyField(templates_source,     copies);
00531     deepCopyField(mappings_source,      copies);
00532     deepCopyField(random_gen,           copies);
00533     deepCopyField(outputs,              copies);
00534     varDeepCopyField(final_output,      copies);
00535     deepCopyField(molecule_templates,   copies);
00536     deepCopyField(molecules,            copies);
00537     deepCopyField(scaling_coeffs,       copies);
00538 }
00539 
00541 // recomputeSize //
00543 void ScoreLayerVariable::recomputeSize(int& l, int& w) const
00544 {
00545     if (final_output) {
00546         l = final_output->length();
00547         w = final_output->width();
00548     } else {
00549         l = w = 0;
00550     }
00551 }
00552 
00554 // setMappingsSource //
00556 void ScoreLayerVariable::setMappingsSource(const VMat& source_vmat)
00557 {
00558     mappings_source = source_vmat;
00559 }
00560 
00562 // setScalingCoefficient //
00564 void ScoreLayerVariable::setScalingCoefficient(int i, real coeff)
00565 {
00566     PLASSERT( i << scaling_coeffs.length() );
00567     PLASSERT( scaling_coeffs[i]->nelems() == 1 );
00568     scaling_coeffs[i]->value[0] = coeff;
00569 }
00570 
00571 } // end of namespace PLearn
00572 
00573 
00574 /*
00575   Local Variables:
00576   mode:c++
00577   c-basic-offset:4
00578   c-file-style:"stroustrup"
00579   c-file-offsets:((innamespace . 0)(inline-open . 0))
00580   indent-tabs-mode:nil
00581   fill-column:79
00582   End:
00583 */
00584 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=79 :
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines