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

Dynamic properties for ParticleSlice #1160

Merged
merged 5 commits into from
May 11, 2017
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
2 changes: 1 addition & 1 deletion src/python/espressomd/particle_data.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ cdef class ParticleHandle(object):
cdef int update_particle_data(self) except -1


cdef class ParticleSlice:
cdef class _ParticleSliceImpl:

cdef particle particle_data
cdef int update_particle_data(self, id) except -1
Expand Down
280 changes: 60 additions & 220 deletions src/python/espressomd/particle_data.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -1148,10 +1148,11 @@ cdef class ParticleHandle:
setattr(self, k, P[k])


cdef class ParticleSlice:
cdef class _ParticleSliceImpl:
"""
Handles slice inputs e.g. part[0:2]. Sets values for selected slices or returns values as a single list.

This is the base class that should not be used directly as it lacks some properties. Use :class:`espressomd.ParticleSlice` instead.
"""

def __cinit__(self, slice_):
Expand All @@ -1178,232 +1179,20 @@ cdef class ParticleSlice:
else:
return 0

# Particle Type
property type:
"""
Particle type.

"""
def __get__(self):
type_list = []
for id in self.id_selection:
type_list.append(ParticleHandle(id).type)
return type_list

def __set__(self, _type_list):
if isinstance(_type_list, int):
for id in self.id_selection:
ParticleHandle(id).type = _type_list
return
if len(self.id_selection) != len(_type_list):
raise Exception("Input list size (%i) does not match slice size (%i)." % (
len(_type_list), len(self.id_selection)))
for i in range(len(self.id_selection)):
ParticleHandle(self.id_selection[i]).type = _type_list[i]

# Position
property pos:
"""
Particle position (not folded into central image).

"""
def __set__(self, _pos_array):
if len(self.id_selection) != len(_pos_array):
raise Exception("Input list size (%i) does not match slice size (%i)." % (
len(_pos_array), len(self.id_selection)))

cdef double mypos[3]
for i in range(len(_pos_array)):
ParticleHandle(self.id_selection[i]).pos = _pos_array[i]

def __get__(self):
pos_array = np.zeros((len(self.id_selection), 3))
for i in range(len(self.id_selection)):
pos_array[i, :] = ParticleHandle(self.id_selection[i]).pos
return pos_array

property pos_folded:
"""
Particle position (folded into central image).

"""

def __set__(self, d):
raise Exception("setting a folded position is not implemented.")

def __get__(self):
pos_array = np.zeros((len(self.id_selection), 3))
for i in range(len(self.id_selection)):
pos_array[i, :] = ParticleHandle(
self.id_selection[i]).pos_folded
return pos_array

# Velocity
property v:
"""
Particle velocity.

"""

def __set__(self, _v_array):
if len(np.array(_v_array).shape) == 1:
for id in self.id_selection:
ParticleHandle(id).v = _v_array
return

if len(self.id_selection) != len(_v_array):
raise Exception("Input list size (%i) does not match slice size (%i)." % (
len(_v_array), len(self.id_selection)))

for i in range(len(self.id_selection)):
ParticleHandle(self.id_selection[i]).v = _v_array[i]

def __get__(self):
v_array = np.zeros((len(self.id_selection), 3))
for i in range(len(self.id_selection)):
v_array[i, :] = ParticleHandle(self.id_selection[i]).v
return v_array

# Force
property f:
"""
Particle force.

"""

def __set__(self, _f_array):
if len(np.array(_f_array).shape) == 1:
for id in self.id_selection:
ParticleHandle(id).f = _f_array
return

if len(self.id_selection) != len(_f_array):
raise Exception("Input list size (%i) does not match slice size (%i)." % (
len(_f_array), len(self.id_selection)))
for i in range(len(_f_array)):
ParticleHandle(self.id_selection[i]).f = _f_array[i]

def __get__(self):
f_array = np.zeros((len(self.id_selection), 3))
for i in range(len(self.id_selection)):
f_array[i, :] = ParticleHandle(self.id_selection[i]).f
return f_array

property mass:
"""
Particle mass.

.. note::

If not set the particle mass is ``1`` in reduced units.

"""

