Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Parameter objects for indegree and outdegree in syn_specs #2501

Merged
merged 6 commits into from
Oct 31, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 18 additions & 8 deletions nestkernel/connection_creator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,33 @@ ConnectionCreator::ConnectionCreator( DictionaryDatum dict )
, delay_()
{
Name connection_type;
long number_of_connections( -1 ); // overwritten by dict entry

updateValue< std::string >( dict, names::connection_type, connection_type );
updateValue< bool >( dict, names::allow_autapses, allow_autapses_ );
updateValue< bool >( dict, names::allow_multapses, allow_multapses_ );
updateValue< bool >( dict, names::allow_oversized_mask, allow_oversized_ );

// Need to store number of connections in a temporary variable to be able to detect negative values.
if ( updateValue< long >( dict, names::number_of_connections, number_of_connections ) )

if ( dict->known( names::number_of_connections ) )
{
if ( number_of_connections < 0 )
ParameterDatum* pd = dynamic_cast< ParameterDatum* >( ( *dict )[ names::number_of_connections ].datum() );
if ( pd )
{
throw BadProperty( "Number of connections cannot be less than zero." );
number_of_connections_ = *pd;
}
else
{
// Assume indegree is a scalar.
const long value = ( *dict )[ names::number_of_connections ];
if ( value < 0 )
{
throw BadProperty( "Number of connections cannot be less than zero." );
}
number_of_connections_ = std::shared_ptr< Parameter >( new ConstantParameter( value ) );
}
// We are sure that number of connections isn't negative, so it is safe to store it in a size_t.
number_of_connections_ = number_of_connections;
}

if ( dict->known( names::mask ) )
{
mask_ = NestModule::create_mask( ( *dict )[ names::mask ] );
Expand Down Expand Up @@ -116,7 +126,7 @@ ConnectionCreator::ConnectionCreator( DictionaryDatum dict )
if ( connection_type == names::pairwise_bernoulli_on_source )
{

if ( number_of_connections >= 0 )
if ( dict->known( names::number_of_connections ) )
{
type_ = Fixed_indegree;
}
Expand All @@ -128,7 +138,7 @@ ConnectionCreator::ConnectionCreator( DictionaryDatum dict )
else if ( connection_type == names::pairwise_bernoulli_on_target )
{

if ( number_of_connections >= 0 )
if ( dict->known( names::number_of_connections ) )
{
type_ = Fixed_outdegree;
}
Expand Down
2 changes: 1 addition & 1 deletion nestkernel/connection_creator.h
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ class ConnectionCreator
bool allow_autapses_;
bool allow_multapses_;
bool allow_oversized_;
index number_of_connections_;
std::shared_ptr< Parameter > number_of_connections_;
std::shared_ptr< AbstractMask > mask_;
std::shared_ptr< Parameter > kernel_;
std::vector< index > synapse_model_;
Expand Down
48 changes: 23 additions & 25 deletions nestkernel/connection_creator_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -344,11 +344,6 @@ ConnectionCreator::fixed_indegree_( Layer< D >& source,
Layer< D >& target,
NodeCollectionPTR target_nc )
{
if ( number_of_connections_ < 1 )
{
return;
}

// fixed_indegree connections (fixed fan in)
//
// For each local target node:
Expand Down Expand Up @@ -398,6 +393,9 @@ ConnectionCreator::fixed_indegree_( Layer< D >& source,
std::vector< double > source_pos_vector( D );
const std::vector< double > target_pos_vector = target_pos.get_vector();

const unsigned long target_number_connections =
std::round( number_of_connections_->value( rng, source_pos_vector, target_pos_vector, source, tgt ) );

// Get (position,node ID) pairs for sources inside mask
positions.resize( std::distance( masked_source.begin( target_pos ), masked_source_end ) );
std::copy( masked_source.begin( target_pos ), masked_source_end, positions.begin() );
Expand All @@ -423,7 +421,7 @@ ConnectionCreator::fixed_indegree_( Layer< D >& source,

if ( positions.empty()
or ( not allow_autapses_ and ( positions.size() == 1 ) and positions[ 0 ].second == target_id )
or ( not allow_multapses_ and ( positions.size() < number_of_connections_ ) ) )
or ( not allow_multapses_ and ( positions.size() < target_number_connections ) ) )
{
std::string msg = String::compose( "Global target ID %1: Not enough sources found inside mask", target_id );
throw KernelException( msg.c_str() );
Expand All @@ -439,8 +437,8 @@ ConnectionCreator::fixed_indegree_( Layer< D >& source,
// sources have been selected already.
std::vector< bool > is_selected( positions.size() );

// Draw `number_of_connections_` sources
for ( int i = 0; i < ( int ) number_of_connections_; ++i )
// Draw `target_number_connections` sources
for ( int i = 0; i < ( int ) target_number_connections; ++i )
{
index random_id = lottery( rng );
if ( not allow_multapses_ and is_selected[ random_id ] )
Expand Down Expand Up @@ -474,7 +472,7 @@ ConnectionCreator::fixed_indegree_( Layer< D >& source,

if ( positions.empty()
or ( not allow_autapses_ and ( positions.size() == 1 ) and positions[ 0 ].second == target_id )
or ( not allow_multapses_ and ( positions.size() < number_of_connections_ ) ) )
or ( not allow_multapses_ and ( positions.size() < target_number_connections ) ) )
{
std::string msg = String::compose( "Global target ID %1: Not enough sources found inside mask", target_id );
throw KernelException( msg.c_str() );
Expand All @@ -484,8 +482,8 @@ ConnectionCreator::fixed_indegree_( Layer< D >& source,
// sources have been selected already.
std::vector< bool > is_selected( positions.size() );

// Draw `number_of_connections_` sources
for ( int i = 0; i < ( int ) number_of_connections_; ++i )
// Draw `target_number_connections` sources
for ( int i = 0; i < ( int ) target_number_connections; ++i )
{
index random_id = rng->ulrand( positions.size() );
if ( not allow_multapses_ and is_selected[ random_id ] )
Expand Down Expand Up @@ -523,18 +521,20 @@ ConnectionCreator::fixed_indegree_( Layer< D >& source,
RngPtr rng = get_vp_specific_rng( target_thread );
Position< D > target_pos = target.get_position( ( *tgt_it ).lid );

const unsigned long target_number_connections = std::round( number_of_connections_->value( rng, tgt ) );

std::vector< double > source_pos_vector( D );
const std::vector< double > target_pos_vector = target_pos.get_vector();

if ( ( positions->size() == 0 )
or ( not allow_autapses_ and ( positions->size() == 1 ) and ( ( *positions )[ 0 ].second == target_id ) )
or ( not allow_multapses_ and ( positions->size() < number_of_connections_ ) ) )
or ( not allow_multapses_ and ( positions->size() < target_number_connections ) ) )
{
std::string msg = String::compose( "Global target ID %1: Not enough sources found", target_id );
throw KernelException( msg.c_str() );
}

// We will select `number_of_connections_` sources within the mask.
// We will select `target_number_connections` sources within the mask.
// If there is no kernel, we can just draw uniform random numbers,
// but with a kernel we have to set up a probability distribution
// function using a discrete_distribution.
Expand Down Expand Up @@ -563,8 +563,8 @@ ConnectionCreator::fixed_indegree_( Layer< D >& source,
// sources have been selected already.
std::vector< bool > is_selected( positions->size() );

// Draw `number_of_connections_` sources
for ( int i = 0; i < ( int ) number_of_connections_; ++i )
// Draw `target_number_connections` sources
for ( int i = 0; i < ( int ) target_number_connections; ++i )
{
index random_id = lottery( rng );
if ( not allow_multapses_ and is_selected[ random_id ] )
Expand Down Expand Up @@ -601,8 +601,8 @@ ConnectionCreator::fixed_indegree_( Layer< D >& source,
// sources have been selected already.
std::vector< bool > is_selected( positions->size() );

// Draw `number_of_connections_` sources
for ( int i = 0; i < ( int ) number_of_connections_; ++i )
// Draw `target_number_connections` sources
for ( int i = 0; i < ( int ) target_number_connections; ++i )
{
index random_id = rng->ulrand( positions->size() );
if ( not allow_multapses_ and is_selected[ random_id ] )
Expand Down Expand Up @@ -642,11 +642,6 @@ ConnectionCreator::fixed_outdegree_( Layer< D >& source,
Layer< D >& target,
NodeCollectionPTR target_nc )
{
if ( number_of_connections_ < 1 )
{
return;
}

// protect against connecting to devices without proxies
// we need to do this before creating the first connection to leave
// the network untouched if any target does not have proxies
Expand Down Expand Up @@ -689,6 +684,7 @@ ConnectionCreator::fixed_outdegree_( Layer< D >& source,
{
const Position< D > source_pos = source_pos_node_id_pair.first;
const index source_id = source_pos_node_id_pair.second;
const auto src = kernel().node_manager.get_node_or_proxy( source_id );
const std::vector< double > source_pos_vector = source_pos.get_vector();

// We create a target pos vector here that can be updated with the
Expand Down Expand Up @@ -718,8 +714,10 @@ ConnectionCreator::fixed_outdegree_( Layer< D >& source,
probabilities.resize( target_pos_node_id_pairs.size(), 1.0 );
}

const auto number_of_connections = std::round( number_of_connections_->value( grng, src ) );

if ( target_pos_node_id_pairs.empty()
or ( not allow_multapses_ and ( target_pos_node_id_pairs.size() < number_of_connections_ ) ) )
or ( not allow_multapses_ and ( target_pos_node_id_pairs.size() < number_of_connections ) ) )
{
std::string msg = String::compose( "Global source ID %1: Not enough targets found", source_id );
throw KernelException( msg.c_str() );
Expand All @@ -735,8 +733,8 @@ ConnectionCreator::fixed_outdegree_( Layer< D >& source,
// targets have been selected already.
std::vector< bool > is_selected( target_pos_node_id_pairs.size() );

// Draw `number_of_connections_` targets
for ( long i = 0; i < ( long ) number_of_connections_; ++i )
// Draw `number_of_connections` targets
for ( long i = 0; i < ( long ) number_of_connections; ++i )
{
index random_id = lottery( get_rank_synced_rng() );
if ( not allow_multapses_ and is_selected[ random_id ] )
Expand Down
16 changes: 16 additions & 0 deletions testsuite/pytests/test_spatial/test_connect_layers.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,22 @@ def test_connect_layers_indegree_kernel_mask(self):
}
self._check_connections(conn_spec, 20)

def test_connect_layers_indegree_parameter(self):
"""Connecting layers with fixed_indegree and parameter as indegree."""
# Parameter will give numbers close to 5. With std=0.1 they should all be rounded to an indegree of exactly 5.
mean = 5.
param = nest.random.normal(mean=mean, std=0.1)
conn_spec = {'rule': 'fixed_indegree', 'indegree': param, 'p': 1.0, 'allow_multapses': True}
self._check_connections(conn_spec, int(mean) * 20)

def test_connect_layers_outdegree_parameter(self):
"""Connecting layers with fixed_indegree and parameter as indegree."""
# Parameter will give numbers close to 5. With std=0.1 they should all be rounded to an outdegree of exactly 5.
mean = 5.
param = nest.random.normal(mean=mean, std=0.1)
conn_spec = {'rule': 'fixed_outdegree', 'outdegree': param, 'p': 1.0, 'allow_multapses': True}
self._check_connections(conn_spec, int(mean) * 20)

def test_connect_layers_outdegree_mask(self):
"""Connecting layers with fixed_outdegree and mask"""
conn_spec = {
Expand Down