}
/* Update the flags */
_gmx_selelem_update_flags(root);
+ gmx::ExceptionInitializer errors("Invalid index group reference(s)");
+ root->checkUnsortedAtoms(true, &errors);
+ if (errors.hasNestedExceptions())
+ {
+ GMX_THROW(gmx::InconsistentInputError(errors));
+ }
root->fillNameIfMissing(_gmx_sel_lexer_pselstr(scanner));
{
/* If so, just assign the constant value to the variable */
sc->symtab->addVariable(name, expr);
- goto finish;
}
/* Check if we are assigning a variable to another variable */
- if (expr->type == SEL_SUBEXPRREF)
+ else if (expr->type == SEL_SUBEXPRREF)
{
/* If so, make a simple alias */
sc->symtab->addVariable(name, expr->child);
- goto finish;
}
- /* Create the root element */
- root.reset(new SelectionTreeElement(SEL_ROOT));
- root->setName(name);
- /* Create the subexpression element */
- root->child.reset(new SelectionTreeElement(SEL_SUBEXPR));
- root->child->setName(name);
- _gmx_selelem_set_vtype(root->child, expr->v.type);
- root->child->child = expr;
- /* Update flags */
- _gmx_selelem_update_flags(root);
- /* Add the variable to the symbol table */
- sc->symtab->addVariable(name, root->child);
-finish:
+ else
+ {
+ /* Create the root element */
+ root.reset(new SelectionTreeElement(SEL_ROOT));
+ root->setName(name);
+ /* Create the subexpression element */
+ root->child.reset(new SelectionTreeElement(SEL_SUBEXPR));
+ root->child->setName(name);
+ _gmx_selelem_set_vtype(root->child, expr->v.type);
+ root->child->child = expr;
+ /* Update flags */
+ _gmx_selelem_update_flags(root);
+ gmx::ExceptionInitializer errors("Invalid index group reference(s)");
+ root->checkUnsortedAtoms(true, &errors);
+ if (errors.hasNestedExceptions())
+ {
+ GMX_THROW(gmx::InconsistentInputError(errors));
+ }
+ /* Add the variable to the symbol table */
+ sc->symtab->addVariable(name, root->child);
+ }
srenew(sc->varstrs, sc->nvars + 1);
sc->varstrs[sc->nvars] = strdup(pselstr);
++sc->nvars;
impl_->grps_ = grps;
impl_->bExternalGroupsSet_ = true;
- ExceptionInitializer errors("Unknown index group references encountered");
+ ExceptionInitializer errors("Invalid index group reference(s)");
SelectionTreeElementPointer root = impl_->sc_.root;
while (root)
{
impl_->resolveExternalGroups(root, &errors);
+ root->checkUnsortedAtoms(true, &errors);
root = root->next;
}
if (errors.hasNestedExceptions())
#include "keywords.h"
#include "mempool.h"
#include "selelem.h"
+#include "selmethod.h"
/*!
* \param[in] sel Selection for which the string is requested
}
}
+void SelectionTreeElement::checkUnsortedAtoms(
+ bool bUnsortedAllowed, ExceptionInitializer *errors) const
+{
+ const bool bUnsortedSupported
+ = (type == SEL_CONST && v.type == GROUP_VALUE)
+ || type == SEL_ROOT || type == SEL_SUBEXPR || type == SEL_SUBEXPRREF
+ // TODO: Consolidate.
+ || type == SEL_MODIFIER
+ || (type == SEL_EXPRESSION && (u.expr.method->flags & SMETH_ALLOW_UNSORTED));
+
+ // TODO: For some complicated selections, this may result in the same
+ // index group reference being flagged as an error multiple times for the
+ // same selection.
+ SelectionTreeElementPointer child = this->child;
+ while (child)
+ {
+ child->checkUnsortedAtoms(bUnsortedAllowed && bUnsortedSupported,
+ errors);
+ child = child->next;
+ }
+
+ // The logic here is simplified by the fact that only constant groups can
+ // currently be the root cause of SEL_UNSORTED being set, so only those
+ // need to be considered in triggering the error.
+ if (!bUnsortedAllowed && (flags & SEL_UNSORTED)
+ && type == SEL_CONST && v.type == GROUP_VALUE)
+ {
+ std::string message = formatString(
+ "Group '%s' cannot be used in selections except "
+ "as a full value of the selection, "
+ "because atom indices in it are not sorted and/or "
+ "it contains duplicate atoms.",
+ name().c_str());
+ errors->addNested(InconsistentInputError(message));
+ }
+}
+
void SelectionTreeElement::resolveIndexGroupReference(gmx_ana_indexgrps_t *grps)
{
GMX_RELEASE_ASSERT(type == SEL_GROUPREF,
if (!gmx_ana_index_check_sorted(&foundGroup))
{
flags |= SEL_UNSORTED;
- // TODO: Add this test elsewhere, where it does not break valid use cases.
-#if 0
- gmx_ana_index_deinit(&foundGroup);
- std::string message = formatString(
- "Group '%s' ('%s') cannot be used in selections, "
- "because atom indices in it are not sorted and/or "
- "it contains duplicate atoms.",
- foundName.c_str(), name().c_str());
- GMX_THROW(InconsistentInputError(message));
-#endif
}
sfree(u.gref.name);
namespace gmx
{
+class ExceptionInitializer;
+
/*! \brief
* Function pointer for evaluating a gmx::SelectionTreeElement.
*/
*/
void fillNameIfMissing(const char *selectionText);
+ /*! \brief
+ * Checks that this element and its children do not contain unsupported
+ * elements with unsorted atoms.
+ *
+ * \param[in] bUnsortedAllowed Whether this element's parents allow it
+ * to have unsorted atoms.
+ * \param errors Object for reporting any error messages.
+ * \throws std::bad_alloc if out of memory.
+ *
+ * Errors are reported as nested exceptions in \p errors.
+ */
+ void checkUnsortedAtoms(bool bUnsortedAllowed,
+ ExceptionInitializer *errors) const;
/*! \brief
* Resolved an unresolved reference to an index group.
*
* - \ref SMETH_MODIFIER : If set, the method is a selection modifier and
* not an actual selection method.
* For more details, see \ref selmethods_modifiers.
+ * - \ref SMETH_ALLOW_UNSORTED : If set, the method supports unsorted atoms
+ * in its input parameters. \ref SMETH_MODIFIER methods are assumed to always
+ * support unsorted atoms, as their purpose is to affect the ordering.
*
* There are two additional flags that specify the number of values the
* method returns. Only one of them can be set at a time.
* the string pointers.
*/
#define SMETH_CHARVAL 64
+/*! \brief
+ * If set, the method accepts unsorted atoms in its input parameters.
+ *
+ * Currently, the support for this functionality is fairly limited, and only
+ * static index group references can actually contain unsorted atoms.
+ * But to make this single case work, the position evaluation must support
+ * unsorted atoms as well.
+ */
+#define SMETH_ALLOW_UNSORTED 128
/*! \brief
* If set, the method is a selection modifier.
*
/** Selection method data for position keyword evaluation. */
gmx_ana_selmethod_t sm_keyword_pos = {
- "kw_pos", POS_VALUE, SMETH_DYNAMIC | SMETH_VARNUMVAL,
+ "kw_pos", POS_VALUE, SMETH_DYNAMIC | SMETH_VARNUMVAL | SMETH_ALLOW_UNSORTED,
asize(smparams_keyword_pos), smparams_keyword_pos,
&init_data_pos,
&set_poscoll_pos,
<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
<ReferenceData>
<ParsedSelections Name="Parsed">
+ <ParsedVariable Name="Variable1">
+ <String Name="Input">foo = group "GrpUnsorted"</String>
+ </ParsedVariable>
<ParsedSelection Name="Selection1">
<String Name="Input">group "GrpUnsorted"</String>
<String Name="Name">GrpUnsorted</String>
<String Name="Text">group "GrpUnsorted" permute 2 1</String>
<Bool Name="Dynamic">false</Bool>
</ParsedSelection>
+ <ParsedSelection Name="Selection6">
+ <String Name="Input">foo</String>
+ <String Name="Name">foo</String>
+ <String Name="Text">foo</String>
+ <Bool Name="Dynamic">false</Bool>
+ </ParsedSelection>
</ParsedSelections>
<CompiledSelections Name="Compiled">
<Selection Name="Selection1">
</Position>
</Sequence>
</Selection>
+ <Selection Name="Selection6">
+ <String Name="Name">foo</String>
+ <Sequence Name="Atoms">
+ <Int Name="Length">8</Int>
+ <Int>0</Int>
+ <Int>2</Int>
+ <Int>1</Int>
+ <Int>8</Int>
+ <Int>6</Int>
+ <Int>4</Int>
+ <Int>2</Int>
+ <Int>11</Int>
+ </Sequence>
+ <Sequence Name="Positions">
+ <Int Name="Length">8</Int>
+ <Position>
+ <Sequence Name="Atoms">
+ <Int Name="Length">1</Int>
+ <Int>0</Int>
+ </Sequence>
+ <Int Name="RefId">0</Int>
+ <Int Name="MappedId">0</Int>
+ </Position>
+ <Position>
+ <Sequence Name="Atoms">
+ <Int Name="Length">1</Int>
+ <Int>2</Int>
+ </Sequence>
+ <Int Name="RefId">1</Int>
+ <Int Name="MappedId">2</Int>
+ </Position>
+ <Position>
+ <Sequence Name="Atoms">
+ <Int Name="Length">1</Int>
+ <Int>1</Int>
+ </Sequence>
+ <Int Name="RefId">2</Int>
+ <Int Name="MappedId">1</Int>
+ </Position>
+ <Position>
+ <Sequence Name="Atoms">
+ <Int Name="Length">1</Int>
+ <Int>8</Int>
+ </Sequence>
+ <Int Name="RefId">3</Int>
+ <Int Name="MappedId">8</Int>
+ </Position>
+ <Position>
+ <Sequence Name="Atoms">
+ <Int Name="Length">1</Int>
+ <Int>6</Int>
+ </Sequence>
+ <Int Name="RefId">4</Int>
+ <Int Name="MappedId">6</Int>
+ </Position>
+ <Position>
+ <Sequence Name="Atoms">
+ <Int Name="Length">1</Int>
+ <Int>4</Int>
+ </Sequence>
+ <Int Name="RefId">5</Int>
+ <Int Name="MappedId">4</Int>
+ </Position>
+ <Position>
+ <Sequence Name="Atoms">
+ <Int Name="Length">1</Int>
+ <Int>2</Int>
+ </Sequence>
+ <Int Name="RefId">6</Int>
+ <Int Name="MappedId">2</Int>
+ </Position>
+ <Position>
+ <Sequence Name="Atoms">
+ <Int Name="Length">1</Int>
+ <Int>11</Int>
+ </Sequence>
+ <Int Name="RefId">7</Int>
+ <Int Name="MappedId">11</Int>
+ </Position>
+ </Sequence>
+ </Selection>
</CompiledSelections>
</ReferenceData>
--- /dev/null
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+ <ParsedSelections Name="Parsed">
+ <ParsedVariable Name="Variable1">
+ <String Name="Input">foo = group "GrpUnsorted"</String>
+ </ParsedVariable>
+ <ParsedSelection Name="Selection1">
+ <String Name="Input">group "GrpUnsorted"</String>
+ <String Name="Text">group "GrpUnsorted"</String>
+ <Bool Name="Dynamic">false</Bool>
+ </ParsedSelection>
+ <ParsedSelection Name="Selection2">
+ <String Name="Input">GrpUnsorted</String>
+ <String Name="Text">GrpUnsorted</String>
+ <Bool Name="Dynamic">false</Bool>
+ </ParsedSelection>
+ <ParsedSelection Name="Selection3">
+ <String Name="Input">2</String>
+ <String Name="Text">2</String>
+ <Bool Name="Dynamic">false</Bool>
+ </ParsedSelection>
+ <ParsedSelection Name="Selection4">
+ <String Name="Input">res_cog of group "GrpUnsorted"</String>
+ <String Name="Text">res_cog of group "GrpUnsorted"</String>
+ <Bool Name="Dynamic">false</Bool>
+ </ParsedSelection>
+ <ParsedSelection Name="Selection5">
+ <String Name="Input">group "GrpUnsorted" permute 2 1</String>
+ <String Name="Text">group "GrpUnsorted" permute 2 1</String>
+ <Bool Name="Dynamic">false</Bool>
+ </ParsedSelection>
+ <ParsedSelection Name="Selection6">
+ <String Name="Input">foo</String>
+ <String Name="Text">foo</String>
+ <Bool Name="Dynamic">false</Bool>
+ </ParsedSelection>
+ </ParsedSelections>
+ <CompiledSelections Name="Compiled">
+ <Selection Name="Selection1">
+ <Sequence Name="Atoms">
+ <Int Name="Length">8</Int>
+ <Int>0</Int>
+ <Int>2</Int>
+ <Int>1</Int>
+ <Int>8</Int>
+ <Int>6</Int>
+ <Int>4</Int>
+ <Int>2</Int>
+ <Int>11</Int>
+ </Sequence>
+ </Selection>
+ <Selection Name="Selection2">
+ <Sequence Name="Atoms">
+ <Int Name="Length">8</Int>
+ <Int>0</Int>
+ <Int>2</Int>
+ <Int>1</Int>
+ <Int>8</Int>
+ <Int>6</Int>
+ <Int>4</Int>
+ <Int>2</Int>
+ <Int>11</Int>
+ </Sequence>
+ </Selection>
+ <Selection Name="Selection3">
+ <Sequence Name="Atoms">
+ <Int Name="Length">8</Int>
+ <Int>0</Int>
+ <Int>2</Int>
+ <Int>1</Int>
+ <Int>8</Int>
+ <Int>6</Int>
+ <Int>4</Int>
+ <Int>2</Int>
+ <Int>11</Int>
+ </Sequence>
+ </Selection>
+ <Selection Name="Selection4">
+ <Sequence Name="Atoms">
+ <Int Name="Length">8</Int>
+ <Int>0</Int>
+ <Int>2</Int>
+ <Int>1</Int>
+ <Int>8</Int>
+ <Int>6</Int>
+ <Int>4</Int>
+ <Int>2</Int>
+ <Int>11</Int>
+ </Sequence>
+ </Selection>
+ <Selection Name="Selection5">
+ <Sequence Name="Atoms">
+ <Int Name="Length">8</Int>
+ <Int>2</Int>
+ <Int>0</Int>
+ <Int>8</Int>
+ <Int>1</Int>
+ <Int>4</Int>
+ <Int>6</Int>
+ <Int>11</Int>
+ <Int>2</Int>
+ </Sequence>
+ </Selection>
+ <Selection Name="Selection6">
+ <Sequence Name="Atoms">
+ <Int Name="Length">8</Int>
+ <Int>0</Int>
+ <Int>2</Int>
+ <Int>1</Int>
+ <Int>8</Int>
+ <Int>6</Int>
+ <Int>4</Int>
+ <Int>2</Int>
+ <Int>11</Int>
+ </Sequence>
+ </Selection>
+ </CompiledSelections>
+</ReferenceData>
EXPECT_THROW_GMX(sc_.compile(), gmx::APIError);
}
-// TODO: Make the check less eager so that it doesn't break other tests, and
-// adapt these tests accordingly.
-TEST_F(SelectionCollectionTest, DISABLED_HandlesUnsortedGroupReference)
+TEST_F(SelectionCollectionTest, HandlesUnsortedGroupReference)
{
ASSERT_NO_THROW_GMX(loadIndexGroups("simple.ndx"));
- EXPECT_THROW_GMX(sc_.parseFromString("group \"GrpUnsorted\""),
+ EXPECT_THROW_GMX(sc_.parseFromString("atomnr 1 to 3 and group \"GrpUnsorted\""),
+ gmx::InconsistentInputError);
+ EXPECT_THROW_GMX(sc_.parseFromString("group 2 or atomnr 2 to 5"),
+ gmx::InconsistentInputError);
+ EXPECT_THROW_GMX(sc_.parseFromString("within 1 of group 2"),
gmx::InconsistentInputError);
- EXPECT_THROW_GMX(sc_.parseFromString("2"), gmx::InconsistentInputError);
}
-TEST_F(SelectionCollectionTest, DISABLED_HandlesUnsortedGroupReferenceDelayed)
+TEST_F(SelectionCollectionTest, HandlesUnsortedGroupReferenceDelayed)
{
- ASSERT_NO_THROW_GMX(sc_.parseFromString("group 2; group \"GrpUnsorted\""));
+ ASSERT_NO_THROW_GMX(sc_.parseFromString("atomnr 1 to 3 and group \"GrpUnsorted\""));
+ ASSERT_NO_THROW_GMX(sc_.parseFromString("atomnr 1 to 3 and group 2"));
EXPECT_THROW_GMX(loadIndexGroups("simple.ndx"), gmx::InconsistentInputError);
- EXPECT_THROW_GMX(sc_.compile(), gmx::APIError);
+ // TODO: Add a separate check in the selection compiler for a safer API
+ // (makes sense in the future if the compiler needs the information for
+ // other purposes as well).
+ // EXPECT_THROW_GMX(sc_.compile(), gmx::APIError);
}
TEST_F(SelectionCollectionTest, RecoversFromMissingMoleculeInfo)
TEST_F(SelectionCollectionDataTest, HandlesUnsortedIndexGroupsInSelections)
{
static const char * const selections[] = {
+ "foo = group \"GrpUnsorted\"",
"group \"GrpUnsorted\"",
"GrpUnsorted",
"2",
"res_cog of group \"GrpUnsorted\"",
- "group \"GrpUnsorted\" permute 2 1"
+ "group \"GrpUnsorted\" permute 2 1",
+ "foo"
};
setFlags(TestFlags() | efTestPositionAtoms | efTestPositionMapping
| efTestSelectionNames);
runTest("simple.gro", selections);
}
+TEST_F(SelectionCollectionDataTest, HandlesUnsortedIndexGroupsInSelectionsDelayed)
+{
+ static const char * const selections[] = {
+ "foo = group \"GrpUnsorted\"",
+ "group \"GrpUnsorted\"",
+ "GrpUnsorted",
+ "2",
+ "res_cog of group \"GrpUnsorted\"",
+ "group \"GrpUnsorted\" permute 2 1",
+ "foo"
+ };
+ ASSERT_NO_FATAL_FAILURE(runParser(selections));
+ ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
+ ASSERT_NO_THROW_GMX(loadIndexGroups("simple.ndx"));
+ ASSERT_NO_FATAL_FAILURE(runCompiler());
+}
+
TEST_F(SelectionCollectionDataTest, HandlesConstantPositions)
{
static const char * const selections[] = {
/*
* This file is part of the GROMACS molecular simulation package.
*
- * Copyright (c) 2011,2012,2013, by the GROMACS development team, led by
+ * Copyright (c) 2011,2012,2013,2014, by the GROMACS development team, led by
* Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
* and including many others, as listed in the AUTHORS file in the
* top-level source directory and at http://www.gromacs.org.
{
nested_.push_back(boost::current_exception());
}
+ /*! \brief
+ * Adds the specified exception as a nested exception.
+ *
+ * May be called multiple times; all provided exceptions will be added
+ * in a list of nested exceptions.
+ *
+ * This is equivalent to throwing \p ex and calling
+ * addCurrentExceptionAsNested() in the catch block, but potentially
+ * more efficient.
+ */
+ template <class Exception>
+ void addNested(const Exception &ex)
+ {
+ nested_.push_back(boost::copy_exception(ex));
+ }
private:
std::string reason_;