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

tonegas / nnodely / 16502811447

24 Jul 2025 04:44PM UTC coverage: 97.767% (+0.1%) from 97.651%
16502811447

push

github

web-flow
New version 1.5.0

This pull request introduces version 1.5.0 of **nnodely**, featuring several updates:
1. Improved clarity of documentation and examples.
2. Support for managing multi-dataset features is now available.
3. DataFrames can now be used to create datasets.
4. Datasets can now be resampled.
5. Random data training has been fixed for both classic and recurrent training.
6. The `state` variable has been removed.
7. It is now possible to add or remove a connection or a closed loop.
8. Partial models can now be exported.
9. The `train` function and the result analysis have been separated.
10. A new function, `trainAndAnalyse`, is now available.
11. The report now works across all network types.
12. The training function code has been reorganized.

2901 of 2967 new or added lines in 53 files covered. (97.78%)

16 existing lines in 6 files now uncovered.

12652 of 12941 relevant lines covered (97.77%)

0.98 hits per line

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

98.28
/nnodely/basic/relation.py
1
import copy
1✔
2

3
import numpy as np
1✔
4

5
from nnodely.support.utils import check, enforce_types, ForbiddenTags, is_notebook
1✔
6
from nnodely.support.jsonutils import merge, stream_to_str
1✔
7

8
from nnodely.support.logger import logging, nnLogger
1✔
9
log = nnLogger(__name__, logging.WARNING)
1✔
10

11
MAIN_JSON = {
1✔
12
                'Info' : {},
13
                'Inputs' : {},
14
                'Constants': {},
15
                'Parameters' : {},
16
                'Functions' : {},
17
                'Relations': {},
18
                'Outputs': {}
19
            }
20

21
CHECK_NAMES = False if is_notebook() else True
1✔
22

23
def toStream(obj):
1✔
24
    from nnodely.layers.parameter import Parameter, Constant
1✔
25
    if type(obj) in (int,float,list,np.ndarray):
1✔
26
        obj = Constant('Constant'+str(NeuObj.count), obj)
1✔
27
        #obj = Stream(obj, MAIN_JSON, {'dim': 1}) if type(obj) in (int, float) else obj
28
    if type(obj) is Parameter or type(obj) is Constant:
1✔
29
        obj = Stream(obj.name, obj.json, obj.dim)
1✔
30
    return obj
1✔
31

32
def check_names(name:str, name_list, list_type):
1✔
33
    check(name not in ForbiddenTags, NameError, f"The name '{name}' is a forbidden tag.")
1✔
34
    if CHECK_NAMES == True:
1✔
35
        check(name not in name_list, NameError, f"The name '{name}' is already used as {list_type}.")
1✔
NEW
36
    elif name in name_list:
×
NEW
37
        log.warning(f"The name '{name}' is already in defined as {list_type} but it is overwritten.")
×
38

39
class NeuObj():
1✔
40
    count = 0
1✔
41
    names = []
1✔
42
    @classmethod
1✔
43
    @enforce_types
1✔
44
    def clearNames(self, names:str|list|None=None):
1✔
45
        if names is None:
1✔
46
            NeuObj.count = 0
1✔
47
            NeuObj.names = []
1✔
48
        else:
49
            if type(names) is list:
1✔
50
                for name in names:
1✔
51
                    if name in NeuObj.names:
1✔
52
                        NeuObj.names.remove(name)
1✔
53
            else:
54
                if names in NeuObj.names:
1✔
55
                    NeuObj.names.remove(names)
1✔
56

57
    def __init__(self, name='', json={}, dim=0):
1✔
58
        NeuObj.count += 1
1✔
59
        if name == '':
1✔
60
            name = 'Auto'+str(NeuObj.count)
×
61
        check_names(name, NeuObj.names, "NeuObj")
1✔
62
        NeuObj.names.append(name)
1✔
63
        self.name = name
1✔
64
        self.dim = dim
1✔
65
        if json:
