Skip to content

Commit

Permalink
enabled nested with of a sile and changed to SileBinder
Browse files Browse the repository at this point in the history
The sile_read_multiple has been deleted, now the way to
do it is SileBinder.
This is more versatile and allows better handling.
It also patches the documentation.

This changes the behaviour of all files using this.
By default a read_* with the SileBinder will
only read the next entry, however to read
multiple one should use read_*[...] for slicing
the data.
The bound method will try and be smart about only reading
the needed elements, there is a bit of overhead, but
largely it should be fast.

All tests are functional again.

Signed-off-by: Nick Papior <[email protected]>
  • Loading branch information
zerothi committed Jun 16, 2023
1 parent 1512eee commit 2182392
Show file tree
Hide file tree
Showing 10 changed files with 144 additions and 234 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ we hit release version 1.0.0.
## [X.Y.Z] - YYYY-MM-DD

### Added
- slicing io files multiple output (still WIP), see #584 for details
Intention is to have all methods use this method for returning
multiple values, it should streamline the API.
- allowed xyz files to read Origin entries in the comment field
- allowed sile specifiers to be more explicit:
- "hello.xyz{contains=<name>}" equivalent to "hello.xyz{<name>}"
Expand Down
81 changes: 69 additions & 12 deletions src/sisl/io/_multiple.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
import functools
from functools import (
update_wrapper,
reduce
)
from textwrap import dedent
from typing import Any, Callable, Optional, Type
from numbers import Integral

Func = Callable[..., Optional[Any]]


class SileSlicer:
""" Handling io-methods in sliced behaviour for multiple returns
Expand All @@ -19,44 +24,91 @@ class SileSlicer:
"""
def __init__(self,
obj: Type[Any],
func: Callable[..., Optional[Any]],
func: Func,
key: Type[Any],
*,
skip_func: Optional[Func]=None,
postprocess: Optional[Callable[..., Any]]=None):
# this makes it work like a function bound to an instance (func.__self__
# works for instances)
self.__self__ = obj
self.__func__ = func
self.key = key
if skip_func is None:
self.skip_func = func
else:
self.skip_func = skip_func
self.postprocess = postprocess
# this is already sliced, sub-slicing shouldn't work (at least for now)
functools.update_wrapper(self, func)
update_wrapper(self, func)

def __call__(self, *args, **kwargs):
""" Defer call to the function """
# Now handle the arguments
obj = self.__self__
func = self.__func__
key = self.key
skip_func = self.skip_func

# quick return if no slicing
if key is None:
return func(obj, *args, **kwargs)

inf = 100000000000000

def check_none(r):
if isinstance(r, tuple):
return reduce(lambda x, y: x and y is None, r, True)
return r is None

# Determine whether we can reduce the call overheads
start = 0
stop = inf

if isinstance(key, Integral):
if key >= 0:
start = key
stop = key + 1
elif key.step is None or key.step > 0: # step size of 1
if key.start is not None:
start = key.start
if key.stop is not None:
stop = key.stop
elif key.step < 0:
if key.stop is not None:
start = key.stop
if key.start is not None:
stop = key.start

if start < 0:
start = 0
if stop < 0:
stop = inf
assert stop >= start

# collect returning values
retvals = []
retvals = [None] * start
append = retvals.append
with obj: # open sile
try:
# quick-skip using the skip-function
for _ in range(start):
skip_func(obj, *args, **kwargs)

# now do actual parsing
retval = func(obj, *args, **kwargs)
while retval is not None:
while not check_none(retval):
append(retval)
if len(retvals) >= stop:
# quick exit
break
retval = func(obj, *args, **kwargs)
except Exception:
except Exception as e:
print("something", e)
pass
finally:
if not retvals:
raise RuntimeError(f"{obj.__class__.__name__}.{self.func.__name__} could not read any entries?")
if len(retvals) == start:
raise RuntimeError(f"{obj.__class__.__name__}.{func.__name__} could not read any entries?")

