• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

sandialabs / sdynpy / 17048523223

18 Aug 2025 06:02PM UTC coverage: 16.91% (-0.2%) from 17.141%
17048523223

push

github

web-flow
Still trying to fix actions.

Still trying to fix actions.

3179 of 18800 relevant lines covered (16.91%)

0.17 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

60.68
/src/sdynpy/core/sdynpy_array.py
1
# -*- coding: utf-8 -*-
2
"""
3
Base class for all SDynPy object arrays.
4

5
SDynPy object arrays are subclasses of numpy's ndarray.  SDynPy uses structured
6
arrays to store the underlying data objects, resulting in potentially complex
7
data types while still achieving the efficiency and flexibility of numpy arrays.
8

9
This module defines the SdynpyArray, which is a subclass of numpy ndarray.  The
10
core SDynPy objects inherit from this class.  The main contribution of this
11
array is allowing users to access the underlying structured array fields using
12
attribute notation rather than the index notation used by numpy
13
(e.g. object.field rather than object["field"]).
14

15
Copyright 2022 National Technology & Engineering Solutions of Sandia,
16
LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S.
17
Government retains certain rights in this software.
18

19
This program is free software: you can redistribute it and/or modify
20
it under the terms of the GNU General Public License as published by
21
the Free Software Foundation, either version 3 of the License, or
22
(at your option) any later version.
23

24
This program is distributed in the hope that it will be useful,
25
but WITHOUT ANY WARRANTY; without even the implied warranty of
26
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
27
GNU General Public License for more details.
28

29
You should have received a copy of the GNU General Public License
30
along with this program.  If not, see <https://www.gnu.org/licenses/>.
31
"""
32

33
import numpy as np
1✔
34
from scipy.io import savemat as scipy_savemat
1✔
35

36

37
class SdynpyArray(np.ndarray):
1✔
38
    """Superclass of the core SDynPy objects
39

40
    The core SDynPy object arrays inherit from this class.  The class is a
41
    subclass of numpy's ndarray.  The underlying data structure is stored as a
42
    structured array, but the class's implementation allows accessing the array
43
    fields as if they were attributes.
44
    """
45
    def __new__(subtype, shape, dtype=float, buffer=None, offset=0,
1✔
46
                strides=None, order=None):
47
        # Create the ndarray instance of our type, given the usual
48
        # ndarray input arguments.  This will call the standard
49
        # ndarray constructor, but return an object of our type.
50
        # It also triggers a call to InfoArray.__array_finalize__
51
        obj = super(SdynpyArray, subtype).__new__(subtype, shape, dtype,
1✔
52
                                                  buffer, offset, strides,
53
                                                  order)
54
        # Finally, we must return the newly created object:
55
        return obj
1✔
56

57
    def __array_finalize__(self, obj):
1✔
58
        # ``self`` is a new object resulting from
59
        # ndarray.__new__(InfoArray, ...), therefore it only has
60
        # attributes that the ndarray.__new__ constructor gave it -
61
        # i.e. those of a standard ndarray.
62
        #
63
        # We could have got to the ndarray.__new__ call in 3 ways:
64
        # From an explicit constructor - e.g. InfoArray():
65
        #    obj is None
66
        #    (we're in the middle of the InfoArray.__new__
67
        #    constructor, and self.info will be set when we return to
68
        #    InfoArray.__new__)
69
        # if obj is None:
70
        #     return
71
        # From view casting - e.g arr.view(InfoArray):
72
        #    obj is arr
73
        #    (type(obj) can be InfoArray)
74
        # From new-from-template - e.g infoarr[:3]
75
        #    type(obj) is InfoArray
76
        #
77
        # Note that it is here, rather than in the __new__ method,
78
        # that we set the default value for 'info', because this
79
        # method sees all creation of default objects - with the
80
        # InfoArray.__new__ constructor, but also with
81
        # arr.view(InfoArray).
82
        # We do not need to return anything
83
        pass
1✔
84

85
    # this method is called whenever you use a ufunc
86
    def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
1✔
87
        f = {
×
88
            "reduce": ufunc.reduce,
89
            "accumulate": ufunc.accumulate,
90
            "reduceat": ufunc.reduceat,
91
            "outer": ufunc.outer,
92
            "at": ufunc.at,
93
            "__call__": ufunc,
94
        }
