symcad.core.ML.NeuralNet

  1#!/usr/bin/env python3
  2# Copyright (C) 2022, Will Hedgecock
  3#
  4# This program is free software: you can redistribute it and/or modify
  5# it under the terms of the GNU General Public License as published by
  6# the Free Software Foundation, either version 3 of the License, or
  7# (at your option) any later version.
  8#
  9# This program is distributed in the hope that it will be useful,
 10# but WITHOUT ANY WARRANTY; without even the implied warranty of
 11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 12# GNU General Public License for more details.
 13#
 14# You should have received a copy of the GNU General Public License
 15# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 16
 17from constraint_prog.sympy_func import NeuralFunc
 18from .NeuralNetTrainer import NeuralNetTrainer
 19from typing import Dict, List, Optional, Tuple, TypeVar, Union
 20from ..CAD import CadGeneral
 21from pathlib import Path
 22import io, tarfile, torch
 23
 24SymPart = TypeVar('SymPart', bound='SymPart')
 25
 26class NeuralNet(object):
 27   """Private container that houses all neural networks for a given `SymPart`."""
 28
 29   # Public attributes ----------------------------------------------------------------------------
 30
 31   networks: Dict[str, torch.ScriptModule]
 32   """Dictionary of neural networks corresponding to each learned geometric property."""
 33
 34   sympy_networks: Dict[str, NeuralFunc]
 35   """Dictionary of sympy wrappers for the neural networks corresponding to each learned
 36   geometric property."""
 37
 38   param_order: List[str]
 39   """List containing the expected input order of geometric parameters to each neural network."""
 40
 41   param_transformations: Dict[str, Tuple[float, float, float, float]]
 42   """Dictionary of bounds, scalers, and biases for each geometric parameter in the neural net."""
 43
 44
 45   # Constructor ----------------------------------------------------------------------------------
 46
 47   def __init__(self, part_type_name: str,
 48                      net_storage_path: str,
 49                      part: Optional[SymPart] = None,
 50                      auto_train_network: Optional[bool] = False) -> None:
 51      """Initializes a container to house all neural networks for the geometric properties of
 52      the given `part`.
 53
 54      If `part` is not specified or is `None`, or if `auto_train_network` is set to `False`, a
 55      `RuntimeError` will be raised if the specified neural network does not already exist. If
 56      `auto_train_network` is `True` and no trained neural network exists, a new neural network
 57      will be trained to learn all available geometric properties for the specified `part`.
 58      """
 59
 60      # Initialize NeuralNet data structure
 61      super().__init__()
 62      self.networks = {}
 63      self.sympy_networks = {}
 64      self.param_order = []
 65      self.param_transformations = {}
 66
 67      # Ensure that the neural network exists and has been trained
 68      net_file_path = CadGeneral.CAD_BASE_PATH.joinpath(net_storage_path).absolute().resolve()
 69      if not net_file_path.exists():
 70         net_file_path = Path(net_storage_path).absolute().resolve()
 71      if not net_file_path.exists():
 72         net_converted_path = Path('converted').joinpath(Path(net_storage_path).stem + '.tar.xz')
 73         net_file_path = CadGeneral.CAD_BASE_PATH.joinpath(net_converted_path)
 74      if not net_file_path.exists():
 75         if auto_train_network and part is not None:
 76            net_file_path = \
 77               CadGeneral.CAD_BASE_PATH.joinpath(net_storage_path).absolute().resolve()
 78            trainer = NeuralNetTrainer(part, ['xlen', 'ylen', 'zlen', 'cg_x', 'cg_y', 'cg_z',
 79                                              'cb_x', 'cb_y', 'cb_z', 'material_volume',
 80                                              'displaced_volume', 'surface_area'])
 81            trainer.learn_parameters(32)
 82            trainer.save(str(net_file_path))
 83         else:
 84            raise RuntimeError('No trained neural network exists at "{}". '
 85                               'Set "auto_train_network" to True or manually train the '
 86                               'required network to continue'.format(net_storage_path))
 87
 88      # Load and parse all trained neural networks within the specified tarball
 89      with tarfile.open(net_file_path, 'r:xz') as zip_file:
 90         for filename in zip_file.getnames():
 91            data = zip_file.extractfile(filename)
 92            if filename == 'param_order.txt':
 93               self.param_order = data.read().decode('utf-8').split(';')
 94            elif filename == 'param_stats.txt':
 95               stats = data.read().decode('utf-8').split(';')
 96               for stat in stats:
 97                  param, val = stat.split(':')
 98                  self.param_transformations[param] = eval(val)
 99            else:
