3ee5a474e5e3661b91473ca6189112ff245384db
[alexxy/gromacs.git] / src / gromacs / selection / selmethod.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2009,2010,2011,2012,2014,2015,2017,2018, by the GROMACS development team, led by
5  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6  * and including many others, as listed in the AUTHORS file in the
7  * top-level source directory and at http://www.gromacs.org.
8  *
9  * GROMACS is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * as published by the Free Software Foundation; either version 2.1
12  * of the License, or (at your option) any later version.
13  *
14  * GROMACS is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with GROMACS; if not, see
21  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
23  *
24  * If you want to redistribute modifications to GROMACS, please
25  * consider that scientific software is very special. Version
26  * control is crucial - bugs must be traceable. We will be happy to
27  * consider code for inclusion in the official distribution, but
28  * derived work must not be called official GROMACS. Details are found
29  * in the README & COPYING files - if they are missing, get the
30  * official version at http://www.gromacs.org.
31  *
32  * To help us fund GROMACS development, we humbly ask that you cite
33  * the research papers on the package. Check out http://www.gromacs.org.
34  */
35 /*! \internal \file
36  * \brief
37  * Implements functions in selmethod.h.
38  *
39  * \author Teemu Murtola <teemu.murtola@gmail.com>
40  * \ingroup module_selection
41  */
42 #include "gmxpre.h"
43
44 #include "selmethod.h"
45
46 #include <ctype.h>
47 #include <stdarg.h>
48
49 #include "gromacs/utility/arraysize.h"
50 #include "gromacs/utility/cstringutil.h"
51 #include "gromacs/utility/exceptions.h"
52
53 #include "selmethod-impl.h"
54 #include "symrec.h"
55
56 /*! \internal \brief
57  * Helper structure for defining selection methods.
58  */
59 typedef struct {
60     /*! \brief
61      * Name to register the method under.
62      *
63      * If NULL, use the actual name of the method.
64      * This field is used for defining synonyms.
65      */
66     const char            *name;
67     /** Method data structure to register. */
68     gmx_ana_selmethod_t   *method;
69 } t_register_method;
70
71 /*! \brief
72  * Convenience function for reporting errors found in selection methods.
73  */
74 static void
75 report_error(FILE *fp, const char *name, const char *fmt, ...)
76 {
77     va_list ap;
78     va_start(ap, fmt);
79     if (fp)
80     {
81         fprintf(fp, "selection method '%s': ", name);
82         vfprintf(fp, fmt, ap);
83         fprintf(fp, "\n");
84     }
85     va_end(ap);
86 }
87
88 /*! \brief
89  * Convenience function for reporting errors found in selection method parameters.
90  */
91 static void
92 report_param_error(FILE *fp, const char *mname, const char *pname,
93                    const char *fmt, ...)
94 {
95     va_list ap;
96     va_start(ap, fmt);
97     if (fp)
98     {
99         fprintf(fp, "selection method '%s': parameter '%s': ", mname, pname);
100         vfprintf(fp, fmt, ap);
101         fprintf(fp, "\n");
102     }
103     va_end(ap);
104 }
105
106 /*! \brief
107  * Checks the validity of parameters.
108  *
109  * \param[in]     fp      File handle to use for diagnostic messages
110  *   (can be NULL).
111  * \param[in]     name    Name of the method (used for error messages).
112  * \param[in]     nparams Number of parameters in \p param.
113  * \param[in,out] param   Parameter array
114  *   (only the \c flags field of boolean parameters may be modified).
115  * \param[in]     symtab  Symbol table (used for checking overlaps).
116  * \returns       true if there are no problems with the parameters,
117  *   false otherwise.
118  *
119  * This function performs some checks common to both check_method() and
120  * check_modifier().
121  * The purpose of these checks is to ensure that the selection parser does not
122  * need to check for the validity of the parameters at each turn, and to
123  * report programming errors as early as possible.
124  * If you remove a check, make sure that the parameter parser can handle the
125  * resulting parameters.
126  */
127 static bool
128 check_params(FILE *fp, const char *name, int nparams, gmx_ana_selparam_t param[],
129              const gmx::SelectionParserSymbolTable &symtab)
130 {
131     bool              bOk = true;
132     int               i, j;
133
134     if (nparams > 0 && !param)
135     {
136         report_error(fp, name, "error: missing parameter data");
137         return false;
138     }
139     if (nparams == 0 && param)
140     {
141         report_error(fp, name, "warning: parameter data unused because nparams=0");
142     }
143     /* Check each parameter */
144     for (i = 0; i < nparams; ++i)
145     {
146         /* Check that there is at most one NULL name, in the beginning */
147         if (param[i].name == nullptr && i > 0)
148         {
149             report_error(fp, name, "error: NULL parameter should be the first one");
150             bOk = false;
151             continue;
152         }
153         /* Check for duplicates */
154         for (j = 0; j < i; ++j)
155         {
156             if (param[j].name == nullptr)
157             {
158                 continue;
159             }
160             if (!gmx_strcasecmp(param[i].name, param[j].name))
161             {
162                 report_error(fp, name, "error: duplicate parameter name '%s'", param[i].name);
163                 bOk = false;
164                 break;
165             }
166         }
167         /* Check flags */
168         if (param[i].flags & SPAR_SET)
169         {
170             report_param_error(fp, name, param[i].name, "warning: flag SPAR_SET is set");
171             param[i].flags &= ~SPAR_SET;
172         }
173         if (param[i].flags & SPAR_RANGES)
174         {
175             if (param[i].val.type != INT_VALUE && param[i].val.type != REAL_VALUE)
176             {
177                 report_param_error(fp, name, param[i].name, "error: SPAR_RANGES cannot be set for a non-numeric parameter");
178                 bOk = false;
179             }
180             if (param[i].flags & SPAR_DYNAMIC)
181             {
182                 report_param_error(fp, name, param[i].name, "warning: SPAR_DYNAMIC does not have effect with SPAR_RANGES");
183                 param[i].flags &= ~SPAR_DYNAMIC;
184             }
185             if (!(param[i].flags & SPAR_VARNUM) && param[i].val.nr != 1)
186             {
187                 report_param_error(fp, name, param[i].name, "error: range should take either one or an arbitrary number of values");
188                 bOk = false;
189             }
190             if (param[i].flags & SPAR_ATOMVAL)
191             {
192                 report_param_error(fp, name, param[i].name, "error: SPAR_RANGES and SPAR_ATOMVAL both set");
193                 bOk = false;
194             }
195         }
196         if ((param[i].flags & SPAR_VARNUM) && (param[i].flags & SPAR_ATOMVAL))
197         {
198             report_param_error(fp, name, param[i].name, "error: SPAR_VARNUM and SPAR_ATOMVAL both set");
199             bOk = false;
200         }
201         if (param[i].flags & SPAR_ENUMVAL)
202         {
203             if (param[i].val.type != STR_VALUE)
204             {
205                 report_param_error(fp, name, param[i].name, "error: SPAR_ENUMVAL can only be set for string parameters");
206                 bOk = false;
207             }
208             if (param[i].val.nr != 1)
209             {
210                 report_param_error(fp, name, param[i].name, "error: SPAR_ENUMVAL parameters should take exactly one value");
211                 bOk = false;
212             }
213             if (param[i].flags & (SPAR_DYNAMIC | SPAR_VARNUM | SPAR_ATOMVAL))
214             {
215                 report_param_error(fp, name, param[i].name, "error: only SPAR_OPTIONAL supported with SPAR_ENUMVAL");
216                 bOk = false;
217             }
218         }
219         /* Check boolean parameters */
220         if (param[i].val.type == NO_VALUE)
221         {
222             if (param[i].val.nr != 0)
223             {
224                 report_param_error(fp, name, param[i].name, "error: number of values should be zero for boolean parameters");
225                 bOk = false;
226             }
227             /* The boolean parameters should always be optional, so set the
228              * flag for convenience. */
229             param[i].flags |= SPAR_OPTIONAL;
230             /* Any other flags should not be specified */
231             if (param[i].flags & ~SPAR_OPTIONAL)
232             {
233                 report_param_error(fp, name, param[i].name, "error: boolean parameter should not have any flags set");
234                 bOk = false;
235             }
236         }
237         /* Check val.nr */
238         if (param[i].flags & (SPAR_VARNUM | SPAR_ATOMVAL))
239         {
240             if (param[i].val.nr != -1)
241             {
242                 report_param_error(fp, name, param[i].name, "warning: val.nr is not -1 although SPAR_VARNUM/SPAR_ATOMVAL is set");
243             }
244             param[i].val.nr = -1;
245         }
246         else if (param[i].val.type != NO_VALUE)
247         {
248             if (param[i].val.nr <= 0)
249             {
250                 report_param_error(fp, name, param[i].name, "error: val.nr <= 0");
251                 bOk = false;
252             }
253         }
254         /* Check that the value pointer is NULL */
255         if (param[i].nvalptr != nullptr)
256         {
257             report_param_error(fp, name, param[i].name, "warning: nvalptr is set");
258         }
259         if (param[i].val.u.ptr != nullptr && !(param[i].flags & SPAR_ENUMVAL))
260         {
261             report_param_error(fp, name, param[i].name, "warning: value pointer is set");
262         }
263         /* Check that the name contains only valid characters */
264         if (param[i].name == nullptr)
265         {
266             continue;
267         }
268         if (!isalpha(param[i].name[0]))
269         {
270             report_param_error(fp, name, param[i].name, "error: name does not begin with a letter");
271             bOk = false;
272             continue;
273         }
274         for (j = 1; param[i].name[j] != 0; ++j)
275         {
276             if (param[i].name[j] != '_' && !isalnum(param[i].name[j]))
277             {
278                 report_param_error(fp, name, param[i].name, "error: name contains non-alphanumeric characters");
279                 bOk = false;
280                 break;
281             }
282         }
283         if (param[i].name[j] != 0)
284         {
285             continue;
286         }
287         /* Check that the name does not conflict with a method */
288         if (symtab.findSymbol(param[i].name))
289         {
290             report_param_error(fp, name, param[i].name, "error: name conflicts with another method or a keyword");
291             bOk = false;
292         }
293     } /* End of parameter loop */
294       /* Check parameters of existing methods */
295     gmx::SelectionParserSymbolIterator symbol
296         = symtab.beginIterator(gmx::SelectionParserSymbol::MethodSymbol);
297     while (symbol != symtab.endIterator())
298     {
299         gmx_ana_selmethod_t *method = symbol->methodValue();
300         gmx_ana_selparam_t  *param  =
301             gmx_ana_selmethod_find_param(name, method);
302         if (param)
303         {
304             report_param_error(fp, method->name, param->name, "error: name conflicts with another method or a keyword");
305             bOk = false;
306         }
307         ++symbol;
308     }
309     return bOk;
310 }
311
312 /*! \brief
313  * Checks the validity of selection method callback functions.
314  *
315  * \param[in] fp        File handle to use for diagnostic messages
316  *   (can be NULL).
317  * \param[in] method    The method to check.
318  * \returns   true if there are no problems, false otherwise.
319  *
320  * This function performs some checks common to both check_method() and
321  * check_modifier().
322  * This function checks that all the required callbacks are defined, i.e.,
323  * not NULL, to find programming errors.
324  */
325 static bool
326 check_callbacks(FILE *fp, gmx_ana_selmethod_t *method)
327 {
328     bool         bOk = true;
329     bool         bNeedInit;
330     int          i;
331
332     /* Make some checks on init_data and free */
333     if (method->nparams > 0 && !method->init_data)
334     {
335         report_error(fp, method->name, "error: init_data should be provided because the method has parameters");
336         bOk = false;
337     }
338     if (method->free && !method->init_data)
339     {
340         report_error(fp, method->name, "warning: free is not used because of missing init_data");
341     }
342     /* Check presence of outinit for position-valued methods */
343     if (method->type == POS_VALUE && !method->outinit)
344     {
345         report_error(fp, method->name, "error: outinit should be provided because the method has POS_VALUE");
346         bOk = false;
347     }
348     /* Check presence of outinit for variable output count methods */
349     if ((method->flags & SMETH_VARNUMVAL) && !method->outinit)
350     {
351         report_error(fp, method->name, "error: outinit should be provided because the method has SMETH_VARNUMVAL");
352         bOk = false;
353     }
354     /* Warn of dynamic callbacks in static methods */
355     if (!(method->flags & SMETH_MODIFIER))
356     {
357         if (method->pupdate && !(method->flags & SMETH_DYNAMIC))
358         {
359             report_error(fp, method->name, "warning: pupdate not used because the method is static");
360             method->pupdate = nullptr;
361         }
362     }
363     /* Check that there is an evaluation function */
364     if (method->type != NO_VALUE && !method->update && !method->pupdate)
365     {
366         report_error(fp, method->name, "error: evaluation function missing");
367         bOk = false;
368     }
369     /* Loop through the parameters to determine if initialization callbacks
370      * are needed. */
371     bNeedInit = false;
372     for (i = 0; i < method->nparams; ++i)
373     {
374         if (method->param[i].val.type != POS_VALUE
375             && (method->param[i].flags & (SPAR_VARNUM | SPAR_ATOMVAL)))
376         {
377             bNeedInit = true;
378         }
379     }
380     /* Check that the callbacks required by the parameters are present */
381     if (bNeedInit && !method->init)
382     {
383         report_error(fp, method->name, "error: init should be provided");
384         bOk = false;
385     }
386     return bOk;
387 }
388
389 /*! \brief
390  * Checks the validity of a selection method.
391  *
392  * \param[in]     fp     File handle to use for diagnostic messages
393  *   (can be NULL).
394  * \param[in,out] method Method to check.
395  * \param[in]     symtab Symbol table (used for checking overlaps).
396  *
397  * Checks the validity of the given selection method data structure
398  * that does not have \ref SMETH_MODIFIER set.
399  * If you remove a check, please make sure that the selection parser,
400  * compiler, and evaluation functions can deal with the method.
401  */
402 static bool
403 check_method(FILE *fp, gmx_ana_selmethod_t *method,
404              const gmx::SelectionParserSymbolTable &symtab)
405 {
406     bool         bOk = true;
407
408     /* Check the type */
409     if (method->type == NO_VALUE)
410     {
411         report_error(fp, method->name, "error: no value type specified");
412         bOk = false;
413     }
414     if (method->type == STR_VALUE && method->nparams > 0)
415     {
416         report_error(fp, method->name, "error: evaluates to a string but is not a keyword");
417         bOk = false;
418     }
419     /* Check flags */
420     if (method->type == GROUP_VALUE)
421     {
422         /* Group methods should always have SMETH_SINGLEVAL,
423          * so set it for convenience. */
424         method->flags |= SMETH_SINGLEVAL;
425         /* Check that conflicting flags are not present. */
426         if (method->flags & SMETH_VARNUMVAL)
427         {
428             report_error(fp, method->name, "error: SMETH_VARNUMVAL cannot be set for group-valued methods");
429             bOk = false;
430         }
431     }
432     else
433     {
434         if ((method->flags & SMETH_SINGLEVAL)
435             && (method->flags & SMETH_VARNUMVAL))
436         {
437             report_error(fp, method->name, "error: SMETH_SINGLEVAL and SMETH_VARNUMVAL both set");
438             bOk = false;
439         }
440     }
441     if ((method->flags & SMETH_CHARVAL) && method->type != STR_VALUE)
442     {
443         report_error(fp, method->name, "error: SMETH_CHARVAL can only be specified for STR_VALUE methods");
444         bOk = false;
445     }
446     /* Check the parameters */
447     if (!check_params(fp, method->name, method->nparams, method->param, symtab))
448     {
449         bOk = false;
450     }
451     /* Check the callback pointers */
452     if (!check_callbacks(fp, method))
453     {
454         bOk = false;
455     }
456
457     return bOk;
458 }
459
460 /*! \brief
461  * Checks the validity of a selection modifier method.
462  *
463  * \param[in]     fp     File handle to use for diagnostic messages
464  *   (can be NULL).
465  * \param[in,out] method Method to check.
466  * \param[in]     symtab Symbol table (used for checking overlaps).
467  *
468  * Checks the validity of the given selection method data structure
469  * that has \ref SMETH_MODIFIER set.
470  * If you remove a check, please make sure that the selection parser,
471  * compiler, and evaluation functions can deal with the method.
472  */
473 static bool
474 check_modifier(FILE *fp, gmx_ana_selmethod_t *method,
475                const gmx::SelectionParserSymbolTable &symtab)
476 {
477     bool         bOk = true;
478
479     /* Check the type */
480     if (method->type != NO_VALUE && method->type != POS_VALUE)
481     {
482         report_error(fp, method->name, "error: modifier should have type POS_VALUE or NO_VALUE");
483         bOk = false;
484     }
485     /* Check flags */
486     if (method->flags & (SMETH_SINGLEVAL | SMETH_VARNUMVAL))
487     {
488         report_error(fp, method->name, "error: modifier should not have SMETH_SINGLEVAL or SMETH_VARNUMVAL set");
489         bOk = false;
490     }
491     /* Check the parameters */
492     /* The first parameter is skipped */
493     if (!check_params(fp, method->name, method->nparams-1, method->param+1, symtab))
494     {
495         bOk = false;
496     }
497     /* Check the callback pointers */
498     if (!check_callbacks(fp, method))
499     {
500         bOk = false;
501     }
502     if (method->update)
503     {
504         report_error(fp, method->name, "error: modifier should not have update");
505         bOk = false;
506     }
507     if (method->type == POS_VALUE && !method->pupdate)
508     {
509         report_error(fp, method->name, "error: evaluation function missing");
510         bOk = false;
511     }
512
513     return bOk;
514 }
515
516 /*!
517  * \param[in,out] symtab Symbol table to register the method to.
518  * \param[in]     name   Name under which the method should be registered.
519  * \param[in]     method Method to register.
520  * \returns       0 on success, -1 if there was something wrong with the
521  *   method.
522  *
523  * \p name does not need to match the name of the method, and the same
524  * method can be registered multiple times under different names.
525  * If \p name equals some previously registered name,
526  * an error message is printed and the method is not registered.
527  *
528  * The function also performs some sanity checking on the input method,
529  * and refuses to register it if there are problems.
530  * Some problems only generate warnings.
531  * All problems are described to \p stderr.
532  */
533 int
534 gmx_ana_selmethod_register(gmx::SelectionParserSymbolTable *symtab,
535                            const char *name, gmx_ana_selmethod_t *method)
536 {
537     bool bOk;
538
539     /* Check the method */
540     if (method->flags & SMETH_MODIFIER)
541     {
542         bOk = check_modifier(stderr, method, *symtab);
543     }
544     else
545     {
546         bOk = check_method(stderr, method, *symtab);
547     }
548     /* Try to register the method if everything is ok */
549     if (bOk)
550     {
551         try
552         {
553             symtab->addMethod(name, method);
554         }
555         catch (const gmx::APIError &ex)
556         {
557             report_error(stderr, name, "%s", ex.what());
558             bOk = false;
559         }
560     }
561     if (!bOk)
562     {
563         report_error(stderr, name, "warning: not registered");
564         return -1;
565     }
566     return 0;
567 }
568
569 /*!
570  * \param[in,out] symtab Symbol table to register the methods to.
571  * \returns       0 on success, -1 if any of the default methods could not be
572  *   registered.
573  */
574 int
575 gmx_ana_selmethod_register_defaults(gmx::SelectionParserSymbolTable *symtab)
576 {
577     /* Array of selection methods defined in the library. */
578     const t_register_method smtable_def[] = {
579         {nullptr,         &sm_cog},
580         {nullptr,         &sm_com},
581
582         {nullptr,         &sm_all},
583         {nullptr,         &sm_none},
584         {nullptr,         &sm_atomnr},
585         {nullptr,         &sm_resnr},
586         {"resid",      &sm_resnr},
587         {nullptr,         &sm_resindex},
588         {"residue",    &sm_resindex},
589         {nullptr,         &sm_molindex},
590         {"mol",        &sm_molindex},
591         {"molecule",   &sm_molindex},
592         {nullptr,         &sm_atomname},
593         {"name",       &sm_atomname},
594         {nullptr,         &sm_pdbatomname},
595         {"pdbname",    &sm_pdbatomname},
596         {nullptr,         &sm_atomtype},
597         {"type",       &sm_atomtype},
598         {nullptr,         &sm_resname},
599         {nullptr,         &sm_insertcode},
600         {nullptr,         &sm_chain},
601         {nullptr,         &sm_mass},
602         {nullptr,         &sm_charge},
603         {nullptr,         &sm_altloc},
604         {nullptr,         &sm_occupancy},
605         {nullptr,         &sm_betafactor},
606         {"beta",       &sm_betafactor},
607         {nullptr,         &sm_x},
608         {nullptr,         &sm_y},
609         {nullptr,         &sm_z},
610
611         {nullptr,         &sm_distance},
612         {"dist",       &sm_distance},
613         {nullptr,         &sm_mindistance},
614         {"mindist",    &sm_mindistance},
615         {nullptr,         &sm_within},
616         {nullptr,         &sm_insolidangle},
617         {nullptr,         &sm_same},
618
619         {nullptr,         &sm_merge},
620         {nullptr,         &sm_plus},
621         {nullptr,         &sm_permute},
622     };
623
624     size_t                  i;
625     int                     rc;
626     bool                    bOk;
627
628     bOk = true;
629     for (i = 0; i < asize(smtable_def); ++i)
630     {
631         gmx_ana_selmethod_t *method = smtable_def[i].method;
632
633         if (smtable_def[i].name == nullptr)
634         {
635             rc = gmx_ana_selmethod_register(symtab, method->name, method);
636         }
637         else
638         {
639             rc = gmx_ana_selmethod_register(symtab, smtable_def[i].name, method);
640         }
641         if (rc != 0)
642         {
643             bOk = false;
644         }
645     }
646     return bOk ? 0 : -1;
647 }
648
649 /*!
650  * \param[in] name   Name of the parameter to search.
651  * \param[in] method Method to search for the parameter.
652  * \returns   Pointer to the parameter in the
653  *   \ref gmx_ana_selmethod_t::param "method->param" array,
654  *   or NULL if no parameter with name \p name was found.
655  *
656  * This is a simple wrapper for gmx_ana_selparam_find().
657  */
658 gmx_ana_selparam_t *
659 gmx_ana_selmethod_find_param(const char *name, gmx_ana_selmethod_t *method)
660 {
661     return gmx_ana_selparam_find(name, method->nparams, method->param);
662 }