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
« 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.
4- Author - Joshua Eckels (eckelsjd@umich.edu)
5- License - GPL-3.0
7The `amisc` package takes an object-oriented approach to building a surrogate of a multidisciplinary system. From the
8bottom up, you have:
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.
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`.
18Variables additionally use `Transform`, `Distribution`, and `Compression` interfaces to manage normalization, PDFs,
19and field quantity compression, respectively.
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.
25Here is a class diagram summary of this workflow:
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```
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.
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
106import yaml as _yaml
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
113__version__ = "0.7.1"
114__all__ = ['System', 'Component', 'Variable', 'VariableList', 'FileLoader', 'YamlLoader',
115 'to_model_dataset', 'to_surrogate_dataset']
118class FileLoader(_ABC):
119 """Common interface for loading and dumping `amisc` objects to/from file."""
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
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
134class YamlLoader(FileLoader):
135 """YAML file loader for `amisc` objects."""
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
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
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())
168 return _yaml.load(stream, Loader=cls._yaml_loader())
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)
183 return _yaml.dump(obj, stream, Dumper=cls._yaml_dumper(), **kwargs)