95
        # print('In ufunc\n  ufunc: {:}\n  method: {:}\n  inputs: {:}\n  kwargs: {:}\n'.format(
96
        # ufunc,method,inputs,kwargs))
97
        # convert the inputs to np.ndarray to prevent recursion, call the function, then cast it back as CoordinateArray
98
        output = f[method](*(i.view(np.ndarray) for i in inputs), **kwargs).view(self.__class__)
×
99
        return output
×
100

101
    def __array_function__(self, func, types, args, kwargs):
1✔
102
        # print('In Array Function\n  func: {:}\n  types: {:}\n  args: {:}\n  kwargs: {:}'.format(
103
        # func,types,args,kwargs))
104
        output = super().__array_function__(func, types, args, kwargs)
1✔
105
        # print('  Output Type: {:}'.format(type(output)))
106
        # if isinstance(output,tuple):
107
        #     print('Tuple Types: {:}'.format(type(val) for val in output))
108
        # print('Output: {:}'.format(output))
109
        if output is NotImplemented:
1✔
110
            return NotImplemented
×
111
        else:
112
            if isinstance(output, np.ndarray) and (self.dtype == output.dtype):
1✔
113
                return output.view(self.__class__)
1✔
114
            elif isinstance(output, np.ndarray):
1✔
115
                return output.view(np.ndarray)
1✔
116
            else:
117
                output_values = []
1✔
118
                for val in output:
1✔
119
                    # print('  Tuple Output Type: {:}'.format(type(val)))
120
                    if isinstance(val, np.ndarray):
1✔
121
                        # print('    Output dtypes {:},{:}'.format(self.dtype,val.dtype))
122
                        if self.dtype == val.dtype:
1✔
123
                            # print('    Appending {:}'.format(self.__class__))
124
                            output_values.append(val.view(self.__class__))
1✔
125
                        else:
126
                            # print('    Appending {:}'.format(np.ndarray))
127
                            output_values.append(val.view(np.ndarray))
1✔
128
                    else:
129
                        output_values.append(val)
×
130
                return output_values
1✔
131

132
    def __getattr__(self, attr):
1✔
133
        try:
1✔
134
            return self[attr]
1✔
135
        except ValueError:
×
136
            raise AttributeError("'{:}' object has no attribute '{:}'".format(self.__class__, attr))
×
137

138
    def __setattr__(self, attr, value):
1✔
139
        try:
1✔
140
            self[attr] = value
1✔
141
        except (ValueError, IndexError) as e:
×
142
            # # Check and make sure you don't have an attribute already with that
143
            # # name
144
            if attr in self.dtype.fields:
×
145
                # print('ERROR: Assignment to item failed, attempting to assign item to attribute!')
146
                raise e
×
147
            super().__setattr__(attr, value)
×
148

149
    def __getitem__(self, key):
1✔
150
        # print('Key is type {:}'.format(type(key)))
151
        # print('Key is {:}'.format(key))
152
        return_val = super().__getitem__(key)
1✔
153
        try:
1✔
154
            if isinstance(key, str) and (not isinstance(return_val, np.void)) and key in self.dtype.names:
1✔
155
                return_val = return_val.view(np.ndarray)
1✔
156
                if return_val.ndim == 0:
1✔
157
                    return_val = return_val[()]
1✔
158
        except TypeError:
×
159
            pass
×
160
        if isinstance(return_val, np.void):
1✔
161
            return_val = np.asarray(return_val).view(self.__class__)
1✔
162
        # print('Returning a {:}'.format(type(return_val)))
163
        return return_val
1✔
164

165
    def __setitem__(self, key, value):
1✔
166
        try:
1✔
167
            if key in self.dtype.fields:
1✔
168
                self[key][...] = value
1✔
169
            else:
170
                super().__setitem__(key, value)
1✔
171
        except TypeError:
1✔
172
            super().__setitem__(key, value)
1✔
173

174
    def ndenumerate(self):
1✔
175
        """
176
        Enumerates over all entries in the array
177

178
        Yields
179
        ------
180
        tuple
181
            indices corresponding to each entry in the array
182
        array
183
            entry in the array corresponding to the index
184
        """
185
        for key, val in np.ndenumerate(self):
1✔
186
            yield (key, np.asarray(val).view(self.__class__))
1✔
187

188
    def __str__(self):
1✔
189
        return self.__repr__()