1✔
66
            self.json = copy.deepcopy(json)
1✔
67
        else:
68
            self.json = copy.deepcopy(MAIN_JSON)
1✔
69

70
class Relation():
1✔
71
    def __add__(self, obj):
1✔
72
        from nnodely.layers.arithmetic import Add
1✔
73
        return Add(self, obj)
1✔
74

75
    def __radd__(self, obj):
1✔
76
        from nnodely.layers.arithmetic import Add
1✔
77
        return Add(obj, self)
1✔
78

79
    def __sub__(self, obj):
1✔
80
        from nnodely.layers.arithmetic import Sub
1✔
81
        return Sub(self, obj)
1✔
82

83
    def __rsub__(self, obj):
1✔
84
        from nnodely.layers.arithmetic import Sub
1✔
85
        return Sub(obj, self)
1✔
86

87
    def __truediv__(self, obj):
1✔
88
        from nnodely.layers.arithmetic import Div
1✔
89
        return Div(self, obj)
1✔
90

91
    def __rtruediv__(self, obj):
1✔
92
        from nnodely.layers.arithmetic import Div
1✔
93
        return Div(obj, self)
1✔
94

95
    def __mul__(self, obj):
1✔
96
        from nnodely.layers.arithmetic import Mul
1✔
97
        return Mul(self, obj)
1✔
98

99
    def __rmul__(self, obj):
1✔
100
        from nnodely.layers.arithmetic import Mul
1✔
101
        return Mul(obj, self)
1✔
102

103
    def __pow__(self, obj):
1✔
104
        from nnodely.layers.arithmetic import Pow
1✔
105
        return Pow(self, obj)
1✔
106

107
    def __rpow__(self, obj):
1✔
108
        from nnodely.layers.arithmetic import Pow
1✔
109
        return Pow(obj, self)
1✔
110

111
    def __neg__(self):
1✔
112
        from nnodely.layers.arithmetic import Neg
1✔
113
        return Neg(self)
1✔
114

115
class Stream(Relation):
1✔
116
    """
117
    Represents a stream of data inside the neural network. A Stream is automatically create when you operate over a Input, Parameter, or Constant object.
118
    """
119
    count = 0
1✔
120
    @classmethod
1✔
121
    def resetCount(self):
1✔
122
        Stream.count = 0
1✔
123

124
    def __init__(self, name, json, dim, count = 1):
1✔
125
        Stream.count += count
1✔
126
        check(name not in ForbiddenTags, NameError, f"The name '{name}' is a forbidden tag.")
1✔
127
        self.name = name
1✔
128
        self.json = copy.deepcopy(json)
1✔
129
        self.dim = dim
1✔
130

131
    def __str__(self):
1✔
132
        return stream_to_str(self)
1✔
133

134
    def __repr__(self):
1✔
135
        return self.__str__()
1✔
136

137
    @enforce_types
1✔
138
    def tw(self, tw:float|int|list, offset:float|int|None = None, *, name:str|None = None) -> "Stream":
1✔
139
        """
140
        Selects a time window on Stream. It is possible to create a smaller or bigger time window on the stream.
141
        The Time Window must be in the past not in the future.
142

143
        Parameters
144
        ----------
145
        tw : float, int, list
146
            The time window represents the time in the past. If a list, it should contain the start and end times, both indexes must be in the past.
147
        offset : float, int, optional
148
            The offset for the sample window. Default is None.
149
        name : str, None
150
            The name of the internal variable
151

152
        Returns
153
        -------
154
        Stream
155
            A Stream representing the TimePart object with the selected time window.
156

157
        """
158
        from nnodely.layers.input import Input
1✔
159
        from nnodely.layers.part import TimePart
1✔
160
        if name is None:
1✔
161
            name = self.name+"_tw"+str(NeuObj.count)
1✔
162
        if type(tw) is list:
1✔
163
            check(0 >= tw[1] > tw[0] and tw[0] < 0, ValueError, "The dimension of the sample window must be in the past.")
