Statistiques
| Révision :

root / ase / utils / memory.py @ 1

Historique | Voir | Annoter | Télécharger (16,32 ko)

1 1 tkerber
import os
2 1 tkerber
import numpy as np
3 1 tkerber
4 1 tkerber
from UserDict import DictMixin
5 1 tkerber
6 1 tkerber
# -------------------------------------------------------------------
7 1 tkerber
8 1 tkerber
class MemoryBase(object, DictMixin):
9 1 tkerber
    """Virtual memory (VM) statistics of the current process
10 1 tkerber
    obtained from the relevant entries in /proc/<pid>/status:
11 1 tkerber
    VmPeak       Peak virtual memory size in bytes.
12 1 tkerber
    VmLck        ???
13 1 tkerber
    VmHWM        Peak resident set size ("high water mark") in bytes.
14 1 tkerber
    VmRSS        Resident memory usage in bytes.
15 1 tkerber
    VmSize       VM usage of the entire process in bytes.
16 1 tkerber
    VmData       VM usage of heap in bytes.
17 1 tkerber
    VmStk        VM usage of stack in bytes.
18 1 tkerber
    VmExe        VM usage of exe's and statically linked libraries in bytes.
19 1 tkerber
    VmLib        VM usage of dynamically linked libraries in bytes.
20 1 tkerber
    VmPTE        ???
21 1 tkerber

22 1 tkerber
    Note that VmSize > VmData + VmStk + VmExe + VmLib due to overhead.
23 1 tkerber
    """
24 1 tkerber
25 1 tkerber
    _scale = {'KB':1024.0, 'MB':1024.0**2}
26 1 tkerber
    _keys = ('VmPeak', 'VmLck', 'VmHWM', 'VmRSS', 'VmSize', 'VmData', \
27 1 tkerber
            'VmStk', 'VmExe', 'VmLib', 'VmPTE')
28 1 tkerber
29 1 tkerber
    def __init__(self, verbose=0):
30 1 tkerber
        self.verbose = verbose
31 1 tkerber
        if self.verbose>=2: print 'MemoryBase.__init__'
32 1 tkerber
        object.__init__(self)
33 1 tkerber
        self._values = np.empty(len(self._keys), dtype=np.float)
34 1 tkerber
35 1 tkerber
    def __repr__(self):
36 1 tkerber
        """Return a representation of recorded VM statistics.
37 1 tkerber
        x.__repr__() <==> repr(x)"""
38 1 tkerber
        if self.verbose>=2: print 'MemoryBase.__repr__'
39 1 tkerber
        s = object.__repr__(self)
40 1 tkerber
        w = max(map(len, self._keys))
41 1 tkerber
        unit = 'MB'
42 1 tkerber
        for k,v in self.iteritems():
43 1 tkerber
            res = '<N/A>'
44 1 tkerber
            if not np.isnan(v):
45 1 tkerber
                res = '%8.3f %s' % (v/self._scale[unit], unit)
46 1 tkerber
            s += '\n\t' + k.ljust(w) + ': ' + res.rjust(8)
47 1 tkerber
        return s
48 1 tkerber
49 1 tkerber
    def __len__(self):
50 1 tkerber
        """Number of VM keys which have not been outdated.
51 1 tkerber
        x.__len__() <==> len(x)"""
52 1 tkerber
        if self.verbose>=3: print 'MemoryBase.__len__'
53 1 tkerber
        return np.sum(~np.isnan(self._values))
54 1 tkerber
55 1 tkerber
    def __getitem__(self, key):
56 1 tkerber
        """Return floating point number associated with a VM key.
57 1 tkerber
        x.__getitem__(y) <==> x[y]"""
58 1 tkerber
        if self.verbose>=2: print 'MemoryBase.__getitem__'
59 1 tkerber
        if key not in self:
60 1 tkerber
            raise KeyError(key)
61 1 tkerber
        i = self.keys().index(key)
62 1 tkerber
        return self._values[i]
63 1 tkerber
64 1 tkerber
    def __setitem__(self, key, value):
65 1 tkerber
        """x.__setitem__(i, y) <==> x[i]=y"""
66 1 tkerber
        if self.verbose>=2: print 'MemoryBase.__setitem__'
67 1 tkerber
        raise Exception('Virtual member function.')
68 1 tkerber
69 1 tkerber
    def __delitem__(self, key):
