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)
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.
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.
Dictionary of neural networks corresponding to each learned geometric property.
Dictionary of sympy wrappers for the neural networks corresponding to each learned geometric property.
List containing the expected input order of geometric parameters to each neural network.
Dictionary of bounds, scalers, and biases for each geometric parameter in the neural net.
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
- Lengths:
- **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.