NEST main@caf0ae8
 
Loading...
Searching...
No Matches
free_layer.h
Go to the documentation of this file.
1/*
2 * free_layer.h
3 *
4 * This file is part of NEST.
5 *
6 * Copyright (C) 2004 The NEST Initiative
7 *
8 * NEST is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * NEST is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with NEST. If not, see <http://www.gnu.org/licenses/>.
20 *
21 */
22
23#ifndef FREE_LAYER_H
24#define FREE_LAYER_H
25
26// C++ includes:
27#include <algorithm>
28#include <limits>
29#include <vector>
30
31// Includes from nestkernel:
32#include "dictionary.h"
33#include "nest_names.h"
34
35// Includes from spatial:
36#include "layer.h"
37#include "ntree_impl.h"
38
39namespace nest
40{
41
45template < int D >
46class FreeLayer : public Layer< D >
47{
48public:
49 Position< D > get_position( size_t sind ) const override;
50 void set_status( const Dictionary& ) override;
51 void get_status( Dictionary&, NodeCollection const* const ) const override;
52
53protected:
59 template < class Ins >
60 void communicate_positions_( Ins iter, NodeCollectionPTR node_collection );
61
62 void insert_global_positions_ntree_( Ntree< D, size_t >& tree, NodeCollectionPTR node_collection ) override;
63 void insert_global_positions_vector_( std::vector< std::pair< Position< D >, size_t > >& vec,
64 NodeCollectionPTR node_collection ) override;
65
72 size_t lid_to_position_id_( size_t lid ) const;
73
75 std::vector< Position< D > > positions_;
76
77 size_t num_local_nodes_ = 0;
78
83 {
84 public:
85 size_t
87 {
88 return node_id_;
89 }
92 {
93 return Position< D >( pos_ );
94 }
95 bool
96 operator<( const NodePositionData& other ) const
97 {
98 return node_id_ < other.node_id_;
99 }
100 bool
101 operator==( const NodePositionData& other ) const
102 {
103 return node_id_ == other.node_id_;
104 }
105
106 private:
107 double node_id_;
108 double pos_[ D ];
109 };
110};
111
112template < int D >
113void
115{
117
118 Position< D > max_point; // for each dimension, the largest value of the positions, aka upper right
119
120 for ( int d = 0; d < D; ++d )
121 {
122 this->lower_left_[ d ] = std::numeric_limits< double >::infinity();
123 max_point[ d ] = -std::numeric_limits< double >::infinity();
124 }
125
126 num_local_nodes_ = std::accumulate( this->node_collection_->begin(),
127 this->node_collection_->end(),
128 0,
129 []( size_t a, NodeIDTriple b )
130 {
131 const auto node = kernel().node_manager.get_mpi_local_node_or_device_head( b.node_id );
132 return node->is_proxy() ? a : a + 1;
133 } );
134
135 // Read positions from dictionary
136 if ( d.known( names::positions ) )
137 {
138 const auto positions = d.at( names::positions );
139 if ( std::holds_alternative< std::vector< std::vector< double > > >( positions ) )
140 {
141 positions_.clear();
142 positions_.reserve( num_local_nodes_ );
143
144 auto nc_it = this->node_collection_->begin();
145 const auto pos = std::get< std::vector< std::vector< double > > >( positions );
146 for ( auto it = pos.begin(); it != pos.end(); ++it, ++nc_it )
147 {
148 assert( nc_it != this->node_collection_->end() );
149 Position< D > point = *it;
150 const auto node = kernel().node_manager.get_mpi_local_node_or_device_head( ( *nc_it ).node_id );
151 assert( node );
152 if ( not node->is_proxy() )
153 {
154 positions_.push_back( point );
155 }
156 // We do the calculation for lower_left_ and max_point even if we don't add the position, to keep the size of
157 // the layer consistent over processes.
158 for ( int d = 0; d < D; ++d )
159 {
160 if ( point[ d ] < this->lower_left_[ d ] )
161 {
162 this->lower_left_[ d ] = point[ d ];
163 }
164 if ( point[ d ] > max_point[ d ] )
165 {
166 max_point[ d ] = point[ d ];
167 }
168 }
169 }
170 assert( positions_.size() == num_local_nodes_ );
171 }
172 else if ( std::holds_alternative< std::shared_ptr< Parameter > >( positions ) )
173 {
174 auto pd = d.get< ParameterPTR >( names::positions );
175 auto pos = dynamic_cast< DimensionParameter* >( pd.get() );
176 positions_.clear();
177 positions_.reserve( num_local_nodes_ );
178
180
181 for ( auto nc_it = this->node_collection_->begin(); nc_it < this->node_collection_->end(); ++nc_it )
182 {
183 // We generate the position here, even if we do not store it, to do the same calculations of lower_left_ and
184 // max_point on all processes.
185 Position< D > point = pos->get_values( rng );
186
187 const auto node = kernel().node_manager.get_mpi_local_node_or_device_head( ( *nc_it ).node_id );
188 assert( node );
189 if ( not node->is_proxy() )
190 {
191 positions_.push_back( point );
192 }
193 // We do the calculation for lower_left_ and max_point even if we don't add the position, to keep the size of
194 // the layer consistent over processes.
195 for ( int d = 0; d < D; ++d )
196 {
197 if ( point[ d ] < this->lower_left_[ d ] )
198 {
199 this->lower_left_[ d ] = point[ d ];
200 }
201 if ( point[ d ] > max_point[ d ] )
202 {
203 max_point[ d ] = point[ d ];
204 }
205 }
206 }
207 assert( positions_.size() == num_local_nodes_ );
208 }
209 else
210 {
211 throw KernelException( "'positions' must be an array or a DimensionParameter." );
212 }
213 }
214 if ( d.known( names::extent ) )
215 {
216 this->extent_ = d.get< std::vector< double > >( names::extent );
217
218 Position< D > center = ( max_point + this->lower_left_ ) / 2;
219 auto lower_left_point = this->lower_left_; // save lower-left-most point
220 this->lower_left_ = center - this->extent_ / 2;
221
222 // check if all points are inside the specified layer extent
223 auto upper_right_limit = center + this->extent_ / 2;
224 for ( int d = 0; d < D; ++d )
225 {
226 if ( lower_left_point[ d ] < this->lower_left_[ d ] or max_point[ d ] > upper_right_limit[ d ] )
227 {
228 throw BadProperty( "Node position outside of layer" );
229 }
230 }
231 }
232 else
233 {
234 if ( this->node_collection_->size() <= 1 )
235 {
236 throw KernelException( "If only one node is created, 'extent' must be specified." );
237 }
238
239 const auto positional_extent = max_point - this->lower_left_;
240 const auto center = ( max_point + this->lower_left_ ) / 2;
241 for ( int d = 0; d < D; ++d )
242 {
243 // Set extent to be extent of the points, rounded up in each dimension.
244 this->extent_[ d ] = std::ceil( positional_extent[ d ] );
245 }
246
247 // Adjust lower_left relative to the rounded center with the rounded up extent.
248 this->lower_left_ = center - this->extent_ / 2;
249 }
250}
251
252template < int D >
253void
255{
256 Layer< D >::get_status( d, nc );
257
258 std::vector< std::vector< double > > points;
259
260 if ( not nc )
261 {
262 // This is needed by NodeCollectionMetadata::operator==() which does not have access to the node collection
263 for ( const auto& pos : positions_ )
264 {
265 points.emplace_back( pos.get_vector() );
266 }
267 }
268 else
269 {
270 // Selecting the right positions
271 // - Coordinates for all nodes in the underlying primitive node collection
272 // which belong to this rank are stored in positions_
273 // - nc has information on which nodes actually belong to it, especially
274 // important for sliced collections with step > 1.
275 // - Use the rank-local iterator over the node collection to pick the right
276 // nodes, then step in lockstep through the positions_ array.
277 auto nc_it = nc->rank_local_begin();
278 const auto nc_end = nc->end();
279 if ( nc_it < nc_end )
280 {
281 // Node index in node collection is global to NEST, so we need to scale down
282 // to get right indices into positions_, which has only rank-local data.
283 const size_t n_procs = kernel().mpi_manager.get_num_processes();
284 size_t pos_idx = ( *nc_it ).nc_index / n_procs;
285 const size_t step = nc_it.get_step_size() / n_procs;
286
287 for ( ; nc_it < nc->end(); pos_idx += step, ++nc_it )
288 {
289 points.emplace_back( positions_.at( pos_idx ).get_vector() );
290 }
291 }
292 }
293
294 d[ names::positions ] = points;
295}
296
297template < int D >
300{
301 return positions_.at( lid_to_position_id_( lid ) );
302}
303
304template < int D >
305template < class Ins >
306void
308{
309 // This array will be filled with node ID,pos_x,pos_y[,pos_z] for local nodes:
310 std::vector< double > local_node_id_pos;
311
312 // If the NodeCollection has proxies, nodes and positions are distributed over MPI processes,
313 // and we must iterate only the local nodes. If not, all nodes and positions are on all MPI processes.
314 // All models in a layer are the same, so if has_proxies() for the NodeCollection returns true, we
315 // know that all nodes in the NodeCollection have proxies. Likewise, if it returns false we know that
316 // no nodes have proxies.
318 node_collection->has_proxies() ? node_collection->rank_local_begin() : node_collection->begin();
319 NodeCollection::const_iterator nc_end = node_collection->end();
320
321 // Reserve capacity in the vector based on number of local nodes. If the NodeCollection is sliced,
322 // it may need less than the reserved capacity.
323 local_node_id_pos.reserve( ( D + 1 ) * num_local_nodes_ );
324 for ( NodeCollection::const_iterator nc_it = nc_begin; nc_it < nc_end; ++nc_it )
325 {
326 // Push node ID into array to communicate
327 local_node_id_pos.push_back( ( *nc_it ).node_id );
328 // Push coordinates one by one
329 const auto pos = get_position( ( *nc_it ).nc_index );
330 for ( int j = 0; j < D; ++j )
331 {
332 local_node_id_pos.push_back( pos[ j ] );
333 }
334 }
335
336 // This array will be filled with node ID,pos_x,pos_y[,pos_z] for global nodes:
337 std::vector< double > global_node_id_pos;
338 std::vector< int > displacements;
339 kernel().mpi_manager.communicate( local_node_id_pos, global_node_id_pos, displacements );
340
341 // To avoid copying the vector one extra time in order to sort, we
342 // sneakishly use reinterpret_cast
343 NodePositionData* pos_ptr;
344 NodePositionData* pos_end;
345 pos_ptr = reinterpret_cast< NodePositionData* >( &global_node_id_pos[ 0 ] );
346 pos_end = pos_ptr + global_node_id_pos.size() / ( D + 1 );
347
348 // Get rid of any multiple entries
349 std::sort( pos_ptr, pos_end );
350 pos_end = std::unique( pos_ptr, pos_end );
351
352 // Unpack node IDs and coordinates
353 for ( ; pos_ptr < pos_end; pos_ptr++ )
354 {
355 *iter++ = std::pair< Position< D >, size_t >( pos_ptr->get_position(), pos_ptr->get_node_id() );
356 }
357}
358
359template < int D >
360void
362{
363
364 communicate_positions_( std::back_inserter( tree ), node_collection );
365}
366
367// Helper function to compare node IDs used for sorting (Position,node ID) pairs
368template < int D >
369static bool
370node_id_less( const std::pair< Position< D >, size_t >& a, const std::pair< Position< D >, size_t >& b )
371{
372 return a.second < b.second;
373}
374
375template < int D >
376void
377FreeLayer< D >::insert_global_positions_vector_( std::vector< std::pair< Position< D >, size_t > >& vec,
378 NodeCollectionPTR node_collection )
379{
380
381 communicate_positions_( std::back_inserter( vec ), node_collection );
382
383 // Sort vector to ensure consistent results
384 std::sort( vec.begin(), vec.end(), node_id_less< D > );
385}
386
387template < int D >
388size_t
390{
391 // If the NodeCollection has proxies, nodes and positions are distributed over MPI processes,
392 // and we must iterate only the local nodes. If not, all nodes and positions are on all MPI processes.
393 // All models in a layer are the same, so if has_proxies() for the NodeCollection returns true, we
394 // know that all nodes in the NodeCollection have proxies. Likewise, if it returns false we know that
395 // no nodes have proxies.
396 if ( not this->node_collection_->has_proxies() )
397 {
398 return lid;
399 }
400 else
401 {
402 const auto num_procs = kernel().mpi_manager.get_num_processes();
403 return lid / num_procs;
404 }
405}
406
407} // namespace nest
408
409#endif
Dictionary class for interface to Python and C++ API.
Definition dictionary.h:213
Exception to be thrown if a status parameter is incomplete or inconsistent.
Definition exceptions.h:680
Base class for RNG engine wrappers.
Definition random_generators.h:67
Position-generating Parameter class.
Definition parameter.h:1155
Class to be used when communicating positions across MPI processes.
Definition free_layer.h:83
size_t get_node_id() const
Definition free_layer.h:86
Position< D > get_position() const
Definition free_layer.h:91
bool operator<(const NodePositionData &other) const
Definition free_layer.h:96
bool operator==(const NodePositionData &other) const
Definition free_layer.h:101
double pos_[D]
Definition free_layer.h:108
double node_id_
Definition free_layer.h:107
Layer with free positioning of neurons, positions specified by user.
Definition free_layer.h:47
void insert_global_positions_ntree_(Ntree< D, size_t > &tree, NodeCollectionPTR node_collection) override
Insert global position info into ntree.
Definition free_layer.h:361
size_t lid_to_position_id_(size_t lid) const
Calculate the index in the position vector on this MPI process based on the local ID.
Definition free_layer.h:389
void get_status(Dictionary &, NodeCollection const *const) const override
Export properties of the layer by setting entries in the status dictionary, respects slicing of given...
Definition free_layer.h:254
void insert_global_positions_vector_(std::vector< std::pair< Position< D >, size_t > > &vec, NodeCollectionPTR node_collection) override
Insert global position info into vector.
Definition free_layer.h:377
std::vector< Position< D > > positions_
Vector of positions.
Definition free_layer.h:75
Position< D > get_position(size_t sind) const override
Get position of node.
Definition free_layer.h:299
void communicate_positions_(Ins iter, NodeCollectionPTR node_collection)
Communicate positions across MPI processes.
Definition free_layer.h:307
void set_status(const Dictionary &) override
Change properties of the layer according to the entries in the dictionary.
Definition free_layer.h:114
size_t num_local_nodes_
Definition free_layer.h:77
Base class for all Kernel exceptions.
Definition exceptions.h:65
Abstract base class for Layer of given dimension (D=2 or 3).
Definition layer.h:218
void set_status(const Dictionary &) override
Change properties of the layer according to the entries in the dictionary.
Definition layer_impl.h:80
void get_status(Dictionary &, NodeCollection const *const) const override
Retrieve status, slice according to node collection if given.
Definition layer_impl.h:93
size_t get_num_processes() const
Return the number of processes used during simulation.
Definition mpi_manager.h:492
void communicate(std::vector< size_t > &send_buffer, std::vector< size_t > &recv_buffer)
Definition mpi_manager.cpp:1085
Superclass for NodeCollections.
Definition node_collection.h:565
virtual const_iterator rank_local_begin(NodeCollectionPTR=NodeCollectionPTR(nullptr)) const =0
Method to get an iterator representing the beginning of the NodeCollection.
virtual const_iterator end(NodeCollectionPTR=NodeCollectionPTR(nullptr)) const =0
Method to get an iterator representing the end of the NodeCollection.
Represent single node entry in node collection.
Definition node_collection.h:92
Node * get_mpi_local_node_or_device_head(size_t)
Return pointer of Node on the thread we are on.
Definition node_manager.cpp:465
A Ntree object represents a subtree or leaf in a Ntree structure.
Definition ntree.h:55
Definition position.h:57
Iterator for NodeCollections.
Definition node_collection.h:415
NodeManager node_manager
Definition kernel_manager.h:245
MPIManager mpi_manager
Definition kernel_manager.h:233
const std::string positions("positions")
const std::string extent("extent")
Namespace for the NEST simulation kernel.
Definition beta_normalization_factor.h:33
RngPtr get_rank_synced_rng()
Definition kernel_manager.h:286
static bool node_id_less(const std::pair< Position< D >, size_t > &a, const std::pair< Position< D >, size_t > &b)
Definition free_layer.h:370
KernelManager & kernel()
Definition kernel_manager.h:311
std::shared_ptr< Parameter > ParameterPTR
Definition parameter.h:40
std::shared_ptr< NodeCollection > NodeCollectionPTR
Definition node_collection.h:50
std::vector< std::vector< double > > get_position(NodeCollectionPTR layer_nc)
Definition spatial.cpp:83