100               network_bytes = io.BytesIO(data.read())
101               network_name = '.'.join(filename.split('.')[:-1])
102               self.networks[network_name] = torch.jit.load(network_bytes)
103               self.networks[network_name].eval()
104      for network_name in self.networks.keys():
105         self.sympy_networks[network_name] = type(part_type_name + '_' + network_name,
106                                                  (NeuralFunc,),
107                                                  {'arity': len(self.param_order),
108                                                   'network': self.networks[network_name]})
109
110
111   # Public methods -------------------------------------------------------------------------------
112
113   def evaluate(self, property: str, **kwargs) -> Union[NeuralFunc, float]:
114      """Evaluates the specified parameters in `**kwargs` through the neural network
115      corresponding to the indicated geometric `property`.
116
117      Parameters
118      ----------
119      property : `str`
120         Geometric property for which the neural network should be evaluated. Available
121         options (if a trained network exists for the corresponding property) are:
122         - Lengths: `xlen`, `ylen`, `zlen`
123         - Centers of Gravity: `cg_x`, `cg_y`, `cg_z`
124         - Centers of Buoyancy: `cb_x`, `cb_y`, `cb_z`
125         - Volume and Area: `material_volume`, `displaced_volume`, `surface_area`
126
127      **kwargs : `Dict`
128         Set of named parameters that define the underlying geometry of this part.
129
130      Returns
131      -------
132      `Union[NeuralFunc, float]`
133         The requested property as evaluated by the underlying neural network.
134      """
135      if all([isinstance(val, float) or isinstance(val, int) for val in kwargs.values()]):
136         inputs = torch.empty(len(self.param_order))
137         for idx, param in enumerate(self.param_order):
138            transforms = self.param_transformations[param]
139            inputs[idx] = (kwargs.get(param) * transforms[2]) + transforms[3]
140         return self.networks[property](inputs).item()
141      else:
142         inputs = []
143         for param in self.param_order:
144            transforms = self.param_transformations[param]
145            inputs.append((kwargs.get(param) * transforms[2]) + transforms[3])
146         return self.sympy_networks[property](*inputs)
class NeuralNet:
 27class NeuralNet(object):
 28   """Private container that houses all neural networks for a given `SymPart`."""
 29
 30   # Public attributes ----------------------------------------------------------------------------
 31
 32   networks: Dict[str, torch.ScriptModule]
 33   """Dictionary of neural networks corresponding to each learned geometric property."""
 34
 35   sympy_networks: Dict[str, NeuralFunc]
 36   """Dictionary of sympy wrappers for the neural networks corresponding to each learned
 37   geometric property."""
 38
 39   param_order: List[str]
 40   """List containing the expected input order of geometric parameters to each neural network."""
 41
 42   param_transformations: Dict[str, Tuple[float, float, float, float]]
 43   """Dictionary of bounds, scalers, and biases for each geometric parameter in the neural net."""
 44
 45
 46   # Constructor ----------------------------------------------------------------------------------
 47
 48   def __init__(self, part_type_name: str,
 49                      net_storage_path: str,
 50                      part: Optional[SymPart] = None,
 51                      auto_train_network: Optional[bool] = False) -> None:
 52      """Initializes a container to house all neural networks for the geometric properties of
 53      the given `part`.
 54
 55      If `part` is not specified or is `None`, or if `auto_train_network` is set to `False`, a
 56      `RuntimeError` will be raised if the specified neural network does not already exist. If
 57      `auto_train_network` is `True` and no trained neural network exists, a new neural network
 58      will be trained to learn all available geometric properties for the specified `part`.
 59      """
 60
 61      # Initialize NeuralNet data structure
 62      super().__init__()
 63      self.networks = {}
 64      self.sympy_networks = {}
 65      self.param_order = []
 66      self.param_transformations = {}
 67
 68      # Ensure that the neural network exists and has been trained
 69      net_file_path = CadGeneral.CAD_BASE_PATH.joinpath(net_storage_path).absolute().resolve()
 70      if not net_file_path.exists():
 71         net_file_path = Path(net_storage_path).absolute().resolve()
 72      if not net_file_path.exists():
 73         net_converted_path = Path('converted').joinpath(Path(net_storage_path).stem + '.tar.xz')
 74         net_file_path = CadGeneral.CAD_BASE_PATH.joinpath(net_converted_path)
 75      if not net_file_path.exists():
 76         if auto_train_network and part is not None:
 77            net_file_path = \
 78               CadGeneral.CAD_BASE_PATH.joinpath(net_storage_path).absolute().resolve()
 79            trainer = NeuralNetTrainer(part, ['xlen', 'ylen', 'zlen', 'cg_x', 'cg_y', 'cg_z',
 80                                              'cb_x', 'cb_y', 'cb_z', 'material_volume',
 81                                              'displaced_volume', 'surface_area'])
 82            trainer.learn_parameters(32)
 83            trainer.save(str(net_file_path))
 84         else:
 85            raise RuntimeError('No trained neural network exists at "{}". '
 86                               'Set "auto_train_network" to True or manually train the '
 87                               'required network to continue'.format(net_storage_path))
 88
 89      # Load and parse all trained neural networks within the specified tarball
 90      with tarfile.open(net_file_path, 'r:xz') as zip_file:
 91         for filename in zip_file.getnames():
 92            data = zip_file.extractfile(filename)
 93            if filename == 'param_order.txt':
 94               self.param_order = data.read().decode('utf-8').split(';')
 95            elif filename == 'param_stats.txt':
 96               stats = data.read().decode('utf-8').split(';')
 97               for stat in stats:
 98                  param, val = stat.split(':')
 99                  self.param_transformations[param] = eval(val)