70 1 tkerber
        """x.__delitem__(y) <==> del x[y]"""
71 1 tkerber
        if self.verbose>=2: print 'MemoryBase.__delitem__'
72 1 tkerber
        raise Exception('Virtual member function.')
73 1 tkerber
74 1 tkerber
    def clear(self):
75 1 tkerber
        """D.clear() -> None.  Remove all items from D."""
76 1 tkerber
        if self.verbose>=1: print 'MemoryBase.clear'
77 1 tkerber
        raise Exception('Virtual member function.')
78 1 tkerber
79 1 tkerber
    def update(self, other=None):
80 1 tkerber
        """D.update(E) -> None.  Update D from E: for k in E.keys(): D[k] = E[k]"""
81 1 tkerber
        if self.verbose>=1: print 'MemoryBase.update'
82 1 tkerber
        DictMixin.update(self, other)
83 1 tkerber
84 1 tkerber
    def copy(self):
85 1 tkerber
        """Return a shallow copy of a VM statistics instance.
86 1 tkerber
        D.copy() -> a shallow copy of D"""
87 1 tkerber
        if self.verbose>=1: print 'MemoryBase.copy'
88 1 tkerber
        res = object.__new__(self.__class__)
89 1 tkerber
        MemoryBase.__init__(res, self.verbose)
90 1 tkerber
        DictMixin.update(res, self)
91 1 tkerber
        return res
92 1 tkerber
93 1 tkerber
    def has_key(self, key): #necessary to avoid infinite recursion
94 1 tkerber
        """Return boolean to indicate whether key is a supported VM key.
95 1 tkerber
        D.has_key(k) -> True if D has a key k, else False"""
96 1 tkerber
        if self.verbose>=3: print 'MemoryBase.has_key'
97 1 tkerber
        return key in self._keys
98 1 tkerber
99 1 tkerber
    def keys(self):
100 1 tkerber
        """Return list of supported VM keys.
101 1 tkerber
        D.keys() -> list of D's keys"""
102 1 tkerber
        if self.verbose>=3: print 'MemoryBase.keys'
103 1 tkerber
        return list(self._keys)
104 1 tkerber
105 1 tkerber
    def values(self):
106 1 tkerber
        """Return list of recorded VM statistics.
107 1 tkerber
        D.values() -> list of D's values"""
108 1 tkerber
        if self.verbose>=3: print 'MemoryBase.values'
109 1 tkerber
        return list(self._values)
110 1 tkerber
111 1 tkerber
    def get(self, key, default=None):
112 1 tkerber
        """Return floating point number associated with a VM key.
113 1 tkerber
        D.get(k[,d]) -> D[k] if k in D, else d.  d defaults to None."""
114 1 tkerber
        if self.verbose>=1: print 'MemoryBase.get'
115 1 tkerber
        v = self[key]
116 1 tkerber
117 1 tkerber
        if type(default) in [int,float]:
118 1 tkerber
            default = np.float_(default)
119 1 tkerber
        if default is not None and not isinstance(default, np.floating):
120 1 tkerber
            raise ValueError('Default value must be a floating point number.')
121 1 tkerber
122 1 tkerber
        if default is not None and np.isnan(v):
123 1 tkerber
            return default
124 1 tkerber
        else:
125 1 tkerber
            return v
126 1 tkerber
127 1 tkerber
    def setdefault(self, key, default=None):
128 1 tkerber
        """Return floating point number associated with a VM key.
129 1 tkerber
        D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D"""
130 1 tkerber
        if self.verbose>=1: print 'MemoryBase.setdefault'
131 1 tkerber
        v = self[key]
132 1 tkerber
133 1 tkerber
        if type(default) in [int,float]:
134 1 tkerber
            default = np.float_(default)
135 1 tkerber
        if default is not None and not isinstance(default, np.floating):
136 1 tkerber
            raise ValueError('Default value must be a floating point number.')
137 1 tkerber
138 1 tkerber
        if default is not None and np.isnan(v):
139 1 tkerber
            self[key] = default
140 1 tkerber
            return default
141 1 tkerber
        else:
142 1 tkerber
            return v
143 1 tkerber
144 1 tkerber
    def pop(self, key, default=None):
145 1 tkerber
        """Return floating point number for a VM key and mark it as outdated.
146 1 tkerber
        D.pop(k[,d]) -> v, remove specified key and return the corresponding value
147 1 tkerber
        If key is not found, d is returned if given, otherwise KeyError is raised"""
