bc755086c4e51833d0330ff26694eef9089e31e4
[alexxy/gromacs.git] / src / gromacs / selection / selectionoptionmanager.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2012,2013,2014,2015,2016 by the GROMACS development team.
5  * Copyright (c) 2019,2020, by the GROMACS development team, led by
6  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
7  * and including many others, as listed in the AUTHORS file in the
8  * top-level source directory and at http://www.gromacs.org.
9  *
10  * GROMACS is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Lesser General Public License
12  * as published by the Free Software Foundation; either version 2.1
13  * of the License, or (at your option) any later version.
14  *
15  * GROMACS is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public
21  * License along with GROMACS; if not, see
22  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
23  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
24  *
25  * If you want to redistribute modifications to GROMACS, please
26  * consider that scientific software is very special. Version
27  * control is crucial - bugs must be traceable. We will be happy to
28  * consider code for inclusion in the official distribution, but
29  * derived work must not be called official GROMACS. Details are found
30  * in the README & COPYING files - if they are missing, get the
31  * official version at http://www.gromacs.org.
32  *
33  * To help us fund GROMACS development, we humbly ask that you cite
34  * the research papers on the package. Check out http://www.gromacs.org.
35  */
36 /*! \internal \file
37  * \brief
38  * Implements gmx::SelectionOptionManager.
39  *
40  * \author Teemu Murtola <teemu.murtola@gmail.com>
41  * \ingroup module_selection
42  */
43 #include "gmxpre.h"
44
45 #include "selectionoptionmanager.h"
46
47 #include <cstdio>
48
49 #include "gromacs/selection/selection.h"
50 #include "gromacs/selection/selectioncollection.h"
51 #include "gromacs/selection/selectionoption.h"
52 #include "gromacs/utility/exceptions.h"
53 #include "gromacs/utility/stringutil.h"
54
55 #include "selectionoptionstorage.h"
56
57 namespace gmx
58 {
59
60 /********************************************************************
61  * SelectionOptionManager::Impl
62  */
63
64 /*! \internal \brief
65  * Private implemention class for SelectionOptionManager.
66  *
67  * \ingroup module_selection
68  */
69 class SelectionOptionManager::Impl
70 {
71 public:
72     /*! \brief
73      * Request for postponed parsing of selections.
74      */
75     struct SelectionRequest
76     {
77         //! Initializes a request for the given option.
78         explicit SelectionRequest(SelectionOptionStorage* storage) : storage_(storage) {}
79
80         //! Returns name of the requested selection optin.
81         const std::string& name() const { return storage_->name(); }
82         //! Returns description for the requested selection option.
83         const std::string& description() const { return storage_->description(); }
84         /*! \brief
85          * Returns the number of selections requested in this request.
86          *
87          * -1 indicates no upper limit.
88          */
89         int count() const { return storage_->maxValueCount(); }
90
91         //! Storage object to which the selections will be added.
92         SelectionOptionStorage* storage_;
93     };
94
95     //! Collection for a list of selection requests.
96     typedef std::vector<SelectionRequest> RequestList;
97     //! Collection for list of option storage objects.
98     typedef std::vector<SelectionOptionStorage*> OptionList;
99
100     /*! \brief
101      * Helper class that clears a request list on scope exit.
102      *
103      * Methods in this class do not throw.
104      */
105     class RequestsClearer
106     {
107     public:
108         //! Constructs an object that clears given list on scope exit.
109         explicit RequestsClearer(RequestList* requests) : requests_(requests) {}
110         //! Clears the request list given to the constructor.
111         ~RequestsClearer() { requests_->clear(); }
112
113     private:
114         RequestList* requests_;
115     };
116
117     /*! \brief
118      * Creates a new selection collection.
119      *
120      * \throws  std::bad_alloc if out of memory.
121      */
122     explicit Impl(SelectionCollection* collection);
123
124     /*! \brief
125      * Assign selections from a list to pending requests.
126      *
127      * \param[in] selections  List of selections to assign.
128      * \throws    std::bad_alloc if out of memory.
129      * \throws    InvalidInputError if the assignment cannot be done
130      *      (see parseRequestedFromFile() for documented conditions).
131      *
132      * Loops through \p selections and the pending requests lists in order,
133      * and for each requests, assigns the first yet unassigned selections
134      * from the list.
135      */
136     void placeSelectionsInRequests(const SelectionList& selections);
137     /*! \brief
138      * Adds a request for each required option that is not yet set.
139      *
140      * \throws    std::bad_alloc if out of memory.
141      */
142     void requestUnsetRequiredOptions();
143
144     //! Selection collection to which selections are stored.
145     SelectionCollection& collection_;
146     //! List of selection options (storage objects) this manager manages.
147     OptionList options_;
148     //! List of selections requested for later parsing.
149     RequestList requests_;
150 };
151
152 SelectionOptionManager::Impl::Impl(SelectionCollection* collection) : collection_(*collection) {}
153
154 void SelectionOptionManager::Impl::placeSelectionsInRequests(const SelectionList& selections)
155 {
156     if (requests_.empty())
157     {
158         requestUnsetRequiredOptions();
159     }
160
161     RequestsClearer clearRequestsOnExit(&requests_);
162
163     SelectionList::const_iterator first = selections.begin();
164     SelectionList::const_iterator last  = first;
165     RequestList::const_iterator   i;
166     for (i = requests_.begin(); i != requests_.end(); ++i)
167     {
168         const SelectionRequest& request = *i;
169         if (request.count() > 0)
170         {
171             int remaining = selections.end() - first;
172             if (remaining < request.count())
173             {
174                 int assigned = first - selections.begin();
175                 GMX_THROW(InvalidInputError(
176                         formatString("Too few selections provided for '%s': "
177                                      "Expected %d selections, but only %d left "
178                                      "after assigning the first %d to other selections.",
179                                      request.name().c_str(), request.count(), remaining, assigned)));
180             }
181             last = first + request.count();
182         }
183         else
184         {
185             RequestList::const_iterator nextRequest = i;
186             ++nextRequest;
187             if (nextRequest != requests_.end())
188             {
189                 const char* name         = request.name().c_str();
190                 const char* conflictName = nextRequest->name().c_str();
191                 GMX_THROW(InvalidInputError(
192                         formatString("Ambiguous selections for '%s' and '%s': "
193                                      "Any number of selections is acceptable for "
194                                      "'%s', but you have requested subsequent "
195                                      "selections to be assigned to '%s'. "
196                                      "Resolution for such cases is not implemented, "
197                                      "and may be impossible.",
198                                      name, conflictName, name, conflictName)));
199             }
200             last = selections.end();
201         }
202         SelectionList curr(first, last);
203         request.storage_->addSelections(curr, true);
204         first = last;
205     }
206     if (last != selections.end())
207     {
208         int count     = selections.end() - selections.begin();
209         int remaining = selections.end() - last;
210         int assigned  = last - selections.begin();
211         GMX_THROW(InvalidInputError(
212                 formatString("Too many selections provided: "
213                              "Expected %d selections, but %d provided. "
214                              "Last %d selections could not be assigned to any option.",
215                              assigned, count, remaining)));
216     }
217 }
218
219 void SelectionOptionManager::Impl::requestUnsetRequiredOptions()
220 {
221     OptionList::const_iterator i;
222     for (i = options_.begin(); i != options_.end(); ++i)
223     {
224         SelectionOptionStorage& storage = **i;
225         if (storage.isRequired() && !storage.isSet())
226         {
227             requests_.emplace_back(&storage);
228         }
229     }
230 }
231
232
233 /********************************************************************
234  * SelectionOptionManager
235  */
236
237 SelectionOptionManager::SelectionOptionManager(SelectionCollection* collection) :
238     impl_(new Impl(collection))
239 {
240 }
241
242 SelectionOptionManager::~SelectionOptionManager() {}
243
244 void SelectionOptionManager::registerOption(SelectionOptionStorage* storage)
245 {
246     impl_->requests_.reserve(impl_->options_.size() + 1);
247     impl_->options_.push_back(storage);
248 }
249
250 void SelectionOptionManager::convertOptionValue(SelectionOptionStorage* storage,
251                                                 const std::string&      value,
252                                                 bool                    bFullValue)
253 {
254     SelectionList selections = impl_->collection_.parseFromString(value);
255     storage->addSelections(selections, bFullValue);
256 }
257
258 void SelectionOptionManager::requestOptionDelayedParsing(SelectionOptionStorage* storage)
259 {
260     impl_->requests_.emplace_back(storage);
261 }
262
263 bool SelectionOptionManager::hasRequestedSelections() const
264 {
265     return !impl_->requests_.empty();
266 }
267
268 void SelectionOptionManager::initOptions(IOptionsContainer* options)
269 {
270     bool                             allowOnlyAtomOutput = true;
271     Impl::OptionList::const_iterator iter;
272     for (iter = impl_->options_.begin(); iter != impl_->options_.end(); ++iter)
273     {
274         if (!(*iter)->allowsOnlyAtoms())
275         {
276             allowOnlyAtomOutput = false;
277         }
278     }
279
280     SelectionCollection::SelectionTypeOption typeOption =
281             allowOnlyAtomOutput ? SelectionCollection::AlwaysAtomSelections
282                                 : SelectionCollection::IncludeSelectionTypeOption;
283     impl_->collection_.initOptions(options, typeOption);
284 }
285
286 void SelectionOptionManager::parseRequestedFromStdin(bool bInteractive)
287 {
288     Impl::RequestsClearer clearRequestsOnExit(&impl_->requests_);
289
290     Impl::RequestList::const_iterator i;
291     for (i = impl_->requests_.begin(); i != impl_->requests_.end(); ++i)
292     {
293         const Impl::SelectionRequest& request = *i;
294         std::string   context = formatString("for option '%s'\n(%s)", request.name().c_str(),
295                                            request.description().c_str());
296         SelectionList selections =
297                 impl_->collection_.parseFromStdin(request.count(), bInteractive, context);
298         request.storage_->addSelections(selections, true);
299     }
300 }
301
302 void SelectionOptionManager::parseRequestedFromFile(const std::string& filename)
303 {
304     SelectionList selections = impl_->collection_.parseFromFile(filename);
305     try
306     {
307         impl_->placeSelectionsInRequests(selections);
308     }
309     catch (GromacsException& ex)
310     {
311         ex.prependContext(formatString("Error in adding selections from file '%s'", filename.c_str()));
312         throw;
313     }
314 }
315
316 void SelectionOptionManager::parseRequestedFromString(const std::string& str)
317 {
318     SelectionList selections = impl_->collection_.parseFromString(str);
319     impl_->placeSelectionsInRequests(selections);
320 }
321
322 } // namespace gmx