# ensure the next call won't use this key
# This will prohibit the use
Expand Down Expand Up @@ -87,13 +139,16 @@ class SileBound:
def __init__(self,
obj: Type[Any],
func: Callable[..., Any],
*,
slicer: Type[SileSlicer]=SileSlicer,
default_slice: Optional[Any]=None,
**kwargs):
self.__self__ = obj
self.__func__ = func
self.slicer = slicer
self.default_slice = default_slice
self.kwargs = kwargs
functools.update_wrapper(self, func)
update_wrapper(self, func)
self._update_doc()

def _update_doc(self):
Expand Down Expand Up @@ -132,7 +187,9 @@ def _update_doc(self):
pass

def __call__(self, *args, **kwargs):
return self.__func__(self.__self__, *args, **kwargs)
if self.default_slice is None:
return self.__func__(self.__self__, *args, **kwargs)
return self[self.default_slice](*args, **kwargs)

def __getitem__(self, key):
return self.slicer(
Expand Down Expand Up @@ -163,13 +220,13 @@ def __init__(self, **kwargs):
def __call__(self, func):
self.__func__ = func
# update doc str etc.
functools.update_wrapper(self, func)
update_wrapper(self, func)
return self

def __get__(self, obj, objtype=None):
func = self.__func__
if obj is None:
raise TypeError(f"{objtype.__name__}.{func.__name__} missing (at least) 1 required positional argument: 'self'")
raise TypeError(f"[{self.__class__.__name__}]{objtype.__name__}.{func.__name__} missing (at least) 1 required positional argument: 'self'")
bound = SileBound(
obj=obj,
func=func,
Expand Down
4 changes: 1 addition & 3 deletions src/sisl/io/openmx/md.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@

@set_module("sisl.io.openmx")
class mdSileOpenMX(xyzSile, SileOpenMX):

def read_geometry(self, *args, all=True, **kwargs):
return super().read_geometry(*args, all=all, **kwargs)
pass


add_sile('md', mdSileOpenMX, gzip=True)
4 changes: 1 addition & 3 deletions src/sisl/io/siesta/ani.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@

@set_module("sisl.io.siesta")
class aniSileSiesta(xyzSile, SileSiesta):

def read_geometry(self, *args, all=True, **kwargs):
return super().read_geometry(*args, all=all, **kwargs)
pass


add_sile('ANI', aniSileSiesta, gzip=True)
20 changes: 10 additions & 10 deletions src/sisl/io/siesta/tests/test_ani.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,41 +32,41 @@ def test_ani(sisl_tmp):
C 3.00000 0.00000000 0.00000000
""")
a = aniSileSiesta(f)
g = a.read_geometry()
g = a.read_geometry[:]()
assert isinstance(g, GeometryCollection)
assert len(g) == 4
assert g[0].na == 1

g = a.read_geometry(start=1)
g = a.read_geometry[1:]()
assert len(g) == 3
assert g[0].na == 2

g = a.read_geometry(all=True)
g = a.read_geometry[:]()
assert len(g) == 4
assert g[0].na == 1 and g[2].na == 3

g = a.read_geometry(start=1, step=1)
g = a.read_geometry[1::1]()
assert len(g) == 3
assert g[0].na == 2 and g[1].na == 3

g = a.read_geometry(step=2)
g = a.read_geometry[::2]()
assert len(g) == 2
assert g[0].na == 1 and g[1].na == 3

g = a.read_geometry(stop=2, step=1)
g = a.read_geometry[:2:1]()
assert len(g) == 2
assert g[0].na == 1 and g[1].na == 2

g = a.read_geometry(start=1, step=None)
g = a.read_geometry[1:]()
assert g[0].na == 2 and len(g) == 3

g = a.read_geometry(start=1, all=False)
g = a.read_geometry[1]()
assert g.na == 2

g = a.read_geometry(start=1, stop=3, step=1)
g = a.read_geometry[1:3:1]()
assert g[1].na == 3 and len(g) == 2

g = a.read_geometry(start=1, stop=3, step=2, all=True)
g = a.read_geometry[1:3:2]()
assert g[0].na == 2 and len(g) == 1

g = a.read_geometry(lattice=None, atoms=None)
Loading

0 comments on commit 2182392

Please sign in to comment.