100            else:
101               network_bytes = io.BytesIO(data.read())
102               network_name = '.'.join(filename.split('.')[:-1])
103               self.networks[network_name] = torch.jit.load(network_bytes)
104               self.networks[network_name].eval()
105      for network_name in self.networks.keys():
106         self.sympy_networks[network_name] = type(part_type_name + '_' + network_name,
107                                                  (NeuralFunc,),
108                                                  {'arity': len(self.param_order),
109                                                   'network': self.networks[network_name]})
110
111
112   # Public methods -------------------------------------------------------------------------------
113
114   def evaluate(self, property: str, **kwargs) -> Union[NeuralFunc, float]:
115      """Evaluates the specified parameters in `**kwargs` through the neural network
116      corresponding to the indicated geometric `property`.
117
118      Parameters
119      ----------
120      property : `str`
121         Geometric property for which the neural network should be evaluated. Available
122         options (if a trained network exists for the corresponding property) are:
123         - Lengths: `xlen`, `ylen`, `zlen`
124         - Centers of Gravity: `cg_x`, `cg_y`, `cg_z`
125         - Centers of Buoyancy: `cb_x`, `cb_y`, `cb_z`
126         - Volume and Area: `material_volume`, `displaced_volume`, `surface_area`
127
128      **kwargs : `Dict`
129         Set of named parameters that define the underlying geometry of this part.
130
131      Returns
132      -------
133      `Union[NeuralFunc, float]`
134         The requested property as evaluated by the underlying neural network.
135      """
136      if all([isinstance(val, float) or isinstance(val, int) for val in kwargs.values()]):
137         inputs = torch.empty(len(self.param_order))
138         for idx, param in enumerate(self.param_order):
139            transforms = self.param_transformations[param]
140            inputs[idx] = (kwargs.get(param) * transforms[2]) + transforms[3]
141         return self.networks[property](inputs).item()
142      else:
143         inputs = []
144         for param in self.param_order:
145            transforms = self.param_transformations[param]
146            inputs.append((kwargs.get(param) * transforms[2]) + transforms[3])
147         return self.sympy_networks[property](*inputs)

Private container that houses all neural networks for a given SymPart.

NeuralNet( part_type_name: str, net_storage_path: str, part: Optional[~SymPart] = None, auto_train_network: Optional[bool] = False)
 48   def __init__(self, part_type_name: str,
 49                      net_storage_path: str,
 50                      part: Optional[SymPart] = None,
 51                      auto_train_network: Optional[bool] = False) -> None:
 52      """Initializes a container to house all neural networks for the geometric properties of
 53      the given `part`.
 54
 55      If `part` is not specified or is `None`, or if `auto_train_network` is set to `False`, a
 56      `RuntimeError` will be raised if the specified neural network does not already exist. If
 57      `auto_train_network` is `True` and no trained neural network exists, a new neural network
 58      will be trained to learn all available geometric properties for the specified `part`.
 59      """
 60
 61      # Initialize NeuralNet data structure
 62      super().__init__()
 63      self.networks = {}
 64      self.sympy_networks = {}
 65      self.param_order = []
 66      self.param_transformations = {}
 67
 68      # Ensure that the neural network exists and has been trained
 69      net_file_path = CadGeneral.CAD_BASE_PATH.joinpath(net_storage_path).absolute().resolve()
 70      if not net_file_path.exists():
 71         net_file_path = Path(net_storage_path).absolute().resolve()
 72      if not net_file_path.exists():
 73         net_converted_path = Path('converted').joinpath(Path(net_storage_path).stem + '.tar.xz')
 74         net_file_path = CadGeneral.CAD_BASE_PATH.joinpath(net_converted_path)
 75      if not net_file_path.exists():
 76         if auto_train_network and part is not None:
 77            net_file_path = \
 78               CadGeneral.CAD_BASE_PATH.joinpath(net_storage_path).absolute().resolve()
 79            trainer = NeuralNetTrainer(part, ['xlen', 'ylen', 'zlen', 'cg_x', 'cg_y', 'cg_z',
 80                                              'cb_x', 'cb_y', 'cb_z', 'material_volume',
 81                                              'displaced_volume', 'surface_area'])
 82            trainer.learn_parameters(32)
 83            trainer.save(str(net_file_path))
 84         else:
 85            raise RuntimeError('No trained neural network exists at "{}". '
 86                               'Set "auto_train_network" to True or manually train the '
 87                               'required network to continue'.format(net_storage_path))
 88
 89      # Load and parse all trained neural networks within the specified tarball
 90      with tarfile.open(net_file_path, 'r:xz') as zip_file:
 91         for filename in zip_file.getnames():
 92            data = zip_file.extractfile(filename)
 93            if filename == 'param_order.txt':
 94               self.param_order = data.read().decode('utf-8').split(';')
 95            elif filename == 'param_stats.txt':
 96               stats = data.read().decode('utf-8').split(';')
 97               for stat in stats:
 98                  param, val = stat.split(':')
 99                  self.param_transformations[param] = eval(val)