1✔
164
        if 'tw' not in self.dim:
1✔
165
            self.dim['tw'] = 0
1✔
166
        if type(tw) is not list:
1✔
167
            tw = [-tw,0]
1✔
168
        delayed_input = Input(name, dimensions=self.dim['dim']).connect(self).tw([tw[0],0], offset)
1✔
169
        return TimePart(delayed_input,tw[0]-tw[0],tw[1]-tw[0])
1✔
170

171
    @enforce_types
1✔
172
    def sw(self, sw:int|list, offset:int|None = None, *, name:str|None = None) -> "Stream":
1✔
173
        """
174
        Selects a sample window on Stream. It is possible to create a smaller or bigger window on the stream.
175
        The Sample Window must be in the past not in the future.
176

177
        Parameters
178
        ----------
179
        sw : int, list
180
            The sample window represents the number of steps in the past. If a list, it should contain the start and end indices, both indexes must be in the past.
181
        offset : int, optional
182
            The offset for the sample window. Default is None.
183
        name : str, None
184
            The name of the internal variable
185

186
        Returns
187
        -------
188
        Stream
189
            A Stream representing the SamplePart object with the selected samples.
190

191
        """
192
        from nnodely.layers.input import Input
1✔
193
        from nnodely.layers.part import SamplePart
1✔
194
        if name is None:
1✔
195
            name = self.name+"_sw"+str(NeuObj.count)
1✔
196
        if type(sw) is list:
1✔
197
            check(0 >= sw[1] > sw[0] and sw[0] < 0, ValueError, "The dimension of the sample window must be in the past.")
1✔
198
        if 'sw' not in self.dim:
1✔
199
            self.dim['sw'] = 0
1✔
200
        if type(sw) is not list:
1✔
201
            sw = [-sw,0]
1✔
202
        delayed_input = Input(name, dimensions=self.dim['dim']).connect(self).sw([sw[0],0], offset)
1✔
203
        return SamplePart(delayed_input,sw[0]-sw[0],sw[1]-sw[0])
1✔
204

205
    @enforce_types
1✔
206
    def z(self, delay:int|float, *, name:str|None = None) -> "Stream":
1✔
207
        # TODO fix the convetion z-1 means a dealy z+1 means unitary advance
208
        """
209
        Considering the Zeta transform notation. The function is used to delay a Stream.
210
        The value of the delay can be only positive.
211

212
        Parameters
213
        ----------
214
        delay : int
215
            The delay value.
216

217
        Returns
218
        -------
219
        Stream
220
            A Stream representing the delayed Stream
221
        """
222
        check(delay > 0, ValueError, "The delay must be a positive integer")
1✔
223
        check('sw' in self.dim, TypeError, "The stream is not defined in samples but in time")
1✔
224
        return self.sw([-self.dim['sw']-delay,-delay], name = name)
1✔
225

226
    @enforce_types
1✔
227
    def delay(self, delay:int|float, *, name:str|None = None) -> "Stream":
1✔
228
        """
229
        The function is used to delay a Stream.
230
        The value of the delay can be only positive.
231

232
        Parameters
233
        ----------
234
        delay : int, float
235
            The delay value.
236

237
        Returns
238
        -------
239
        Stream
240
            A Stream representing the delayed Stream
241
        """
242
        check(delay > 0, ValueError, "The delay must be a positive integer")
1✔
243
        check('tw' in self.dim, TypeError, "The stream is not defined in time but in sample")
1✔
244
        return self.tw([-self.dim['tw']-delay,-delay], name = name)
1✔
245

246
    @enforce_types
1✔
247
    def s(self, order:int, *, int_name:str|None = None, der_name:str|None = None, method:str = 'euler') -> "Stream":