148 1 tkerber
149 1 tkerber
        if self.verbose>=1: print 'MemoryBase.pop'
150 1 tkerber
        v = self[key]
151 1 tkerber
152 1 tkerber
        if type(default) in [int,float]:
153 1 tkerber
            default = np.float_(default)
154 1 tkerber
        if default is not None and not isinstance(default, np.floating):
155 1 tkerber
            raise ValueError('Default value must be a floating point number.')
156 1 tkerber
157 1 tkerber
        if default is not None and np.isnan(v):
158 1 tkerber
            return default
159 1 tkerber
        else:
160 1 tkerber
            del self[key]
161 1 tkerber
            return v
162 1 tkerber
163 1 tkerber
    def popitem(self):
164 1 tkerber
        """Return floating point number for some not-yet outdated VM key.
165 1 tkerber
        D.popitem() -> (k, v), remove and return some (key, value) pair as a
166 1 tkerber
        2-tuple; but raise KeyError if D is empty"""
167 1 tkerber
        if self.verbose>=1: print 'MemoryBase.popitem'
168 1 tkerber
169 1 tkerber
        for k,v in self.iteritems():
170 1 tkerber
            if not np.isnan(v):
171 1 tkerber
                del self[k]
172 1 tkerber
                return (k,v)
173 1 tkerber
        raise KeyError
174 1 tkerber
175 1 tkerber
    def __add__(self, other):
176 1 tkerber
        """x.__add__(y) <==> x+y"""
177 1 tkerber
        if self.verbose>=1: print 'MemoryBase.__add__(%s,%s)' \
178 1 tkerber
            % (object.__repr__(self), object.__repr__(other))
179 1 tkerber
        res = self.copy()
180 1 tkerber
        if isinstance(other, MemoryBase):
181 1 tkerber
            res._values.__iadd__(other._values)
182 1 tkerber
        elif type(other) in [int,float]:
183 1 tkerber
            res._values.__iadd__(other)
184 1 tkerber
        else:
185 1 tkerber
            raise TypeError('Unsupported operand type')
186 1 tkerber
        return res
187 1 tkerber
188 1 tkerber
    def __sub__(self, other):
189 1 tkerber
        """x.__sub__(y) <==> x-y"""
190 1 tkerber
        if self.verbose>=1: print 'MemoryBase.__sub__(%s,%s)' \
191 1 tkerber
            % (object.__repr__(self), object.__repr__(other))
192 1 tkerber
        res = self.copy()
193 1 tkerber
        if isinstance(other, MemoryBase):
194 1 tkerber
            res._values.__isub__(other._values)
195 1 tkerber
        elif type(other) in [int,float]:
196 1 tkerber
            res._values.__isub__(other)
197 1 tkerber
        else:
198 1 tkerber
            raise TypeError('Unsupported operand type')
199 1 tkerber
        return res
200 1 tkerber
201 1 tkerber
    def __radd__(self, other):
202 1 tkerber
        """x.__radd__(y) <==> y+x"""
203 1 tkerber
        if self.verbose>=1: print 'MemoryBase.__radd__(%s,%s)' \
204 1 tkerber
            % (object.__repr__(self), object.__repr__(other))
205 1 tkerber
        res = self.copy()
206 1 tkerber
        if isinstance(other, MemoryBase):
207 1 tkerber
            res._values.__iadd__(other._values)
208 1 tkerber
        elif type(other) in [int,float]:
209 1 tkerber
            res._values.__iadd__(other)
210 1 tkerber
        else:
211 1 tkerber
            raise TypeError('Unsupported operand type')
212 1 tkerber
        return res
213 1 tkerber
214 1 tkerber
    def __rsub__(self, other):
215 1 tkerber
        """x.__rsub__(y) <==> y-x"""
216 1 tkerber
        if self.verbose>=1: print 'MemoryBase.__rsub__(%s,%s)' \
217 1 tkerber
            % (object.__repr__(self), object.__repr__(other))
218 1 tkerber
        res = self.copy()
219 1 tkerber
        res._values.__imul__(-1.0)
220 1 tkerber
        if isinstance(other, MemoryBase):
221 1 tkerber
            res._values.__iadd__(other._values)
222 1 tkerber
        elif type(other) in [int,float]:
223 1 tkerber
            res._values.__iadd__(other)
