Skip to content

Commit

Permalink
feat: completes overhaul to v0.5.0
Browse files Browse the repository at this point in the history
BREAKING CHANGE: mostly all APIs affected, resolves #23, #20, #19, #17, #16, #12, #11, #10, and JANUS-Institute/HallThrusterPEM#6
  • Loading branch information
eckelsjd committed Nov 4, 2024
1 parent a1331b2 commit 0afceb8
Show file tree
Hide file tree
Showing 22 changed files with 106 additions and 74 deletions.
File renamed without changes.
5 changes: 3 additions & 2 deletions src/amisc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,16 +98,17 @@ class Compression {
We recommend using the built-in `YamlLoader` for this purpose, as it includes custom YAML tags for reading/writing
`amisc` objects from file.
"""
from abc import ABC as _ABC, abstractmethod as _abstractmethod
from abc import ABC as _ABC
from abc import abstractmethod as _abstractmethod
from pathlib import Path as _Path
from typing import Any as _Any

import yaml as _yaml

from amisc.variable import Variable, VariableList
from amisc.component import Component
from amisc.system import System
from amisc.utils import to_model_dataset, to_surrogate_dataset
from amisc.variable import Variable, VariableList

__version__ = "0.4.0"
__all__ = ['System', 'Component', 'Variable', 'VariableList', 'FileLoader', 'YamlLoader',
Expand Down
31 changes: 20 additions & 11 deletions src/amisc/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,21 @@
from pydantic import BaseModel, ConfigDict, ValidationInfo, field_validator
from typing_extensions import TypedDict

from amisc.interpolator import InterpolatorState, Interpolator, Lagrange
from amisc.serialize import YamlSerializable, PickleSerializable, Serializable, StringSerializable
from amisc.training import TrainingData, SparseGrid
from amisc.typing import MultiIndex, Dataset, LATENT_STR_ID
from amisc.utils import get_logger, format_inputs, format_outputs, search_for_file, to_model_dataset, \
to_surrogate_dataset
from amisc.utils import _get_yaml_path, _inspect_assignment, _inspect_function
from amisc.interpolator import Interpolator, InterpolatorState, Lagrange
from amisc.serialize import PickleSerializable, Serializable, StringSerializable, YamlSerializable
from amisc.training import SparseGrid, TrainingData
from amisc.typing import LATENT_STR_ID, Dataset, MultiIndex
from amisc.utils import (
_get_yaml_path,
_inspect_assignment,
_inspect_function,
format_inputs,
format_outputs,
get_logger,
search_for_file,
to_model_dataset,
to_surrogate_dataset,
)
from amisc.variable import Variable, VariableList

__all__ = ["ModelKwargs", "StringKwargs", "IndexSet", "MiscTree", "Component"]
Expand Down Expand Up @@ -706,15 +714,16 @@ def _match_index_set(self, index_set, misc_coeff):
case 'test':
misc_coeff = self.misc_coeff_test
case other:
raise ValueError(f"Index set must be 'train' or 'test' if you do not provide `misc_coeff`.")
raise ValueError(f"Index set must be 'train' or 'test' if you do not provide `misc_coeff`. "
f"{other} not recognized.")
if isinstance(index_set, str):
match index_set:
case 'train':
index_set = self.active_set
case 'test':
index_set = self.active_set.union(self.candidate_set)
case other:
raise ValueError(f"Index set must be 'train' or 'test'.")
raise ValueError(f"Index set must be 'train' or 'test'. {other} not recognized.")

return index_set, misc_coeff

Expand Down Expand Up @@ -868,7 +877,7 @@ def call_model(self, inputs: dict | Dataset,
ret = (ret,) if not isinstance(ret, tuple) else ret
ret = {out_var.name: ret[i] for i, out_var in enumerate(self.outputs)}
results.append(ret)
except Exception as e:
except Exception:
results.append({'inputs': {k: v[i] for k, v in inputs.items()}, 'index': i,
'model_kwargs': kwargs.copy(), 'error': traceback.format_exc()})
else: # Parallel
Expand All @@ -893,7 +902,7 @@ def call_model(self, inputs: dict | Dataset,
ret = (ret,) if not isinstance(ret, tuple) else ret
ret = {out_var.name: ret[i] for i, out_var in enumerate(self.outputs)}
results.append(ret)
except Exception as e:
except Exception:
results.append({'inputs': {k: v[i] for k, v in inputs.items()}, 'index': i,
'model_kwargs': kwargs.copy(), 'error': traceback.format_exc()})

Expand Down
2 changes: 1 addition & 1 deletion src/amisc/distribution.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"""
from __future__ import annotations

from abc import abstractmethod, ABC
from abc import ABC, abstractmethod

import numpy as np

Expand Down
4 changes: 2 additions & 2 deletions src/amisc/examples/tutorial.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def fun2(y):

def single_component():
# --8<-- [start:single]
from amisc import Variable, Component, System
from amisc import Component, System, Variable

def fun(inputs):
return {'y': inputs['x'] ** 2}
Expand Down Expand Up @@ -69,7 +69,7 @@ def field_quantity():
# --8<-- [start:field_qty]
import numpy as np

from amisc import Variable, Component, System, to_model_dataset
from amisc import Component, System, Variable, to_model_dataset
from amisc.compression import SVD

def my_model(inputs):
Expand Down
17 changes: 9 additions & 8 deletions src/amisc/interpolator.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@

import numpy as np

from amisc.serialize import Serializable, Base64Serializable, StringSerializable
from amisc.typing import MultiIndex, Dataset
from amisc.serialize import Base64Serializable, Serializable, StringSerializable
from amisc.typing import Dataset, MultiIndex

__all__ = ["InterpolatorState", "LagrangeState", "Interpolator", "Lagrange"]

Expand Down Expand Up @@ -120,7 +120,7 @@ def from_dict(cls, config: dict) -> Interpolator:
case 'lagrange':
return Lagrange(**config)
case other:
raise NotImplementedError(f"Unknown interpolator method: {method}")
raise NotImplementedError(f"Unknown interpolator method: {other}")


@dataclass
Expand Down Expand Up @@ -401,7 +401,8 @@ def hessian(self, x: Dataset, state: LagrangeState, training_data: tuple[Dataset
curr_j_idx = div_zero_idx[..., k, j[k]]
other_j_idx = np.any(other_pts[..., k, :], axis=-1)
dLJ_dx[curr_j_idx] = -np.nansum((w_j[k, p_idx] / w_j[k, j[k]]) /
(x_arr[curr_j_idx, k, np.newaxis] - x_j[k, p_idx]), axis=-1)
(x_arr[curr_j_idx, k, np.newaxis] - x_j[k, p_idx]),
axis=-1)
dLJ_dx[other_j_idx] = ((w_j[k, j[k]] / w_j_large[other_pts[..., k, :]]) /
(x_arr[other_j_idx, k] - x_j[k, j[k]]))

Expand Down Expand Up @@ -430,11 +431,11 @@ def hessian(self, x: Dataset, state: LagrangeState, training_data: tuple[Dataset

# if these points are at the current j interpolation point
d2LJ_dx2[curr_j_idx] = (2 * np.nansum((w_j[m, p_idx] / w_j[m, j[m]]) /
(x_arr[curr_j_idx, m, np.newaxis] - x_j[m, p_idx]),
axis=-1) ** 2 + # noqa: E501
(x_arr[curr_j_idx, m, np.newaxis] - x_j[m, p_idx]), # noqa: E501
axis=-1) ** 2 +
2 * np.nansum((w_j[m, p_idx] / w_j[m, j[m]]) /
(x_arr[curr_j_idx, m, np.newaxis] - x_j[m, p_idx]) ** 2,
axis=-1)) # noqa: E501
(x_arr[curr_j_idx, m, np.newaxis] - x_j[m, p_idx]) ** 2, # noqa: E501
axis=-1))

# if these points are at any other interpolation point
other_pts_inv = other_pts.copy()
Expand Down
30 changes: 19 additions & 11 deletions src/amisc/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@
import random
import string
import time
from collections import ChainMap, deque, UserList
from concurrent.futures import Executor, wait, ALL_COMPLETED
from collections import ChainMap, UserList, deque
from concurrent.futures import ALL_COMPLETED, Executor, wait
from datetime import timezone
from pathlib import Path
from typing import ClassVar, Annotated, Optional, Callable, Literal
from typing import Annotated, Callable, ClassVar, Literal, Optional

import matplotlib.pyplot as plt
import networkx as nx
Expand All @@ -46,10 +46,18 @@

from amisc.component import Component, IndexSet, MiscTree
from amisc.serialize import Serializable, _builtin
from amisc.utils import get_logger, format_inputs, format_outputs, constrained_lls, relative_error, to_model_dataset, \
to_surrogate_dataset, _combine_latent_arrays
from amisc.typing import LATENT_STR_ID, Dataset, MultiIndex, TrainIteration
from amisc.utils import (
_combine_latent_arrays,
constrained_lls,
format_inputs,
format_outputs,
get_logger,
relative_error,
to_model_dataset,
to_surrogate_dataset,
)
from amisc.variable import VariableList
from amisc.typing import Dataset, TrainIteration, MultiIndex, LATENT_STR_ID

__all__ = ['TrainHistory', 'System']

Expand Down Expand Up @@ -650,8 +658,8 @@ def fit(self, targets: list = None,
var.update_domain(var.denormalize(new_domain), override=True)
del y_samples
else:
self.logger.warning(f'Could not estimate bounds for coupling variables: no test set provided. '
f'Make sure you manually provide (good) coupling variable domains.')
self.logger.warning('Could not estimate bounds for coupling variables: no test set provided. '
'Make sure you manually provide (good) coupling variable domains.')

# Track convergence progress on the error indicator and test set (plot to file)
if self.root_dir is not None:
Expand Down Expand Up @@ -1153,8 +1161,8 @@ def _end_conditions_met():
start_idx = 0
for j, var in enumerate(coupling_prev):
end_idx = start_idx + xdims[j]
coupling_snap[:, start_idx:end_idx, i] = coupling_iter[var][samples.curr_idx, ...].reshape((N_curr, -1))
res_snap[:, start_idx:end_idx, i] = residual_iter[var][samples.curr_idx, ...].reshape((N_curr, -1))
coupling_snap[:, start_idx:end_idx, i] = coupling_iter[var][samples.curr_idx, ...].reshape((N_curr, -1)) # noqa: E501
res_snap[:, start_idx:end_idx, i] = residual_iter[var][samples.curr_idx, ...].reshape((N_curr, -1)) # noqa: E501
start_idx = end_idx
C = np.ones((N_curr, 1, mk))
b = np.zeros((N_curr, N_couple, 1))
Expand All @@ -1164,7 +1172,7 @@ def _end_conditions_met():
start_idx = 0
for j, var in enumerate(coupling_prev):
end_idx = start_idx + xdims[j]
coupling_prev[var][samples.curr_idx, ...] = coupling_new[:, start_idx:end_idx].reshape((N_curr, *var_shapes[j]))
coupling_prev[var][samples.curr_idx, ...] = coupling_new[:, start_idx:end_idx].reshape((N_curr, *var_shapes[j])) # noqa: E501
start_idx = end_idx
k += 1

Expand Down
14 changes: 7 additions & 7 deletions src/amisc/training.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@
import copy
import itertools
from abc import ABC, abstractmethod
from dataclasses import field, dataclass
from dataclasses import dataclass, field
from typing import Any, ClassVar

import numpy as np
from numpy.typing import ArrayLike
from scipy.optimize import direct
from sklearn.linear_model import Ridge
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import MaxAbsScaler
from numpy.typing import ArrayLike

from amisc.serialize import PickleSerializable, Serializable
from amisc.typing import MultiIndex, Dataset, LATENT_STR_ID
from amisc.typing import LATENT_STR_ID, Dataset, MultiIndex

__all__ = ['TrainingData', 'SparseGrid']

Expand Down Expand Up @@ -124,7 +124,7 @@ def from_dict(cls, config: dict) -> TrainingData:
case 'sparse-grid':
return SparseGrid(**config)
case other:
raise NotImplementedError(f"Unknown training data method: {method}")
raise NotImplementedError(f"Unknown training data method: {other}")


@dataclass
Expand Down Expand Up @@ -245,7 +245,7 @@ def set(self, alpha: MultiIndex, beta: MultiIndex, coords: list, yi_dict: dict[s
new_yi = {}
for var, yi in yi_dict.items():
yi = np.atleast_1d(yi[i])
new_yi[var] = (float(yi[0]) if self._is_numeric(yi) else yi[0]) if self._is_singleton(yi) else yi.tolist()
new_yi[var] = (float(yi[0]) if self._is_numeric(yi) else yi[0]) if self._is_singleton(yi) else yi.tolist() # noqa: E501
self.yi_map[alpha][coord] = copy.deepcopy(new_yi)

def impute_missing_data(self, alpha: MultiIndex, beta: MultiIndex):
Expand Down Expand Up @@ -419,7 +419,7 @@ def _numeric_outputs(cls, yi_dict: dict[str, ArrayLike]) -> list[str]:
try:
if cls._is_numeric(np.atleast_1d(yi_dict[var])):
output_vars.append(var)
except:
except Exception:
continue
return output_vars

Expand Down Expand Up @@ -507,6 +507,6 @@ def collocation_1d(N: int, z_bds: tuple, z_pts: np.ndarray = None,
z_star = res.x
z_pts = np.concatenate((z_pts, z_star))
case other:
raise NotImplementedError(f"Unknown collocation method: {method}")
raise NotImplementedError(f"Unknown collocation method: {other}")

return z_pts
3 changes: 2 additions & 1 deletion src/amisc/transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ def from_string(cls, transform_spec: str | list[str]) -> list[Transform] | None:
default, `(a, b)` is the Variable's domain and `(l, u)` is `(0, 1)`. Use simply as `minmax`
to use all defaults.
- **zscore** — $x_{norm} = \\frac{x - m}{s}$ specified as `zscore(m, s)` or `zscore(mu=m, std=s)`. If the
Variable is specified as `distribution=normal`, then `zscore` defaults to the Variable's own `mu, std`.
Variable is specified as `distribution=normal`, then `zscore` defaults to the Variable's
own `mu, std`.
!!! Example
```python
Expand Down
4 changes: 1 addition & 3 deletions src/amisc/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,9 @@
from pathlib import Path as _Path
from typing import Optional as _Optional

from typing_extensions import TypedDict as _TypedDict

import numpy as _np
from numpy.typing import ArrayLike as _ArrayLike

from typing_extensions import TypedDict as _TypedDict

__all__ = ["MultiIndex", "Dataset", "TrainIteration", "CompressionData", "LATENT_STR_ID"]

Expand Down
15 changes: 9 additions & 6 deletions src/amisc/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,18 @@
import re
import sys
from pathlib import Path
from typing import TYPE_CHECKING

import numpy as np
import yaml

__all__ = ['parse_function_string', 'relative_error', 'get_logger', 'format_inputs', 'format_outputs',
'search_for_file', 'constrained_lls', 'to_surrogate_dataset', 'to_model_dataset']

import amisc.variable
from amisc.typing import Dataset, LATENT_STR_ID
from amisc.typing import LATENT_STR_ID, Dataset

if TYPE_CHECKING:
import amisc.variable

LOG_FORMATTER = logging.Formatter(u"%(asctime)s — [%(levelname)s] — %(name)-15s — %(message)s")

Expand All @@ -44,7 +47,7 @@ def _combine_latent_arrays(arr):
del arr[var]


def to_surrogate_dataset(dataset: Dataset, variables: amisc.variable.VariableList, del_fields: bool = True,
def to_surrogate_dataset(dataset: Dataset, variables: 'amisc.variable.VariableList', del_fields: bool = True,
**field_coords) -> tuple[Dataset, list[str]]:
"""Convert true model input/output dataset to a form usable by the surrogate. Primarily, compress field
quantities and normalize.
Expand Down Expand Up @@ -80,7 +83,7 @@ def to_surrogate_dataset(dataset: Dataset, variables: amisc.variable.VariableLis
return dataset, surr_vars


def to_model_dataset(dataset: Dataset, variables: amisc.variable.VariableList, del_latent: bool = True,
def to_model_dataset(dataset: Dataset, variables: 'amisc.variable.VariableList', del_latent: bool = True,
**field_coords) -> tuple[Dataset, Dataset]:
"""Convert surrogate input/output dataset to a form usable by the true model. Primarily, reconstruct
field quantities and denormalize.
Expand Down Expand Up @@ -181,7 +184,7 @@ def visit_Return(self, node):
return_visitor.visit(tree)

return pos_args, return_visitor.return_values
except:
except Exception:
return [], []


Expand Down Expand Up @@ -221,7 +224,7 @@ def __init__(self):
if isinstance(assignment.value, ast.Call) and isinstance(assignment.value.func, ast.Name):
if assignment.value.func.id == class_name:
variable_name = target_name
except:
except Exception:
variable_name = None
finally:
# del current_frame, caller_frame
Expand Down
8 changes: 4 additions & 4 deletions src/amisc/variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@
from pydantic import BaseModel, ConfigDict, ValidationInfo, field_validator

from amisc.compression import Compression
from amisc.distribution import Distribution, Normal, Uniform, LogUniform
from amisc.distribution import Distribution, LogUniform, Normal, Uniform
from amisc.serialize import Serializable
from amisc.transform import Transform, Minmax, Zscore
from amisc.utils import search_for_file, _get_yaml_path, _inspect_assignment
from amisc.typing import CompressionData, LATENT_STR_ID
from amisc.transform import Minmax, Transform, Zscore
from amisc.typing import LATENT_STR_ID, CompressionData
from amisc.utils import _get_yaml_path, _inspect_assignment, search_for_file

__all__ = ['Variable', 'VariableList']
_TransformLike = Union[str, Transform, list[str | Transform]] # something that can be converted to a Transform
Expand Down
2 changes: 1 addition & 1 deletion tests/profile_fit.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import numpy as np
from matplotlib import pyplot as plt

from amisc import Variable, Component, System
from amisc import Component, System, Variable


def io_bound_model(inputs, model_cost=1):
Expand Down
Loading

0 comments on commit 0afceb8

Please sign in to comment.