BudgetBox

The widget that displays budget options is an instance of a class named BudgetBox. It has a set of three tabs that display different options for specifying a budget:

  • the default view is a slider that sets the maximum budget value; OptiPass will be run 10 times with budgets ranging from 0 up to the maximum

  • an advanced view allows users to set the maximum, the interval between budgets, and the total number of budgets to consider

  • the fixed view has a text entry widget there the user enters a budget, Optipass will be run once using this budget

The BudgetBox class and the three budget views are all defined in src/budgets.py.

There are three ways users can specify the range of budget values when running OptiPass. A BudgetBox widget has one tab for each option. The widgets displayed inside a tab are defined by their own classes (BasicBudgetBox, AdvancedBudgetBox, and FixedBudgetBox).

Source code in src/tidegates/budgets.py
19
20
21
22
23
24
25
26
def __init__(self):
    super(BudgetBox, self).__init__()
    self.tabs = pn.Tabs(
        ('Basic', BasicBudgetBox()),
        ('Advanced', AdvancedBudgetBox()),
        ('Fixed', FixedBudgetBox()),
    )
    self.append(self.tabs)

set_budget_max(n)

When the user selects or deselects a region the budget widgets need to know the new total cost for all the selected regions. This method passes that information to each of the budget widgets.

Parameters:
  • n (int) –

    the new maximum budget amount

src/tidegates/budgets.py
28
29
30
31
32
33
34
35
36
37
38
def set_budget_max(self, n: int):
    """
    When the user selects or deselects a region the budget widgets need
    to know the new total cost for all the selected regions.  This method
    passes that information to each of the budget widgets.

    Arguments:
      n: the new maximum budget amount
    """
    for t in self.tabs:
        t.set_budget_max(n)

values()

Return the budget settings for the currently selected budget type.

Returns:
  • bmax

    the maximum budget to pass to OptiPass

  • binc

    the increment between budget values

src/tidegates/budgets.py
40
41
42
43
44
45
46
47
48
def values(self):
    """
    Return the budget settings for the currently selected budget type.

    Returns:
      bmax:  the maximum budget to pass to OptiPass
      binc:  the increment between budget values
    """
    return self.tabs[self.tabs.active].values()

BasicBudgetBox

The default budget widget displays a slider that ranges from 0 up to a maximum value based on the total cost of all barriers in currently selected regions.

Source code in src/tidegates/budgets.py
72
73
74
75
76
77
78
79
80
81
82
83
def __init__(self):
    super(BasicBudgetBox, self).__init__(margin=(15,0,15,5))
    self.labels = [ x[0] for x in self.levels ]
    self.map = { x[0]: x[1] for x in self.levels }
    self.slider = pn.widgets.DiscreteSlider(
        options = self.labels[:1], 
        value = self.labels[0],
        name = 'Maximum Budget',
        margin=(20,20,20,20),
        stylesheets=[slider_style_sheet],
    )
    self.append(self.slider)

set_budget_max(n)

Choose a maximum budget by scanning a table of budget levels to find the first one less than the total cost.

Parameters:
  • n

    the total cost of all barriers in the current selection.

src/tidegates/budgets.py
85
86
87
88
89
90
91
92
93
94
95
96
def set_budget_max(self, n):
    """
    Choose a maximum budget by scanning a table of budget levels to
    find the first one less than the total cost.

    Arguments:
      n: the total cost of all barriers in the current selection.
    """
    for i in range(len(self.levels)-1, -1, -1):
        if n >= self.levels[i][1]:
            self.slider.options = self.labels[:i+1]
            break

values()

Return the selected budget level (based on the slider position) and the number of budgets. For basic budgets the interval between budgets is computed by dividing the select budget value by the number of increments (a constant defined in the class, currently equal to 10).

Returns:
  • bmax

    the highest budget to pass to OptiPass

  • binc

    the increment between budgets

src/tidegates/budgets.py
 98
 99
100
101
102
103
104
105
106
107
108
109
110
def values(self):
    """
    Return the selected budget level (based on the slider position) and
    the number of budgets.  For basic budgets the interval between budgets
    is computed by dividing the select budget value by the number of increments
    (a constant defined in the class, currently equal to 10).

    Returns:
      bmax:  the highest budget to pass to OptiPass
      binc:  the increment between budgets
    """
    x = self.map[self.slider.value]
    return x, (x // self.increments)

AdvancedBudgetBox

The "advanced" option gives the user the most control over the budget values processed by OptiPass by letting them specify the number of budget levels (in the basic budget there are always 10 budget levels).

This box has three widgets: a slider to specify the maximum amount, another slider to specify the increment between budgets, and an input box to specify the number of budgets. Adjusting the value of any of these widgets automatically updates the other two. For example, if the maximum is set to $1M and the number of budgets is 10, the increment is $100K. If the user changes the number of budgets to 20, the increment drops to $50K. Or if they change the maximum to $2M, the increment increases to $200K.

Source code in src/tidegates/budgets.py
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
def __init__(self):
    super(AdvancedBudgetBox, self).__init__(margin=(15,0,15,5))

    self.cap = 0

    self.max_slider = pn.widgets.FloatSlider(
        name='Maximum Budget', 
        start=0, 
        end=1, 
        step=self.MAX_STEP,
        value=0,
        width=self.SLIDER_WIDTH,
        format=NumeralTickFormatter(format='$0,0'),
        stylesheets=[slider_style_sheet],
    )

    self.inc_slider = pn.widgets.FloatSlider(
        name='Budget Interval', 
        start=0, 
        end=1, 
        step=self.INC_STEP,
        value=0,
        width=self.SLIDER_WIDTH//2,
        format=NumeralTickFormatter(format='$0,0'),
        stylesheets=[slider_style_sheet],
    )

    self.count_input = pn.widgets.IntInput(
        name='Number of Budgets', 
        value=10, 
        step=1, 
        start=self.COUNT_MIN,
        end=self.COUNT_MAX,
        width=75,
    )

    self.append(pn.Row(self.max_slider, pn.pane.HTML('<b>Limit: N/A<b>')))
    self.append(pn.Row(self.inc_slider, self.count_input))

    self.max_slider.param.watch(self.max_updated, ['value'])
    self.inc_slider.param.watch(self.inc_updated, ['value'])
    self.count_input.param.watch(self.count_updated, ['value'])

count_updated(e)

Callback function invoked when the user changes the number of budget levels. Computes a new budget increment.

src/tidegates/budgets.py
273
274
275
276
277
278
def count_updated(self, e):
    """
    Callback function invoked when the user changes the number of budget
    levels.  Computes a new budget increment.
    """
    self.inc_slider.value = self.max_slider.value // self.count_input.value

inc_updated(e)

Callback function invoked when the user changes the budget increment. Computes a new number of budgets.

src/tidegates/budgets.py
264
265
266
267
268
269
270
271
def inc_updated(self, e):
    """
    Callback function invoked when the user changes the budget increment.
    Computes a new number of budgets.
    """
    c = max(self.COUNT_MIN, self.max_slider.value // self.inc_slider.value)
    c = min(self.COUNT_MAX, c)
    self.count_input.value = c

max_updated(e)

Callback function invoked when the user moves the maximum budget slider. Computes a new budget increment.

src/tidegates/budgets.py
257
258
259
260
261
262
def max_updated(self, e):
    """
    Callback function invoked when the user moves the maximum budget
    slider.  Computes a new budget increment.
    """
    self.inc_slider.value = self.max_slider.value // self.count_input.value

set_budget_max(n)

Called when the user selects or deselects a region. Save the new maximum, and update the value of the increment based on the new maximum.

Parameters:
  • n

    the total cost of all barriers in the selected regions.

src/tidegates/budgets.py
242
243
244
245
246
247
248
249
250
251
252
253
254
255
def set_budget_max(self, n):
    """
    Called when the user selects or deselects a region.  Save the new
    maximum, and update the value of the increment based on the new maximum.

    Arguments:
      n:  the total cost of all barriers in the selected regions.
    """
    self.max_slider.end = max(1, n)
    self.max_slider.start = self.MAX_STEP
    self.inc_slider.end = max(1, n // 2)
    self.inc_slider.start = max(self.INC_STEP, n / self.COUNT_MAX)
    lim = 'N/A' if n == 0 else f'${n/1000000:.2f}M'
    self[0][1] = pn.pane.HTML(f'<b>Limit: {lim}</b>')

values()

In this widget the maximum budget and budget increment are determined by the values in the corresponding widgets.

src/tidegates/budgets.py
235
236
237
238
239
240
def values(self):
    """
    In this widget the maximum budget and budget increment are determined
    by the values in the corresponding widgets.
    """
    return self.max_slider.value, self.inc_slider.value

FixedBudgetBox

This option is for situations where a user knows exactly how much money they have to spend and want to know the optimal set of barriers to replace for that amount of money. OptiPass is run twice -- once to determine the current passabilities, and once to compute the benefit from the specified budget. The widget simply displays a box where the user enters the dollar amount for their budget.

Source code in src/tidegates/budgets.py
122
123
124
125
def __init__(self):
    super(FixedBudgetBox, self).__init__(margin=(15,0,15,5))
    self.input = pn.widgets.TextInput(name='Budget Amount', value='$')
    self.append(self.input)

parse_dollar_amount(s)

Make sure the string entered by the user has an acceptable format. It can be all digits (e.g. "1500000"), or digits separated by commas (e.g. "1,500,000"), or a number followed by a K or M (e.g. "1.5M"). There can be a dollar sign at the front of the string.

Parameters:
  • s (str) –

    the string entered into the text box

Returns:
  • the value of the string converted into an integer

src/tidegates/budgets.py
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
def parse_dollar_amount(self, s: str):
    """
    Make sure the string entered by the user has an acceptable format.
    It can be all digits (e.g. "1500000"), or digits separated by commas
    (e.g. "1,500,000"), or a number followed by a K or M (e.g. "1.5M").
    There can be a dollar sign at the front of the string.

    Arguments:
      s:  the string entered into the text box

    Returns:
      the value of the string converted into an integer

    """
    try:
        if s.startswith('$'):
            s = s[1:]
        if s.endswith(('K','M')):
            multiplier = 1000 if s.endswith('K') else 1000000
            res = int(float(s[:-1]) * multiplier)
        elif ',' in s:
            parts = s.split(',')
            assert len(parts[0]) <= 3 and (len(parts) == 1 or all(len(p) == 3 for p in parts[1:]))
            res = int(''.join(parts))
        else:
            res = int(s)
        return res
    except Exception:
        raise ValueError('unexpected format in dollar amount')

values()

Return the specified budget amount as both the maximum budget and the budget interval.

src/tidegates/budgets.py
130
131
132
133
134
135
136
137
138
139
def values(self):
    """
    Return the specified budget amount as both the maximum budget and the
    budget interval.
    """
    s = self.input.value
    if s.startswith('$'):
        s = s[1:]
    n = self.parse_dollar_amount(self.input.value)
    return n, n