224 1 tkerber
        else:
225 1 tkerber
            raise TypeError('Unsupported operand type')
226 1 tkerber
        return res
227 1 tkerber
228 1 tkerber
# -------------------------------------------------------------------
229 1 tkerber
230 1 tkerber
class MemoryStatistics(MemoryBase):
231 1 tkerber
232 1 tkerber
    def __init__(self, verbose=0):
233 1 tkerber
        MemoryBase.__init__(self, verbose)
234 1 tkerber
        self.update()
235 1 tkerber
236 1 tkerber
    def __setitem__(self, key, value):
237 1 tkerber
        """Set VM key to a floating point number.
238 1 tkerber
        x.__setitem__(i, y) <==> x[i]=y"""
239 1 tkerber
        if self.verbose>=2: print 'MemoryStatistics.__setitem__'
240 1 tkerber
        if key not in self:
241 1 tkerber
            raise KeyError(key)
242 1 tkerber
        if type(value) in [int,float]:
243 1 tkerber
            value = np.float_(value)
244 1 tkerber
        if not isinstance(value, np.floating):
245 1 tkerber
            raise ValueError('Value must be a floating point number.')
246 1 tkerber
        i = self.keys().index(key)
247 1 tkerber
        self._values[i] = value
248 1 tkerber
249 1 tkerber
    def __delitem__(self, key):
250 1 tkerber
        """Mark a VK key as outdated.
251 1 tkerber
        x.__delitem__(y) <==> del x[y]"""
252 1 tkerber
        if self.verbose>=2: print 'MemoryStatistics.__delitem__'
253 1 tkerber
        if key not in self:
254 1 tkerber
            raise KeyError(key)
255 1 tkerber
        self[key] = np.nan
256 1 tkerber
257 1 tkerber
    def clear(self):
258 1 tkerber
        """Mark all supported VM keys as outdated.
259 1 tkerber
        D.clear() -> None.  Remove all items from D."""
260 1 tkerber
        if self.verbose>=1: print 'MemoryStatistics.clear'
261 1 tkerber
        self._values[:] = np.nan
262 1 tkerber
263 1 tkerber
    def refresh(self):
264 1 tkerber
        """Refresh all outdated VM keys by reading /proc/<pid>/status."""
265 1 tkerber
        if self.verbose>=1: print 'MemoryBase.refresh'
266 1 tkerber
267 1 tkerber
        # NB: Linux /proc is for humans; Solaris /proc is for programs!
268 1 tkerber
        # TODO: Use pipe from 'prstat -p <pid>' or 'pmap -x <pid> 1 1'
269 1 tkerber
270 1 tkerber
        # Skip refresh if none are outdated (i.e. nan)
271 1 tkerber
        if not np.isnan(self._values).any():
272 1 tkerber
            if self.verbose>=2: print 'refresh: skipping...'
273 1 tkerber
            return
274 1 tkerber
275 1 tkerber
        try:
276 1 tkerber
            f = open('/proc/%d/status' % os.getpid(), 'r')
277 1 tkerber
            for line in f:
278 1 tkerber
                k, v = line.decode('ascii').split(':')
279 1 tkerber
280 1 tkerber
                # Only refresh supported keys that are outdated (i.e. nan)
281 1 tkerber
                if k in self and np.isnan(self[k]):
282 1 tkerber
                    t, s = v.strip().split(None, 1)
283 1 tkerber
                    if self.verbose >= 2:
284 1 tkerber
                        print 'refresh: k=%s, t=%s, s=%s' % (k, t, s)
285 1 tkerber
                    self[k] = float(t) * self._scale[s.upper()]
286 1 tkerber
287 1 tkerber
            f.close()
288 1 tkerber
        except (IOError, UnicodeError, ValueError):
289 1 tkerber
            # Reset on error
290 1 tkerber
            self.clear()
291 1 tkerber
292 1 tkerber
    def update(self, other=None):
293 1 tkerber
        """Update VM statistics from a supplied dict, else clear and refresh.
294 1 tkerber
        D.update(E) -> None.  Update D from E: for k in E.keys(): D[k] = E[k]"""
295 1 tkerber
        if self.verbose>=1: print 'MemoryStatistics.update'
296 1 tkerber
297 1 tkerber
        # Call to update without arguments has special meaning
298 1 tkerber
        if other is None:
299 1 tkerber
            self.clear()