×
190

191
    def __repr__(self):
192
        return 'Shape {:} {:} with fields {:}'.format(' x '.join(str(v) for v in self.shape), self.__class__.__name__, self.dtype.names)
193

194
    def save(self, filename: str):
1✔
195
        """
196
        Save the array to a numpy file
197

198
        Parameters
199
        ----------
200
        filename : str
201
            Filename that the array will be saved to.  Will be appended with
202
            .npy if not specified in the filename
203

204
        """
205
        np.save(filename, self.view(np.ndarray))
1✔
206

207
    def assemble_mat_dict(self):
1✔
208
        """
209
        Assembles a dictionary of fields
210

211
        Returns
212
        -------
213
        output_dict : dict
214
            A dictionary of contents of the file
215
        """
216
        output_dict = {}
×
217
        for field in self.fields:
×
218
            val = self[field]
×
219
            if isinstance(val, SdynpyArray):
×
220
                val = val.assemble_mat_dict()
×
221
            else:
222
                val = np.ascontiguousarray(val)
×
223
            output_dict[field] = val
×
224
        return output_dict
×
225

226
    def savemat(self, filename):
1✔
227
        """
228
        Save array to a Matlab `*.mat` file.
229

230
        Parameters
231
        ----------
232
        filename : str
233
            Name of the file in which the data will be saved
234

235
        Returns
236
        -------
237
        None.
238

239
        """
240
        scipy_savemat(filename, self.assemble_mat_dict())
×
241

242
    @classmethod
1✔
243
    def load(cls, filename):
1✔
244
        """
245
        Load in the specified file into a SDynPy array object
246

247
        Parameters
248
        ----------
249
        filename : str
250
            Filename specifying the file to load.  If the filename has
251
            extension .unv or .uff, it will be loaded as a universal file.
252
            Otherwise, it will be loaded as a NumPy file.
253

254
        Raises
255
        ------
256
        AttributeError
257
            Raised if a unv file is loaded from a class that does not have a
258
            from_unv attribute defined.
259

260
        Returns
261
        -------
262
        cls
263
            SDynpy array of the appropriate type from the loaded file.
264

265
        """
266
        if filename[-4:].lower() in ['.unv', '.uff']:
1✔
267
            try:
×
268
                from ..fileio.sdynpy_uff import readunv
×
269
                unv_dict = readunv(filename)
×
270
                return cls.from_unv(unv_dict)
×
271
            except AttributeError:
×
272
                raise AttributeError('Class {:} has no from_unv attribute defined'.format(cls))
×
273
        else:
274
            return np.load(filename, allow_pickle=True).view(cls)
1✔
275

276
    def __eq__(self, other):
1✔
277
        if not isinstance(self, other.__class__):
1✔
278
            return NotImplemented
×
279
        equal_array = []
1✔
280
        for field, (dtype, extra) in self.dtype.fields.items():
1✔
281
            if dtype.kind == 'O':
1✔
282
                self_data = self[field]
×
283
                other_data = other[field]
×
284
                if self.ndim == 0:
×
285
                    obj_arr = np.ndarray((), 'object')
×
286
                    obj_arr[()] = self_data
×
287
                    self_data = obj_arr
×
288
                if other.ndim == 0:
×
289
                    obj_arr = np.ndarray((), 'object')
×
290
                    obj_arr[()] = other_data
×
291
                    other_data = obj_arr
×
292
                self_data, other_data = np.broadcast_arrays(self_data, other_data)
×
293
                truth_array = np.zeros(self_data.shape, dtype=bool)
×
294
                for key in np.ndindex(truth_array.shape):
×
295
                    truth_array[key] = np.array_equal(self_data[key], other_data[key])
×
296
            else:
297
                truth_array = self[field] == other[field]
1✔
298
            if len(dtype.shape) != 0:
1✔
299
                truth_array = np.all(truth_array, axis=tuple(-1 - np.arange(len(dtype.shape))))
1✔
300
            equal_array.append(truth_array)
1✔
301
        return np.all(equal_array, axis=0)
1✔
302

303
    def __ne__(self, other):
1✔
304
        return ~self.__eq__(other)
×
305

306
    @property
1✔
307
    def fields(self):
1✔
308
        """Returns the fields of the structured array.
309

310
        These fields can be accessed through attribute syntax."""
311
        return self.dtype.names
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc