Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions documentation/docs/guides/gui-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ While the optimization is running, the values of the variables, objectives, and
#### Specify variable range
Each optimzation variable must have defined bounds that specify the valid search space. These ranges serve dual purposes: they constrain the optimization algorithm to explore only physically meaningful parameter values, and they enforce safety limits to prevent damage to the equipment. Variable ranges are defined in the Environment + VOCS section.

Use **Set Variable Range** above the table to apply a range option to all variables, or click a row's gear button to adjust one variable. The individual range dialog offers three relative options and `exact_bounds`; relative bounds are calculated from the selected option and the variable's current value.

![Variable Range](/img/gui/variablerange.png)

#### Incorporate algorithm parameters
Expand Down
7 changes: 6 additions & 1 deletion documentation/docs/guides/minimode.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,17 @@ When "Show checked only" is unchecked, the table scrolls to display the first se
### Variable Range Dialog

<!-- Screenshot: individual variable range dialog with bounds preview bar -->
![Mini Mode Variable Dialog](/img/guides/minimode-variable2.png)
![Mini Mode Variable Dialog](/img/guides/minimode-variable-dialog.png)

Clicking the gear icon on a variable row opens a dialog with:

- The current value of the variable
- The current low and high bounds for the variable
- Options for range selection method
- ratio wrt current value: Bounds are calculated as a fraction of the current value on either side of the current value
- ratio wrt full range: Bounds are calculated as a fraction of the full range on either side of the current value
- delta around current value: Bounds are calculated as a delta on either side of the current value
- exact bounds: the input numerical bounds are used.
- A **bounds preview bar** showing what the resulting bounds will be
- Hard limits displayed (but not editable)

Expand Down
24 changes: 17 additions & 7 deletions documentation/docs/guides/templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,31 +36,41 @@ vocs: # XOPT VOCS

# Important note about variable bounds: the bounds set here will be used
# if relative_to_current is set to false. If relative_to_current is true,
# these bounds will not be used and the bounds for each variable will be
# determined based on the vrange_limit_options below.
# the bounds for each variable will be determined based on the
# vrange_limit_options below and the value of each variable when the template
# is loaded. In that case, bounds specified here will be used as hard
# variable limits.

vrange_limit_options: {}

# for each variable:
# variable: {limit_option_idx: 0 or 1 or 2, ratio_curr: ratio (float), ratio_full: ratio (float), delta: abs value (float)}
# variable: {limit_option_idx: 0 or 1 or 2 or 3, ratio_curr: ratio (float), ratio_full: ratio (float), delta: abs value (float), exact_bounds: [lower, upper] (list[float, float])}
#
# For example:
# QUAD:LTUH:620:BCTRL:
# limit_option_idx: 2
# ratio_curr: 0.1
# ratio_full: 0.1
# delta: 1.2
# exact_bounds: [45.0, 57.0]
# Will set the variable range for QUAD:LTUH:620:BCTRL to a delta (option 2) of +- 1.2 from the current value of the variable.
#
# Note that ratio_curr is the ratio with respect to the current value,
# ratio_full is the ratio with respect to the full variable range, and
# delta is an absolute delta around the current value. Include values
# for all three options even if you do not plan to use them.
# delta is an absolute delta around the current value.
# limit_option_idx sets the desired option: 0 will use ratio_curr,
# 1 is ratio_full, 2 is delta.
# 1 is ratio_full, 2 is delta, 3 will use exact_bounds
#
# The first three methods will calculate bounds around the current variable value when the template
# is loaded based on the selected option. If limit_option_idx == 3 for exact_bounds,
# the specified bounds will be used regardless of the current value of the variable. If the
# exact_bounds option is selected without specifying numerical bounds in vrange_limit_options,
# the GUI will use the hard bounds defined above in vocs/variables as a fallback.

relative_to_current: true # (bool) true or false. If true, variable ranges will be set
# for each variable based on vrange_limit_options. If False, variable
# for each variable based on vrange_limit_options, and initial points will be calculated
# automatically when the variable is added. The upper and lower bounds specified in the
# vocs/variables dictionary will be used as hard bounds. If False, variable
# ranges will be set to the specified upper and lower bounds from the
# variables dictionary in vocs.
initial_point_actions: [{}] # list of dictionaries
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
62 changes: 59 additions & 3 deletions src/badger/gui/components/routine_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
LessThanConstraint,
MaximizeObjective,
MinimizeObjective,
ContinuousVariable,
)
from pydantic import ValidationError
from PyQt5.QtCore import Qt, QTimer, pyqtSignal
Expand Down Expand Up @@ -1521,7 +1522,27 @@ def set_vrange(self, set_all=True):
for vname in vname_selected:
self.ratio_var_ranges[vname] = copy.deepcopy(self.limit_option)