300 1 tkerber
            self.refresh()
301 1 tkerber
        else:
302 1 tkerber
            MemoryBase.update(self, other)
303 1 tkerber
304 1 tkerber
    def __iadd__(self, other):
305 1 tkerber
        """x.__iadd__(y) <==> x+=y"""
306 1 tkerber
        if self.verbose>=1: print 'MemoryStatistics.__iadd__(%s,%s)' \
307 1 tkerber
            % (object.__repr__(self), object.__repr__(other))
308 1 tkerber
        if isinstance(other, MemoryBase):
309 1 tkerber
            self._values.__iadd__(other._values)
310 1 tkerber
        elif type(other) in [int,float]:
311 1 tkerber
            self._values.__iadd__(other)
312 1 tkerber
        else:
313 1 tkerber
            raise TypeError('Unsupported operand type')
314 1 tkerber
        return self
315 1 tkerber
316 1 tkerber
    def __isub__(self, other):
317 1 tkerber
        """x.__isub__(y) <==> x-=y"""
318 1 tkerber
        if self.verbose>=1: print 'MemoryStatistics.__isub__(%s,%s)' \
319 1 tkerber
            % (object.__repr__(self), object.__repr__(other))
320 1 tkerber
        if isinstance(other, MemoryBase):
321 1 tkerber
            self._values.__isub__(other._values)
322 1 tkerber
        elif type(other) in [int,float]:
323 1 tkerber
            self._values.__isub__(other)
324 1 tkerber
        else:
325 1 tkerber
            raise TypeError('Unsupported operand type')
326 1 tkerber
        return self
327 1 tkerber
328 1 tkerber
# -------------------------------------------------------------------
329 1 tkerber
330 1 tkerber
#http://www.eecho.info/Echo/python/singleton/
331 1 tkerber
#http://mail.python.org/pipermail/python-list/2007-July/622333.html
332 1 tkerber
333 1 tkerber
class Singleton(object):
334 1 tkerber
    """A Pythonic Singleton object."""
335 1 tkerber
    def __new__(cls, *args, **kwargs):
336 1 tkerber
        if '_inst' not in vars(cls):
337 1 tkerber
            cls._inst = object.__new__(cls, *args, **kwargs)
338 1 tkerber
            #cls._inst = super(type, cls).__new__(cls, *args, **kwargs)
339 1 tkerber
        return cls._inst
340 1 tkerber
341 1 tkerber
class MemorySingleton(MemoryBase, Singleton):
342 1 tkerber
    __doc__ = MemoryBase.__doc__ + """
343 1 tkerber
    The singleton variant is immutable once it has been instantiated, which
344 1 tkerber
    makes it suitable for recording the initial overhead of starting Python."""
345 1 tkerber
346 1 tkerber
    def __init__(self, verbose=0):
347 1 tkerber
        if verbose>=1: print 'MemorySingleton.__init__'
348 1 tkerber
        if '_values' not in vars(self):
349 1 tkerber
            if verbose>=1: print 'MemorySingleton.__init__ FIRST!'
350 1 tkerber
            # Hack to circumvent singleton immutability
351 1 tkerber
            self.__class__ = MemoryStatistics
352 1 tkerber
            self.__init__(verbose)
353 1 tkerber
            self.__class__ = MemorySingleton
354 1 tkerber
355 1 tkerber
    def __setitem__(self, key, value):
356 1 tkerber
        """Disabled for the singleton.
357 1 tkerber
        x.__setitem__(i, y) <==> x[i]=y"""
358 1 tkerber
        if self.verbose>=2: print 'MemorySingleton.__setitem__'
359 1 tkerber
        raise ReferenceError('Singleton is immutable.')
360 1 tkerber
361 1 tkerber
    def __delitem__(self, key):
362 1 tkerber
        """Disabled for the singleton.
363 1 tkerber
        x.__delitem__(y) <==> del x[y]"""
364 1 tkerber
        if self.verbose>=2: print 'MemorySingleton.__delitem__'
365 1 tkerber
        raise ReferenceError('Singleton is immutable.')
366 1 tkerber
367 1 tkerber
    def clear(self):
368 1 tkerber
        """Disabled for the singleton.
369 1 tkerber
        D.clear() -> None.  Remove all items from D."""
370 1 tkerber
        if self.verbose>=1: print 'MemorySingleton.clear'
