Skip to content

Commit

Permalink
addressed PR review
Browse files Browse the repository at this point in the history
  • Loading branch information
Thomas Morris committed Jan 6, 2024
1 parent 9aae8cc commit f14bd55
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 84 deletions.
1 change: 0 additions & 1 deletion bloptools/bayesian/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from .agent import * # noqa F401
from .devices import * # noqa F401
from .dofs import * # noqa F401
from .objectives import * # noqa F401
59 changes: 0 additions & 59 deletions bloptools/bayesian/devices.py

This file was deleted.

50 changes: 34 additions & 16 deletions bloptools/bayesian/dofs.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,42 @@ def _validate_dofs(dofs):
unique_dof_names, counts = np.unique(dof_names, return_counts=True)
duplicate_dof_names = unique_dof_names[counts > 1]
if len(duplicate_dof_names) > 0:
raise ValueError(f'Duplicate name(s) in supplied dofs: "{duplicate_dof_names}"')
raise ValueError(f"Duplicate name(s) in supplied dofs: {duplicate_dof_names}")

return list(dofs)


@dataclass
class DOF:
"""A degree of freedom (DOF), to be used by an agent.
Parameters
----------
name: str
The name of the DOF. This is used as a key.
description: str
A longer name for the DOF.
device: Signal, optional
An ophyd device. If None, a dummy ophyd device is generated.
limits: tuple, optional
A tuple of the lower and upper limit of the DOF. If the DOF is not read-only, the agent
will not explore outside the limits. If the DOF is read-only, the agent will reject all
sampled data where the DOF is outside the limits.
read_only: bool
If True, the agent will not try to set the DOF. Must be set to True if the supplied ophyd
device is read-only.
active: bool
If True, the agent will try to use the DOF in its optimization. If False, the agent will
still read the DOF but not include it any model or acquisition function.
units: str
The units of the DOF (e.g. mm or deg). This is only for plotting and general housekeeping.
tags: list
A list of tags. These make it easier to subset large groups of dofs.
latent_group: optional
An agent will fit latent dimensions to all DOFs with the same latent_group. If None, the
DOF will be modeled independently.
"""

device: Signal = None
description: str = None
name: str = None
Expand All @@ -40,8 +69,10 @@ class DOF:
read_only: bool = False
active: bool = True
tags: list = field(default_factory=list)
log: bool = False
latent_group: str = None

# Some post-processing. This is specific to dataclasses
def __post_init__(self):
self.uuid = str(uuid.uuid4())

Expand Down Expand Up @@ -100,6 +131,8 @@ def __getitem__(self, i):
return self.dofs[i]
elif type(i) is str:
return self.dofs[self.names.index(i)]
else:
raise ValueError(f"Invalid index {i}. A DOFList must be indexed by either an integer or a string.")

def __len__(self):
return len(self.dofs)
Expand Down Expand Up @@ -179,21 +212,6 @@ def _dof_mask(self, active=None, read_only=None, tags=[]):
def subset(self, active=None, read_only=None, tags=[]):
return DOFList([dof for dof, m in zip(self.dofs, self._dof_mask(active, read_only, tags)) if m])

# def _subset_devices(self, read_only=None, active=None, tags=[]):
# return [dof["device"] for dof in self._subset_dofs(read_only, active, tags)]

# def _read_subset_devices(self, read_only=None, active=None, tags=[]):
# return [device.read()[device.name]["value"] for device in self._subset_devices(read_only, active, tags)]

# def _subset_dof_names(self, read_only=None, active=None, tags=[]):
# return [device.name for device in self._subset_devices(read_only, active, tags)]

# def _subset_dof_limits(self, read_only=None, active=None, tags=[]):
# dofs_subset = self._subset_dofs(read_only, active, tags)
# if len(dofs_subset) > 0:
# return torch.tensor([dof["limits"] for dof in dofs_subset], dtype=torch.float64).T
# return torch.empty((2, 0))

def activate(self, read_only=None, active=None, tags=[]):
for dof in self._subset_dofs(read_only, active, tags):
dof.active = True
Expand Down
42 changes: 34 additions & 8 deletions bloptools/bayesian/objectives.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,36 @@ def _validate_objectives(objectives):
unique_names, counts = np.unique(names, return_counts=True)
duplicate_names = unique_names[counts > 1]
if len(duplicate_names) > 0:
raise DuplicateNameError(f'Duplicate name(s) in supplied objectives: "{duplicate_names}"')
raise DuplicateNameError(f"Duplicate name(s) in supplied objectives: {duplicate_names}")


@dataclass
class Objective:
"""An objective to be used by an agent.
Parameters
----------
name: str
The name of the objective. This is used as a key.
description: str
A longer description for the objective.
target: float or str
One of 'min' or 'max', or a number. The agent will respectively minimize or maximize the
objective, or target the supplied number.
log: bool
Whether to apply a log to the objective, to make it more Gaussian.
weight: float
The relative importance of this objective, used when scalarizing in multi-objective optimization.
active: bool
If True, the agent will care about this objective during optimization.
limits: tuple of floats
The range of reliable measurements for the obejctive. Outside of this, data points will be rejected.
min_snr: float
The minimum signal-to-noise ratio of the objective, used when fitting the model.
units: str
A label representing the units of the objective.
"""

name: str
description: str = None
target: Union[float, str] = "max"
Expand All @@ -46,8 +71,6 @@ def __post_init__(self):
if self.target not in ["min", "max"]:
raise ValueError("'target' must be either 'min', 'max', or a number.")

# self.device = Signal(name=self.name)

@property
def label(self):
return f"{'log ' if self.log else ''}{self.description}"
Expand Down Expand Up @@ -102,21 +125,24 @@ def summary(self):
def __repr__(self):
return self.summary.__repr__()

# @property
# def descriptions(self) -> list:
# return [obj.description for obj in self.objectives]
@property
def descriptions(self) -> list:
"""
Returns an array of the objective names.
"""
return [obj.description for obj in self.objectives]

@property
def names(self) -> list:
"""
Returns an array of the objective weights.
Returns an array of the objective names.
"""
return [obj.name for obj in self.objectives]

@property
def targets(self) -> np.array:
"""
Returns an array of the objective weights.
Returns an array of the objective targets.
"""
return [obj.target for obj in self.objectives]

Expand Down

0 comments on commit f14bd55

Please sign in to comment.