Coverage for src/amisc/__init__.py: 96%

57 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2025-01-24 04:51 +0000

1"""Efficient framework for building surrogates of multidisciplinary systems using the adaptive multi-index stochastic 

2collocation (AMISC) technique. 

3 

4- Author - Joshua Eckels (eckelsjd@umich.edu) 

5- License - GPL-3.0 

6 

7The `amisc` package takes an object-oriented approach to building a surrogate of a multidisciplinary system. From the 

8bottom up, you have: 

9 

10- **variables** that serve as inputs and outputs for the models, 

11- **interpolators** that define a specific input → output mathematical relationship to approximate a function, 

12- **components** that wrap a model for a single discipline, and a 

13- **system** that defines the connections between components in a multidisciplinary system. 

14 

15The **system** is ultimately independent of the specific models, interpolation methods, or underlying variables. 

16As such, the primary top-level object that users of the `amisc` package will interact with is the `System`. 

17 

18Variables additionally use `Transform`, `Distribution`, and `Compression` interfaces to manage normalization, PDFs, 

19and field quantity compression, respectively. 

20 

21Currently, only Lagrange polynomial interpolation is implemented as the underlying surrogate method with a 

22sparse grid data structure. SVD is also the only currently implemented method for compression. However, interfaces 

23are provided for `Interpolator`, `TrainingData`, and `Compression` to allow for easy extension to other methods. 

24 

25Here is a class diagram summary of this workflow: 

26 

27``` mermaid 

28classDiagram 

29 namespace Core { 

30 class System { 

31 +list[Component] components 

32 +TrainHistory train_history 

33 +fit() 

34 +predict(x) 

35 } 

36 class Component { 

37 +callable model 

38 +list[Variable] inputs 

39 +list[Variable] outputs 

40 +Interpolator interpolator 

41 +TrainingData training_data 

42 +activate_index(alpha, beta) 

43 +predict(x) 

44 } 

45 class Variable { 

46 +str name 

47 +tuple domain 

48 +Distribution dist 

49 +Transform norm 

50 +Compression comp 

51 +sample() 

52 +normalize() 

53 +compress() 

54 } 

55 } 

56 class Interpolator { 

57 <<abstract>> 

58 + refine() 

59 + predict(x) 

60 } 

61 class TrainingData { 

62 <<abstract>> 

63 +get() 

64 +set() 

65 +refine() 

66 } 

67 class Transform { 

68 <<abstract>> 

69 +transform(values) 

70 } 

71 class Distribution { 

72 <<abstract>> 

73 +sample(size) 

74 +pdf(x) 

75 } 

76 class Compression { 

77 <<abstract>> 

78 +compress(values) 

79 +reconstruct(values) 

80 } 

81 System --o "1..n" Component 

82 Component --o "1..n" Variable 

83 direction TD 

84 Component --* Interpolator 

85 Component --* TrainingData 

86 Variable --o Transform 

87 Variable --o Distribution 

88 Variable --o Compression 

89``` 

90 

91Note how the `System` aggregates the `Component`, which aggregates the `Variable`. In other 

92words, variables can act independently of components, and components can act independently of systems. Components 

93make use of `Interpolator` and `TrainingData` interfaces to manage the underlying surrogate method 

94and training data, respectively. Similarly, `Variables` use `Transform`, `Distribution`, and `Compression` 

95interfaces to manage normalization, PDFs, and field quantity compression, respectively. 

96 

97The `amisc` package also includes a `FileLoader` interface for loading and dumping `amisc` objects to/from file. 

98We recommend using the built-in `YamlLoader` for this purpose, as it includes custom YAML tags for reading/writing 

99`amisc` objects from file. 

100""" 

101from abc import ABC as _ABC 

102from abc import abstractmethod as _abstractmethod 

103from pathlib import Path as _Path 

104from typing import Any as _Any 

105 

106import yaml as _yaml 

107 

108from amisc.component import Component 

109from amisc.system import System 

110from amisc.utils import to_model_dataset, to_surrogate_dataset 

111from amisc.variable import Variable, VariableList 

112 

113__version__ = "0.7.1" 

114__all__ = ['System', 'Component', 'Variable', 'VariableList', 'FileLoader', 'YamlLoader', 

115 'to_model_dataset', 'to_surrogate_dataset'] 

116 

117 

118class FileLoader(_ABC): 

119 """Common interface for loading and dumping `amisc` objects to/from file.""" 

120 

121 @classmethod 

122 @_abstractmethod 

123 def load(cls, stream: str | _Path | _Any, **kwargs): 

124 """Load an `amisc` object from a stream. If a file path is given, will attempt to open the file.""" 

125 raise NotImplementedError 

126 

127 @classmethod 

128 @_abstractmethod 

129 def dump(cls, obj, stream: str | _Path | _Any, **kwargs): 

130 """Save an `amisc` object to a stream. If a file path is given, will attempt to write to the file.""" 

131 raise NotImplementedError 

132 

133 

134class YamlLoader(FileLoader): 

135 """YAML file loader for `amisc` objects.""" 

136 

137 @staticmethod 

138 def _yaml_loader(): 

139 """Custom YAML loader that includes `amisc` object tags.""" 

140 loader = _yaml.Loader 

141 loader.add_constructor(Variable.yaml_tag, Variable._yaml_constructor) 

142 loader.add_constructor(VariableList.yaml_tag, VariableList._yaml_constructor) 

143 loader.add_constructor(Component.yaml_tag, Component._yaml_constructor) 

144 loader.add_constructor(System.yaml_tag, System._yaml_constructor) 

145 return loader 

146 

147 @staticmethod 

148 def _yaml_dumper(): 

149 """Custom YAML dumper that includes `amisc` object tags.""" 

150 dumper = _yaml.Dumper 

151 dumper.add_representer(Variable, Variable._yaml_representer) 

152 dumper.add_representer(VariableList, VariableList._yaml_representer) 

153 dumper.add_representer(Component, Component._yaml_representer) 

154 dumper.add_representer(System, System._yaml_representer) 

155 return dumper 

156 

157 @classmethod 

158 def load(cls, stream, **kwargs): 

159 """Extra kwargs ignored for YAML loading (since they are not used by `yaml.load`).""" 

160 try: 

161 p = _Path(stream).with_suffix('.yml') 

162 except (TypeError, ValueError): 

163 pass 

164 else: 

165 with open(p, 'r', encoding='utf-8') as fd: 

166 return _yaml.load(fd, Loader=cls._yaml_loader()) 

167 

168 return _yaml.load(stream, Loader=cls._yaml_loader()) 

169 

170 @classmethod 

171 def dump(cls, obj, stream, **kwargs): 

172 """Extra kwargs get passed to `yaml.dump`.""" 

173 kwargs['allow_unicode'] = kwargs.get('allow_unicode', True) 

174 kwargs['sort_keys'] = kwargs.get('sort_keys', False) 

175 try: 

176 p = _Path(stream).with_suffix('.yml') 

177 except (TypeError, ValueError): 

178 pass 

179 else: 

180 with open(p, 'w', encoding='utf-8') as fd: 

181 return _yaml.dump(obj, fd, Dumper=cls._yaml_dumper(), **kwargs) 

182 

183 return _yaml.dump(obj, stream, Dumper=cls._yaml_dumper(), **kwargs)