nnodely 1.3.1__tar.gz → 1.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nnodely-1.3.1/nnodely.egg-info → nnodely-1.4.0}/PKG-INFO +20 -8
- {nnodely-1.3.1 → nnodely-1.4.0}/README.md +13 -2
- nnodely-1.4.0/nnodely/__init__.py +47 -0
- nnodely-1.4.0/nnodely/basic/__init__.py +0 -0
- {nnodely-1.3.1/nnodely → nnodely-1.4.0/nnodely/basic}/loss.py +13 -6
- {nnodely-1.3.1/nnodely → nnodely-1.4.0/nnodely/basic}/model.py +5 -13
- {nnodely-1.3.1/nnodely → nnodely-1.4.0/nnodely/basic}/modeldef.py +93 -90
- {nnodely-1.3.1/nnodely → nnodely-1.4.0/nnodely/basic}/optimizer.py +1 -1
- {nnodely-1.3.1/nnodely → nnodely-1.4.0/nnodely/basic}/relation.py +20 -20
- {nnodely-1.3.1 → nnodely-1.4.0}/nnodely/exporter/__init__.py +1 -1
- nnodely-1.3.1/nnodely/exporter/exporter.py → nnodely-1.4.0/nnodely/exporter/emptyexporter.py +1 -1
- {nnodely-1.3.1 → nnodely-1.4.0}/nnodely/exporter/export.py +7 -7
- {nnodely-1.3.1 → nnodely-1.4.0}/nnodely/exporter/reporter.py +10 -10
- {nnodely-1.3.1 → nnodely-1.4.0}/nnodely/exporter/standardexporter.py +8 -6
- nnodely-1.4.0/nnodely/layers/__init__.py +0 -0
- {nnodely-1.3.1/nnodely → nnodely-1.4.0/nnodely/layers}/activation.py +3 -3
- {nnodely-1.3.1/nnodely → nnodely-1.4.0/nnodely/layers}/arithmetic.py +17 -15
- {nnodely-1.3.1/nnodely → nnodely-1.4.0/nnodely/layers}/equationlearner.py +12 -9
- {nnodely-1.3.1/nnodely → nnodely-1.4.0/nnodely/layers}/fir.py +11 -8
- {nnodely-1.3.1/nnodely → nnodely-1.4.0/nnodely/layers}/fuzzify.py +10 -4
- {nnodely-1.3.1/nnodely → nnodely-1.4.0/nnodely/layers}/input.py +9 -5
- {nnodely-1.3.1/nnodely → nnodely-1.4.0/nnodely/layers}/interpolation.py +8 -4
- {nnodely-1.3.1/nnodely → nnodely-1.4.0/nnodely/layers}/linear.py +9 -6
- {nnodely-1.3.1/nnodely → nnodely-1.4.0/nnodely/layers}/localmodel.py +6 -3
- {nnodely-1.3.1/nnodely → nnodely-1.4.0/nnodely/layers}/output.py +2 -2
- {nnodely-1.3.1/nnodely → nnodely-1.4.0/nnodely/layers}/parameter.py +12 -5
- {nnodely-1.3.1/nnodely → nnodely-1.4.0/nnodely/layers}/parametricfunction.py +13 -8
- {nnodely-1.3.1/nnodely → nnodely-1.4.0/nnodely/layers}/part.py +50 -13
- nnodely-1.4.0/nnodely/layers/timeoperation.py +75 -0
- {nnodely-1.3.1/nnodely → nnodely-1.4.0/nnodely/layers}/trigonometric.py +4 -4
- nnodely-1.4.0/nnodely/nnodely.py +144 -0
- nnodely-1.4.0/nnodely/operators/__init__.py +0 -0
- nnodely-1.4.0/nnodely/operators/exporter.py +402 -0
- nnodely-1.4.0/nnodely/operators/loader.py +348 -0
- nnodely-1.4.0/nnodely/operators/memory.py +47 -0
- nnodely-1.4.0/nnodely/operators/network.py +412 -0
- nnodely-1.4.0/nnodely/operators/trainer.py +815 -0
- nnodely-1.4.0/nnodely/operators/validator.py +218 -0
- nnodely-1.4.0/nnodely/support/__init__.py +0 -0
- nnodely-1.4.0/nnodely/support/fixstepsolver.py +26 -0
- {nnodely-1.3.1/nnodely → nnodely-1.4.0/nnodely/support}/utils.py +74 -5
- {nnodely-1.3.1 → nnodely-1.4.0}/nnodely/visualizer/mplnotebookvisualizer.py +14 -12
- nnodely-1.4.0/nnodely/visualizer/mplvisualizer.py +215 -0
- {nnodely-1.3.1 → nnodely-1.4.0}/nnodely/visualizer/textvisualizer.py +47 -47
- {nnodely-1.3.1 → nnodely-1.4.0}/nnodely/visualizer/visualizer.py +3 -3
- {nnodely-1.3.1 → nnodely-1.4.0/nnodely.egg-info}/PKG-INFO +20 -8
- {nnodely-1.3.1 → nnodely-1.4.0}/nnodely.egg-info/SOURCES.txt +36 -25
- {nnodely-1.3.1 → nnodely-1.4.0}/nnodely.egg-info/requires.txt +3 -5
- {nnodely-1.3.1 → nnodely-1.4.0}/pyproject.toml +9 -5
- {nnodely-1.3.1 → nnodely-1.4.0}/tests/test_dataset.py +270 -169
- {nnodely-1.3.1 → nnodely-1.4.0}/tests/test_export.py +8 -4
- {nnodely-1.3.1 → nnodely-1.4.0}/tests/test_export_recurrent.py +16 -16
- {nnodely-1.3.1 → nnodely-1.4.0}/tests/test_input_dimensions.py +91 -91
- {nnodely-1.3.1 → nnodely-1.4.0}/tests/test_json.py +6 -6
- nnodely-1.4.0/tests/test_losses.py +189 -0
- {nnodely-1.3.1 → nnodely-1.4.0}/tests/test_model_predict.py +112 -4
- {nnodely-1.3.1 → nnodely-1.4.0}/tests/test_model_predict_recurrent.py +133 -48
- {nnodely-1.3.1 → nnodely-1.4.0}/tests/test_network_element.py +51 -16
- {nnodely-1.3.1 → nnodely-1.4.0}/tests/test_parameters_of_train.py +32 -22
- {nnodely-1.3.1 → nnodely-1.4.0}/tests/test_results.py +20 -20
- {nnodely-1.3.1 → nnodely-1.4.0}/tests/test_train.py +117 -76
- {nnodely-1.3.1 → nnodely-1.4.0}/tests/test_train_recurrent.py +483 -376
- {nnodely-1.3.1 → nnodely-1.4.0}/tests/test_utils.py +2 -2
- {nnodely-1.3.1 → nnodely-1.4.0}/tests/test_visualizer.py +64 -35
- nnodely-1.3.1/nnodely/__init__.py +0 -45
- nnodely-1.3.1/nnodely/nnodely.py +0 -1994
- nnodely-1.3.1/nnodely/timeoperation.py +0 -46
- nnodely-1.3.1/nnodely/visualizer/mplvisualizer.py +0 -215
- nnodely-1.3.1/tests/test_losses.py +0 -171
- {nnodely-1.3.1 → nnodely-1.4.0}/LICENSE +0 -0
- {nnodely-1.3.1 → nnodely-1.4.0}/mplplots/__init__.py +0 -0
- {nnodely-1.3.1 → nnodely-1.4.0}/mplplots/plots.py +0 -0
- {nnodely-1.3.1/nnodely → nnodely-1.4.0/nnodely/support}/earlystopping.py +0 -0
- {nnodely-1.3.1/nnodely → nnodely-1.4.0/nnodely/support}/initializer.py +0 -0
- {nnodely-1.3.1/nnodely → nnodely-1.4.0/nnodely/support}/logger.py +0 -0
- {nnodely-1.3.1 → nnodely-1.4.0}/nnodely/visualizer/__init__.py +0 -0
- {nnodely-1.3.1 → nnodely-1.4.0}/nnodely/visualizer/dynamicmpl/functionplot.py +0 -0
- {nnodely-1.3.1 → nnodely-1.4.0}/nnodely/visualizer/dynamicmpl/fuzzyplot.py +0 -0
- {nnodely-1.3.1 → nnodely-1.4.0}/nnodely/visualizer/dynamicmpl/resultsplot.py +0 -0
- {nnodely-1.3.1 → nnodely-1.4.0}/nnodely/visualizer/dynamicmpl/trainingplot.py +0 -0
- {nnodely-1.3.1 → nnodely-1.4.0}/nnodely.egg-info/dependency_links.txt +0 -0
- {nnodely-1.3.1 → nnodely-1.4.0}/nnodely.egg-info/top_level.txt +0 -0
- {nnodely-1.3.1 → nnodely-1.4.0}/setup.cfg +0 -0
- {nnodely-1.3.1 → nnodely-1.4.0}/setup.py +0 -0
- {nnodely-1.3.1 → nnodely-1.4.0}/tests/test_documentation.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: nnodely
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: Model-structured neural network framework for the modeling and control of physical systems
|
|
5
5
|
Author-email: Gastone Pietro Rosati Papini <tonegas@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -32,15 +32,16 @@ Classifier: Operating System :: OS Independent
|
|
|
32
32
|
Requires-Python: <3.13,>=3.10
|
|
33
33
|
Description-Content-Type: text/markdown
|
|
34
34
|
License-File: LICENSE
|
|
35
|
-
Requires-Dist: numpy==1.26.4; platform_machine == "x86_64"
|
|
36
|
-
Requires-Dist: torch==2.2.2; platform_machine == "x86_64"
|
|
37
|
-
Requires-Dist: numpy
|
|
38
|
-
Requires-Dist: torch
|
|
35
|
+
Requires-Dist: numpy==1.26.4; platform_machine == "x86_64" and python_version == "3.10"
|
|
36
|
+
Requires-Dist: torch==2.2.2; platform_machine == "x86_64" and python_version == "3.10"
|
|
37
|
+
Requires-Dist: numpy
|
|
38
|
+
Requires-Dist: torch
|
|
39
39
|
Requires-Dist: onnx
|
|
40
40
|
Requires-Dist: pandas
|
|
41
41
|
Requires-Dist: reportlab
|
|
42
42
|
Requires-Dist: matplotlib
|
|
43
43
|
Requires-Dist: onnxruntime
|
|
44
|
+
Dynamic: license-file
|
|
44
45
|
|
|
45
46
|
<p align="center">
|
|
46
47
|
<img src="https://raw.githubusercontent.com/tonegas/nnodely/main/imgs/logo_white_info.png" alt="logo" >
|
|
@@ -308,7 +309,7 @@ This operation is presented in [[1]](#1).
|
|
|
308
309
|
3. __localmodel.py__ this file contains the logic for build a local model. This operation is presented in [[1]](#1), [[3]](#3), [[4]](#4) and [[5]](#5).
|
|
309
310
|
4. __parametricfunction.py__ are the user custom function. The function can use the pytorch syntax. A parametric function is presented in [[3]](#3), [[4]](#4), [[5]](#5).
|
|
310
311
|
5. __equationlearner.py__ contains the logic for the equation learner. The equation learner is used for learn a relation input outpur following a list of activation functions. The first implementation is presented in [[6]](#6).
|
|
311
|
-
6. __timeoperation.py__ contains the time operation functions. The time operation are used for extract a time window from a signal.
|
|
312
|
+
6. __timeoperation.py__ contains the time operation functions. The time operation are used for extract a time window from a signal. The derivative operation can be used to implement Physics-informed neural network [[7]](#7) Sobolev learning [[8]](#8).
|
|
312
313
|
|
|
313
314
|
<a name="testsfolder"></a>
|
|
314
315
|
### Tests Folder
|
|
@@ -359,6 +360,17 @@ and [[code extended]](https://github.com/tonegas/nnodely-applications/blob/main/
|
|
|
359
360
|
<a id="6">[6]</a>
|
|
360
361
|
Hector Perez-Villeda, Justus Piater, Matteo Saveriano. (2023).
|
|
361
362
|
Learning and extrapolation of robotic skills using task-parameterized equation learner networks.
|
|
362
|
-
Robotics and Autonomous Systems. https://doi.org/10.1016/j.robot.2022.104309
|
|
363
|
+
Robotics and Autonomous Systems. https://doi.org/10.1016/j.robot.2022.104309 (look the [[code]](https://github.com/tonegas/nnodely-applications/blob/main/equation_learner/equation_learner.ipynb))
|
|
364
|
+
|
|
365
|
+
<a id="7">[7]</a>
|
|
366
|
+
M. Raissi. P. Perdikaris b, G.E. Karniadakis a. (2019).
|
|
367
|
+
Physics-informed neural networks: A deep learning framework for solving forward and inverse problems involving nonlinear partial differential equations
|
|
368
|
+
Journal of Computational Physics. https://doi.org/10.1016/j.jcp.2018.10.045 (look the [[example Burger's equation]](https://github.com/tonegas/nnodely-applications/blob/main/pinn/pinn_Burgers_equation.ipynb))
|
|
369
|
+
|
|
370
|
+
<a id="8">[8]</a>
|
|
371
|
+
Wojciech Marian Czarnecki, Simon Osindero, Max Jaderberg, Grzegorz Świrszcz, Razvan Pascanu. (2017).
|
|
372
|
+
Sobolev Training for Neural Networks.
|
|
373
|
+
arXiv. https://doi.org/10.48550/arXiv.1706.04859 (look the [[code]](https://github.com/tonegas/nnodely-applications/blob/main/sobolev/Sobolev_learning.ipynb))
|
|
374
|
+
|
|
363
375
|
|
|
364
376
|
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
@@ -264,7 +264,7 @@ This operation is presented in [[1]](#1).
|
|
|
264
264
|
3. __localmodel.py__ this file contains the logic for build a local model. This operation is presented in [[1]](#1), [[3]](#3), [[4]](#4) and [[5]](#5).
|
|
265
265
|
4. __parametricfunction.py__ are the user custom function. The function can use the pytorch syntax. A parametric function is presented in [[3]](#3), [[4]](#4), [[5]](#5).
|
|
266
266
|
5. __equationlearner.py__ contains the logic for the equation learner. The equation learner is used for learn a relation input outpur following a list of activation functions. The first implementation is presented in [[6]](#6).
|
|
267
|
-
6. __timeoperation.py__ contains the time operation functions. The time operation are used for extract a time window from a signal.
|
|
267
|
+
6. __timeoperation.py__ contains the time operation functions. The time operation are used for extract a time window from a signal. The derivative operation can be used to implement Physics-informed neural network [[7]](#7) Sobolev learning [[8]](#8).
|
|
268
268
|
|
|
269
269
|
<a name="testsfolder"></a>
|
|
270
270
|
### Tests Folder
|
|
@@ -315,6 +315,17 @@ and [[code extended]](https://github.com/tonegas/nnodely-applications/blob/main/
|
|
|
315
315
|
<a id="6">[6]</a>
|
|
316
316
|
Hector Perez-Villeda, Justus Piater, Matteo Saveriano. (2023).
|
|
317
317
|
Learning and extrapolation of robotic skills using task-parameterized equation learner networks.
|
|
318
|
-
Robotics and Autonomous Systems. https://doi.org/10.1016/j.robot.2022.104309
|
|
318
|
+
Robotics and Autonomous Systems. https://doi.org/10.1016/j.robot.2022.104309 (look the [[code]](https://github.com/tonegas/nnodely-applications/blob/main/equation_learner/equation_learner.ipynb))
|
|
319
|
+
|
|
320
|
+
<a id="7">[7]</a>
|
|
321
|
+
M. Raissi. P. Perdikaris b, G.E. Karniadakis a. (2019).
|
|
322
|
+
Physics-informed neural networks: A deep learning framework for solving forward and inverse problems involving nonlinear partial differential equations
|
|
323
|
+
Journal of Computational Physics. https://doi.org/10.1016/j.jcp.2018.10.045 (look the [[example Burger's equation]](https://github.com/tonegas/nnodely-applications/blob/main/pinn/pinn_Burgers_equation.ipynb))
|
|
324
|
+
|
|
325
|
+
<a id="8">[8]</a>
|
|
326
|
+
Wojciech Marian Czarnecki, Simon Osindero, Max Jaderberg, Grzegorz Świrszcz, Razvan Pascanu. (2017).
|
|
327
|
+
Sobolev Training for Neural Networks.
|
|
328
|
+
arXiv. https://doi.org/10.48550/arXiv.1706.04859 (look the [[code]](https://github.com/tonegas/nnodely-applications/blob/main/sobolev/Sobolev_learning.ipynb))
|
|
329
|
+
|
|
319
330
|
|
|
320
331
|
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
|
|
2
|
+
__version__ = '1.4.0'
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
major, minor = sys.version_info.major, sys.version_info.minor
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
LOG_LEVEL = logging.INFO
|
|
9
|
+
|
|
10
|
+
if major < 3:
|
|
11
|
+
sys.exit("Sorry, Python 2 is not supported. You need Python >= 3.10 for "+__package__+".")
|
|
12
|
+
elif minor < 9:
|
|
13
|
+
sys.exit("Sorry, You need Python >= 3.10 for "+__package__+".")
|
|
14
|
+
else:
|
|
15
|
+
print(f'>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>'+
|
|
16
|
+
f' {__package__}_v{__version__} '.center(20, '-')+
|
|
17
|
+
f'<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<')
|
|
18
|
+
|
|
19
|
+
# Network input, outputs and parameters
|
|
20
|
+
from nnodely.layers.input import Input, State, Connect, ClosedLoop
|
|
21
|
+
from nnodely.layers.parameter import Parameter, Constant, SampleTime
|
|
22
|
+
from nnodely.layers.output import Output
|
|
23
|
+
|
|
24
|
+
# Network elements
|
|
25
|
+
from nnodely.layers.activation import Relu, ELU, Softmax, Sigmoid, Identity
|
|
26
|
+
from nnodely.layers.fir import Fir
|
|
27
|
+
from nnodely.layers.linear import Linear
|
|
28
|
+
from nnodely.layers.arithmetic import Add, Sum, Sub, Mul, Div, Pow, Neg
|
|
29
|
+
from nnodely.layers.trigonometric import Sin, Cos, Tan, Cosh, Tanh, Sech
|
|
30
|
+
from nnodely.layers.parametricfunction import ParamFun
|
|
31
|
+
from nnodely.layers.fuzzify import Fuzzify
|
|
32
|
+
from nnodely.layers.part import Part, Select, Concatenate, SamplePart, SampleSelect, TimePart, TimeConcatenate
|
|
33
|
+
from nnodely.layers.localmodel import LocalModel
|
|
34
|
+
from nnodely.layers.equationlearner import EquationLearner
|
|
35
|
+
from nnodely.layers.timeoperation import Integrate, Derivate
|
|
36
|
+
from nnodely.layers.interpolation import Interpolation
|
|
37
|
+
|
|
38
|
+
# Main nnodely classes
|
|
39
|
+
from nnodely.nnodely import nnodely, Modely, clearNames
|
|
40
|
+
from nnodely.visualizer import Visualizer, TextVisualizer, MPLVisualizer, MPLNotebookVisualizer
|
|
41
|
+
from nnodely.exporter import StandardExporter
|
|
42
|
+
|
|
43
|
+
# Basic nnodely
|
|
44
|
+
from nnodely.basic.optimizer import Optimizer, SGD, Adam
|
|
45
|
+
|
|
46
|
+
# Support functions
|
|
47
|
+
from nnodely.support.initializer import init_negexp, init_lin, init_constant, init_exp
|
|
File without changes
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import torch.nn as nn
|
|
2
2
|
import torch
|
|
3
|
-
from nnodely.utils import check
|
|
3
|
+
from nnodely.support.utils import check
|
|
4
4
|
|
|
5
|
-
available_losses = ['mse', 'rmse', 'mae']
|
|
5
|
+
available_losses = ['mse', 'rmse', 'mae', 'cross_entropy']
|
|
6
6
|
|
|
7
7
|
# class CustomRMSE(nn.Module):
|
|
8
8
|
# def __init__(self):
|
|
@@ -15,15 +15,22 @@ available_losses = ['mse', 'rmse', 'mae']
|
|
|
15
15
|
# return self.mse(inA, inB)
|
|
16
16
|
|
|
17
17
|
class CustomLoss(nn.Module):
|
|
18
|
-
def __init__(self, loss_type='mse'):
|
|
18
|
+
def __init__(self, loss_type='mse', **kwargs):
|
|
19
19
|
super(CustomLoss, self).__init__()
|
|
20
20
|
check(loss_type in available_losses, TypeError, f'The \"{loss_type}\" loss is not available. Possible losses are: {available_losses}.')
|
|
21
21
|
self.loss_type = loss_type
|
|
22
|
-
self.loss = nn.MSELoss()
|
|
23
|
-
if
|
|
24
|
-
self.loss =
|
|
22
|
+
self.loss = nn.MSELoss(**kwargs)
|
|
23
|
+
if callable(loss_type):
|
|
24
|
+
self.loss = loss_type
|
|
25
|
+
elif self.loss_type == 'mae':
|
|
26
|
+
self.loss = nn.L1Loss(**kwargs)
|
|
27
|
+
elif self.loss_type == 'cross_entropy':
|
|
28
|
+
self.loss = nn.CrossEntropyLoss(**kwargs)
|
|
25
29
|
|
|
26
30
|
def forward(self, inA, inB):
|
|
31
|
+
if self.loss_type == 'cross_entropy':
|
|
32
|
+
inB = inB.squeeze().float() if inA.shape == inB.shape else inB.squeeze().long()
|
|
33
|
+
inA = inA.squeeze()
|
|
27
34
|
res = self.loss(inA,inB)
|
|
28
35
|
if self.loss_type == 'rmse':
|
|
29
36
|
res = torch.sqrt(res)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from itertools import product
|
|
2
|
-
from nnodely.utils import TORCH_DTYPE
|
|
3
|
-
from nnodely import initializer
|
|
2
|
+
from nnodely.support.utils import TORCH_DTYPE
|
|
3
|
+
from nnodely.support import initializer
|
|
4
4
|
import numpy as np
|
|
5
5
|
|
|
6
6
|
import torch.nn as nn
|
|
@@ -34,14 +34,6 @@ class Model(nn.Module):
|
|
|
34
34
|
self.input_n_samples = {key:value['ntot'] for key, value in (model_def['Inputs']|model_def['States']).items()}
|
|
35
35
|
self.minimizers_keys = [self.minimizers[key]['A'] for key in self.minimizers] + [self.minimizers[key]['B'] for key in self.minimizers]
|
|
36
36
|
|
|
37
|
-
#print('inputs: ',self.inputs)
|
|
38
|
-
#print('outputs: ',self.outputs)
|
|
39
|
-
#print('relations: ',self.relations)
|
|
40
|
-
#print('params: ',self.params)
|
|
41
|
-
#print('constants: ',self.constants)
|
|
42
|
-
#print('sample_time: ',self.sample_time)
|
|
43
|
-
#print('states: ',self.states)
|
|
44
|
-
|
|
45
37
|
## Build the network
|
|
46
38
|
self.all_parameters = {}
|
|
47
39
|
self.all_constants = {}
|
|
@@ -149,7 +141,7 @@ class Model(nn.Module):
|
|
|
149
141
|
self.network_output_predictions = set(self.outputs.values())
|
|
150
142
|
|
|
151
143
|
## list of network minimization outputs
|
|
152
|
-
self.network_output_minimizers = []
|
|
144
|
+
self.network_output_minimizers = []
|
|
153
145
|
for _,value in self.minimizers.items():
|
|
154
146
|
self.network_output_minimizers.append(self.outputs[value['A']]) if value['A'] in self.outputs.keys() else self.network_output_minimizers.append(value['A'])
|
|
155
147
|
self.network_output_minimizers.append(self.outputs[value['B']]) if value['B'] in self.outputs.keys() else self.network_output_minimizers.append(value['B'])
|
|
@@ -178,7 +170,7 @@ class Model(nn.Module):
|
|
|
178
170
|
layer_inputs.append(self.all_constants[key])
|
|
179
171
|
elif key in available_states: ## relation that takes a state
|
|
180
172
|
layer_inputs.append(kwargs[key])
|
|
181
|
-
elif key in available_inputs: ## relation that takes inputs
|
|
173
|
+
elif key in available_inputs: ## relation that takes inputs
|
|
182
174
|
layer_inputs.append(kwargs[key])
|
|
183
175
|
elif key in self.all_parameters.keys(): ## relation that takes parameters
|
|
184
176
|
layer_inputs.append(self.all_parameters[key])
|
|
@@ -205,7 +197,7 @@ class Model(nn.Module):
|
|
|
205
197
|
minimize_dict = {}
|
|
206
198
|
for key in self.minimizers_keys:
|
|
207
199
|
minimize_dict[key] = result_dict[self.outputs[key]] if key in self.outputs.keys() else result_dict[key]
|
|
208
|
-
|
|
200
|
+
|
|
209
201
|
return output_dict, minimize_dict, closed_loop_update_dict, connect_update_dict
|
|
210
202
|
|
|
211
203
|
|
|
@@ -2,90 +2,100 @@ import copy
|
|
|
2
2
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
|
|
5
|
-
from nnodely.
|
|
6
|
-
from nnodely.
|
|
7
|
-
from nnodely.
|
|
8
|
-
from nnodely.output import Output
|
|
5
|
+
from nnodely.support.utils import check, merge
|
|
6
|
+
from nnodely.basic.relation import MAIN_JSON, Stream
|
|
7
|
+
from nnodely.layers.output import Output
|
|
9
8
|
|
|
10
|
-
from nnodely.logger import logging, nnLogger
|
|
9
|
+
from nnodely.support.logger import logging, nnLogger
|
|
11
10
|
log = nnLogger(__name__, logging.INFO)
|
|
12
11
|
|
|
13
12
|
class ModelDef():
|
|
14
13
|
def __init__(self, model_def = MAIN_JSON):
|
|
15
14
|
# Models definition
|
|
16
|
-
self.
|
|
15
|
+
self.__json_base = copy.deepcopy(model_def)
|
|
17
16
|
|
|
18
17
|
# Inizialize the model definition
|
|
19
|
-
self.
|
|
20
|
-
if "SampleTime" in self.
|
|
21
|
-
self.
|
|
18
|
+
self.__json = copy.deepcopy(self.__json_base)
|
|
19
|
+
if "SampleTime" in self.__json['Info']:
|
|
20
|
+
self.__sample_time = self.__json['Info']["SampleTime"]
|
|
22
21
|
else:
|
|
23
|
-
self.
|
|
24
|
-
self.
|
|
25
|
-
self.
|
|
26
|
-
self.
|
|
22
|
+
self.__sample_time = None
|
|
23
|
+
self.__model_dict = {}
|
|
24
|
+
self.__minimize_dict = {}
|
|
25
|
+
self.__update_state_dict = {}
|
|
27
26
|
|
|
28
27
|
def __contains__(self, key):
|
|
29
|
-
return key in self.
|
|
28
|
+
return key in self.__json
|
|
30
29
|
|
|
31
30
|
def __getitem__(self, key):
|
|
32
|
-
return self.
|
|
31
|
+
return self.__json[key]
|
|
33
32
|
|
|
34
33
|
def __setitem__(self, key, value):
|
|
35
|
-
self.
|
|
34
|
+
self.__json[key] = value
|
|
35
|
+
|
|
36
|
+
#TODO to remove when getJson takes a model list as argment
|
|
37
|
+
def getModelDict(self):
|
|
38
|
+
return copy.deepcopy(self.__model_dict)
|
|
39
|
+
|
|
40
|
+
def getJson(self):
|
|
41
|
+
return copy.deepcopy(self.__json)
|
|
42
|
+
|
|
43
|
+
def getSampleTime(self):
|
|
44
|
+
check(self.__sample_time is not None, AttributeError, "Sample time is not defined the model is not neuralized!")
|
|
45
|
+
return self.__sample_time
|
|
36
46
|
|
|
37
47
|
def isDefined(self):
|
|
38
|
-
return self.
|
|
48
|
+
return self.__json is not None
|
|
39
49
|
|
|
40
50
|
def update(self, model_def = None, model_dict = None, minimize_dict = None, update_state_dict = None):
|
|
41
|
-
self.
|
|
42
|
-
model_dict = copy.deepcopy(model_dict) if model_dict is not None else self.
|
|
43
|
-
minimize_dict = copy.deepcopy(minimize_dict) if minimize_dict is not None else self.
|
|
44
|
-
update_state_dict = copy.deepcopy(update_state_dict) if update_state_dict is not None else self.
|
|
51
|
+
self.__json = copy.deepcopy(model_def) if model_def is not None else copy.deepcopy(self.__json_base)
|
|
52
|
+
model_dict = copy.deepcopy(model_dict) if model_dict is not None else self.__model_dict
|
|
53
|
+
minimize_dict = copy.deepcopy(minimize_dict) if minimize_dict is not None else self.__minimize_dict
|
|
54
|
+
update_state_dict = copy.deepcopy(update_state_dict) if update_state_dict is not None else self.__update_state_dict
|
|
45
55
|
|
|
46
56
|
# Add models to the model_def
|
|
47
57
|
for key, stream_list in model_dict.items():
|
|
48
58
|
for stream in stream_list:
|
|
49
|
-
self.
|
|
59
|
+
self.__json = merge(self.__json, stream.json)
|
|
50
60
|
if len(model_dict) > 1:
|
|
51
|
-
if 'Models' not in self.
|
|
52
|
-
self.
|
|
61
|
+
if 'Models' not in self.__json:
|
|
62
|
+
self.__json['Models'] = {}
|
|
53
63
|
for model_name, model_params in model_dict.items():
|
|
54
|
-
self.
|
|
64
|
+
self.__json['Models'][model_name] = {'Inputs': [], 'States': [], 'Outputs': [], 'Parameters': [],
|
|
55
65
|
'Constants': []}
|
|
56
66
|
parameters, constants, inputs, states = set(), set(), set(), set()
|
|
57
67
|
for param in model_params:
|
|
58
|
-
self.
|
|
68
|
+
self.__json['Models'][model_name]['Outputs'].append(param.name)
|
|
59
69
|
parameters |= set(param.json['Parameters'].keys())
|
|
60
70
|
constants |= set(param.json['Constants'].keys())
|
|
61
71
|
inputs |= set(param.json['Inputs'].keys())
|
|
62
72
|
states |= set(param.json['States'].keys())
|
|
63
|
-
self.
|
|
64
|
-
self.
|
|
65
|
-
self.
|
|
66
|
-
self.
|
|
73
|
+
self.__json['Models'][model_name]['Parameters'] = list(parameters)
|
|
74
|
+
self.__json['Models'][model_name]['Constants'] = list(constants)
|
|
75
|
+
self.__json['Models'][model_name]['Inputs'] = list(inputs)
|
|
76
|
+
self.__json['Models'][model_name]['States'] = list(states)
|
|
67
77
|
elif len(model_dict) == 1:
|
|
68
|
-
self.
|
|
78
|
+
self.__json['Models'] = list(model_dict.keys())[0]
|
|
69
79
|
|
|
70
|
-
if 'Minimizers' not in self.
|
|
71
|
-
self.
|
|
80
|
+
if 'Minimizers' not in self.__json:
|
|
81
|
+
self.__json['Minimizers'] = {}
|
|
72
82
|
for key, minimize in minimize_dict.items():
|
|
73
|
-
self.
|
|
74
|
-
self.
|
|
75
|
-
self.
|
|
76
|
-
self.
|
|
77
|
-
self.
|
|
78
|
-
self.
|
|
83
|
+
self.__json = merge(self.__json, minimize['A'].json)
|
|
84
|
+
self.__json = merge(self.__json, minimize['B'].json)
|
|
85
|
+
self.__json['Minimizers'][key] = {}
|
|
86
|
+
self.__json['Minimizers'][key]['A'] = minimize['A'].name
|
|
87
|
+
self.__json['Minimizers'][key]['B'] = minimize['B'].name
|
|
88
|
+
self.__json['Minimizers'][key]['loss'] = minimize['loss']
|
|
79
89
|
|
|
80
90
|
for key, update_state in update_state_dict.items():
|
|
81
|
-
self.
|
|
91
|
+
self.__json = merge(self.__json, update_state.json)
|
|
82
92
|
|
|
83
|
-
if "SampleTime" in self.
|
|
84
|
-
self.
|
|
93
|
+
if "SampleTime" in self.__json['Info']:
|
|
94
|
+
self.__sample_time = self.__json['Info']["SampleTime"]
|
|
85
95
|
|
|
86
96
|
|
|
87
97
|
def __update_state(self, stream_out, state_list_in, UpdateState):
|
|
88
|
-
from nnodely.input import State
|
|
98
|
+
from nnodely.layers.input import State
|
|
89
99
|
if type(state_list_in) is not list:
|
|
90
100
|
state_list_in = [state_list_in]
|
|
91
101
|
for state_in in state_list_in:
|
|
@@ -96,17 +106,17 @@ class ModelDef():
|
|
|
96
106
|
check(stream_out.dim['dim'] == state_in.dim['dim'], ValueError,
|
|
97
107
|
f"The dimension of {stream_out.name} is not equal to the dimension of {state_in.name} ({stream_out.dim['dim']}!={state_in.dim['dim']}).")
|
|
98
108
|
if type(stream_out) is Output:
|
|
99
|
-
stream_name = self.
|
|
109
|
+
stream_name = self.__json['Outputs'][stream_out.name]
|
|
100
110
|
stream_out = Stream(stream_name,stream_out.json,stream_out.dim, 0)
|
|
101
|
-
self.
|
|
111
|
+
self.__update_state_dict[state_in.name] = UpdateState(stream_out, state_in)
|
|
102
112
|
|
|
103
113
|
def addConnect(self, stream_out, state_list_in):
|
|
104
|
-
from nnodely.input import Connect
|
|
114
|
+
from nnodely.layers.input import Connect
|
|
105
115
|
self.__update_state(stream_out, state_list_in, Connect)
|
|
106
116
|
self.update()
|
|
107
117
|
|
|
108
118
|
def addClosedLoop(self, stream_out, state_list_in):
|
|
109
|
-
from nnodely.input import ClosedLoop
|
|
119
|
+
from nnodely.layers.input import ClosedLoop
|
|
110
120
|
self.__update_state(stream_out, state_list_in, ClosedLoop)
|
|
111
121
|
self.update()
|
|
112
122
|
|
|
@@ -114,8 +124,8 @@ class ModelDef():
|
|
|
114
124
|
if isinstance(stream_list, (Output,Stream)):
|
|
115
125
|
stream_list = [stream_list]
|
|
116
126
|
if type(stream_list) is list:
|
|
117
|
-
check(name not in self.
|
|
118
|
-
self.
|
|
127
|
+
check(name not in self.__model_dict.keys(), ValueError, f"The name '{name}' of the model is already used")
|
|
128
|
+
self.__model_dict[name] = copy.deepcopy(stream_list)
|
|
119
129
|
else:
|
|
120
130
|
raise TypeError(f'stream_list is type {type(stream_list)} but must be an Output or Stream or a list of them')
|
|
121
131
|
self.update()
|
|
@@ -125,15 +135,15 @@ class ModelDef():
|
|
|
125
135
|
name_list = [name_list]
|
|
126
136
|
if type(name_list) is list:
|
|
127
137
|
for name in name_list:
|
|
128
|
-
check(name in self.
|
|
129
|
-
del self.
|
|
138
|
+
check(name in self.__model_dict, IndexError, f"The name {name} is not part of the available models")
|
|
139
|
+
del self.__model_dict[name]
|
|
130
140
|
self.update()
|
|
131
141
|
|
|
132
142
|
def addMinimize(self, name, streamA, streamB, loss_function='mse'):
|
|
133
143
|
check(isinstance(streamA, (Output, Stream)), TypeError, 'streamA must be an instance of Output or Stream')
|
|
134
144
|
check(isinstance(streamB, (Output, Stream)), TypeError, 'streamA must be an instance of Output or Stream')
|
|
135
|
-
check(streamA.dim == streamB.dim, ValueError, f'Dimension of streamA={streamA.dim} and streamB={streamB.dim} are not equal.')
|
|
136
|
-
self.
|
|
145
|
+
#check(streamA.dim == streamB.dim, ValueError, f'Dimension of streamA={streamA.dim} and streamB={streamB.dim} are not equal.')
|
|
146
|
+
self.__minimize_dict[name]={'A':copy.deepcopy(streamA), 'B': copy.deepcopy(streamB), 'loss':loss_function}
|
|
137
147
|
self.update()
|
|
138
148
|
|
|
139
149
|
def removeMinimize(self, name_list):
|
|
@@ -141,23 +151,23 @@ class ModelDef():
|
|
|
141
151
|
name_list = [name_list]
|
|
142
152
|
if type(name_list) is list:
|
|
143
153
|
for name in name_list:
|
|
144
|
-
check(name in self.
|
|
145
|
-
del self.
|
|
154
|
+
check(name in self.__minimize_dict, IndexError, f"The name {name} is not part of the available minimuzes")
|
|
155
|
+
del self.__minimize_dict[name]
|
|
146
156
|
self.update()
|
|
147
157
|
|
|
148
158
|
def setBuildWindow(self, sample_time = None):
|
|
149
|
-
check(self.
|
|
159
|
+
check(self.__json is not None, RuntimeError, "No model is defined!")
|
|
150
160
|
if sample_time is not None:
|
|
151
161
|
check(sample_time > 0, RuntimeError, 'Sample time must be strictly positive!')
|
|
152
|
-
self.
|
|
162
|
+
self.__sample_time = sample_time
|
|
153
163
|
else:
|
|
154
|
-
if self.
|
|
155
|
-
self.
|
|
164
|
+
if self.__sample_time is None:
|
|
165
|
+
self.__sample_time = 1
|
|
156
166
|
|
|
157
|
-
self.
|
|
167
|
+
self.__json['Info'] = {"SampleTime": self.__sample_time}
|
|
158
168
|
|
|
159
|
-
check(self.
|
|
160
|
-
json_inputs = self.
|
|
169
|
+
check(self.__json['Inputs'] | self.__json['States'] != {}, RuntimeError, "No model is defined!")
|
|
170
|
+
json_inputs = self.__json['Inputs'] | self.__json['States']
|
|
161
171
|
|
|
162
172
|
# for key,value in self.json['States'].items():
|
|
163
173
|
# check(closedloop_name in self.json['States'][key].keys() or connect_name in self.json['States'][key].keys(),
|
|
@@ -167,16 +177,16 @@ class ModelDef():
|
|
|
167
177
|
for key, value in json_inputs.items():
|
|
168
178
|
if value['sw'] == [0,0] and value['tw'] == [0,0]:
|
|
169
179
|
assert(False), f"Input '{key}' has no time window or sample window"
|
|
170
|
-
if value['sw'] == [0, 0] and self.
|
|
180
|
+
if value['sw'] == [0, 0] and self.__sample_time is not None:
|
|
171
181
|
## check if value['tw'] is a multiple of sample_time
|
|
172
182
|
absolute_tw = abs(value['tw'][0]) + abs(value['tw'][1])
|
|
173
|
-
check(round(absolute_tw % self.
|
|
183
|
+
check(round(absolute_tw % self.__sample_time) == 0, ValueError,
|
|
174
184
|
f"Time window of input '{key}' is not a multiple of sample time. This network cannot be neuralized")
|
|
175
|
-
input_ns_backward[key] = round(-value['tw'][0] / self.
|
|
176
|
-
input_ns_forward[key] = round(value['tw'][1] / self.
|
|
177
|
-
elif self.
|
|
178
|
-
input_ns_backward[key] = max(round(-value['tw'][0] / self.
|
|
179
|
-
input_ns_forward[key] = max(round(value['tw'][1] / self.
|
|
185
|
+
input_ns_backward[key] = round(-value['tw'][0] / self.__sample_time)
|
|
186
|
+
input_ns_forward[key] = round(value['tw'][1] / self.__sample_time)
|
|
187
|
+
elif self.__sample_time is not None:
|
|
188
|
+
input_ns_backward[key] = max(round(-value['tw'][0] / self.__sample_time), -value['sw'][0])
|
|
189
|
+
input_ns_forward[key] = max(round(value['tw'][1] / self.__sample_time), value['sw'][1])
|
|
180
190
|
else:
|
|
181
191
|
check(value['tw'] == [0,0], RuntimeError, f"Sample time is not defined for input '{key}'")
|
|
182
192
|
input_ns_backward[key] = -value['sw'][0]
|
|
@@ -184,35 +194,28 @@ class ModelDef():
|
|
|
184
194
|
value['ns'] = [input_ns_backward[key], input_ns_forward[key]]
|
|
185
195
|
value['ntot'] = sum(value['ns'])
|
|
186
196
|
|
|
187
|
-
self.
|
|
188
|
-
self.
|
|
189
|
-
if self.
|
|
197
|
+
self.__json['Info']['ns'] = [max(input_ns_backward.values()), max(input_ns_forward.values())]
|
|
198
|
+
self.__json['Info']['ntot'] = sum(self.__json['Info']['ns'])
|
|
199
|
+
if self.__json['Info']['ns'][0] < 0:
|
|
190
200
|
log.warning(
|
|
191
|
-
f"The input is only in the far past the max_samples_backward is: {self.
|
|
192
|
-
if self.
|
|
201
|
+
f"The input is only in the far past the max_samples_backward is: {self.__json['Info']['ns'][0]}")
|
|
202
|
+
if self.__json['Info']['ns'][1] < 0:
|
|
193
203
|
log.warning(
|
|
194
|
-
f"The input is only in the far future the max_sample_forward is: {self.
|
|
204
|
+
f"The input is only in the far future the max_sample_forward is: {self.__json['Info']['ns'][1]}")
|
|
195
205
|
|
|
196
|
-
for k, v in (self.
|
|
206
|
+
for k, v in (self.__json['Parameters'] | self.__json['Constants']).items():
|
|
197
207
|
if 'values' in v:
|
|
198
208
|
window = 'tw' if 'tw' in v.keys() else ('sw' if 'sw' in v.keys() else None)
|
|
199
209
|
if window == 'tw':
|
|
200
|
-
check(np.array(v['values']).shape[0] == v['tw']/self.
|
|
210
|
+
check(np.array(v['values']).shape[0] == v['tw'] / self.__sample_time, ValueError,
|
|
201
211
|
f"{k} has a different number of values for this sample time.")
|
|
202
212
|
if v['values'] == "SampleTime":
|
|
203
|
-
v['values'] = self.
|
|
204
|
-
|
|
213
|
+
v['values'] = self.__sample_time
|
|
205
214
|
|
|
206
|
-
def updateParameters(self, model
|
|
215
|
+
def updateParameters(self, model):
|
|
207
216
|
if model is not None:
|
|
208
|
-
for key in self.
|
|
209
|
-
# if recurrent:
|
|
210
|
-
# if key in model.Cell.all_parameters:
|
|
211
|
-
# self.json['Parameters'][key]['values'] = model.Cell.all_parameters[key].tolist()
|
|
212
|
-
# if 'init_fun' in self.json['Parameters'][key]:
|
|
213
|
-
# del self.json['Parameters'][key]['init_fun']
|
|
214
|
-
# else:
|
|
217
|
+
for key in self.__json['Parameters'].keys():
|
|
215
218
|
if key in model.all_parameters:
|
|
216
|
-
self.
|
|
217
|
-
if 'init_fun' in self.
|
|
218
|
-
del self.
|
|
219
|
+
self.__json['Parameters'][key]['values'] = model.all_parameters[key].tolist()
|
|
220
|
+
if 'init_fun' in self.__json['Parameters'][key]:
|
|
221
|
+
del self.__json['Parameters'][key]['init_fun']
|