diff --git a/.github/workflows/build-lint-test.yml b/.github/workflows/build-lint-test.yml index a049f1a96..ce42b20e8 100644 --- a/.github/workflows/build-lint-test.yml +++ b/.github/workflows/build-lint-test.yml @@ -20,6 +20,7 @@ jobs: run: | python -m pip install --upgrade pip pip install ".[dev]" + pip install -r requirements-fmt.txt - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names @@ -29,6 +30,11 @@ jobs: - name: Type check with mypy run: | mypy --config-file mypy.ini + - uses: omnilib/ufmt@action-v1 + with: + path: aespych tests tests_gpu + requirements: requirements-fmt.txt + python-version: "3.10" build-test: diff --git a/aepsych/__init__.py b/aepsych/__init__.py index 47960f050..9cc33de2c 100644 --- a/aepsych/__init__.py +++ b/aepsych/__init__.py @@ -8,7 +8,6 @@ import sys import torch - from gpytorch.likelihoods import BernoulliLikelihood, GaussianLikelihood from . import ( diff --git a/aepsych/acquisition/bvn.py b/aepsych/acquisition/bvn.py index 9bb10f346..e07a8f500 100644 --- a/aepsych/acquisition/bvn.py +++ b/aepsych/acquisition/bvn.py @@ -7,8 +7,8 @@ from math import pi as _pi from typing import Tuple -import torch +import torch inv_2pi = 1 / (2 * _pi) _neg_inv_sqrt2 = -1 / (2**0.5) diff --git a/aepsych/acquisition/lookahead.py b/aepsych/acquisition/lookahead.py index 08124f5b8..67fcda47b 100644 --- a/aepsych/acquisition/lookahead.py +++ b/aepsych/acquisition/lookahead.py @@ -5,7 +5,7 @@ # This source code is licensed under the license found in the # LICENSE file in the root directory of this source tree. -from typing import Any, Callable, Dict, Optional, Tuple, cast +from typing import Any, Callable, cast, Dict, Optional, Tuple import numpy as np import torch @@ -185,7 +185,7 @@ def _compute_acqf(self, Px: Tensor, P1: Tensor, P0: Tensor, py1: Tensor) -> Tens def construct_inputs_local_lookahead( model: GPyTorchModel, training_data: None, - lookahead_type: str ="levelset", + lookahead_type: str = "levelset", target: Optional[float] = None, posterior_transform: Optional[PosteriorTransform] = None, **kwargs, @@ -350,7 +350,6 @@ def __init__( Xq: Optional[Tensor] = None, k: Optional[float] = 20.0, ) -> None: - super().__init__( model=model, target=target, diff --git a/aepsych/acquisition/lse.py b/aepsych/acquisition/lse.py index 18c8e78c1..a4a7982bb 100644 --- a/aepsych/acquisition/lse.py +++ b/aepsych/acquisition/lse.py @@ -97,7 +97,6 @@ def construct_inputs_lse( sampler: Optional[MCSampler] = None, **kwargs, ) -> Dict[str, Any]: - return { "model": model, "objective": objective, diff --git a/aepsych/acquisition/mutual_information.py b/aepsych/acquisition/mutual_information.py index b3e420e34..56edf70ec 100644 --- a/aepsych/acquisition/mutual_information.py +++ b/aepsych/acquisition/mutual_information.py @@ -5,8 +5,7 @@ # This source code is licensed under the license found in the # LICENSE file in the root directory of this source tree. -r""" -""" +r""" """ from __future__ import annotations diff --git a/aepsych/acquisition/objective/__init__.py b/aepsych/acquisition/objective/__init__.py index 6cbba9050..9e5b3b018 100644 --- a/aepsych/acquisition/objective/__init__.py +++ b/aepsych/acquisition/objective/__init__.py @@ -17,7 +17,6 @@ ) from .semi_p import SemiPProbabilityObjective, SemiPThresholdObjective - __all__ = [ "AEPsychObjective", "FloorGumbelObjective", diff --git a/aepsych/acquisition/objective/objective.py b/aepsych/acquisition/objective/objective.py index da0258f53..ebd821dc4 100644 --- a/aepsych/acquisition/objective/objective.py +++ b/aepsych/acquisition/objective/objective.py @@ -60,7 +60,7 @@ class FloorLinkObjective(AEPsychObjective): the probability is known not to go below it. """ - def __init__(self, floor: float=0.5) -> None: + def __init__(self, floor: float = 0.5) -> None: self.floor = floor super().__init__() diff --git a/aepsych/acquisition/objective/semi_p.py b/aepsych/acquisition/objective/semi_p.py index 97293f014..34f53195e 100644 --- a/aepsych/acquisition/objective/semi_p.py +++ b/aepsych/acquisition/objective/semi_p.py @@ -66,7 +66,6 @@ def forward(self, samples: Tensor, X: Tensor) -> Tensor: @classmethod def from_config(cls, config: Config) -> SemiPProbabilityObjective: - classname = cls.__name__ likelihood_cls = config.getobj(classname, "likelihood", fallback=None) @@ -87,7 +86,13 @@ class SemiPThresholdObjective(SemiPObjectiveBase): that gives the threshold distribution. """ - def __init__(self, target: float, likelihood: Optional[LinearBernoulliLikelihood] =None, *args, **kwargs): + def __init__( + self, + target: float, + likelihood: Optional[LinearBernoulliLikelihood] = None, + *args, + **kwargs, + ): """Evaluates the probability objective. Args: @@ -116,7 +121,6 @@ def forward(self, samples: Tensor, X: Optional[Tensor] = None) -> Tensor: @classmethod def from_config(cls, config: Config) -> SemiPThresholdObjective: - classname = cls.__name__ likelihood_cls = config.getobj(classname, "likelihood", fallback=None) diff --git a/aepsych/benchmark/__init__.py b/aepsych/benchmark/__init__.py index 4860615f9..d393ced77 100644 --- a/aepsych/benchmark/__init__.py +++ b/aepsych/benchmark/__init__.py @@ -6,14 +6,14 @@ # LICENSE file in the root directory of this source tree. from .benchmark import Benchmark, DerivedValue -from .pathos_benchmark import PathosBenchmark, run_benchmarks_with_checkpoints -from .problem import LSEProblem, LSEProblemWithEdgeLogging, Problem from .example_problems import ( - DiscrimLowDim, + ContrastSensitivity6d, DiscrimHighDim, + DiscrimLowDim, Hartmann6Binary, - ContrastSensitivity6d, ) +from .pathos_benchmark import PathosBenchmark, run_benchmarks_with_checkpoints +from .problem import LSEProblem, LSEProblemWithEdgeLogging, Problem from .test_functions import ( discrim_highdim, make_songetal_testfun, diff --git a/aepsych/benchmark/example_problems.py b/aepsych/benchmark/example_problems.py index b8c7bdeac..9e130a2d5 100644 --- a/aepsych/benchmark/example_problems.py +++ b/aepsych/benchmark/example_problems.py @@ -1,16 +1,16 @@ # Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved. import os from typing import List, Optional, Union + import numpy as np import torch -from aepsych.models import GPClassificationModel +from aepsych.benchmark.problem import LSEProblemWithEdgeLogging from aepsych.benchmark.test_functions import ( - modified_hartmann6, discrim_highdim, + modified_hartmann6, novel_discrimination_testfun, ) -from aepsych.benchmark.problem import LSEProblemWithEdgeLogging - +from aepsych.models import GPClassificationModel """The DiscrimLowDim, DiscrimHighDim, ContrastSensitivity6d, and Hartmann6Binary classes are copied from bernoulli_lse github repository (https://github.com/facebookresearch/bernoulli_lse) @@ -21,7 +21,9 @@ class DiscrimLowDim(LSEProblemWithEdgeLogging): name = "discrim_lowdim" bounds = torch.tensor([[-1, 1], [-1, 1]], dtype=torch.double).T - def __init__(self, thresholds: Union[float, List, torch.Tensor, None] = None) -> None: + def __init__( + self, thresholds: Union[float, List, torch.Tensor, None] = None + ) -> None: thresholds = 0.75 if thresholds is None else thresholds super().__init__(thresholds=thresholds) @@ -45,7 +47,9 @@ class DiscrimHighDim(LSEProblemWithEdgeLogging): dtype=torch.double, ).T - def __init__(self, thresholds: Union[float, List, torch.Tensor, None] = None) -> None: + def __init__( + self, thresholds: Union[float, List, torch.Tensor, None] = None + ) -> None: thresholds = 0.75 if thresholds is None else thresholds super().__init__(thresholds=thresholds) @@ -62,7 +66,9 @@ class Hartmann6Binary(LSEProblemWithEdgeLogging): ) ) - def __init__(self, thresholds: Union[float, List, torch.Tensor, None] = None) -> None: + def __init__( + self, thresholds: Union[float, List, torch.Tensor, None] = None + ) -> None: thresholds = 0.5 if thresholds is None else thresholds super().__init__(thresholds=thresholds) @@ -83,7 +89,9 @@ class ContrastSensitivity6d(LSEProblemWithEdgeLogging): dtype=torch.double, ).T - def __init__(self, thresholds: Union[float, List, torch.Tensor, None] = None) -> None: + def __init__( + self, thresholds: Union[float, List, torch.Tensor, None] = None + ) -> None: thresholds = 0.75 if thresholds is None else thresholds super().__init__(thresholds=thresholds) diff --git a/aepsych/benchmark/pathos_benchmark.py b/aepsych/benchmark/pathos_benchmark.py index 1a835fa81..f5e4a6643 100644 --- a/aepsych/benchmark/pathos_benchmark.py +++ b/aepsych/benchmark/pathos_benchmark.py @@ -96,7 +96,6 @@ def run_experiment( try: return super().run_experiment(problem, local_config, seed, rep) except Exception as e: - logging.error( f"Error on config {config_dict}: {e}!" + f"Traceback follows:\n{traceback.format_exc()}" @@ -229,7 +228,7 @@ def run_benchmarks_with_checkpoints( final_results = bench.pandas() final_results.to_csv(out_fname) else: - for chunk in range(start_idx, n_chunks+start_idx): + for chunk in range(start_idx, n_chunks + start_idx): out_fname = Path(f"{out_path}/{benchmark_name}_chunk{chunk}_out.csv") intermediate_fname = Path( diff --git a/aepsych/benchmark/problem.py b/aepsych/benchmark/problem.py index 0225c1611..2a8f4bd45 100644 --- a/aepsych/benchmark/problem.py +++ b/aepsych/benchmark/problem.py @@ -5,14 +5,14 @@ # This source code is licensed under the license found in the # LICENSE file in the root directory of this source tree. from functools import cached_property -from typing import Any, Dict, Union, List +from typing import Any, Dict, List, Union import aepsych import numpy as np import torch -from scipy.stats import bernoulli from aepsych.strategy import SequentialStrategy, Strategy from aepsych.utils import make_scaled_sobol +from scipy.stats import bernoulli class Problem: @@ -64,8 +64,9 @@ def p(self, x: torch.Tensor) -> torch.Tensor: normal_dist = torch.distributions.Normal(0, 1) # Standard normal distribution return normal_dist.cdf(self.f(x)) # Use PyTorch's CDF equivalent - - def sample_y(self, x: torch.Tensor) -> np.ndarray: # TODO: This can be done with torch.bernoulli(self.p(x)), but Strategy.add_data() expects a numpy array for now + def sample_y( + self, x: torch.Tensor + ) -> np.ndarray: # TODO: This can be done with torch.bernoulli(self.p(x)), but Strategy.add_data() expects a numpy array for now """Sample a response from test function. Args: @@ -104,8 +105,8 @@ def p_true(self) -> torch.Tensor: Returns: torch.Tensor: Values of true response probability over evaluation grid. """ - normal_dist = torch.distributions.Normal(0, 1) - return normal_dist.cdf(self.f_true) + normal_dist = torch.distributions.Normal(0, 1) + return normal_dist.cdf(self.f_true) def p_hat(self, model: aepsych.models.base.ModelProtocol) -> torch.Tensor: """Generate mean predictions from the model over the evaluation grid. @@ -155,11 +156,15 @@ def evaluate( mae_f = torch.mean(torch.abs(self.f_true - f_hat)) mse_f = torch.mean((self.f_true - f_hat) ** 2) max_abs_err_f = torch.max(torch.abs(self.f_true - f_hat)) - corr_f = torch.corrcoef(torch.stack((self.f_true.flatten(), f_hat.flatten())))[0, 1] + corr_f = torch.corrcoef(torch.stack((self.f_true.flatten(), f_hat.flatten())))[ + 0, 1 + ] mae_p = torch.mean(torch.abs(self.p_true - p_hat)) mse_p = torch.mean((self.p_true - p_hat) ** 2) max_abs_err_p = torch.max(torch.abs(self.p_true - p_hat)) - corr_p = torch.corrcoef(torch.stack((self.p_true.flatten(), p_hat.flatten())))[0, 1] + corr_p = torch.corrcoef(torch.stack((self.p_true.flatten(), p_hat.flatten())))[ + 0, 1 + ] brier = torch.mean(2 * torch.square(self.p_true - p_hat)) # eval in samp-based expectation over posterior instead of just mean @@ -167,12 +172,13 @@ def evaluate( try: psamps = ( model.sample(self.eval_grid, num_samples=1000, probability_space=True) # type: ignore - ) except ( TypeError ): # vanilla models don't have proba_space samps, TODO maybe we should add them - normal_dist = torch.distributions.Normal(0, 1) # Standard normal distribution + normal_dist = torch.distributions.Normal( + 0, 1 + ) # Standard normal distribution psamps = normal_dist.cdf(fsamps) ferrs = fsamps - self.f_true[None, :] @@ -186,21 +192,21 @@ def evaluate( expected_brier = torch.mean((2 * torch.square(self.p_true[None, :] - psamps))) metrics = { - "mean_abs_err_f": mae_f.item(), - "mean_integrated_abs_err_f": miae_f.item(), - "mean_square_err_f": mse_f.item(), - "mean_integrated_square_err_f": mise_f.item(), - "max_abs_err_f": max_abs_err_f.item(), - "pearson_corr_f": corr_f.item(), - "mean_abs_err_p": mae_p.item(), - "mean_integrated_abs_err_p": miae_p.item(), - "mean_square_err_p": mse_p.item(), - "mean_integrated_square_err_p": mise_p.item(), - "max_abs_err_p": max_abs_err_p.item(), - "pearson_corr_p": corr_p.item(), - "brier": brier.item(), - "expected_brier": expected_brier.item(), - } + "mean_abs_err_f": mae_f.item(), + "mean_integrated_abs_err_f": miae_f.item(), + "mean_square_err_f": mse_f.item(), + "mean_integrated_square_err_f": mise_f.item(), + "max_abs_err_f": max_abs_err_f.item(), + "pearson_corr_f": corr_f.item(), + "mean_abs_err_p": mae_p.item(), + "mean_integrated_abs_err_p": miae_p.item(), + "mean_square_err_p": mse_p.item(), + "mean_integrated_square_err_p": mise_p.item(), + "max_abs_err_p": max_abs_err_p.item(), + "pearson_corr_p": corr_p.item(), + "brier": brier.item(), + "expected_brier": expected_brier.item(), + } return metrics @@ -237,16 +243,13 @@ def inverse_link(x): return inverse_torch(x) except AttributeError: + def inverse_link(x): - normal_dist = torch.distributions.Normal(0, 1) + normal_dist = torch.distributions.Normal(0, 1) return normal_dist.icdf(x) - return inverse_link(self.thresholds).float() # Return as float32 tensor - - - @cached_property def true_below_threshold(self) -> torch.Tensor: """ @@ -284,9 +287,7 @@ def evaluate(self, strat: SequentialStrategy) -> Dict[str, float]: # define what "threshold" means in high-dim. # Brier score on level-set probabilities - p_l = model.p_below_threshold( - self.eval_grid, self.f_threshold(model) - ) + p_l = model.p_below_threshold(self.eval_grid, self.f_threshold(model)) true_p_l = self.true_below_threshold assert ( p_l.ndim == 2 @@ -298,12 +299,16 @@ def evaluate(self, strat: SequentialStrategy) -> Dict[str, float]: brier_p_below_thresh = torch.mean(2 * torch.square(true_p_l - p_l), dim=1) # Classification error misclass_on_thresh = torch.mean( - p_l * (1 - true_p_l) + (1 - p_l) * true_p_l, dim=1 + p_l * (1 - true_p_l) + (1 - p_l) * true_p_l, dim=1 ) for i_threshold, threshold in enumerate(self.thresholds): - metrics[f"brier_p_below_{threshold}"] = brier_p_below_thresh.detach().cpu().numpy()[i_threshold] - metrics[f"misclass_on_thresh_{threshold}"] = misclass_on_thresh.detach().cpu().numpy()[i_threshold] + metrics[f"brier_p_below_{threshold}"] = ( + brier_p_below_thresh.detach().cpu().numpy()[i_threshold] + ) + metrics[f"misclass_on_thresh_{threshold}"] = ( + misclass_on_thresh.detach().cpu().numpy()[i_threshold] + ) return metrics diff --git a/aepsych/benchmark/test_functions.py b/aepsych/benchmark/test_functions.py index c4ee29e87..b980f4b44 100644 --- a/aepsych/benchmark/test_functions.py +++ b/aepsych/benchmark/test_functions.py @@ -11,8 +11,8 @@ import numpy as np import pandas as pd -from scipy.interpolate import CubicSpline, interp1d import torch +from scipy.interpolate import CubicSpline, interp1d # manually scraped data from doi:10.1007/s10162-013-0396-x fig 2 raw = """\ @@ -53,7 +53,9 @@ dubno_data = pd.read_csv(io.StringIO(raw)) -def make_songetal_threshfun(x: torch.Tensor, y: torch.Tensor) -> Callable[[torch.Tensor], torch.Tensor]: +def make_songetal_threshfun( + x: torch.Tensor, y: torch.Tensor +) -> Callable[[torch.Tensor], torch.Tensor]: """Generate a synthetic threshold function by interpolation of real data. Real data is from Dubno et al. 2013, and procedure follows Song et al. 2017, 2018. @@ -68,10 +70,10 @@ def make_songetal_threshfun(x: torch.Tensor, y: torch.Tensor) -> Callable[[torch frequencies and thresholds and returns threshold as a function of frequency. """ - x_np = x.cpu().numpy() - y_np = y.cpu().numpy() + x_np = x.cpu().numpy() + y_np = y.cpu().numpy() # These are not directly implemented in pytorch, so we use scipy for now - f_interp = CubicSpline(x_np, y_np, extrapolate=False) + f_interp = CubicSpline(x_np, y_np, extrapolate=False) f_extrap = interp1d(x_np, y_np, fill_value="extrapolate") def f_combo(x): @@ -112,17 +114,21 @@ def make_songetal_testfun( """ valid_phenotypes = ["Metabolic", "Sensory", "Metabolic+Sensory", "Older-normal"] assert phenotype in valid_phenotypes, f"Phenotype must be one of {valid_phenotypes}" - x = torch.tensor(dubno_data[dubno_data.phenotype == phenotype].freq.values, dtype=torch.float64) - y = torch.tensor(dubno_data[dubno_data.phenotype == phenotype].thresh.values, dtype=torch.float64) + x = torch.tensor( + dubno_data[dubno_data.phenotype == phenotype].freq.values, dtype=torch.float64 + ) + y = torch.tensor( + dubno_data[dubno_data.phenotype == phenotype].thresh.values, dtype=torch.float64 + ) # first, make the threshold fun threshfun = make_songetal_threshfun(x, y) # now make it into a test function - def song_testfun(x, cdf = False): + def song_testfun(x, cdf=False): logfreq = x[..., 0] intensity = x[..., 1] - thresh = threshfun(2 ** logfreq) + thresh = threshfun(2**logfreq) return ( torch.distributions.Normal(0, 1).cdf((intensity - thresh) / beta) if cdf @@ -132,7 +138,6 @@ def song_testfun(x, cdf = False): return song_testfun - def novel_discrimination_testfun(x: torch.Tensor) -> torch.Tensor: """Evaluate novel discrimination test function from Owen et al. @@ -203,26 +208,29 @@ def modified_hartmann6(X: torch.Tensor) -> torch.Tensor: The modified Hartmann6 function used in Lyu et al. """ C = torch.tensor([0.2, 0.22, 0.28, 0.3], dtype=torch.float64) - a_t = torch.tensor([ - [8, 3, 10, 3.5, 1.7, 6], - [0.5, 8, 10, 1.0, 6, 9], - [3, 3.5, 1.7, 8, 10, 6], - [10, 6, 0.5, 8, 1.0, 9] - ], dtype=torch.float64) - - p_t = ( - 10 ** (-4) - * torch.tensor([ + a_t = torch.tensor( + [ + [8, 3, 10, 3.5, 1.7, 6], + [0.5, 8, 10, 1.0, 6, 9], + [3, 3.5, 1.7, 8, 10, 6], + [10, 6, 0.5, 8, 1.0, 9], + ], + dtype=torch.float64, + ) + + p_t = 10 ** (-4) * torch.tensor( + [ [1312, 1696, 5569, 124, 8283, 5886], [2329, 4135, 8307, 3736, 1004, 9991], [2348, 1451, 3522, 2883, 3047, 6650], - [4047, 8828, 8732, 5743, 1091, 381] - ], dtype=torch.float64) + [4047, 8828, 8732, 5743, 1091, 381], + ], + dtype=torch.float64, ) y = torch.tensor(0.0, dtype=torch.float64) for i, C_i in enumerate(C): - t = torch.tensor(0.0, dtype=torch.float64) + t = torch.tensor(0.0, dtype=torch.float64) for j in range(6): t += a_t[i, j] * ((X[j] - p_t[i, j]) ** 2) y += C_i * torch.exp(-t) @@ -243,7 +251,9 @@ def f_2d(x: torch.Tensor) -> torch.Tensor: return torch.exp(-torch.norm(x, dim=-1)) -def new_novel_det_params(freq: torch.Tensor, scale_factor: float = 1.0) -> Tuple[torch.Tensor, torch.Tensor]: +def new_novel_det_params( + freq: torch.Tensor, scale_factor: float = 1.0 +) -> Tuple[torch.Tensor, torch.Tensor]: """Get the loc and scale params for 2D synthetic novel_det(frequency) function Keyword arguments: freq -- 1D tensor of frequencies whose thresholds to return @@ -255,7 +265,9 @@ def new_novel_det_params(freq: torch.Tensor, scale_factor: float = 1.0) -> Tuple return loc, scale -def target_new_novel_det(freq: torch.Tensor, scale_factor: float = 1.0, target: float = 0.75) -> torch.Tensor: +def target_new_novel_det( + freq: torch.Tensor, scale_factor: float = 1.0, target: float = 0.75 +) -> torch.Tensor: """Get the target (i.e. threshold) for 2D synthetic novel_det(frequency) function Keyword arguments: freq -- 1D tensor of frequencies whose thresholds to return @@ -292,11 +304,11 @@ def cdf_new_novel_det(x: torch.Tensor, scale_factor: float = 1.0) -> torch.Tenso def new_novel_det_channels_params( - channel: torch.Tensor, - scale_factor: float = 1.0, - wave_freq: float = 1, - target: float = 0.75 - ) -> Tuple[torch.Tensor, torch.Tensor]: + channel: torch.Tensor, + scale_factor: float = 1.0, + wave_freq: float = 1, + target: float = 0.75, +) -> Tuple[torch.Tensor, torch.Tensor]: """Get the target parameters for 2D synthetic novel_det(channel) function Keyword arguments: channel -- 1D tensor of channel locations whose thresholds to return @@ -306,12 +318,19 @@ def new_novel_det_channels_params( """ locs = -0.3 * torch.sin(5 * wave_freq * (channel - 1 / 6) / torch.pi) ** 2 - 0.5 scale = ( - 1 / (10 * scale_factor) * (0.75 + 0.25 * torch.cos(10 * (0.3 + channel) / torch.pi)) + 1 + / (10 * scale_factor) + * (0.75 + 0.25 * torch.cos(10 * (0.3 + channel) / torch.pi)) ) return locs, scale -def target_new_novel_det_channels(channel: torch.Tensor, scale_factor: float = 1.0, wave_freq: float = 1, target: float = 0.75) -> torch.Tensor: +def target_new_novel_det_channels( + channel: torch.Tensor, + scale_factor: float = 1.0, + wave_freq: float = 1, + target: float = 0.75, +) -> torch.Tensor: """Get the target (i.e. threshold) for 2D synthetic novel_det(channel) function Keyword arguments: channel -- 1D tensor of channel locations whose thresholds to return @@ -319,12 +338,20 @@ def target_new_novel_det_channels(channel: torch.Tensor, scale_factor: float = 1 wave_freq -- frequency of location waveform on [-1,1] target -- target threshold """ - locs, scale = new_novel_det_channels_params(channel, scale_factor, wave_freq, target) + locs, scale = new_novel_det_channels_params( + channel, scale_factor, wave_freq, target + ) normal_dist = torch.distributions.Normal(locs, scale) return normal_dist.icdf(torch.tensor(target)) -def new_novel_det_channels(x: torch.Tensor, channel: torch.Tensor, scale_factor: float = 1.0, wave_freq: float = 1, target: float = 0.75) -> torch.Tensor: +def new_novel_det_channels( + x: torch.Tensor, + channel: torch.Tensor, + scale_factor: float = 1.0, + wave_freq: float = 1, + target: float = 0.75, +) -> torch.Tensor: """Get the 2D synthetic novel_det(channel) function Keyword arguments: x -- tensor of shape (n,2) of locations to sample; @@ -332,11 +359,19 @@ def new_novel_det_channels(x: torch.Tensor, channel: torch.Tensor, scale_factor: scale factor -- scale for the novel_det function, where higher is steeper/lower SD wave_freq -- frequency of location waveform on [-1,1] """ - locs, scale = new_novel_det_channels_params(channel, scale_factor, wave_freq, target) + locs, scale = new_novel_det_channels_params( + channel, scale_factor, wave_freq, target + ) return (x[..., 1] - locs) / scale -def cdf_new_novel_det_channels(x: torch.Tensor, channel: torch.Tensor, scale_factor: float = 1.0, wave_freq: float = 1, target: float = 0.75) -> torch.Tensor: +def cdf_new_novel_det_channels( + x: torch.Tensor, + channel: torch.Tensor, + scale_factor: float = 1.0, + wave_freq: float = 1, + target: float = 0.75, +) -> torch.Tensor: """Get the cdf for 2D synthetic novel_det(channel) function Keyword arguments: x -- tensor of shape (n,2) of locations to sample; @@ -348,7 +383,10 @@ def cdf_new_novel_det_channels(x: torch.Tensor, channel: torch.Tensor, scale_fac normal_dist = torch.distributions.Normal(0, 1) # Standard normal distribution return normal_dist.cdf(z) -def new_novel_det_3D_params(x: torch.Tensor, scale_factor: float = 1.0) -> Tuple[torch.Tensor, torch.Tensor]: + +def new_novel_det_3D_params( + x: torch.Tensor, scale_factor: float = 1.0 +) -> Tuple[torch.Tensor, torch.Tensor]: freq = x[..., 0] chan = x[..., 1] locs_freq = -0.32 + 2 * (0.66 * torch.pow(0.8 * freq * (0.2 * freq - 1), 2) + 0.05) @@ -384,7 +422,9 @@ def cdf_new_novel_det_3D(x: torch.Tensor, scale_factor: float = 1.0) -> torch.Te return normal_dist.cdf(z) -def target_new_novel_det_3D(x: torch.Tensor, scale_factor: float = 1.0, target: float = 0.75) -> torch.Tensor: +def target_new_novel_det_3D( + x: torch.Tensor, scale_factor: float = 1.0, target: float = 0.75 +) -> torch.Tensor: """ Get target for 3D synthetic novel_det function at location x @@ -401,4 +441,6 @@ def target_new_novel_det_3D(x: torch.Tensor, scale_factor: float = 1.0, target: def f_pairwise(f: Callable, x: torch.Tensor, noise_scale: float = 1) -> torch.Tensor: normal_dist = torch.distributions.Normal(0, 1) - return normal_dist.cdf((f(x[..., 1]) - f(x[..., 0])) / (noise_scale * torch.sqrt(torch.tensor(2.0)))) \ No newline at end of file + return normal_dist.cdf( + (f(x[..., 1]) - f(x[..., 0])) / (noise_scale * torch.sqrt(torch.tensor(2.0))) + ) diff --git a/aepsych/config.py b/aepsych/config.py index a8e0a4b76..afbc323be 100644 --- a/aepsych/config.py +++ b/aepsych/config.py @@ -27,14 +27,12 @@ import gpytorch import numpy as np import torch - from aepsych.version import __version__ _T = TypeVar("_T") class Config(configparser.ConfigParser): - # names in these packages can be referred to by string name registered_names: ClassVar[Dict[str, object]] = {} @@ -215,7 +213,6 @@ def _str_to_tensor(self, v: str) -> torch.Tensor: return torch.Tensor(self._str_to_array(v)).to(torch.float64) def _str_to_obj(self, v: str, fallback_type: _T = str, warn: bool = True) -> object: - try: return self.registered_names[v] except KeyError: diff --git a/aepsych/database/db.py b/aepsych/database/db.py index 9e6a582c6..37c3b5116 100644 --- a/aepsych/database/db.py +++ b/aepsych/database/db.py @@ -13,9 +13,9 @@ from pathlib import Path from typing import Any, Dict, List, Optional +import aepsych.database.tables as tables from aepsych.config import Config from aepsych.strategy import Strategy -import aepsych.database.tables as tables from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from sqlalchemy.orm.session import close_all_sessions @@ -26,7 +26,7 @@ class Database: def __init__(self, db_path: Optional[str] = None) -> None: """Initialize the database object. - + Args: db_path (str, optional): The path to the database. Defaults to None. """ @@ -46,7 +46,7 @@ def __init__(self, db_path: Optional[str] = None) -> None: def get_engine(self) -> sessionmaker: """Get the engine for the database. - + Returns: sessionmaker: The sessionmaker object for the database. """ @@ -118,11 +118,11 @@ def session_scope(self): # @retry(stop_max_attempt_number=8, wait_exponential_multiplier=1.8) def execute_sql_query(self, query: str, vals: Dict[str, str]) -> List[Any]: """Execute an arbitrary query written in sql. - + Args: query (str): The query to execute. vals (Dict[str, str]): The values to use in the query. - + Returns: List[Any]: The results of the query. """ @@ -131,7 +131,7 @@ def execute_sql_query(self, query: str, vals: Dict[str, str]) -> List[Any]: def get_master_records(self) -> List[tables.DBMasterTable]: """Grab the list of master records. - + Returns: List[tables.DBMasterTable]: The list of master records. """ @@ -140,7 +140,7 @@ def get_master_records(self) -> List[tables.DBMasterTable]: def get_master_record(self, experiment_id: int) -> Optional[tables.DBMasterTable]: """Grab the list of master record for a specific experiment (master) id. - + Args: experiment_id (int): The experiment id. @@ -160,10 +160,10 @@ def get_master_record(self, experiment_id: int) -> Optional[tables.DBMasterTable def get_replay_for(self, master_id: int) -> Optional[List[tables.DbReplayTable]]: """Get the replay records for a specific master row. - + Args: master_id (int): The master id. - + Returns: List[tables.DbReplayTable] or None: The replay records or None if they don't exist. """ @@ -174,12 +174,12 @@ def get_replay_for(self, master_id: int) -> Optional[List[tables.DbReplayTable]] return None - def get_strats_for(self, master_id: int =0) -> Optional[List[Any]]: + def get_strats_for(self, master_id: int = 0) -> Optional[List[Any]]: """Get the strat records for a specific master row. - + Args: master_id (int): The master id. Defaults to 0. - + Returns: List[Any] or None: The strat records or None if they don't exist. """ @@ -190,13 +190,13 @@ def get_strats_for(self, master_id: int =0) -> Optional[List[Any]]: return None - def get_strat_for(self, master_id: int, strat_id: int=-1) -> Optional[Any]: + def get_strat_for(self, master_id: int, strat_id: int = -1) -> Optional[Any]: """Get a specific strat record for a specific master row. - + Args: master_id (int): The master id. strat_id (int): The strat id. Defaults to -1. - + Returns: Any: The strat record. """ @@ -209,10 +209,10 @@ def get_strat_for(self, master_id: int, strat_id: int=-1) -> Optional[Any]: def get_config_for(self, master_id: int) -> Optional[Any]: """Get the strat records for a specific master row. - + Args: master_id (int): The master id. - + Returns: Any: The config records. """ @@ -222,12 +222,12 @@ def get_config_for(self, master_id: int) -> Optional[Any]: return master_record.children_config[0].config return None - def get_raw_for(self, master_id: int)-> Optional[List[tables.DbRawTable]]: + def get_raw_for(self, master_id: int) -> Optional[List[tables.DbRawTable]]: """Get the raw data for a specific master row. - + Args: master_id (int): The master id. - + Returns: List[tables.DbRawTable] or None: The raw data or None if it doesn't exist. """ @@ -240,10 +240,10 @@ def get_raw_for(self, master_id: int)-> Optional[List[tables.DbRawTable]]: def get_all_params_for(self, master_id: int) -> Optional[List[tables.DbRawTable]]: """Get the parameters for all the iterations of a specific experiment. - + Args: master_id (int): The master id. - + Returns: List[tables.DbRawTable] or None: The parameters or None if they don't exist. """ @@ -258,13 +258,15 @@ def get_all_params_for(self, master_id: int) -> Optional[List[tables.DbRawTable] return None - def get_param_for(self, master_id: int, iteration_id: int) -> Optional[List[tables.DbRawTable]]: + def get_param_for( + self, master_id: int, iteration_id: int + ) -> Optional[List[tables.DbRawTable]]: """Get the parameters for a specific iteration of a specific experiment. - + Args: master_id (int): The master id. iteration_id (int): The iteration id. - + Returns: List[tables.DbRawTable] or None: The parameters or None if they don't exist. """ @@ -279,10 +281,10 @@ def get_param_for(self, master_id: int, iteration_id: int) -> Optional[List[tabl def get_all_outcomes_for(self, master_id: int) -> Optional[List[tables.DbRawTable]]: """Get the outcomes for all the iterations of a specific experiment. - + Args: master_id (int): The master id. - + Returns: List[tables.DbRawTable] or None: The outcomes or None if they don't exist. """ @@ -297,13 +299,15 @@ def get_all_outcomes_for(self, master_id: int) -> Optional[List[tables.DbRawTabl return None - def get_outcome_for(self, master_id: int, iteration_id: int) -> Optional[List[tables.DbRawTable]]: + def get_outcome_for( + self, master_id: int, iteration_id: int + ) -> Optional[List[tables.DbRawTable]]: """Get the outcomes for a specific iteration of a specific experiment. - + Args: master_id (int): The master id. iteration_id (int): The iteration id. - + Returns: List[tables.DbRawTable] or None: The outcomes or None if they don't exist. """ @@ -320,10 +324,10 @@ def record_setup( self, description: str, name: str, - extra_metadata: Optional[str] =None, - id: Optional[str] =None, - request: Dict[str, Any] =None, - participant_id: Optional[int] =None, + extra_metadata: Optional[str] = None, + id: Optional[str] = None, + request: Dict[str, Any] = None, + participant_id: Optional[int] = None, ) -> str: """Record the setup of an experiment. @@ -380,9 +384,11 @@ def record_setup( # tis needs to be passed into all future calls to link properly return master_table - def record_message(self, master_table: tables.DBMasterTable, type: str, request: Dict[str, Any]) -> None: + def record_message( + self, master_table: tables.DBMasterTable, type: str, request: Dict[str, Any] + ) -> None: """Record a message in the database. - + Args: master_table (tables.DBMasterTable): The master table. type (str): The type of the message. @@ -402,14 +408,19 @@ def record_message(self, master_table: tables.DBMasterTable, type: str, request: self._session.add(record) self._session.commit() - def record_raw(self, master_table: tables.DBMasterTable, model_data: Any, timestamp: Optional[datetime.datetime] = None) -> tables.DbRawTable: + def record_raw( + self, + master_table: tables.DBMasterTable, + model_data: Any, + timestamp: Optional[datetime.datetime] = None, + ) -> tables.DbRawTable: """Record raw data in the database. - + Args: master_table (tables.DBMasterTable): The master table. model_data (Any): The model data. timestamp (datetime.datetime, optional): The timestamp. Defaults to None. - + Returns: tables.DbRawTable: The raw entry. """ @@ -427,9 +438,11 @@ def record_raw(self, master_table: tables.DBMasterTable, model_data: Any, timest return raw_entry - def record_param(self, raw_table: tables.DbRawTable, param_name: str, param_value: str) -> None: + def record_param( + self, raw_table: tables.DbRawTable, param_name: str, param_value: str + ) -> None: """Record a parameter in the database. - + Args: raw_table (tables.DbRawTable): The raw table. param_name (str): The parameter name. @@ -444,9 +457,11 @@ def record_param(self, raw_table: tables.DbRawTable, param_name: str, param_valu self._session.add(param_entry) self._session.commit() - def record_outcome(self, raw_table: tables.DbRawTable, outcome_name: str, outcome_value: float) -> None: + def record_outcome( + self, raw_table: tables.DbRawTable, outcome_name: str, outcome_value: float + ) -> None: """Record an outcome in the database. - + Args: raw_table (tables.DbRawTable): The raw table. outcome_name (str): The outcome name. @@ -463,7 +478,7 @@ def record_outcome(self, raw_table: tables.DbRawTable, outcome_name: str, outcom def record_strat(self, master_table: tables.DBMasterTable, strat: Strategy) -> None: """Record a strategy in the database. - + Args: master_table (tables.DBMasterTable): The master table. strat (Strategy): The strategy. @@ -478,7 +493,7 @@ def record_strat(self, master_table: tables.DBMasterTable, strat: Strategy) -> N def record_config(self, master_table: tables.DBMasterTable, config: Config) -> None: """Record a config in the database. - + Args: master_table (tables.DBMasterTable): The master table. config (Config): The config. diff --git a/aepsych/database/tables.py b/aepsych/database/tables.py index a42423a1e..1b5637f65 100644 --- a/aepsych/database/tables.py +++ b/aepsych/database/tables.py @@ -24,9 +24,9 @@ PickleType, String, ) +from sqlalchemy.engine import Engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import relationship, sessionmaker -from sqlalchemy.engine import Engine logger = logging.getLogger() @@ -75,7 +75,7 @@ class DBMasterTable(Base): children_raw = relationship("DbRawTable", back_populates="parent") @classmethod - def from_sqlite(cls, row: Dict[str, Any]) -> 'DBMasterTable': + def from_sqlite(cls, row: Dict[str, Any]) -> "DBMasterTable": """Create a DBMasterTable object from a row in the sqlite database. Args: @@ -93,7 +93,7 @@ def from_sqlite(cls, row: Dict[str, Any]) -> 'DBMasterTable': def __repr__(self) -> str: """Return a string representation of the DBMasterTable object. - + Returns: str: A string representation of the DBMasterTable object. """ @@ -107,7 +107,7 @@ def __repr__(self) -> str: @staticmethod def update(engine: Engine) -> None: """Update the master table schema to include extra_metadata and participant_id columns. - + Args: engine (Engine): The sqlalchemy engine. """ @@ -120,7 +120,7 @@ def update(engine: Engine) -> None: @staticmethod def requires_update(engine: Engine) -> bool: """Check if the master table schema requires an update. - + Args: engine (Engine): The sqlalchemy engine. """ @@ -131,11 +131,11 @@ def requires_update(engine: Engine) -> bool: @staticmethod def _has_column(engine: Engine, column: str) -> bool: """Check if the master table has a column. - + Args: engine (Engine): The sqlalchemy engine. column (str): The column name. - + Returns: bool: True if the column exists, False otherwise. """ @@ -151,7 +151,7 @@ def _has_column(engine: Engine, column: str) -> bool: @staticmethod def _add_column(engine: Engine, column: str) -> None: """Add a column to the master table. - + Args: engine (Engine): The sqlalchemy engine. column (str): The column name. @@ -197,12 +197,12 @@ class DbReplayTable(Base): __mapper_args__ = {} @classmethod - def from_sqlite(cls, row: Dict[str, Any]) -> 'DbReplayTable': + def from_sqlite(cls, row: Dict[str, Any]) -> "DbReplayTable": """Create a DbReplayTable object from a row in the sqlite database. - + Args: row (Dict[str, Any]): A row from the sqlite database. - + Returns: DbReplayTable: A DbReplayTable object. """ @@ -223,7 +223,7 @@ def from_sqlite(cls, row: Dict[str, Any]) -> 'DbReplayTable': def __repr__(self) -> str: """Return a string representation of the DbReplayTable object. - + Returns: str: A string representation of the DbReplayTable object. """ @@ -237,10 +237,10 @@ def __repr__(self) -> str: @staticmethod def _has_extra_info(engine: Engine) -> bool: """Check if the replay_data table has an extra_info column. - + Args: engine (Engine): The sqlalchemy engine. - + Returns: bool: True if the extra_info column exists, False otherwise. """ @@ -254,10 +254,10 @@ def _has_extra_info(engine: Engine) -> bool: @staticmethod def _configs_require_conversion(engine: Engine) -> bool: """Check if the replay_data table has any old configs that need to be converted. - + Args: engine (Engine): The sqlalchemy engine. - + Returns: bool: True if any old configs need to be converted, False otherwise. """ @@ -278,7 +278,7 @@ def _configs_require_conversion(engine: Engine) -> bool: @staticmethod def update(engine: Engine) -> None: """Update the replay_data table schema to include an extra_info column and convert old configs. - + Args: engine (Engine): The sqlalchemy engine. """ @@ -293,7 +293,7 @@ def update(engine: Engine) -> None: @staticmethod def requires_update(engine: Engine) -> bool: """Check if the replay_data table schema requires an update. - + Args: engine (Engine): The sqlalchemy engine. """ @@ -304,7 +304,7 @@ def requires_update(engine: Engine) -> bool: @staticmethod def _add_extra_info(engine: Engine) -> None: """Add an extra_info column to the replay_data table. - + Args: engine (Engine): The sqlalchemy engine. """ @@ -327,7 +327,7 @@ def _add_extra_info(engine: Engine) -> None: @staticmethod def _convert_configs(engine: Engine) -> None: """Convert old configs to the latest version. - + Args: engine (Engine): The sqlalchemy engine. """ @@ -364,12 +364,12 @@ class DbStratTable(Base): parent = relationship("DBMasterTable", back_populates="children_strat") @classmethod - def from_sqlite(cls, row: Dict[str, Any]) -> 'DbStratTable': + def from_sqlite(cls, row: Dict[str, Any]) -> "DbStratTable": """Create a DbStratTable object from a row in the sqlite database. - + Args: row (Dict[str, Any]): A row from the sqlite database. - + Returns: DbStratTable: A DbStratTable object. """ @@ -383,7 +383,7 @@ def from_sqlite(cls, row: Dict[str, Any]) -> 'DbStratTable': def __repr__(self) -> str: """Return a string representation of the DbStratTable object. - + Returns: str: A string representation of the DbStratTable object. """ @@ -396,7 +396,7 @@ def __repr__(self) -> str: @staticmethod def update(engine: Engine) -> None: """Update the strat_data table schema. - + Args: engine (Engine): The sqlalchemy engine. """ @@ -405,7 +405,7 @@ def update(engine: Engine) -> None: @staticmethod def requires_update(engine: Engine) -> bool: """Check if the strat_data table schema requires an update.(always False) - + Args: engine (Engine): The sqlalchemy engine. """ @@ -423,12 +423,12 @@ class DbConfigTable(Base): parent = relationship("DBMasterTable", back_populates="children_config") @classmethod - def from_sqlite(cls, row: Dict[str, Any]) -> 'DbConfigTable': + def from_sqlite(cls, row: Dict[str, Any]) -> "DbConfigTable": """Create a DbConfigTable object from a row in the sqlite database. - + Args: row (Dict[str, Any]): A row from the sqlite database. - + Returns: DbConfigTable: A DbConfigTable object. """ @@ -442,7 +442,7 @@ def from_sqlite(cls, row: Dict[str, Any]) -> 'DbConfigTable': def __repr__(self) -> str: """Return a string representation of the DbConfigTable object. - + Returns: str: A string representation of the DbConfigTable object. """ @@ -455,7 +455,7 @@ def __repr__(self) -> str: @staticmethod def update(engine: Engine) -> None: """Update the config_data table schema. - + Args: engine (Engine): The sqlalchemy engine. """ @@ -488,12 +488,12 @@ class DbRawTable(Base): children_outcome = relationship("DbOutcomeTable", back_populates="parent") @classmethod - def from_sqlite(cls, row: Dict[str, Any]) -> 'DbRawTable': + def from_sqlite(cls, row: Dict[str, Any]) -> "DbRawTable": """Create a DbRawTable object from a row in the sqlite database. - + Args: row (Dict[str, Any]): A row from the sqlite database. - + Returns: DbRawTable: A DbRawTable object. """ @@ -507,7 +507,7 @@ def from_sqlite(cls, row: Dict[str, Any]) -> 'DbRawTable': def __repr__(self) -> str: """Return a string representation of the DbRawTable object. - + Returns: str: A string representation of the DbRawTable object. """ @@ -520,7 +520,7 @@ def __repr__(self) -> str: @staticmethod def update(db: Any, engine: Engine) -> None: """Update the raw table with data from the replay table. - + Args: db (Any): The database object. engine (Engine): The sqlalchemy engine. @@ -529,7 +529,6 @@ def update(db: Any, engine: Engine) -> None: # Get every master table for master_table in db.get_master_records(): - # Get raw tab for message in master_table.children_replay: if message.message_type != "tell": @@ -602,10 +601,10 @@ def update(db: Any, engine: Engine) -> None: @staticmethod def requires_update(engine: Engine) -> bool: """Check if the raw table is empty, and data already exists. - + Args: engine (Engine): The sqlalchemy engine. - + Returns: bool: True if the raw table is empty and data already exists, False otherwise. """ @@ -636,12 +635,12 @@ class DbParamTable(Base): parent = relationship("DbRawTable", back_populates="children_param") @classmethod - def from_sqlite(cls, row: Dict[str, Any]) -> 'DbParamTable': + def from_sqlite(cls, row: Dict[str, Any]) -> "DbParamTable": """Create a DbParamTable object from a row in the sqlite database. - + Args: row (Dict[str, Any]): A row from the sqlite database. - + Returns: DbParamTable: A DbParamTable object. """ @@ -655,7 +654,7 @@ def from_sqlite(cls, row: Dict[str, Any]) -> 'DbParamTable': def __repr__(self) -> str: """Return a string representation of the DbParamTable object. - + Returns: str: A string representation of the DbParamTable object. """ @@ -667,7 +666,7 @@ def __repr__(self) -> str: @staticmethod def update(engine: Engine) -> None: """Update the param_data table schema. - + Args: engine (Engine): The sqlalchemy engine. """ @@ -676,7 +675,7 @@ def update(engine: Engine) -> None: @staticmethod def requires_update(engine: Engine) -> bool: """Check if the param_data table schema requires an update.(always False) - + Args: engine (Engine): The sqlalchemy engine. @@ -702,12 +701,12 @@ class DbOutcomeTable(Base): parent = relationship("DbRawTable", back_populates="children_outcome") @classmethod - def from_sqlite(cls, row: Dict[str, Any]) -> 'DbOutcomeTable': + def from_sqlite(cls, row: Dict[str, Any]) -> "DbOutcomeTable": """Create a DbOutcomeTable object from a row in the sqlite database. - + Args: row (Dict[str, Any]): A row from the sqlite database. - + Returns: DbOutcomeTable: A DbOutcomeTable object. """ @@ -721,7 +720,7 @@ def from_sqlite(cls, row: Dict[str, Any]) -> 'DbOutcomeTable': def __repr__(self) -> str: """Return a string representation of the DbOutcomeTable object. - + Returns: str: A string representation of the DbOutcomeTable object. """ diff --git a/aepsych/factory/__init__.py b/aepsych/factory/__init__.py index f68ca13ad..c85f90f93 100644 --- a/aepsych/factory/__init__.py +++ b/aepsych/factory/__init__.py @@ -11,8 +11,8 @@ from .default import default_mean_covar_factory from .monotonic import monotonic_mean_covar_factory from .ordinal import ordinal_mean_covar_factory -from .song import song_mean_covar_factory from .pairwise import pairwise_mean_covar_factory +from .song import song_mean_covar_factory """AEPsych factory functions. These functions generate a gpytorch Mean and Kernel objects from diff --git a/aepsych/factory/default.py b/aepsych/factory/default.py index 30eb3987b..3642675d8 100644 --- a/aepsych/factory/default.py +++ b/aepsych/factory/default.py @@ -12,7 +12,6 @@ import gpytorch import torch from aepsych.config import Config - from scipy.stats import norm from .utils import __default_invgamma_concentration, __default_invgamma_rate @@ -94,7 +93,6 @@ def _get_default_cov_function( stimuli_per_trial: int, active_dims: Optional[List[int]] = None, ) -> gpytorch.kernels.Kernel: - # default priors lengthscale_prior = "lognormal" if stimuli_per_trial == 1 else "gamma" ls_loc = torch.tensor(math.sqrt(2.0), dtype=torch.float64) diff --git a/aepsych/factory/monotonic.py b/aepsych/factory/monotonic.py index 9ee6656fe..67d50a780 100644 --- a/aepsych/factory/monotonic.py +++ b/aepsych/factory/monotonic.py @@ -13,7 +13,6 @@ from aepsych.config import Config from aepsych.kernels.rbf_partial_grad import RBFKernelPartialObsGrad from aepsych.means.constant_partial_grad import ConstantMeanPartialObsGrad - from scipy.stats import norm from .utils import __default_invgamma_concentration, __default_invgamma_rate diff --git a/aepsych/factory/ordinal.py b/aepsych/factory/ordinal.py index 37363b5df..5d1c25953 100644 --- a/aepsych/factory/ordinal.py +++ b/aepsych/factory/ordinal.py @@ -17,7 +17,6 @@ def ordinal_mean_covar_factory( config: Config, ) -> Tuple[gpytorch.means.ConstantMean, gpytorch.kernels.ScaleKernel]: - try: base_factory = config.getobj("ordinal_mean_covar_factory", "base_factory") except NoOptionError: diff --git a/aepsych/factory/pairwise.py b/aepsych/factory/pairwise.py index 453707d40..82934fece 100644 --- a/aepsych/factory/pairwise.py +++ b/aepsych/factory/pairwise.py @@ -3,7 +3,6 @@ from typing import List, Tuple, Union import gpytorch - from aepsych.config import Config from aepsych.factory.default import ( _get_default_cov_function, diff --git a/aepsych/factory/song.py b/aepsych/factory/song.py index 46c5083dd..520ea02be 100644 --- a/aepsych/factory/song.py +++ b/aepsych/factory/song.py @@ -11,7 +11,6 @@ import gpytorch import torch from aepsych.config import Config - from scipy.stats import norm from .utils import __default_invgamma_concentration, __default_invgamma_rate diff --git a/aepsych/generators/__init__.py b/aepsych/generators/__init__.py index 09be37f9d..160d67271 100644 --- a/aepsych/generators/__init__.py +++ b/aepsych/generators/__init__.py @@ -8,6 +8,7 @@ import sys from ..config import Config +from .acqf_thompson_sampler_generator import AcqfThompsonSamplerGenerator from .epsilon_greedy_generator import EpsilonGreedyGenerator from .manual_generator import ManualGenerator, SampleAroundPointsGenerator from .monotonic_rejection_generator import MonotonicRejectionGenerator @@ -18,7 +19,6 @@ from .random_generator import RandomGenerator from .semi_p import IntensityAwareSemiPGenerator from .sobol_generator import SobolGenerator -from .acqf_thompson_sampler_generator import AcqfThompsonSamplerGenerator __all__ = [ "OptimizeAcqfGenerator", diff --git a/aepsych/generators/acqf_thompson_sampler_generator.py b/aepsych/generators/acqf_thompson_sampler_generator.py index 90da2a807..e7058a882 100644 --- a/aepsych/generators/acqf_thompson_sampler_generator.py +++ b/aepsych/generators/acqf_thompson_sampler_generator.py @@ -10,21 +10,21 @@ import numpy as np import torch -from numpy.random import choice from aepsych.config import Config from aepsych.generators.base import AEPsychGenerator from aepsych.models.base import ModelProtocol from aepsych.utils_logging import getLogger -from botorch.acquisition.preference import AnalyticExpectedUtilityOfBestOption -from torch.quasirandom import SobolEngine -from botorch.utils.sampling import draw_sobol_samples, manual_seed from botorch.acquisition import ( AcquisitionFunction, - NoisyExpectedImprovement, - qNoisyExpectedImprovement, LogNoisyExpectedImprovement, + NoisyExpectedImprovement, qLogNoisyExpectedImprovement, + qNoisyExpectedImprovement, ) +from botorch.acquisition.preference import AnalyticExpectedUtilityOfBestOption +from botorch.utils.sampling import draw_sobol_samples, manual_seed +from numpy.random import choice +from torch.quasirandom import SobolEngine logger = getLogger() diff --git a/aepsych/generators/base.py b/aepsych/generators/base.py index 1ea509b97..c1808c786 100644 --- a/aepsych/generators/base.py +++ b/aepsych/generators/base.py @@ -19,7 +19,6 @@ qNoisyExpectedImprovement, ) - AEPsychModelType = TypeVar("AEPsychModelType", bound=AEPsychMixin) diff --git a/aepsych/generators/epsilon_greedy_generator.py b/aepsych/generators/epsilon_greedy_generator.py index 0136c1c20..22b5660f6 100644 --- a/aepsych/generators/epsilon_greedy_generator.py +++ b/aepsych/generators/epsilon_greedy_generator.py @@ -20,7 +20,7 @@ def __init__(self, subgenerator: AEPsychGenerator, epsilon: float = 0.1) -> None self.epsilon = epsilon @classmethod - def from_config(cls, config: Config) -> 'EpsilonGreedyGenerator': + def from_config(cls, config: Config) -> "EpsilonGreedyGenerator": classname = cls.__name__ subgen_cls = config.getobj( classname, "subgenerator", fallback=OptimizeAcqfGenerator diff --git a/aepsych/generators/manual_generator.py b/aepsych/generators/manual_generator.py index 8546c0024..2a3195717 100644 --- a/aepsych/generators/manual_generator.py +++ b/aepsych/generators/manual_generator.py @@ -10,7 +10,6 @@ import numpy as np import torch - from aepsych.config import Config from aepsych.generators.base import AEPsychGenerator from aepsych.models.base import AEPsychMixin diff --git a/aepsych/generators/monotonic_rejection_generator.py b/aepsych/generators/monotonic_rejection_generator.py index d8b4ac7fd..3df99be2e 100644 --- a/aepsych/generators/monotonic_rejection_generator.py +++ b/aepsych/generators/monotonic_rejection_generator.py @@ -12,10 +12,11 @@ from aepsych.config import Config from aepsych.generators.base import AEPsychGenerator from aepsych.models.monotonic_rejection_gp import MonotonicRejectionGP +from botorch.acquisition import AcquisitionFunction from botorch.logging import logger from botorch.optim.initializers import gen_batch_initial_conditions from botorch.optim.utils import columnwise_clamp, fix_features -from botorch.acquisition import AcquisitionFunction + def default_loss_constraint_fun( loss: torch.Tensor, candidates: torch.Tensor @@ -63,7 +64,9 @@ def __init__( self.model_gen_options = model_gen_options self.explore_features = explore_features - def _instantiate_acquisition_fn(self, model: MonotonicRejectionGP) -> AcquisitionFunction: + def _instantiate_acquisition_fn( + self, model: MonotonicRejectionGP + ) -> AcquisitionFunction: return self.acqf( model=model, deriv_constraint_points=model._get_deriv_constraint_points(), @@ -166,7 +169,7 @@ def closure(): return Xopt @classmethod - def from_config(cls, config: Config) -> 'MonotonicRejectionGenerator': + def from_config(cls, config: Config) -> "MonotonicRejectionGenerator": classname = cls.__name__ acqf = config.getobj("common", "acqf", fallback=None) extra_acqf_args = cls._get_acqf_options(acqf, config) @@ -182,7 +185,9 @@ def from_config(cls, config: Config) -> 'MonotonicRejectionGenerator': options["nesterov"] = config.getboolean(classname, "nesterov", fallback=True) options["epochs"] = config.getint(classname, "epochs", fallback=50) options["milestones"] = config.getlist( - classname, "milestones", fallback=[25, 40] # type: ignore + classname, + "milestones", + fallback=[25, 40], # type: ignore ) options["gamma"] = config.getfloat(classname, "gamma", fallback=0.1) # type: ignore options["loss_constraint_fun"] = config.getobj( diff --git a/aepsych/generators/monotonic_thompson_sampler_generator.py b/aepsych/generators/monotonic_thompson_sampler_generator.py index 3e7dd4301..48f3c1e7c 100644 --- a/aepsych/generators/monotonic_thompson_sampler_generator.py +++ b/aepsych/generators/monotonic_thompson_sampler_generator.py @@ -89,7 +89,7 @@ def gen( return torch.Tensor(X[best_indx]) @classmethod - def from_config(cls, config: Config) -> 'MonotonicThompsonSamplerGenerator': + def from_config(cls, config: Config) -> "MonotonicThompsonSamplerGenerator": classname = cls.__name__ n_samples = config.getint(classname, "num_samples", fallback=1) n_rejection_samples = config.getint( @@ -98,7 +98,9 @@ def from_config(cls, config: Config) -> 'MonotonicThompsonSamplerGenerator': num_ts_points = config.getint(classname, "num_ts_points", fallback=1000) target = config.getfloat(classname, "target", fallback=0.75) objective = config.getobj(classname, "objective", fallback=ProbitObjective) - explore_features = config.getlist(classname, "explore_idxs", element_type=int, fallback=None) # type: ignore + explore_features = config.getlist( + classname, "explore_idxs", element_type=int, fallback=None + ) # type: ignore return cls( n_samples=n_samples, diff --git a/aepsych/generators/optimize_acqf_generator.py b/aepsych/generators/optimize_acqf_generator.py index 3f8ebb060..18e301115 100644 --- a/aepsych/generators/optimize_acqf_generator.py +++ b/aepsych/generators/optimize_acqf_generator.py @@ -14,15 +14,15 @@ from aepsych.generators.base import AEPsychGenerator from aepsych.models.base import ModelProtocol from aepsych.utils_logging import getLogger -from botorch.acquisition.preference import AnalyticExpectedUtilityOfBestOption -from botorch.optim import optimize_acqf from botorch.acquisition import ( AcquisitionFunction, - NoisyExpectedImprovement, - qNoisyExpectedImprovement, LogNoisyExpectedImprovement, + NoisyExpectedImprovement, qLogNoisyExpectedImprovement, + qNoisyExpectedImprovement, ) +from botorch.acquisition.preference import AnalyticExpectedUtilityOfBestOption +from botorch.optim import optimize_acqf logger = getLogger() @@ -120,7 +120,7 @@ def _gen( return new_candidate @classmethod - def from_config(cls, config: Config) -> 'OptimizeAcqfGenerator': + def from_config(cls, config: Config) -> "OptimizeAcqfGenerator": classname = cls.__name__ acqf = config.getobj(classname, "acqf", fallback=None) extra_acqf_args = cls._get_acqf_options(acqf, config) diff --git a/aepsych/generators/pairwise_optimize_acqf_generator.py b/aepsych/generators/pairwise_optimize_acqf_generator.py index 9a87f186e..08efd1c4c 100644 --- a/aepsych/generators/pairwise_optimize_acqf_generator.py +++ b/aepsych/generators/pairwise_optimize_acqf_generator.py @@ -17,7 +17,7 @@ class PairwiseOptimizeAcqfGenerator(OptimizeAcqfGenerator): stimuli_per_trial = 2 @classmethod - def from_config(cls, config: Config) -> 'OptimizeAcqfGenerator': + def from_config(cls, config: Config) -> "OptimizeAcqfGenerator": warnings.warn( "PairwiseOptimizeAcqfGenerator is deprecated. Use OptimizeAcqfGenerator instead.", DeprecationWarning, diff --git a/aepsych/generators/random_generator.py b/aepsych/generators/random_generator.py index dae83e09c..5e052f536 100644 --- a/aepsych/generators/random_generator.py +++ b/aepsych/generators/random_generator.py @@ -53,7 +53,7 @@ def gen( return X @classmethod - def from_config(cls, config: Config) -> 'RandomGenerator': + def from_config(cls, config: Config) -> "RandomGenerator": classname = cls.__name__ lb = config.gettensor(classname, "lb") ub = config.gettensor(classname, "ub") diff --git a/aepsych/generators/semi_p.py b/aepsych/generators/semi_p.py index 7c4d3b1b1..48260af56 100644 --- a/aepsych/generators/semi_p.py +++ b/aepsych/generators/semi_p.py @@ -35,7 +35,6 @@ def gen( # type: ignore[override] model: SemiParametricGPModel, # type: ignore[override] context_objective: Type = SemiPThresholdObjective, ) -> torch.Tensor: - fixed_features = {model.stim_dim: 0} next_x = super().gen( num_points=num_points, model=model, fixed_features=fixed_features diff --git a/aepsych/kernels/pairwisekernel.py b/aepsych/kernels/pairwisekernel.py index 9b0daa21c..8584158ac 100644 --- a/aepsych/kernels/pairwisekernel.py +++ b/aepsych/kernels/pairwisekernel.py @@ -1,7 +1,10 @@ from typing import Any, Optional, Union + import torch from gpytorch.kernels import Kernel from linear_operator import to_linear_operator + + class PairwiseKernel(Kernel): """ Wrapper to convert a kernel K on R^k to a kernel K' on R^{2k}, modeling @@ -12,13 +15,17 @@ class PairwiseKernel(Kernel): """ - def __init__(self, latent_kernel: Any, is_partial_obs: bool=False, **kwargs) -> None: + def __init__( + self, latent_kernel: Any, is_partial_obs: bool = False, **kwargs + ) -> None: super(PairwiseKernel, self).__init__(**kwargs) self.latent_kernel = latent_kernel self.is_partial_obs = is_partial_obs - def forward(self, x1: torch.Tensor, x2: torch.Tensor, diag: bool=False, **params) -> Optional[torch.Tensor]: + def forward( + self, x1: torch.Tensor, x2: torch.Tensor, diag: bool = False, **params + ) -> Optional[torch.Tensor]: r""" TODO: make last_batch_dim work properly @@ -39,7 +46,7 @@ def forward(self, x1: torch.Tensor, x2: torch.Tensor, diag: bool=False, **params * `diag`: `n` or `b x n` """ if self.is_partial_obs: - d : Union[torch.Tensor, int] = x1.shape[-1] - 1 + d: Union[torch.Tensor, int] = x1.shape[-1] - 1 assert d == x2.shape[-1] - 1, "tensors not the same dimension" assert d % 2 == 0, "dimension must be even" diff --git a/aepsych/likelihoods/bernoulli.py b/aepsych/likelihoods/bernoulli.py index 0e3a40165..7f6fc66ac 100644 --- a/aepsych/likelihoods/bernoulli.py +++ b/aepsych/likelihoods/bernoulli.py @@ -25,7 +25,9 @@ def __init__(self, objective: Callable) -> None: super().__init__() self.objective = objective - def forward(self, function_samples: torch.Tensor, **kwargs: Any) -> torch.distributions.Bernoulli: + def forward( + self, function_samples: torch.Tensor, **kwargs: Any + ) -> torch.distributions.Bernoulli: """Forward pass for BernoulliObjectiveLikelihood. Args: @@ -38,7 +40,7 @@ def forward(self, function_samples: torch.Tensor, **kwargs: Any) -> torch.distri return torch.distributions.Bernoulli(probs=output_probs) @classmethod - def from_config(cls, config: Config) -> 'BernoulliObjectiveLikelihood': + def from_config(cls, config: Config) -> "BernoulliObjectiveLikelihood": """Create an instance from a configuration object. Args: diff --git a/aepsych/likelihoods/ordinal.py b/aepsych/likelihoods/ordinal.py index 56648a80b..8362907f9 100644 --- a/aepsych/likelihoods/ordinal.py +++ b/aepsych/likelihoods/ordinal.py @@ -7,9 +7,9 @@ from typing import Callable, Optional -from aepsych.config import Config import gpytorch import torch +from aepsych.config import Config from gpytorch.likelihoods import Likelihood from torch.distributions import Categorical, Normal @@ -25,7 +25,7 @@ class OrdinalLikelihood(Likelihood): def __init__(self, n_levels: int, link: Optional[Callable] = None) -> None: """Initialize OrdinalLikelihood. - + Args: n_levels (int): Number of levels in the ordinal scale. link (Callable, optional): Link function. Defaults to None. @@ -49,10 +49,10 @@ def cutpoints(self) -> torch.Tensor: def forward(self, function_samples: torch.Tensor, *params, **kwargs) -> Categorical: """Forward pass for Ordinal - + Args: function_samples (torch.Tensor): Function samples. - + Returns: Categorical: Categorical distribution object. """ @@ -71,15 +71,15 @@ def forward(self, function_samples: torch.Tensor, *params, **kwargs) -> Categori return res @classmethod - def from_config(cls, config: Config) -> 'OrdinalLikelihood': + def from_config(cls, config: Config) -> "OrdinalLikelihood": """Creates an instance fron configuration object - + Args: config (Config): Configuration object. - + Returns: OrdinalLikelihood: OrdinalLikelihood instance""" classname = cls.__name__ n_levels = config.getint(classname, "n_levels") link = config.getobj(classname, "link", fallback=None) - return cls(n_levels=n_levels, link=link) \ No newline at end of file + return cls(n_levels=n_levels, link=link) diff --git a/aepsych/likelihoods/semi_p.py b/aepsych/likelihoods/semi_p.py index c28eff467..f6a337488 100644 --- a/aepsych/likelihoods/semi_p.py +++ b/aepsych/likelihoods/semi_p.py @@ -93,7 +93,9 @@ def forward( output_probs = self.p(function_samples, Xi) return torch.distributions.Bernoulli(probs=output_probs) - def expected_log_prob(self, observations:torch.Tensor, function_dist: torch.Tensor, *args, **kwargs) -> torch.Tensor: + def expected_log_prob( + self, observations: torch.Tensor, function_dist: torch.Tensor, *args, **kwargs + ) -> torch.Tensor: """This has to be overridden to fix a bug in gpytorch where the kwargs aren't being passed along to self.forward. @@ -122,7 +124,7 @@ def log_prob_lambda(function_samples: torch.Tensor) -> torch.Tensor: return log_prob @classmethod - def from_config(cls, config: Config) -> 'LinearBernoulliLikelihood': + def from_config(cls, config: Config) -> "LinearBernoulliLikelihood": """Create an instance from a configuration object. Args: diff --git a/aepsych/models/__init__.py b/aepsych/models/__init__.py index 8febf11b8..a3acf1294 100644 --- a/aepsych/models/__init__.py +++ b/aepsych/models/__init__.py @@ -21,7 +21,6 @@ SemiParametricGPModel, ) - __all__ = [ "GPClassificationModel", "MonotonicRejectionGP", diff --git a/aepsych/models/base.py b/aepsych/models/base.py index 512675cd2..67f2af75a 100644 --- a/aepsych/models/base.py +++ b/aepsych/models/base.py @@ -7,7 +7,6 @@ from __future__ import annotations import abc - import time from collections.abc import Iterable from copy import deepcopy @@ -16,7 +15,6 @@ import gpytorch import numpy as np import torch - from aepsych.config import Config, ConfigurableMixin from aepsych.models.utils import get_extremum, inv_query from aepsych.utils import dim_grid, get_jnd_multid, make_scaled_sobol, promote_0d @@ -28,7 +26,6 @@ from gpytorch.mlls import MarginalLogLikelihood from scipy.stats import norm - logger = getLogger() diff --git a/aepsych/models/gp_classification.py b/aepsych/models/gp_classification.py index 606a83c00..0991d882c 100644 --- a/aepsych/models/gp_classification.py +++ b/aepsych/models/gp_classification.py @@ -7,7 +7,6 @@ from __future__ import annotations import warnings - from copy import deepcopy from typing import Optional, Tuple, Union diff --git a/aepsych/models/monotonic_rejection_gp.py b/aepsych/models/monotonic_rejection_gp.py index b35efcb05..ec391ff50 100644 --- a/aepsych/models/monotonic_rejection_gp.py +++ b/aepsych/models/monotonic_rejection_gp.py @@ -116,7 +116,6 @@ def __init__( mean_module.constant.copy_(torch.tensor(fixed_prior_mean)) if covar_module is None: - ls_prior = gpytorch.priors.GammaPrior( concentration=4.6, rate=1.0, transform=lambda x: 1 / x ) diff --git a/aepsych/models/multitask_regression.py b/aepsych/models/multitask_regression.py index e1b683678..cc73d3967 100644 --- a/aepsych/models/multitask_regression.py +++ b/aepsych/models/multitask_regression.py @@ -9,11 +9,9 @@ from typing import Any, Dict, Optional -from aepsych.config import Config import gpytorch - import torch - +from aepsych.config import Config from aepsych.models import GPRegressionModel @@ -78,7 +76,9 @@ def __init__( self.covar_module, num_tasks=num_outputs, rank=rank ) - def forward(self, x: torch.Tensor) -> gpytorch.distributions.MultitaskMultivariateNormal: + def forward( + self, x: torch.Tensor + ) -> gpytorch.distributions.MultitaskMultivariateNormal: transformed_x = self.normalize_inputs(x) mean_x = self.mean_module(transformed_x) covar_x = self.covar_module(transformed_x) @@ -150,7 +150,9 @@ def __init__( **kwargs, ) # type: ignore # mypy issue 4335 - def forward(self, x: torch.Tensor) -> gpytorch.distributions.MultitaskMultivariateNormal: + def forward( + self, x: torch.Tensor + ) -> gpytorch.distributions.MultitaskMultivariateNormal: base_mvn = super().forward(x) # do transforms return gpytorch.distributions.MultitaskMultivariateNormal.from_batch_mvn( base_mvn diff --git a/aepsych/models/ordinal_gp.py b/aepsych/models/ordinal_gp.py index dc72ead97..c33ed60c2 100644 --- a/aepsych/models/ordinal_gp.py +++ b/aepsych/models/ordinal_gp.py @@ -6,6 +6,7 @@ # LICENSE file in the root directory of this source tree. from typing import Optional, Union + import gpytorch import torch from aepsych.likelihoods import OrdinalLikelihood @@ -51,7 +52,7 @@ def __init__(self, likelihood=None, *args, **kwargs): **kwargs, ) - def predict_probs(self, xgrid:torch.Tensor) -> torch.Tensor: + def predict_probs(self, xgrid: torch.Tensor) -> torch.Tensor: fmean, fvar = self.predict(xgrid) return self.calculate_probs(fmean, fvar) diff --git a/aepsych/models/pairwise_probit.py b/aepsych/models/pairwise_probit.py index f0497d8e7..a57e15f87 100644 --- a/aepsych/models/pairwise_probit.py +++ b/aepsych/models/pairwise_probit.py @@ -28,7 +28,9 @@ class PairwiseProbitModel(PairwiseGP, AEPsychMixin): stimuli_per_trial = 2 outcome_type = "binary" - def _pairs_to_comparisons(self, x: torch.Tensor, y: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]: + def _pairs_to_comparisons( + self, x: torch.Tensor, y: torch.Tensor + ) -> Tuple[torch.Tensor, torch.Tensor]: """ Takes x, y structured as pairs and judgments and returns pairs and comparisons as PairwiseGP requires @@ -127,7 +129,11 @@ def update( self.fit(train_x, train_y) def predict( - self, x: torch.Tensor, probability_space: bool =False, num_samples: int =1000, rereference: str ="x_min" + self, + x: torch.Tensor, + probability_space: bool = False, + num_samples: int = 1000, + rereference: str = "x_min", ) -> Tuple[torch.Tensor, torch.Tensor]: if rereference is not None: samps = self.sample(x, num_samples, rereference) @@ -145,13 +151,19 @@ def predict( return fmean, fvar def predict_probability( - self, x: torch.Tensor, probability_space: bool = False, num_samples: int = 1000, rereference: str = "x_min" + self, + x: torch.Tensor, + probability_space: bool = False, + num_samples: int = 1000, + rereference: str = "x_min", ) -> Tuple[torch.Tensor, torch.Tensor]: return self.predict( x, probability_space=True, num_samples=num_samples, rereference=rereference ) - def sample(self, x: torch.Tensor, num_samples: int, rereference: str = "x_min") -> torch.Tensor: + def sample( + self, x: torch.Tensor, num_samples: int, rereference: str = "x_min" + ) -> torch.Tensor: if len(x.shape) < 2: x = x.reshape(-1, 1) if rereference is None: @@ -179,8 +191,7 @@ def sample(self, x: torch.Tensor, num_samples: int, rereference: str = "x_min") return -samps + samps_ref @classmethod - def from_config(cls, config: Config) -> 'PairwiseProbitModel': - + def from_config(cls, config: Config) -> "PairwiseProbitModel": classname = cls.__name__ mean_covar_factory = config.getobj( diff --git a/aepsych/models/semi_p.py b/aepsych/models/semi_p.py index fd3821188..e1aa518e3 100644 --- a/aepsych/models/semi_p.py +++ b/aepsych/models/semi_p.py @@ -14,13 +14,13 @@ import numpy as np import torch from aepsych.acquisition.objective import FloorLogitObjective - from aepsych.acquisition.objective.semi_p import SemiPThresholdObjective from aepsych.config import Config from aepsych.likelihoods import BernoulliObjectiveLikelihood, LinearBernoulliLikelihood from aepsych.models import GPClassificationModel from aepsych.utils import _process_bounds, promote_0d from aepsych.utils_logging import getLogger +from botorch.acquisition.objective import PosteriorTransform from botorch.optim.fit import fit_gpytorch_mll_scipy from botorch.posteriors import GPyTorchPosterior from gpytorch.distributions import MultivariateNormal @@ -30,13 +30,18 @@ from gpytorch.priors import GammaPrior from torch import Tensor from torch.distributions import Normal -from botorch.acquisition.objective import PosteriorTransform # TODO: Implement a covar factory and analytic method for getting the lse logger = getLogger() -def _hadamard_mvn_approx(x_intensity: torch.Tensor, slope_mean: torch.Tensor, slope_cov: torch.Tensor, offset_mean: torch.Tensor, offset_cov: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]: +def _hadamard_mvn_approx( + x_intensity: torch.Tensor, + slope_mean: torch.Tensor, + slope_cov: torch.Tensor, + offset_mean: torch.Tensor, + offset_cov: torch.Tensor, +) -> Tuple[torch.Tensor, torch.Tensor]: """ MVN approximation to the hadamard product of GPs (from the SemiP paper, extending the @@ -147,7 +152,6 @@ def sample_thresholds( sample_shape: Optional[torch.Size] = None, base_samples: Optional[torch.Tensor] = None, ) -> SemiPThresholdObjective: - fsamps = self.rsample(sample_shape=sample_shape, base_samples=base_samples) return SemiPThresholdObjective( likelihood=self.likelihood, target=threshold_level @@ -376,12 +380,16 @@ def predict( m, v = samps.mean(0), samps.var(0) return promote_0d(m), promote_0d(v) - def posterior(self, X: torch.Tensor, posterior_transform: Optional[PosteriorTransform] = None) -> SemiPPosterior: + def posterior( + self, X: torch.Tensor, posterior_transform: Optional[PosteriorTransform] = None + ) -> SemiPPosterior: # Assume x is (b) x n x d if X.ndim > 3: raise ValueError # Add in the extra 2 batch for the 2 GPs in this model - Xnew = X.unsqueeze(-3).expand( + Xnew = X.unsqueeze( + -3 + ).expand( X.shape[:-2] # (b) + torch.Size([2]) # For the two GPs + X.shape[-2:] # n x d diff --git a/aepsych/models/utils.py b/aepsych/models/utils.py index edcd1fa2a..71fe55781 100644 --- a/aepsych/models/utils.py +++ b/aepsych/models/utils.py @@ -7,7 +7,6 @@ from __future__ import annotations import warnings - from typing import List, Mapping, Optional, Tuple, Union import numpy as np @@ -24,13 +23,12 @@ from botorch.utils.sampling import draw_sobol_samples from gpytorch.distributions import MultivariateNormal from gpytorch.kernels import Kernel -from gpytorch.likelihoods import BernoulliLikelihood +from gpytorch.likelihoods import BernoulliLikelihood, Likelihood from scipy.cluster.vq import kmeans2 from scipy.special import owens_t from scipy.stats import norm from torch import Tensor from torch.distributions import Normal -from gpytorch.likelihoods import Likelihood def compute_p_quantile( @@ -62,11 +60,14 @@ def select_inducing_points( method: str = "auto", ) -> torch.Tensor: with torch.no_grad(): - assert method in ( - "pivoted_chol", - "kmeans++", - "auto", - "sobol", + assert ( + method + in ( + "pivoted_chol", + "kmeans++", + "auto", + "sobol", + ) ), f"Inducing point method should be one of pivoted_chol, kmeans++, sobol, or auto; got {method}" if method == "sobol": @@ -107,7 +108,9 @@ def select_inducing_points( return inducing_points -def get_probability_space(likelihood: Likelihood, posterior: GPyTorchPosterior) -> Tuple[torch.Tensor, torch.Tensor]: +def get_probability_space( + likelihood: Likelihood, posterior: GPyTorchPosterior +) -> Tuple[torch.Tensor, torch.Tensor]: fmean = posterior.mean.squeeze() fvar = posterior.variance.squeeze() if isinstance(likelihood, BernoulliLikelihood): diff --git a/aepsych/plotting.py b/aepsych/plotting.py index 73461dff3..bb4cab779 100644 --- a/aepsych/plotting.py +++ b/aepsych/plotting.py @@ -9,12 +9,12 @@ from typing import Any, Callable, Iterable, List, Optional, Sized, Union import matplotlib.pyplot as plt -from matplotlib.axes import Axes import numpy as np import torch from aepsych.strategy import Strategy from aepsych.utils import get_lse_contour, get_lse_interval, make_scaled_sobol +from matplotlib.axes import Axes from scipy.stats import norm @@ -183,13 +183,9 @@ def _plot_strat_1d( ub = strat.transforms.untransform(strat.ub)[0] threshold_samps = [ - interpolate_monotonic( - x=grid.squeeze(), - y=s, - z=target_level, - min_x=lb, - max_x=ub, - ) + interpolate_monotonic(grid, s, target_level, strat.lb[0], strat.ub[0]) + .cpu() + .numpy() for s in samps ] thresh_med = np.mean(threshold_samps) @@ -335,8 +331,16 @@ def _plot_strat_2d( if true_testfun is not None: true_f = true_testfun(grid).reshape(gridsize, gridsize) - true_thresh = get_lse_contour( - true_f, mono_grid, level=target_level, lb=lb[-1], ub=ub[-1] + true_thresh = ( + get_lse_contour( + true_f, + mono_grid, + level=target_level, + lb=strat.lb[-1], + ub=strat.ub[-1], + ) + .cpu() + .numpy() ) ax.plot(context_grid, true_thresh, label="Ground truth threshold") @@ -379,7 +383,6 @@ def plot_strat_3d( contour_levels_list: List[float] = [] - if parnames is None: parnames = ["x1", "x2", "x3"] # Get global min/max for all slices @@ -436,7 +439,7 @@ def plot_strat_3d( cbar.ax.set_ylabel(f"Probability of {outcome_label}") else: cbar.ax.set_ylabel(outcome_label) - for clevel in contour_levels_list: # type: ignore + for clevel in contour_levels_list: # type: ignore cbar.ax.axhline(y=clevel, c="w") if save_path is not None: @@ -455,7 +458,7 @@ def plot_slice( vmin: float, vmax: float, gridsize: int = 30, - contour_levels:Optional[Sized] = None, + contour_levels: Optional[Sized] = None, lse: bool = False, extent_multiplier: Optional[List] = None, ): @@ -498,7 +501,12 @@ def plot_slice( plt_parnames = np.delete(parnames, slice_dim) img = ax.imshow( - fmean.T, extent=tuple(plt_extents), origin="lower", aspect="auto", vmin=vmin, vmax=vmax + fmean.T, + extent=tuple(plt_extents), + origin="lower", + aspect="auto", + vmin=vmin, + vmax=vmax, ) ax.set_title(parnames[slice_dim] + "=" + str(dim_val_scaled)) ax.set_xlabel(plt_parnames[0]) @@ -513,5 +521,5 @@ def plot_slice( aspect="auto", ) else: - raise(ValueError("Countour Levels should not be None!")) + raise (ValueError("Countour Levels should not be None!")) return img diff --git a/aepsych/server/message_handlers/handle_info.py b/aepsych/server/message_handlers/handle_info.py index 90d9f30c1..910aac720 100644 --- a/aepsych/server/message_handlers/handle_info.py +++ b/aepsych/server/message_handlers/handle_info.py @@ -6,7 +6,6 @@ # LICENSE file in the root directory of this source tree. import logging - from typing import Any, Dict import aepsych.utils_logging as utils_logging @@ -54,9 +53,9 @@ def info(server) -> Dict[str, Any]: "all_strat_names": server.strat_names, "current_strat_index": server.strat_id, "current_strat_name": server.strat.name, - "current_strat_data_pts": server.strat.x.shape[0] - if server.strat.x is not None - else 0, + "current_strat_data_pts": ( + server.strat.x.shape[0] if server.strat.x is not None else 0 + ), "current_strat_model": current_strat_model, "current_strat_acqf": current_strat_acqf, "current_strat_finished": server.strat.finished, diff --git a/aepsych/server/message_handlers/handle_query.py b/aepsych/server/message_handlers/handle_query.py index 595759734..be533fe64 100644 --- a/aepsych/server/message_handlers/handle_query.py +++ b/aepsych/server/message_handlers/handle_query.py @@ -6,10 +6,10 @@ # LICENSE file in the root directory of this source tree. import logging -import torch import aepsych.utils_logging as utils_logging import numpy as np +import torch logger = utils_logging.getLogger(logging.INFO) diff --git a/aepsych/server/message_handlers/handle_setup.py b/aepsych/server/message_handlers/handle_setup.py index 200411f62..df20e95f5 100644 --- a/aepsych/server/message_handlers/handle_setup.py +++ b/aepsych/server/message_handlers/handle_setup.py @@ -9,7 +9,6 @@ import aepsych.utils_logging as utils_logging from aepsych.config import Config - from aepsych.strategy import SequentialStrategy from aepsych.version import __version__ @@ -19,9 +18,7 @@ def _configure(server, config): - server._pregen_asks = ( - [] - ) # TODO: Allow each strategy to have its own stack of pre-generated asks + server._pregen_asks = [] # TODO: Allow each strategy to have its own stack of pre-generated asks parnames = config.getlist("common", "parnames", element_type=str) server.parnames = parnames diff --git a/aepsych/server/replay.py b/aepsych/server/replay.py index 9a3161640..39368142d 100644 --- a/aepsych/server/replay.py +++ b/aepsych/server/replay.py @@ -10,7 +10,6 @@ import aepsych.utils_logging as utils_logging import pandas as pd - from aepsych.server.message_handlers.handle_tell import flatten_tell_record logger = utils_logging.getLogger(logging.INFO) diff --git a/aepsych/server/server.py b/aepsych/server/server.py index 203020ebb..fd2d33e3e 100644 --- a/aepsych/server/server.py +++ b/aepsych/server/server.py @@ -13,17 +13,14 @@ import threading import traceback import warnings - from typing import Optional import aepsych.database.db as db - import aepsych.utils_logging as utils_logging import dill import numpy as np import pandas as pd import torch - from aepsych.server.message_handlers import MESSAGE_MAP from aepsych.server.message_handlers.handle_ask import ask from aepsych.server.message_handlers.handle_setup import configure @@ -33,7 +30,6 @@ get_strats_from_replay, replay, ) - from aepsych.server.sockets import BAD_REQUEST, DummySocket, PySocket logger = utils_logging.getLogger(logging.INFO) diff --git a/aepsych/server/sockets.py b/aepsych/server/sockets.py index aaf10bbc9..12b3e640d 100644 --- a/aepsych/server/sockets.py +++ b/aepsych/server/sockets.py @@ -36,7 +36,6 @@ def close(self): class PySocket(object): def __init__(self, port, ip=""): - addr = (ip, port) # all interfaces if socket.has_dualstack_ipv6(): self.socket = socket.create_server( @@ -72,7 +71,6 @@ def accept_client(self): raise Exception def receive(self, server_exiting): - while not server_exiting: rlist, wlist, xlist = select.select( [self.conn], [], [], 0 diff --git a/aepsych/strategy.py b/aepsych/strategy.py index 4fb89caef..e046aef7d 100644 --- a/aepsych/strategy.py +++ b/aepsych/strategy.py @@ -9,7 +9,6 @@ import time import warnings - from typing import ( Any, Callable, @@ -26,7 +25,6 @@ import numpy as np import torch - from aepsych.config import Config from aepsych.generators.base import AEPsychGenerator from aepsych.models.base import AEPsychMixin @@ -144,13 +142,11 @@ def __init__( len(outcome_types) == 1 and outcome_types[0] == model.outcome_type ), f"Strategy outcome types is {outcome_types} but model outcome type is {model.outcome_type}!" else: - assert set(outcome_types) == set( - model.outcome_type + assert ( + set(outcome_types) == set(model.outcome_type) ), f"Strategy outcome types is {outcome_types} but model outcome type is {model.outcome_type}!" if use_gpu_modeling: - assert ( - torch.cuda.is_available() - ), f"GPU requested for model {type(model).__name__} but GPU is not found!" + assert torch.cuda.is_available(), f"GPU requested for model {type(model).__name__} but GPU is not found!" self.model_device = torch.device("cuda" if use_gpu_modeling else "cpu") @@ -420,7 +416,6 @@ def fit(self) -> None: self.model.to(self.model_device) # type: ignore if self.keep_most_recent is not None: try: - self.model.fit( # type: ignore self.x[-self.keep_most_recent :], # type: ignore self.y[-self.keep_most_recent :], # type: ignore @@ -440,7 +435,6 @@ def fit(self) -> None: warnings.warn("Cannot fit: no model has been initialized!", RuntimeWarning) def update(self) -> None: - if self.can_fit: self.model.to(self.model_device) # type: ignore if self.keep_most_recent is not None: diff --git a/aepsych/utils.py b/aepsych/utils.py index b7ebdf7a5..a43609a17 100644 --- a/aepsych/utils.py +++ b/aepsych/utils.py @@ -12,7 +12,6 @@ import numpy as np import torch from aepsych.config import Config - from botorch.models.gpytorch import GPyTorchModel from scipy.stats import norm from torch.quasirandom import SobolEngine @@ -220,7 +219,6 @@ def get_jnd_multid( lb: Union[torch.Tensor, float] = -float("inf"), ub: Union[torch.Tensor, float] = float("inf"), ) -> torch.Tensor: - # Move mono_dim to the last dimension if it isn't already if mono_dim != -1: post_mean = post_mean.transpose(mono_dim, -1) diff --git a/examples/contrast_discrimination_psychopy/experiment.py b/examples/contrast_discrimination_psychopy/experiment.py index 4761201d5..9e077284c 100644 --- a/examples/contrast_discrimination_psychopy/experiment.py +++ b/examples/contrast_discrimination_psychopy/experiment.py @@ -15,7 +15,6 @@ def run_experiment(): - seed = experiment_config.constants["seed"] config_path = experiment_config.constants["config_path"] torch.manual_seed(seed) @@ -108,7 +107,6 @@ def run_experiment(): fixation_keys = ["space"] ## for debugging if "space" in fixation_keys: - screen_text.setText("+") screen_text.draw(win=win) win.flip() @@ -152,5 +150,4 @@ def run_experiment(): if __name__ == "__main__": - run_experiment() diff --git a/examples/contrast_discrimination_psychopy/experiment_config.py b/examples/contrast_discrimination_psychopy/experiment_config.py index 661e5e166..385b962e6 100644 --- a/examples/contrast_discrimination_psychopy/experiment_config.py +++ b/examples/contrast_discrimination_psychopy/experiment_config.py @@ -9,7 +9,6 @@ import numpy as np - constants = { "savefolder": "./databases/", "timestamp": datetime.now().strftime("%Y-%m-%d_%H-%M-%S"), diff --git a/examples/contrast_discrimination_psychopy/helpers.py b/examples/contrast_discrimination_psychopy/helpers.py index f64d42023..f11defa30 100644 --- a/examples/contrast_discrimination_psychopy/helpers.py +++ b/examples/contrast_discrimination_psychopy/helpers.py @@ -9,7 +9,6 @@ import pyglet from psychopy import core, event from psychopy.visual import Window - from psychopy.visual.image import ImageStim pyglet.options["debug_gl"] = False @@ -27,7 +26,7 @@ def cartesian_to_polar(x, y): class AnimatedGrating: - param_transforms = {"contrast": lambda x: 10 ** x, "pedestal": lambda x: 10 ** x} + param_transforms = {"contrast": lambda x: 10**x, "pedestal": lambda x: 10**x} def __init__( self, @@ -184,15 +183,15 @@ def noisify_half_texture(self, img, noisy_half): img = img.T # transpose so our indexing tricks work flatimg = img.flatten() if noisy_half == "left": - noisy = flatimg[: (self.res ** 2) // 2] + noisy = flatimg[: (self.res**2) // 2] np.random.shuffle(noisy) - img = np.r_[noisy, flatimg[(self.res ** 2) // 2 :]].reshape( + img = np.r_[noisy, flatimg[(self.res**2) // 2 :]].reshape( self.res, self.res ) else: - noisy = flatimg[(self.res ** 2) // 2 :] + noisy = flatimg[(self.res**2) // 2 :] np.random.shuffle(noisy) - img = np.r_[flatimg[: (self.res ** 2) // 2], noisy].reshape( + img = np.r_[flatimg[: (self.res**2) // 2], noisy].reshape( self.res, self.res ) return img.T # untranspose diff --git a/pubs/owenetal/code/benchmark_threshold.py b/pubs/owenetal/code/benchmark_threshold.py index 8ded27f54..f1375019c 100644 --- a/pubs/owenetal/code/benchmark_threshold.py +++ b/pubs/owenetal/code/benchmark_threshold.py @@ -18,11 +18,11 @@ from itertools import product from aepsych.benchmark import ( - Problem, - LSEProblem, BenchmarkLogger, - PathosBenchmark, combine_benchmarks, + LSEProblem, + PathosBenchmark, + Problem, ) from aepsych.benchmark.test_functions import ( make_songetal_testfun, diff --git a/pubs/owenetal/code/prior_plots.py b/pubs/owenetal/code/prior_plots.py index 256798ee3..2441a5a56 100644 --- a/pubs/owenetal/code/prior_plots.py +++ b/pubs/owenetal/code/prior_plots.py @@ -18,8 +18,8 @@ from aepsych.config import Config from aepsych.factory import ( default_mean_covar_factory, - song_mean_covar_factory, monotonic_mean_covar_factory, + song_mean_covar_factory, ) from aepsych.models import GPClassificationModel, MonotonicRejectionGP from aepsych.models.monotonic_rejection_gp import MixedDerivativeVariationalGP @@ -27,6 +27,7 @@ global_seed = 3 + def plot_prior_samps_1d(): config = Config( config_dict={ @@ -230,6 +231,5 @@ def plot_prior_samps_2d(): if __name__ == "__main__": - prior_samps_1d = plot_prior_samps_1d() prior_samps_1d.savefig("./figs/prior_samps.pdf", dpi=200) diff --git a/pubs/owenetal/code/stratplots.py b/pubs/owenetal/code/stratplots.py index 1a6b723a2..762df209f 100644 --- a/pubs/owenetal/code/stratplots.py +++ b/pubs/owenetal/code/stratplots.py @@ -12,11 +12,11 @@ import numpy as np import torch from aepsych.benchmark import ( - Problem, - LSEProblem, - BenchmarkLogger, Benchmark, + BenchmarkLogger, combine_benchmarks, + LSEProblem, + Problem, ) from aepsych.benchmark.test_functions import ( make_songetal_testfun, @@ -151,7 +151,7 @@ def f(self, x): logx=True, show=False, include_legend=False, - include_colorbar=False + include_colorbar=False, ) for ax_, strat_, title_ in zip(plotting_axes, strats, titles) ] @@ -287,7 +287,7 @@ def f(self, x): no_label=no_label, show=False, include_legend=False, - include_colorbar=False + include_colorbar=False, ) for ax_, strat_, title_ in zip(plotting_axes, strats, titles) ] @@ -384,7 +384,7 @@ def f(self, x): true_testfun=true_testfun, target_level=target_level, show=False, - include_legend=False + include_legend=False, ) samps = [ norm.cdf(s.sample(torch.Tensor(g), num_samples=10000)) @@ -418,7 +418,13 @@ def f(self, x): _ = [ plot_strat( - strat=s, title=t, ax=a, true_testfun=true_testfun, target_level=target_level, show=False, include_legend=False + strat=s, + title=t, + ax=a, + true_testfun=true_testfun, + target_level=target_level, + show=False, + include_legend=False, ) for a, s, t in zip(plotting_axes, strats, titles) ] @@ -430,7 +436,6 @@ def f(self, x): if __name__ == "__main__": - audio_lse_grids_fig = plot_audiometric_lse_grids(sobol_trials=5, opt_trials=45) audio_lse_grids_fig.savefig(fname=figdir + "audio_lse_grids_fig.pdf", dpi=200) novel_detection_lse_grids_fig = plot_novel_lse_grids( diff --git a/pyproject.toml b/pyproject.toml index b1abe09cb..6013c201e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,4 +6,8 @@ build-backend = "setuptools.build_meta" first_party_detection = false [tool.black] -target-version = ["py38"] +target-version = ["py310"] + +[tool.ufmt] +formatter = "ruff-api" +sorter = "usort" diff --git a/scripts/parse_demos.py b/scripts/parse_demos.py index 6946b6960..06d22a49b 100644 --- a/scripts/parse_demos.py +++ b/scripts/parse_demos.py @@ -34,6 +34,7 @@ class DemoPage extends React.Component {{ """ + def validate_demo_links(repo_dir: str) -> None: """Checks that all .zip files that present are linked on the website, and vice versa, that any linked demos has an associated .zip file present. @@ -104,9 +105,13 @@ def gen_demos(repo_dir: str) -> None: html_outfile.write(html_out) # generate JS file - has_mac_demo = os.path.exists(os.path.join(repo_dir, "demos", f"{d_id}_Mac.zip")) - has_win_demo = os.path.exists(os.path.join(repo_dir, "demos", f"{d_id}_Win.zip")) - script = TEMPLATE.format(d_id,has_win_demo,has_mac_demo) + has_mac_demo = os.path.exists( + os.path.join(repo_dir, "demos", f"{d_id}_Mac.zip") + ) + has_win_demo = os.path.exists( + os.path.join(repo_dir, "demos", f"{d_id}_Win.zip") + ) + script = TEMPLATE.format(d_id, has_win_demo, has_mac_demo) js_out_path = os.path.join(repo_dir, "website", "pages", "demos", f"{d_id}.js") with open(js_out_path, "w") as js_outfile: js_outfile.write(script) diff --git a/scripts/parse_sphinx.py b/scripts/parse_sphinx.py index 4d95457aa..511edde9d 100644 --- a/scripts/parse_sphinx.py +++ b/scripts/parse_sphinx.py @@ -11,7 +11,7 @@ from bs4 import BeautifulSoup -#The base_url must match the base url in the /website/siteConfig.js +# The base_url must match the base url in the /website/siteConfig.js # Note if it is not updated API doc searchbar will not be displayed # 1) update base_url below base_url = "/" diff --git a/scripts/parse_tutorials.py b/scripts/parse_tutorials.py index aea236f4f..06471827f 100644 --- a/scripts/parse_tutorials.py +++ b/scripts/parse_tutorials.py @@ -14,7 +14,6 @@ from bs4 import BeautifulSoup from nbconvert import HTMLExporter, PythonExporter - TEMPLATE = """const CWD = process.cwd(); const React = require('react'); @@ -63,8 +62,13 @@ def validate_tutorial_links(repo_dir: str) -> None: ) if missing_ids: - print( '\033[93m' + 'Warning: ' + '\x1b[0m' + "The following tutorial files are present, but are not linked on the " - "website: {}.".format(", ".join([nbid + ".ipynb" for nbid in missing_ids]))) + print( + "\033[93m" + + "Warning: " + + "\x1b[0m" + + "The following tutorial files are present, but are not linked on the " + "website: {}.".format(", ".join([nbid + ".ipynb" for nbid in missing_ids])) + ) # raise RuntimeError( # "The following tutorial files are present, but are not linked on the " diff --git a/sphinx/source/conf.py b/sphinx/source/conf.py index 016576c83..674e89f4d 100644 --- a/sphinx/source/conf.py +++ b/sphinx/source/conf.py @@ -12,7 +12,6 @@ # LICENSE file in the scripts directory. - # -- Path setup -------------------------------------------------------------- import os diff --git a/tests/acquisition/test_mi.py b/tests/acquisition/test_mi.py index f12367981..440980f24 100644 --- a/tests/acquisition/test_mi.py +++ b/tests/acquisition/test_mi.py @@ -29,7 +29,6 @@ class SingleProbitMI(unittest.TestCase): def test_1d_monotonic_single_probit(self): - seed = 1 torch.manual_seed(seed) np.random.seed(seed) @@ -76,9 +75,7 @@ def test_1d_monotonic_single_probit(self): normal_dist = torch.distributions.Normal(0, 1) self.assertTrue((((normal_dist.cdf(est) - true) ** 2).mean()) < 0.25) - def test_1d_single_probit(self): - seed = 1 torch.manual_seed(seed) np.random.seed(seed) @@ -127,7 +124,6 @@ def test_1d_single_probit(self): self.assertTrue((((normal_dist.cdf(est) - true) ** 2).mean()) < 0.25) def test_mi_acqf(self): - mean = ConstantMean().initialize(constant=1.2) covar = LinearKernel().initialize(variance=1.0) model = GPClassificationModel( diff --git a/tests/generators/test_manual_generator.py b/tests/generators/test_manual_generator.py index 7f3d91096..d72daf274 100644 --- a/tests/generators/test_manual_generator.py +++ b/tests/generators/test_manual_generator.py @@ -9,7 +9,6 @@ import numpy as np import numpy.testing as npt - from aepsych.config import Config from aepsych.generators import ManualGenerator, SampleAroundPointsGenerator from aepsych.transforms import ParameterTransformedGenerator diff --git a/tests/generators/test_optimize_acqf_generator.py b/tests/generators/test_optimize_acqf_generator.py index fbd94779f..1634f4223 100644 --- a/tests/generators/test_optimize_acqf_generator.py +++ b/tests/generators/test_optimize_acqf_generator.py @@ -13,10 +13,7 @@ from aepsych.acquisition import MCLevelSetEstimation from aepsych.config import Config from aepsych.generators import OptimizeAcqfGenerator -from aepsych.models import ( - GPClassificationModel, - PairwiseProbitModel, -) +from aepsych.models import GPClassificationModel, PairwiseProbitModel from botorch.acquisition.preference import AnalyticExpectedUtilityOfBestOption from sklearn.datasets import make_classification diff --git a/tests/models/test_gp_classification.py b/tests/models/test_gp_classification.py index 3fd861c36..d2d84045a 100644 --- a/tests/models/test_gp_classification.py +++ b/tests/models/test_gp_classification.py @@ -260,7 +260,6 @@ def test_reset_hyperparams(self): self.assertTrue(np.allclose(ls_before, ls_reset)) def test_reset_variational_strategy(self): - model = GPClassificationModel(lb=[-3], ub=[3], inducing_size=20) variational_params_before = [ @@ -315,7 +314,6 @@ def test_predict_p(self): class GPClassificationTest(unittest.TestCase): def test_1d_single_probit_new_interface(self): - seed = 1 torch.manual_seed(seed) np.random.seed(seed) @@ -362,7 +360,6 @@ def test_1d_single_probit_new_interface(self): self.assertTrue(np.abs(x[np.argmax(zhat.detach().numpy())]) < 0.5) def test_1d_single_probit_batched(self): - seed = 1 torch.manual_seed(seed) np.random.seed(seed) @@ -408,7 +405,6 @@ def test_1d_single_probit_batched(self): self.assertTrue(np.abs(x[np.argmax(zhat.detach().numpy())]) < 0.5) def test_1d_single_probit(self): - seed = 1 torch.manual_seed(seed) np.random.seed(seed) @@ -453,7 +449,6 @@ def test_1d_single_probit(self): self.assertTrue(np.abs(x[np.argmax(zhat.detach().numpy())]) < 0.5) def test_1d_single_probit_pure_exploration(self): - seed = 1 torch.manual_seed(seed) np.random.seed(seed) @@ -550,7 +545,6 @@ def test_2d_single_probit_pure_exploration(self): ) def test_1d_single_targeting(self): - seed = 1 torch.manual_seed(seed) np.random.seed(seed) @@ -660,7 +654,6 @@ def obj(x): self.assertTrue(np.abs(est_jnd_taylor - 1.5) < 0.25) def test_1d_single_lse(self): - seed = 1 torch.manual_seed(seed) np.random.seed(seed) @@ -710,7 +703,6 @@ def test_1d_single_lse(self): self.assertTrue(np.abs(est_max - norm.ppf(0.75)) < 0.5) def test_2d_single_probit(self): - seed = 1 torch.manual_seed(seed) np.random.seed(seed) diff --git a/tests/models/test_gp_regression.py b/tests/models/test_gp_regression.py index 5453de4c9..41e3b5ff8 100644 --- a/tests/models/test_gp_regression.py +++ b/tests/models/test_gp_regression.py @@ -13,7 +13,6 @@ import numpy.testing as npt import torch from aepsych.server import AEPsychServer - from aepsych.server.message_handlers.handle_ask import ask from aepsych.server.message_handlers.handle_setup import configure from aepsych.server.message_handlers.handle_tell import tell diff --git a/tests/models/test_multitask_regression.py b/tests/models/test_multitask_regression.py index c1f6ba105..e4cfbaa55 100644 --- a/tests/models/test_multitask_regression.py +++ b/tests/models/test_multitask_regression.py @@ -45,7 +45,6 @@ def setUp(self): @parameterized.expand(models) def test_mtgpr_smoke(self, model): - model.fit(self.x, self.f) ypred, _ = model.predict(self.xtest) diff --git a/tests/models/test_semi_p.py b/tests/models/test_semi_p.py index 7993a0677..becfc130a 100644 --- a/tests/models/test_semi_p.py +++ b/tests/models/test_semi_p.py @@ -9,7 +9,6 @@ import numpy as np import numpy.testing as npt - import torch from aepsych.acquisition import MCPosteriorVariance from aepsych.acquisition.lookahead import GlobalMI @@ -19,22 +18,20 @@ FloorProbitObjective, ProbitObjective, ) -from aepsych.generators import OptimizeAcqfGenerator, SobolGenerator -from aepsych.likelihoods import BernoulliObjectiveLikelihood -from aepsych.strategy import SequentialStrategy, Strategy from aepsych.acquisition.objective.semi_p import ( SemiPProbabilityObjective, SemiPThresholdObjective, ) +from aepsych.generators import OptimizeAcqfGenerator, SobolGenerator +from aepsych.likelihoods import BernoulliObjectiveLikelihood from aepsych.likelihoods.semi_p import LinearBernoulliLikelihood from aepsych.models import HadamardSemiPModel, SemiParametricGPModel -from aepsych.models.semi_p import ( - _hadamard_mvn_approx, - semi_p_posterior_transform, -) +from aepsych.models.semi_p import _hadamard_mvn_approx, semi_p_posterior_transform +from aepsych.strategy import SequentialStrategy, Strategy from gpytorch.distributions import MultivariateNormal from parameterized import parameterized + def _hadamard_model_constructor(lb, ub, stim_dim, floor, objective=FloorLogitObjective): return HadamardSemiPModel( lb=lb, @@ -76,9 +73,7 @@ def setUp(self): xcontext = X[..., self.context_dim] xintensity = X[..., self.stim_dim] # polynomial context - slope = ( - xcontext - 0.7 * xcontext**2 + 0.3 * xcontext**3 - 0.1 * xcontext**4 - ) + slope = xcontext - 0.7 * xcontext**2 + 0.3 * xcontext**3 - 0.1 * xcontext**4 intercept = ( xcontext + 0.03 * xcontext**5 - 0.2 * xcontext**3 - 0.7 * xcontext**4 ) @@ -264,7 +259,6 @@ def test_reset_variational_strategy(self, model_constructor): ub = [3, 3] stim_dim = 0 with self.subTest(model_constructor=model_constructor): - model = model_constructor(lb=lb, ub=ub, stim_dim=stim_dim, floor=0) link = FloorLogitObjective(floor=0) y = torch.bernoulli(link(self.f)) @@ -301,7 +295,6 @@ def test_reset_variational_strategy(self, model_constructor): self.assertFalse(np.allclose(after, reset)) def test_slope_mean_setting(self): - for slope_mean in (2, 4): model = SemiParametricGPModel( lb=self.lb, @@ -338,7 +331,6 @@ def setUp(self): self.y = torch.bernoulli(link(X[:, stim_dim])) def test_reset_hyperparams(self): - model = HadamardSemiPModel(lb=[-3, -3], ub=[3, 3], inducing_size=20) slope_os_before = model.slope_covar_module.outputscale.clone().detach().numpy() diff --git a/tests/models/test_utils.py b/tests/models/test_utils.py index 45ed587f1..b95864588 100644 --- a/tests/models/test_utils.py +++ b/tests/models/test_utils.py @@ -9,10 +9,9 @@ import numpy as np import torch -from sklearn.datasets import make_classification - from aepsych.models import GPClassificationModel from aepsych.models.utils import select_inducing_points +from sklearn.datasets import make_classification class UtilsTestCase(unittest.TestCase): diff --git a/tests/server/test_server.py b/tests/server/test_server.py index f93eb1cd0..610dc3bde 100644 --- a/tests/server/test_server.py +++ b/tests/server/test_server.py @@ -15,7 +15,6 @@ import aepsych.server as server import aepsych.utils_logging as utils_logging - from aepsych.server.sockets import BAD_REQUEST dummy_config = """ diff --git a/tests/test_benchmark.py b/tests/test_benchmark.py index 671d18eb2..dd104748a 100644 --- a/tests/test_benchmark.py +++ b/tests/test_benchmark.py @@ -15,10 +15,10 @@ from aepsych.benchmark import ( Benchmark, DerivedValue, + example_problems, LSEProblem, PathosBenchmark, Problem, - example_problems, ) from aepsych.models import GPClassificationModel from scipy.stats import norm @@ -88,7 +88,9 @@ def unvectorized_p_below_threshold(self, x, f_thresh) -> torch.Tensor: def unvectorized_true_below_threshold(self, threshold): """the original true_below_threshold method in the LSEProblem class""" - return (self.test_problem.p(self.test_problem.eval_grid) <= threshold).to(torch.float32) + return (self.test_problem.p(self.test_problem.eval_grid) <= threshold).to( + torch.float32 + ) def test_vectorized_score_calculation(self): f_thresholds = self.test_problem.f_threshold(self.model) @@ -98,7 +100,7 @@ def test_vectorized_score_calculation(self): brier_p_below_thresh = torch.mean(2 * torch.square(true_p_l - p_l), dim=1) # Classification error misclass_on_thresh = torch.mean( - p_l * (1 - true_p_l) + (1 - p_l) * true_p_l, dim=1 + p_l * (1 - true_p_l) + (1 - p_l) * true_p_l, dim=1 ) assert ( p_l.ndim == 2 @@ -107,8 +109,10 @@ def test_vectorized_score_calculation(self): ) for i_threshold, single_threshold in enumerate(self.thresholds): - normal_dist = torch.distributions.Normal(0, 1) - single_f_threshold = normal_dist.icdf(single_threshold).float() # equivalent to norm.ppf + normal_dist = torch.distributions.Normal(0, 1) + single_f_threshold = normal_dist.icdf( + single_threshold + ).float() # equivalent to norm.ppf assert torch.isclose(single_f_threshold, f_thresholds[i_threshold]) @@ -120,7 +124,9 @@ def test_vectorized_score_calculation(self): unvectorized_true_p_l = self.unvectorized_true_below_threshold( single_threshold ) - assert torch.all(torch.isclose(unvectorized_true_p_l, true_p_l[i_threshold])) + assert torch.all( + torch.isclose(unvectorized_true_p_l, true_p_l[i_threshold]) + ) unvectorized_brier_score = torch.mean( 2 * torch.square(unvectorized_true_p_l - unvectorized_p_l) @@ -140,7 +146,6 @@ def test_vectorized_score_calculation(self): class BenchmarkTestCase(unittest.TestCase): def setUp(self): - # run this single-threaded since we parallelize using pathos self.oldenv = os.environ.copy() os.environ["OMP_NUM_THREADS"] = "1" @@ -206,7 +211,6 @@ def tearDown(self): os.environ.update(self.oldenv) def test_bench_smoke(self): - problem1 = TestProblem() problem2 = LSETestProblem() @@ -271,7 +275,6 @@ def test_bench_smoke(self): self.assertTrue(out[out["final"]]["seed"].is_unique) def test_bench_pathossmoke(self): - problem1 = TestProblem() problem2 = LSETestProblem() @@ -359,7 +362,6 @@ def test_bench_pathos_partial(self): class BenchProblemTestCase(unittest.TestCase): def setUp(self): - seed = 1 torch.manual_seed(seed) np.random.seed(seed) diff --git a/tests/test_config.py b/tests/test_config.py index 2375a166f..1ad61772d 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -28,7 +28,6 @@ PairwiseProbitModel, ) from aepsych.server import AEPsychServer - from aepsych.server.message_handlers.handle_setup import configure from aepsych.strategy import SequentialStrategy, Strategy from aepsych.transforms import ParameterTransforms, transform_options @@ -318,9 +317,7 @@ def test_to_string(self): mean_covar_factory = default_mean_covar_factory [OptimizeAcqfGenerator] restarts = 10 - samps = 1000""".strip().replace( - " ", "" - ) + samps = 1000""".strip().replace(" ", "") config = Config(config_str=in_str) out_str = str(config).strip().replace(" ", "") diff --git a/tests/test_db.py b/tests/test_db.py index e2a2a842f..064b8cdc8 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -241,9 +241,9 @@ def test_update_db_with_raw_data_tables(self): ) outcome_dict = {x: {} for x in range(1, 8)} for outcome in outcome_data: - outcome_dict[outcome.iteration_id][ - outcome.outcome_name - ] = outcome.outcome_value + outcome_dict[outcome.iteration_id][outcome.outcome_name] = ( + outcome.outcome_value + ) self.assertEqual(outcome_dict, outcome_dict_expected) diff --git a/tests/test_lookahead.py b/tests/test_lookahead.py index 2bbc476e6..38a501fb9 100644 --- a/tests/test_lookahead.py +++ b/tests/test_lookahead.py @@ -27,7 +27,6 @@ class BvNCDFTestCase(unittest.TestCase): def test_bvncdf(self): - rhos = np.linspace(0.3, 0.9, 7) xus = [0.3, 0.5, 0.7] yus = [0.3, 0.5, 0.7] @@ -68,7 +67,6 @@ def setUp(self): self.model, self.f, self.covar = model, f, covar def test_posterior_extraction(self): - mu_s, s2_s, mu_q, s2_q, cov_q = posterior_at_xstar_xq( self.model, self.xstar, self.xq ) diff --git a/tests/test_mean_covar_factories.py b/tests/test_mean_covar_factories.py index 4decdc21b..09eb26164 100644 --- a/tests/test_mean_covar_factories.py +++ b/tests/test_mean_covar_factories.py @@ -13,11 +13,11 @@ from aepsych.factory import ( default_mean_covar_factory, monotonic_mean_covar_factory, - song_mean_covar_factory, pairwise_mean_covar_factory, + song_mean_covar_factory, ) -from aepsych.kernels.rbf_partial_grad import RBFKernelPartialObsGrad from aepsych.kernels.pairwisekernel import PairwiseKernel +from aepsych.kernels.rbf_partial_grad import RBFKernelPartialObsGrad from aepsych.means.constant_partial_grad import ConstantMeanPartialObsGrad from scipy.stats import norm @@ -47,7 +47,6 @@ def test_default_factory_1d_dim(self): self._test_mean_covar(meanfun, covarfun) def test_default_factory_args_1d(self): - conf = { "default_mean_covar_factory": { "lb": [0], @@ -293,7 +292,7 @@ def test_pairwise_factory_1d(self): meanfun, covarfun = pairwise_mean_covar_factory(config) def test_pairwise_factory_2d(self): - conf = {"common": {"lb": [0,0], "ub": [1,1]}} + conf = {"common": {"lb": [0, 0], "ub": [1, 1]}} config = Config(config_dict=conf) meanfun, covarfun = pairwise_mean_covar_factory(config) self.assertTrue(covarfun.latent_kernel.ard_num_dims == 1) @@ -308,7 +307,10 @@ def test_pairwise_factory_3d(self): meanfun, covarfun = pairwise_mean_covar_factory(config) def test_pairwise_factory_shared(self): - conf = {"common": {"lb": [0, 0, 0], "ub": [1, 1, 1]}, "pairwise_mean_covar_factory": {"shared_dims": [0]}} + conf = { + "common": {"lb": [0, 0, 0], "ub": [1, 1, 1]}, + "pairwise_mean_covar_factory": {"shared_dims": [0]}, + } config = Config(config_dict=conf) meanfun, covarfun = pairwise_mean_covar_factory(config) self.assertIsInstance(meanfun, gpytorch.means.ConstantMean) diff --git a/tests_gpu/models/test_gp_regression.py b/tests_gpu/models/test_gp_regression.py index ed2fd67de..841362c3c 100644 --- a/tests_gpu/models/test_gp_regression.py +++ b/tests_gpu/models/test_gp_regression.py @@ -12,7 +12,6 @@ import numpy as np import torch from aepsych.server import AEPsychServer - from aepsych.server.message_handlers.handle_ask import ask from aepsych.server.message_handlers.handle_setup import configure from aepsych.server.message_handlers.handle_tell import tell diff --git a/tests_gpu/test_config.py b/tests_gpu/test_config.py index 6c41a53dd..99e0fa73f 100644 --- a/tests_gpu/test_config.py +++ b/tests_gpu/test_config.py @@ -9,7 +9,6 @@ import torch from aepsych.config import Config - from aepsych.strategy import SequentialStrategy from aepsych.version import __version__