def __set__(self, _mass_array):
IF MASS:
if isinstance(_mass_array, int) or isinstance(_mass_array, float):
for i in range(len(self.id_selection)):
ParticleHandle(self.id_selection[i]).mass = _mass_array
return
if len(self.id_selection) != len(_mass_array):
raise Exception("Input list size (%i) does not match slice size (%i)." % (
len(_mass_array), len(self.id_selection)))
for i in range(len(_mass_array)):
ParticleHandle(self.id_selection[i]).mass = _mass_array[i]
ELSE:
raise Exception("You are trying to set the particle mass \
but the mass feature is not compiled in.")

def __get__(self):
mass_array = np.zeros_like(self.id_selection)
for i in range(len(self.id_selection)):
mass_array[i] = ParticleHandle(self.id_selection[i]).mass
return mass_array

IF ELECTROSTATICS == 1:
property q:
"""
Particle charge.

"""

def __set__(self, _q_array):
if isinstance(_q_array, int) or isinstance(_q_array, float):
for i in range(len(self.id_selection)):
ParticleHandle(self.id_selection[i]).q = _q_array
return

if len(self.id_selection) != len(_q_array):
raise Exception("Input list size (%i) does not match slice size (%i)." % (
len(_q_array), len(self.id_selection)))
for i in range(len(self.id_selection)):
ParticleHandle(self.id_selection[i]).q = _q_array[i]

def __get__(self):
q_array = np.zeros_like(self.id_selection)
for i in range(len(self.id_selection)):
q_array[i] = ParticleHandle(self.id_selection[i]).q
return q_array

IF EXTERNAL_FORCES:
property ext_force:
"""
External force on a particle defined by a vector.

"""

def __set__(self, _ext_f_array):
if len(np.array(_ext_f_array).shape) == 1:
for i in range(len(self.id_selection)):
ParticleHandle(self.id_selection[
i]).ext_force = _ext_f_array
return

if len(self.id_selection) != len(_ext_f_array):
raise Exception("Input list size (%i) does not match slice size (%i)." % (
len(_ext_f_array), len(self.id_selection)))

for i in range(len(self.id_selection)):
ParticleHandle(self.id_selection[
i]).ext_force = _ext_f_array[i]

def __get__(self):
ext_f_array = np.zeros((len(self.id_selection), 3))
for i in range(len(self.id_selection)):
ext_f_array[i, :] = ParticleHandle(
self.id_selection[i]).ext_force

return ext_f_array

IF EXCLUSIONS:
property exclude:
"""
Exclude particle from interaction.

"""

def __set__(self, _partners):
if not isinstance(_partners, list):
raise Exception(
"List object expected for exclusion partners.")
if isinstance(_partners[0], list):
for i in range(len(self.id_selection)):
ParticleHandle(self.id_selection[
i]).exclude = _partners[i]
elif isinstance(_partners[0], int):
for i in range(len(self.id_selection)):
ParticleHandle(self.id_selection[
i]).exclude = _partners
else:
raise TypeError("Unexpected exclusion partner type.")

def __get__(self):
_exclude_array = []
for i in range(len(self.id_selection)):
_exclude_array.append(ParticleHandle(
self.id_selection[i]).exclude)
return _exclude_array

def add_exclusion(self, _partners):
self.exclude = _partners

Expand All @@ -1430,9 +1219,9 @@ cdef class ParticleSlice:
pl = ParticleList()
for i in self.id_selection:
if pl.exists(i):
res += str(pl[i]) + "\n"
# Remove final newline
return res[:-1]
res += str(pl[i]) + ", "
# Remove final comma
return "ParticleSlice([" + res[:-2] + "])"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for this awkward comma removal

pl = ParticleList()
return "ParticleSlice([" + ", ".join(str(pl[i]) for i in self.id_selection if pl.exists(i)) + "])"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's nicer, and it's more in line with how Python and Numpy print their own data types. Also, if I remove that space at the end, why not remove the trailing comma along with it.


def update(self, P):
if "id" in P:
Expand Down Expand Up @@ -1479,6 +1268,13 @@ cdef class ParticleSlice:
ParticleHandle(id).remove()


class ParticleSlice(_ParticleSliceImpl):
"""
Handles slice inputs e.g. part[0:2]. Sets values for selected slices or returns values as a single list.

"""
pass

