Skip to content

Commit 418e428

Browse files
committed
Merge branch 'fix-linked-atom-attributes'
* avoid creating linked array values in separate Atom objects. * preserve type when setting linked structure attribute to scalar. * use napoleon format for `_linkAtomAttribute` docstring. Resolve #25.
2 parents b260d60 + 11abc67 commit 418e428

File tree

2 files changed

+42
-9
lines changed

2 files changed

+42
-9
lines changed

src/diffpy/structure/tests/teststructure.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,14 @@ def test_xyz(self):
474474
stru.xyz += 0.1
475475
self.assertTrue(numpy.array_equal([0.1, 0.1, 0.1], stru[0].xyz))
476476
self.assertTrue(numpy.array_equal([1.1, 1.1, 1.1], stru[1].xyz))
477+
stru.xyz = 0
478+
stru[1].xyz[:] = 1
479+
self.assertTrue(numpy.array_equal([0, 0, 0], stru[0].xyz))
480+
self.assertTrue(numpy.array_equal([1, 1, 1], stru[1].xyz))
481+
# verify noop when changing empty slice
482+
xyz0 = numpy.copy(stru.xyz)
483+
stru[1:1].xyz += 1
484+
self.assertTrue(numpy.array_equal(xyz0, stru.xyz))
477485
return
478486

479487

@@ -535,6 +543,8 @@ def test_occupancy(self):
535543
self.assertTrue(numpy.array_equal(numpy.ones(4), cdse.occupancy))
536544
self.stru.occupancy *= 0.5
537545
self.assertEqual(1.0, sum([a.occupancy for a in self.stru]))
546+
cdse.occupancy = 1
547+
self.assertTrue(all(isinstance(a.occupancy, int) for a in cdse))
538548
return
539549

540550

@@ -589,6 +599,9 @@ def test_U(self):
589599
stru.U = 0
590600
self.assertTrue(numpy.all(stru.anisotropy))
591601
self.assertFalse(numpy.any(stru.U != 0.0))
602+
stru[1].U[:] = 1
603+
self.assertTrue(numpy.all(stru[0].U == 0.0))
604+
self.assertTrue(numpy.all(stru[1].U == 1.0))
592605
return
593606

594607

src/diffpy/structure/utils.py

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,25 +56,45 @@ def atomBareSymbol(smbl):
5656

5757
def _linkAtomAttribute(attrname, doc, toarray=numpy.array):
5858
'''Create property wrapper that maps the specified atom attribute.
59+
5960
The returned property object provides convenient access to atom
6061
attributes from the owner Structure class.
6162
62-
attrname -- string name of the Atom class attribute to be mapped
63-
doc -- docstring of the property wrapper
64-
toarray -- factory function that converts list of attributes to
65-
numpy.array. Use numpy.char.array for string attributes.
63+
Parameters
64+
----------
65+
attrname : str
66+
The string name of the Atom class attribute to be mapped.
67+
doc : str
68+
The docstring for the property wrapper.
69+
toarray : callable, optional
70+
Factory function that converts list of attributes to `numpy.ndarray`.
71+
Use `numpy.char.array` for string attributes.
6672
6773
Return a property object.
6874
'''
75+
from itertools import repeat
76+
from operator import setitem
77+
_all = slice(None)
6978
def fget(self):
7079
va = toarray([getattr(a, attrname) for a in self])
7180
return va
7281
def fset(self, value):
73-
if len(self) == 0: return
74-
# dummy array va helps to broadcast the value to proper iterable
75-
va = numpy.asarray(len(self) * [getattr(self[0], attrname)])
76-
for a, v in zip(self, numpy.broadcast_arrays(va, value)[1]):
77-
setattr(a, attrname, v)
82+
n = len(self)
83+
if n == 0:
84+
return
85+
v0 = getattr(self[0], attrname)
86+
# replace scalar values, but change array attributes in place
87+
if numpy.isscalar(v0):
88+
setvalue = lambda a, v: setattr(a, attrname, v)
89+
else:
90+
setvalue = lambda a, v: setitem(getattr(a, attrname), _all, v)
91+
# avoid broadcasting if the new value is a scalar
92+
if numpy.isscalar(value):
93+
genvalues = repeat(value)
94+
else:
95+
genvalues = numpy.broadcast_to(value, (n,) + numpy.shape(v0))
96+
for a, v in zip(self, genvalues):
97+
setvalue(a, v)
7898
return
7999
rv = property(fget, fset, doc=doc)
80100
return rv

0 commit comments

Comments
 (0)