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

preliminary test for 0-orbital Atoms #733

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
88 changes: 48 additions & 40 deletions src/sisl/_core/atom.py
Original file line number Diff line number Diff line change
Expand Up @@ -1076,7 +1076,7 @@ def __init__(
if isinstance(orbitals, (tuple, list, np.ndarray)):
if len(orbitals) == 0:
# This may be the same as only regarding `R` argument
pass
orbitals = None
elif isinstance(orbitals[0], Orbital):
# all is good
self._orbitals = orbitals
Expand Down Expand Up @@ -1105,7 +1105,7 @@ def __init__(
R = _a.asarrayd(kwargs["R"]).ravel()
self._orbitals = [Orbital(r) for r in R]
else:
self._orbitals = [Orbital(-1.0)]
self._orbitals = []

if mass is None:
self._mass = _ptbl.atomic_mass(mass_Z)
Expand Down Expand Up @@ -1247,39 +1247,40 @@ def __getattr__(self, attr):
----------
attr : str
"""

# First we create a list of values that the orbitals have
# Some may have it, others may not
vals = [None] * len(self.orbitals)
found = False
found = True
is_Integral = is_Real = is_callable = True
for io, orb in enumerate(self.orbitals):
try:
vals[io] = getattr(orb, attr)
found = True
is_callable &= callable(vals[io])
is_Integral &= isinstance(vals[io], Integral)
is_Real &= isinstance(vals[io], Real)
except AttributeError:
pass
found = False

if found == 0:
if not found:
# we never got any values, reraise the AttributeError
raise AttributeError(
f"'{self.__class__.__name__}.orbitals' objects has no attribute '{attr}'"
)

# Now parse the data, currently we'll only allow Integral, Real, Complex
if is_Integral:
if is_Integral and vals:
# we use and vals to ensure that emtpy arrays returns as reals
for io in range(len(vals)):
if vals[io] is None:
vals[io] = 0
return _a.arrayi(vals)

elif is_Real:
for io in range(len(vals)):
if vals[io] is None:
vals[io] = 0.0
return _a.arrayd(vals)

elif is_callable:

def _ret_none(*args, **kwargs):
Expand Down Expand Up @@ -1451,13 +1452,13 @@ class repeated `na` times.
# Using the slots should make this class slightly faster.
__slots__ = ("_atom", "_species", "_firsto")

def __init__(self, atoms: AtomsLike = "H", na: Optional[int] = None):
def __init__(self, atoms: Optional[AtomsLike] = None, na: Optional[int] = None):
# Default value of the atom object
if atoms is None:
atoms = Atom("H")
uatoms = []
species = []

# Correct the atoms input to Atom
if isinstance(atoms, Atom):
elif isinstance(atoms, Atom):
uatoms = [atoms]
species = [0]

Expand Down Expand Up @@ -1493,7 +1494,9 @@ def __init__(self, atoms: AtomsLike = "H", na: Optional[int] = None):
species.append(s)

else:
raise ValueError(f"atoms keyword type is not acceptable {type(atoms)}")
raise ValueError(
f"{self.__class__.__name__}(atoms=) keyword type is not acceptable {type(atoms)}"
)

# Default for number of atoms
if na is None:
Expand All @@ -1512,6 +1515,16 @@ def _update_orbitals(self):
uorbs = _a.arrayi([a.no for a in self.atom])
self._firsto = np.insert(_a.cumsumi(uorbs[self.species]), 0, 0)

def __str__(self):
"""Return the `Atoms` in str"""
s = f"{self.__class__.__name__}{{species: {len(self._atom)},\n"
for a, idx in self.iter(True):
s += " {1}: {0},\n".format(len(idx), str(a).replace("\n", "\n "))
return f"{s}}}"

def __repr__(self):
return f"<{self.__module__}.{self.__class__.__name__} nspecies={len(self._atom)}, na={len(self)}, no={self.no}>"

@property
def atom(self):
"""List of unique atoms in this group of atoms"""
Expand Down Expand Up @@ -1539,6 +1552,15 @@ def specie(self):
"""List of atomic species"""
return self._species

def __len__(self):
"""Return number of atoms in the object"""
return len(self._species)

@property
def na(self):
"""Return number of atoms in the object (same as len)"""
return len(self.species)

@property
def no(self):
"""Total number of orbitals in this list of atoms"""
Expand Down Expand Up @@ -1566,6 +1588,18 @@ def q0(self):
q0 = _a.arrayd([a.q0.sum() for a in self.atom])
return q0[self.species]

@property
def mass(self):
"""Array of masses of the contained objects"""
umass = _a.arrayd([a.mass for a in self.atom])
return umass[self.species]

@property
def Z(self):
"""Array of atomic numbers"""
uZ = _a.arrayi([a.Z for a in self.atom])
return uZ[self.species]

def orbital(self, io):
"""Return an array of orbital of the contained objects"""
io = _a.asarrayi(io)
Expand Down Expand Up @@ -1594,18 +1628,6 @@ def maxR(self, all=False):
return maxR[self.species]
return np.amax([a.maxR() for a in self.atom])

@property
def mass(self):
"""Array of masses of the contained objects"""
umass = _a.arrayd([a.mass for a in self.atom])
return umass[self.species]

@property
def Z(self):
"""Array of atomic numbers"""
uZ = _a.arrayi([a.Z for a in self.atom])
return uZ[self.species]

def index(self, atom):
"""Return the indices of the atom object"""
return (self._species == self.species_index(atom)).nonzero()[0]
Expand Down Expand Up @@ -1774,7 +1796,7 @@ def swap_atom(self, a, b):
return atoms

def reverse(self, atoms=None):
"""Returns a reversed geometry
"""Returns a reversed atoms object

Also enables reversing a subset of the atoms.
"""
Expand All @@ -1786,20 +1808,6 @@ def reverse(self, atoms=None):
copy._update_orbitals()
return copy

def __str__(self):
"""Return the `Atoms` in str"""
s = f"{self.__class__.__name__}{{species: {len(self._atom)},\n"
for a, idx in self.iter(True):
s += " {1}: {0},\n".format(len(idx), str(a).replace("\n", "\n "))
return f"{s}}}"

def __repr__(self):
return f"<{self.__module__}.{self.__class__.__name__} nspecies={len(self._atom)}, na={len(self)}, no={self.no}>"

def __len__(self):
"""Return number of atoms in the object"""
return len(self._species)

def iter(self, species=False):
"""Loop on all atoms

Expand Down
8 changes: 4 additions & 4 deletions src/sisl/_core/sparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,10 +261,10 @@ def __init_shape(self, arg1, dim=1, dtype=None, nnzpr=20, nnz=None, **kwargs):

# unpack size and check the sizes are "physical"
M, N, K = arg1
if M <= 0 or N <= 0 or K <= 0:
if M < 0 or N < 0 or K < 0:
raise ValueError(
self.__class__.__name__
+ f" invalid size of sparse matrix, one of the dimensions is zero: M={M}, N={N}, K={K}"
f"{self.__class__.__name__} invalid size of sparse matrix. "
f"Must have finite (or zero) dimension."
)

# Store shape
Expand All @@ -278,7 +278,7 @@ def __init_shape(self, arg1, dim=1, dtype=None, nnzpr=20, nnz=None, **kwargs):
# number of non-zero elements is NOT given
nnz = M * nnzpr

else:
elif M > 0:
# number of non-zero elements is give AND larger
# than the provided non-zero elements per row
nnzpr = nnz // M
Expand Down
16 changes: 14 additions & 2 deletions src/sisl/_core/tests/test_atom.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,19 @@ def test_atom_simple(setup):
assert setup.Au == setup.Au.copy()


def test_atom_empty():
atom = Atom("C")
assert len(atom) == 0
assert atom.no == 0
for attr in ("orbitals", "R", "q0"):
assert len(getattr(atom, attr)) == 0
atoms = Atoms()
assert atoms.no == 0
assert atoms.nspecies == 0
for attr in ("lasto", "orbitals", "mass", "Z"):
assert getattr(atoms, attr).size == 0


def test_atom_ghost():
assert isinstance(Atom[1], Atom)
assert isinstance(Atom[-1], AtomGhost)
Expand Down Expand Up @@ -110,8 +123,7 @@ def test4(setup):


def test5(setup):
assert Atom(Z=1, mass=12).R < 0
assert Atom(Z=1, mass=12).R.size == 1
assert Atom(Z=1, mass=12).R.size == 0
assert Atom(Z=1, mass=12).mass == 12
assert Atom(Z=31, mass=12).mass == 12
assert Atom(Z=31, mass=12).Z == 31
Expand Down
2 changes: 1 addition & 1 deletion src/sisl/_core/tests/test_sparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def test_fail_init1():

def test_fail_init_shape0():
with pytest.raises(ValueError):
SparseCSR((0, 10, 10), dtype=np.int32)
SparseCSR((-1, 10, 10), dtype=np.int32)


def test_fail_init2():
Expand Down
4 changes: 2 additions & 2 deletions src/sisl/_core/tests/test_sparse_geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,8 @@ def test_sp_orb_remove(self, setup):
assert so.geometry.na - 1 == so2.geometry.na

def test_sp_orb_remove_atom(self):
so = SparseOrbital(Geometry([[0] * 3, [1] * 3], [Atom[1], Atom[2]], 2))
so2 = so.remove(Atom[1])
so = SparseOrbital(Geometry([[0] * 3, [1] * 3], [Atom(1, 1), Atom(2, 1)], 2))
so2 = so.remove(Atom(1, 1))
assert so.geometry.na - 1 == so2.geometry.na
assert so.geometry.no - 1 == so2.geometry.no

Expand Down
4 changes: 4 additions & 0 deletions src/sisl/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ def array_fill_repeat(array, size, axis=-1, cls=None):
to be an integer part of `size`.
"""
array = np.asarray(array, dtype=cls)
if size == 0 or array.size == 0:
if cls is None:
cls = array.dtype
return np.empty([0], dtype=cls)
try:
reps = size // array.shape[axis]
except Exception:
Expand Down
Loading