100            else:
101               network_bytes = io.BytesIO(data.read())
102               network_name = '.'.join(filename.split('.')[:-1])
103               self.networks[network_name] = torch.jit.load(network_bytes)
104               self.networks[network_name].eval()
105      for network_name in self.networks.keys():
106         self.sympy_networks[network_name] = type(part_type_name + '_' + network_name,
107                                                  (NeuralFunc,),
108                                                  {'arity': len(self.param_order),
109                                                   'network': self.networks[network_name]})

Initializes a container to house all neural networks for the geometric properties of the given part.

If part is not specified or is None, or if auto_train_network is set to False, a RuntimeError will be raised if the specified neural network does not already exist. If auto_train_network is True and no trained neural network exists, a new neural network will be trained to learn all available geometric properties for the specified part.

networks: Dict[str, torch.ScriptModule]

Dictionary of neural networks corresponding to each learned geometric property.

sympy_networks: Dict[str, constraint_prog.sympy_func.NeuralFunc]

Dictionary of sympy wrappers for the neural networks corresponding to each learned geometric property.

param_order: List[str]

List containing the expected input order of geometric parameters to each neural network.

param_transformations: Dict[str, Tuple[float, float, float, float]]

Dictionary of bounds, scalers, and biases for each geometric parameter in the neural net.

def evaluate( self, property: str, **kwargs) -> Union[constraint_prog.sympy_func.NeuralFunc, float]:
114   def evaluate(self, property: str, **kwargs) -> Union[NeuralFunc, float]:
115      """Evaluates the specified parameters in `**kwargs` through the neural network
116      corresponding to the indicated geometric `property`.
117
118      Parameters
119      ----------
120      property : `str`
121         Geometric property for which the neural network should be evaluated. Available
122         options (if a trained network exists for the corresponding property) are:
123         - Lengths: `xlen`, `ylen`, `zlen`
124         - Centers of Gravity: `cg_x`, `cg_y`, `cg_z`
125         - Centers of Buoyancy: `cb_x`, `cb_y`, `cb_z`
126         - Volume and Area: `material_volume`, `displaced_volume`, `surface_area`
127
128      **kwargs : `Dict`
129         Set of named parameters that define the underlying geometry of this part.
130
131      Returns
132      -------
133      `Union[NeuralFunc, float]`
134         The requested property as evaluated by the underlying neural network.
135      """
136      if all([isinstance(val, float) or isinstance(val, int) for val in kwargs.values()]):
137         inputs = torch.empty(len(self.param_order))
138         for idx, param in enumerate(self.param_order):
139            transforms = self.param_transformations[param]
140            inputs[idx] = (kwargs.get(param) * transforms[2]) + transforms[3]
141         return self.networks[property](inputs).item()
142      else:
143         inputs = []
144         for param in self.param_order:
145            transforms = self.param_transformations[param]
146            inputs.append((kwargs.get(param) * transforms[2]) + transforms[3])
147         return self.sympy_networks[property](*inputs)

Evaluates the specified parameters in **kwargs through the neural network corresponding to the indicated geometric property.

Parameters
  • property (str): Geometric property for which the neural network should be evaluated. Available options (if a trained network exists for the corresponding property) are:
    • Lengths: xlen, ylen, zlen
    • Centers of Gravity: cg_x, cg_y, cg_z
    • Centers of Buoyancy: cb_x, cb_y, cb_z
    • Volume and Area: material_volume, displaced_volume, surface_area
  • **kwargs (Dict): Set of named parameters that define the underlying geometry of this part.
Returns
  • Union[NeuralFunc, float]: The requested property as evaluated by the underlying neural network.