371 1 tkerber
        raise ReferenceError('Singleton is immutable.')
372 1 tkerber
373 1 tkerber
    def update(self):
374 1 tkerber
        """Disabled for the singleton.
375 1 tkerber
        D.update(E) -> None.  Update D from E: for k in E.keys(): D[k] = E[k]"""
376 1 tkerber
        if self.verbose>=1: print 'MemorySingleton.update'
377 1 tkerber
        raise ReferenceError('Singleton is immutable.')
378 1 tkerber
379 1 tkerber
    def copy(self):
380 1 tkerber
        """Return a shallow non-singleton copy of a VM statistics instance.
381 1 tkerber
        D.copy() -> a shallow copy of D"""
382 1 tkerber
        if self.verbose>=1: print 'MemorySingleton.copy'
383 1 tkerber
        # Hack to circumvent singleton self-copy
384 1 tkerber
        self.__class__ = MemoryStatistics
385 1 tkerber
        res = self.copy()
386 1 tkerber
        self.__class__ = MemorySingleton
387 1 tkerber
        return res
388 1 tkerber
389 1 tkerber
# Make sure singleton is instantiated
390 1 tkerber
MemorySingleton()
391 1 tkerber
392 1 tkerber
# -------------------------------------------------------------------
393 1 tkerber
394 1 tkerber
# Helper functions for leak testing with NumPy arrays
395 1 tkerber
396 1 tkerber
def shapegen(size, ndims, ecc=0.5):
397 1 tkerber
    """Return a generator of an N-dimensional array shape
398 1 tkerber
    which approximately contains a given number of elements.
399 1 tkerber

400 1 tkerber
        size:       int or long in [1,inf[
401 1 tkerber
                    The total number of elements
402 1 tkerber
        ndims=3:    int in [1,inf[
403 1 tkerber
                    The number of dimensions
404 1 tkerber
        ecc=0.5:    float in ]0,1[
405 1 tkerber
                    The eccentricity of the distribution
406 1 tkerber
    """
407 1 tkerber
    assert type(size) in [int,float] and size>=1
408 1 tkerber
    assert type(ndims) is int and ndims>=1
409 1 tkerber
    assert type(ecc) in [int,float] and ecc>0 and ecc<1
410 1 tkerber
411 1 tkerber
    for i in range(ndims-1):
412 1 tkerber
        scale = size**(1.0/(ndims-i))
413 1 tkerber
        c = round(np.random.uniform((1-ecc)*scale, 1.0/(1-ecc)*scale))
414 1 tkerber
        size/=c
415 1 tkerber
        yield c
416 1 tkerber
    yield round(size)
417 1 tkerber
418 1 tkerber
def shapeopt(maxseed, size, ndims, ecc=0.5):
419 1 tkerber
    """Return optimal estimate of an N-dimensional array shape
420 1 tkerber
    which is closest to containing a given number of elements.
421 1 tkerber

422 1 tkerber
        maxseed:    int in [1,inf[
423 1 tkerber
                    The maximal number of seeds to try
424 1 tkerber
        size:       int or long in [1,inf[
425 1 tkerber
                    The total number of elements
426 1 tkerber
        ndims=3:    int in [1,inf[
427 1 tkerber
                    The number of dimensions
428 1 tkerber
        ecc=0.5:    float in ]0,1[
429 1 tkerber
                    The eccentricity of the distribution
430 1 tkerber
    """
431 1 tkerber
    assert type(maxseed) is int and maxseed>=1
432 1 tkerber
    assert type(size) in [int,float] and size>=1
433 1 tkerber
    assert type(ndims) is int and ndims>=1
434 1 tkerber
    assert type(ecc) in [int,float] and ecc>0 and ecc<1
435 1 tkerber
436 1 tkerber
    digits_best = np.inf
437 1 tkerber
    shape_best = None
438 1 tkerber
    for seed in range(maxseed):
439 1 tkerber
        np.random.seed(seed)
440 1 tkerber
        shape = tuple(shapegen(size, ndims, ecc))
441 1 tkerber
        if np.prod(shape) == size:
442 1 tkerber
            return -np.inf, shape
443 1 tkerber
        digits = np.log10(abs(np.prod(shape)-size))
444 1 tkerber
        if digits < digits_best:
445 1 tkerber
            (digits_best, shape_best) = (digits, shape)
446 1 tkerber
    return digits_best, shape_best