def set_ind_vrange(self, vname, config):
def set_ind_vrange(self, vname: str, config: dict) -> None:
"""
Apply a variable-specific range policy to a single environment variable.

The selected mode is determined by ``config["limit_option_idx"]`` and can
represent a fixed percentage of the current value, a percentage of the full
hard-range width, a fixed absolute delta around the current value, or an exact
bound pair. The method reads the live environment value for ``vname``, computes
the candidate bounds, clips them to the hard limits from ``config["lower_bound"]``
and ``config["upper_bound"]``, and applies the result to the GUI variable table.
Initial points for that variable are recalculated with the new bounds.

Parameters
----------
vname : str
Name of the variable whose bounds should be updated.
config : dict
Bounds configuration for the variable, including the hard limits,
``limit_option_idx``, ``ratio_full``, ``ratio_curr``, ``delta``, and
``exact_bounds``.
"""
logger.info(
f"Setting individual variable range for {vname} with config: {config}"
)
Expand All @@ -1531,14 +1552,15 @@ def set_ind_vrange(self, vname, config):
"ratio_full": config["ratio_full"],
"ratio_curr": config["ratio_curr"],
"delta": config["delta"],
"exact_bounds": config.get("exact_bounds", hard_bounds),
}

option_idx = option["limit_option_idx"]

env = self.create_env()
curr = env.get_variables([vname])[vname]

# 0: ratio with current value, 1: ratio with full range, 2: delta around current value
# 0: ratio with current value, 1: ratio with full range, 2: delta around current value, 3: exact bounds
if option_idx == 1:
ratio = option["ratio_full"]
delta = 0.5 * ratio * (hard_bounds[1] - hard_bounds[0])
Expand All @@ -1548,6 +1570,9 @@ def set_ind_vrange(self, vname, config):
delta = option["delta"]
bounds = [curr - delta, curr + delta]
bounds = np.clip(bounds, hard_bounds[0], hard_bounds[1]).tolist()
elif option_idx == 3:
bounds = sorted(option.get("exact_bounds", hard_bounds))
bounds = np.clip(bounds, hard_bounds[0], hard_bounds[1]).tolist()
else:
ratio = option["ratio_curr"]
sign = np.sign(curr)
Expand Down Expand Up @@ -1612,6 +1637,23 @@ def update_init_table(self, force=False):
self._fill_init_table()

def calc_auto_bounds(self):
"""
Compute auto-derived bounds for all selected variables.

The method of calculation is determined for each variable by that variable's
configured `limit_option_idx`, stored in `self.ratio_var_ranges[var_name]`.
- For `option_idx == 1`, bounds are calculated as a fraction of the full
variable range.
- For `option_idx == 2`, bounds are calculated as a delta on either side of the
current value.
- For `option_idx == 3`, the stored exact numerical bounds are used.
- Otherwise, (`option_idx == 0`) the ratio_curr mode calculated bounds as a
fraction of the current value around the current point.
In every case, the resulting bounds are
clipped to the variable's hard limits and the `clipped` dictionary records
whether a bound was reduced by that clip.
"""

logger.info("Calculating auto bounds for selected variables.")
vname_selected = []
vrange = {}
Expand All @@ -1634,7 +1676,7 @@ def calc_auto_bounds(self):
limit_option = self.limit_option

option_idx = limit_option["limit_option_idx"]
# 0: ratio with current value, 1: ratio with full range, 2: delta around current value
# 0: ratio with current value, 1: ratio with full range, 2: delta around current value, 3: exact bounds
if option_idx == 1:
ratio = limit_option["ratio_full"]
hard_bounds = vrange[name]
Expand All @@ -1650,6 +1692,13 @@ def calc_auto_bounds(self):
bounds = np.clip(bounds, hard_bounds[0], hard_bounds[1]).tolist()
vrange[name] = bounds
logger.info(f"Auto bounds for {name} (delta): {bounds}")
elif option_idx == 3:
hard_bounds = vrange[name]
exact_bounds = limit_option.get("exact_bounds")
bounds = sorted(exact_bounds) if exact_bounds else list(hard_bounds)
bounds = np.clip(bounds, hard_bounds[0], hard_bounds[1]).tolist()
vrange[name] = bounds
logger.info(f"Auto bounds for {name} (exact): {bounds}")
else:
ratio = limit_option["ratio_curr"]
hard_bounds = vrange[name]
Expand Down Expand Up @@ -1749,10 +1798,17 @@ def handle_var_config(self, vname):
except KeyError:
option = self.limit_option

current_bounds = self.env_box.var_table.bounds.get(vname, bounds)
if current_bounds is None:
current_bounds = bounds
if isinstance(current_bounds, ContinuousVariable):
current_bounds = current_bounds.domain

configs = {
"current_value": curr,
"lower_bound": bounds[0],
"upper_bound": bounds[1],
"current_bounds": current_bounds,
**option,
}

Expand Down
7 changes: 2 additions & 5 deletions src/badger/gui/mini/components/var_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,16 +227,13 @@ def __init__(
lower, upper = bounds

delta = 0.5 * abs(upper - lower)
is_centered = self._is_centered(value, lower, upper)

if is_clipped:
lower_delta = abs(value - lower)
upper_delta = abs(upper - value)
delta = max(lower_delta, upper_delta)

self.line_edit = QLineEdit(
f"±{delta:.3f}{'*' if is_clipped or not is_centered else ''}"
)
self.line_edit = QLineEdit(f"±{delta:.3f}{'*' if is_clipped else ''}")
self.line_edit.setReadOnly(True)
self.line_edit.setAlignment(
Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter
Expand Down Expand Up @@ -264,7 +261,7 @@ def __init__(
layout.addWidget(self.line_edit)
layout.addWidget(self._button_stack)

if is_clipped or not is_centered:
if is_clipped:
self.setToolTip("Requested bounds are clipped by hardware limits")

def set_selected(self, is_selected: bool):
Expand Down
Loading
Loading