Skip to content

Commit 8c8f7df

Browse files
committed
Support sending empty lists from Python to NEST
1 parent 16a66fc commit 8c8f7df

File tree

2 files changed

+77
-6
lines changed

2 files changed

+77
-6
lines changed

libnestutil/dictionary.h

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ class dictionary : public std::map< std::string, DictEntry_ >
9595
using maptype_::maptype_; // Inherit constructors
9696

9797
/**
98-
* @brief Cast the specified value to the specified type.
98+
* @brief Cast the specified non-vector value to the specified type.
9999
*
100100
* @tparam T Type of element. If the value is not of the specified type, a TypeMismatch error is thrown.
101101
* @param value the any object to cast.
@@ -119,6 +119,44 @@ class dictionary : public std::map< std::string, DictEntry_ >
119119
}
120120
}
121121

122+
/**
123+
* @brief Cast the specified vector value to the specified type.
124+
*
125+
* @tparam T Type of vector element. If the value is not of the specified type, a TypeMismatch error is thrown.
126+
* @param value the any object to cast.
127+
* @param key key where the value is located in the dictionary, for information upon cast errors.
128+
* @throws TypeMismatch if the value is not of specified type T.
129+
* @return value cast to the specified type.
130+
*
131+
* @note A dedicated cast_vector_value_() allows handling of empty vectors passed from the Python level.
132+
*/
133+
template < typename T >
134+
std::vector< T >
135+
cast_vector_value_( const boost::any& value, const std::string& key ) const
136+
{
137+
// PyNEST passes vector with element type any if and only if it needs to pass
138+
// and empty vector, because the element type of empty lists cannot be inferred
139+
// at the Python level. The assertion just double-checks that we never get a
140+
// non-empty vector-of-any.
141+
if ( value.type() == typeid( std::vector< boost::any > ) )
142+
{
143+
assert( boost::any_cast< std::vector< boost::any > >( value ).empty() );
144+
return std::vector< T >();
145+
}
146+
147+
// Now handle vectors with elements
148+
try
149+
{
150+
return boost::any_cast< std::vector< T > >( value );
151+
}
152+
catch ( const boost::bad_any_cast& )
153+
{
154+
std::string msg = std::string( "Failed to cast '" ) + key + "' from " + debug_type( value ) + " to type "
155+
+ std::string( boost::core::demangle( typeid( std::vector< T > ).name() ) );
156+
throw nest::TypeMismatch( msg );
157+
}
158+
}
159+
122160
/**
123161
* @brief Cast the specified value to an integer.
124162
*
@@ -180,7 +218,7 @@ class dictionary : public std::map< std::string, DictEntry_ >
180218
}
181219

182220
/**
183-
* @brief Update the specified value if there exists a value at key.
221+
* @brief Update the specified non-vector value if there exists a value at key.
184222
*
185223
* @param key key where the value may be located in the dictionary.
186224
* @param value object to update if there exists a value at key.
@@ -200,6 +238,29 @@ class dictionary : public std::map< std::string, DictEntry_ >
200238
return false;
201239
}
202240

241+
/**
242+
* @brief Update the specified vector value if there exists a value at key.
243+
*
244+
* @param key key where the value may be located in the dictionary.
245+
* @param value object to update if there exists a value at key.
246+
* @throws TypeMismatch if the value at key is not the same type as the value argument.
247+
* @return Whether value was updated.
248+
*
249+
* @note The specialisation for values that are vectors allows handling of empty vectors passed from the Python level.
250+
*/
251+
template < typename T >
252+
bool
253+
update_value( const std::string& key, std::vector< T >& value ) const
254+
{
255+
auto it = find( key );
256+
if ( it != end() )
257+
{
258+
value = cast_vector_value_< T >( it->second.item, key );
259+
return true;
260+
}
261+
return false;
262+
}
263+
203264
/**
204265
* @brief Update the specified value if there exists an integer value at key.
205266
*

pynest/nestkernel_api.pyx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ cdef dictionary pydict_to_dictionary(object py_dict) except *: # Adding "except
221221
for key, value in py_dict.items():
222222
if type(value) is tuple:
223223
value = list(value)
224+
224225
if type(value) is int or isinstance(value, numpy.integer):
225226
# PYTEST-NG: Should we guard against overflow given that python int has infinite range?
226227
cdict[pystr_to_string(key)] = <long>value
@@ -230,6 +231,11 @@ cdef dictionary pydict_to_dictionary(object py_dict) except *: # Adding "except
230231
cdict[pystr_to_string(key)] = <cbool>value
231232
elif type(value) is str:
232233
cdict[pystr_to_string(key)] = <string>pystr_to_string(value)
234+
elif type(value) is list and len(value) == 0:
235+
# We cannot infer the intended element type from an empty list.
236+
# We therefore pass an empty vector[any]. vector[any] will always be empty
237+
# and an empty vector will always be vector[any] in the PyNEST interface.
238+
cdict[pystr_to_string(key)] = empty_any_vec()
233239
elif is_list_tuple_ndarray_of_float(value):
234240
cdict[pystr_to_string(key)] = pylist_or_ndarray_to_doublevec(value)
235241
elif is_list_tuple_ndarray_of_int(value):
@@ -253,11 +259,10 @@ cdef dictionary pydict_to_dictionary(object py_dict) except *: # Adding "except
253259
else:
254260
typename = type(value)
255261
if type(value) is list:
256-
if len(value) > 0:
257-
typename = f"list of {type(value[0])}"
258-
else:
259-
typename = f"empty list (for which the element type cannot be determined)"
262+
assert len(value) > 0 # empty list should have been caught above
263+
typename = f"list of {type(value[0])}"
260264
raise AttributeError(f'when converting Python dictionary: value of key ({key}) is not a known type, got {typename}')
265+
261266
return cdict
262267

263268

@@ -270,6 +275,11 @@ cdef object vec_of_dict_to_list(vector[dictionary] cvec):
270275
return tmp
271276

272277

278+
cdef vector[any] empty_any_vec():
279+
cdef vector[any] empty_vec
280+
return empty_vec
281+
282+
273283
cdef vector[dictionary] list_of_dict_to_vec(object pylist):
274284
cdef vector[dictionary] vec
275285
# PYNEST-NG: reserve the correct size and use index-based

0 commit comments

Comments
 (0)