cdef class ParticleList:
"""
Provides access to the particles via [i], where i is the particle id. Returns a ParticleHandle object.
Expand Down Expand Up @@ -1625,7 +1421,7 @@ cdef class ParticleList:
yield self[i]

def exists(self, idx):
if isinstance(idx, int):
if isinstance(idx, int) or issubclass(type(idx), np.integer):
return particle_exists(idx)
if isinstance(idx, slice) or isinstance(idx, tuple) or isinstance(idx, list) or isinstance(idx, np.ndarray):
tf_array = np.zeros(len(idx), dtype=np.bool)
Expand All @@ -1640,9 +1436,9 @@ cdef class ParticleList:
res = ""
for i in range(max_seen_particle + 1):
if self.exists(i):
res += str(self[i]) + "\n"
# Remove final newline
return res[:-1]
res += str(self[i]) + ", "
# Remove final comma
return "ParticleList([" + res[:-2] + "])"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for this awkward comma removal

return "ParticleList([" + ", ".join(str(self[i]) for i in range(max_seen_particle + 1) if self.exists(i)) + "])"


def writevtk(self, fname, types='all'):
"""
Expand Down Expand Up @@ -1721,3 +1517,47 @@ cdef class ParticleList:
if not (self.exists(i) and self.exists(j)):
continue
yield (self[i], self[j])

class _InjectParticleProperty:
def __init__(injector, attribute):
injector.attribute = attribute

def set(injector, particle_slice, values):
target = getattr(ParticleHandle(particle_slice.id_selection[0]), injector.attribute)
target_shape = np.shape(target)
N = len(particle_slice.id_selection)

if not target_shape: # scalar quantity
if not np.shape(values): # one value provided
for i in range(N):
setattr(ParticleHandle(particle_slice.id_selection[i]), injector.attribute, values)
elif np.shape(values)[0] == N: # one value for each particle provided
for i in range(N):
setattr(ParticleHandle(particle_slice.id_selection[i]), injector.attribute, values[i])
else:
raise Exception("Shape of value (%s) does not broadcast to shape of attribute (%s)." % (
np.shape(values), target_shape))
return

if target_shape == np.shape(values): # one value provided
for i in range(N):
setattr(ParticleHandle(particle_slice.id_selection[i]), injector.attribute, values)
elif target_shape == tuple(np.shape(values)[1:]) and np.shape(values)[0] == N: # one value for each particle provided
for i in range(N):
setattr(ParticleHandle(particle_slice.id_selection[i]), injector.attribute, values[i])
else:
raise Exception("Shape of value (%s) does not broadcast to shape of attribute (%s)." % (
np.shape(values), target_shape))

def get(injector, particle_slice):
values = []
for i in particle_slice.id_selection:
values.append(getattr(ParticleHandle(i), injector.attribute))
return np.array(values)

# auto-add missing particle properties to ParticleSlice
for attribute in particle_attributes:
if not attribute in dir(ParticleSlice):
ipp = _InjectParticleProperty(attribute)
new_property = property(ipp.get, ipp.set, doc=getattr(ParticleHandle, attribute).__doc__)
setattr(ParticleSlice, attribute, new_property)
4 changes: 2 additions & 2 deletions src/python/espressomd/utils.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ cdef check_type_or_throw_except(x, n, t, msg):
if hasattr(x, "__getitem__"):
for i in range(len(x)):
if not isinstance(x[i], t):
if not (t == float and isinstance(x[i], int)):
if not (t == float and isinstance(x[i], int)) and not (t == int and issubclass(type(x[i]), np.integer)):
raise ValueError(
msg + " -- Item " + str(i) + " was of type " + type(x[i]).__name__)
else:
Expand All @@ -71,7 +71,7 @@ cdef check_type_or_throw_except(x, n, t, msg):
else:
# N=1 and a single value
if not isinstance(x, t):
if not (t == float and isinstance(x, int)):
if not (t == float and isinstance(x, int)) and not (t == int and issubclass(type(x), np.integer)):
raise ValueError(msg + " -- Got an " + type(x).__name__)


Expand Down
1 change: 1 addition & 0 deletions testsuite/python/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ set(py_tests bondedInteractions.py
observables.py
p3m_gpu.py
particle.py
particle_slice.py
rotational_inertia.py
lbgpu_remove_total_momentum.py
tabulated.py
Expand Down
Loading