1✔
248
        """
249
        Considering the Laplace transform notation. The function is used to operate an integral or derivate operation on a Stream.
250
        The order of the integral or the derivative operation is indicated by the order parameter.
251

252
        Parameters
253
        ----------
254
        order : int
255
            Order of the Laplace transform
256
        method : str, optional
257
            Integration or derivation method
258

259
        Returns
260
        -------
261
        Stream
262
            A Stream of the signal represents the integral or derivation operation.
263
        """
264
        from nnodely.layers.timeoperation import Derivate, Integrate
1✔
265
        check(order != 0, ValueError, "The order must be a positive or negative integer not a zero")
1✔
266
        if order > 0:
1✔
267
            for i in range(order):
1✔
268
                o = Derivate(self, der_name = der_name, int_name = int_name, method = method)
1✔
269
        elif order < 0:
1✔
270
            for i in range(-order):
1✔
271
                o = Integrate(self, der_name = der_name, int_name = int_name, method = method)
1✔
272
        return o
1✔
273

274
    def connect(self, obj) -> "Stream":
1✔
275
        """
276
        Update the Stream adding a connects with a given input object.
277

278
        Parameters
279
        ----------
280
        obj : Input
281
            The Input object to connect to.
282

283
        Returns
284
        -------
285
        Stream
286
            A Stream of the signal that updates the Inputs with the connection.
287

288
        Raises
289
        ------
290
        TypeError
291
            If the provided object is not of type Input.
292
        KeyError
293
            If the input variable is already connected.
294
        """
295
        from nnodely.layers.input import Input
1✔
296
        check(type(obj) is Input, TypeError,
1✔
297
              f"The {obj} must be a Input and not a {type(obj)}.")
298
        self.json = merge(self.json, obj.json)
1✔
299
        check('closedLoop' not in self.json['Inputs'][obj.name] or 'connect' not in self.json['Inputs'][obj.name], KeyError,
1✔
300
              f"The input variable {obj.name} is already connected.")
301
        self.json['Inputs'][obj.name]['connect'] = self.name
1✔
302
        self.json['Inputs'][obj.name]['local'] = 1
1✔
303
        return self
1✔
304

305
    def closedLoop(self, obj) -> "Stream":
1✔
306
        """
307
        Update the Stream adding a closed loop connection with a given input object.
308

309
        Parameters
310
        ----------
311
        obj : Input
312
            The Input object to create a closed loop with.
313

314
        Returns
315
        -------
316
        Stream
317
            A Stream of the signal that updates the Inputs with the connection.
318

319
        Raises
320
        ------
321
        TypeError
322
            If the provided object is not of type Input.
323
        KeyError
324
            If the input variable is already connected.
325
        """
326
        from nnodely.layers.input import Input
1✔
327
        check(type(obj) is Input, TypeError,
1✔
328
              f"The {obj} must be a Input and not a {type(obj)}.")
329
        self.json = merge(self.json, obj.json)
1✔
330
        check('closedLoop' not in self.json['Inputs'][obj.name] or 'connect' not in self.json['Inputs'][obj.name],
1✔
331
              KeyError,
332
              f"The input variable {obj.name} is already connected.")
333
        self.json['Inputs'][obj.name]['closedLoop'] = self.name
1✔
334
        self.json['Inputs'][obj.name]['local'] = 1
1✔
335
        return self
1✔
336

337
class ToStream():
1✔
338
    def __new__(cls, *args, **kwargs):
1✔
339
        out = super(ToStream,cls).__new__(cls)
1✔
340
        out.__init__(*args, **kwargs)
1✔
341
        return Stream(out.name,out.json,out.dim,0)
1✔
342

343
class AutoToStream():
1✔
344
    def __new__(cls, *args,  **kwargs):
1✔
345
        if len(args) > 0 and (issubclass(type(args[0]),NeuObj) or type(args[0]) is Stream):
1✔
346
            instance = super().__new__(cls)
1✔
347
            #instance.__init__(**kwargs)
348
            instance.__init__()
1✔
349
            return instance(args[0])
1✔
350
        instance = super().__new__(cls)
1✔
351
        return instance
1✔
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

© 2025 Coveralls, Inc