Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/nl formulation #29

Closed
wants to merge 49 commits into from
Closed
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
88ebe91
add nl formulation function stubs
jlalbers Jul 4, 2024
fae8bb1
move multiprocess import to top of file
jlalbers Jul 5, 2024
f20dbf3
Functional nl formulation
jlalbers Sep 6, 2024
c7a7a66
Move sampling to __main__
jlalbers Sep 11, 2024
dbaa866
add build_nl() function
jlalbers Sep 13, 2024
1307b74
require `dwave-optimization 0.3.0` for scalar broadcasting
jlalbers Sep 13, 2024
73e8fdc
add nl model validation function to utils.py
jlalbers Sep 13, 2024
680f8bb
refactor `msgs` to module constant
jlalbers Sep 13, 2024
63595ad
add ModelParams dataclass
jlalbers Sep 13, 2024
a65c2f6
refactor build_nl to use ModelParams
jlalbers Sep 13, 2024
513c815
move ModelParams to utils
jlalbers Sep 13, 2024
aff21f3
update validation functions to use ModelParams
jlalbers Sep 13, 2024
99d1abe
remove unused import
jlalbers Sep 13, 2024
6a3c9e0
add time limit heuristic for run_nl
jlalbers Sep 13, 2024
db02c64
update manager constraint to equality instead of geq
jlalbers Sep 17, 2024
ec1839b
add build_schedule_from_state function
jlalbers Sep 17, 2024
caed39d
fix FutureWarning in build_random_sched function
jlalbers Sep 17, 2024
e3284b0
change parameter from BinaryVariable to state ndarray
jlalbers Sep 17, 2024
11075f1
add unit tests for NL formulation
jlalbers Sep 17, 2024
19161de
add solver options to app_configs.py
jlalbers Sep 17, 2024
3e55fc8
add solver selection dropdown to control card
jlalbers Sep 17, 2024
2ba145a
fix max_consecutive_shifts constraint in cqm formulation so that the …
jlalbers Sep 17, 2024
86e1ec5
fix Solver dropdown value initialization
jlalbers Sep 17, 2024
105dd9a
add NL solver to run_optimization callback
jlalbers Sep 18, 2024
26d5407
fix error message string
jlalbers Sep 18, 2024
bba4021
add .venv to gitignore
jlalbers Sep 18, 2024
d1f4bb6
delete nl_formulation.py development file
jlalbers Sep 18, 2024
8d47156
fix shift label bug with NL error msgs
jlalbers Sep 18, 2024
3a19e58
remove time limit heuristic
jlalbers Sep 18, 2024
3a80f5f
create SolverType enum
jlalbers Sep 18, 2024
801eb22
remove SOLVERS option from app_configs
jlalbers Sep 18, 2024
8950790
add `dropdown` function to app_html
jlalbers Sep 18, 2024
79b8610
refactor solver dropdown to use `dropdown` function
jlalbers Sep 18, 2024
cb3e2e3
refactor presets dropdown to use `dropdown` function
jlalbers Sep 18, 2024
3ad7d4f
change SolverType.NL label
jlalbers Sep 18, 2024
4178948
use SolverType in app.py
jlalbers Sep 18, 2024
40a571e
fix typo in utils.py
jlalbers Sep 18, 2024
ca13584
remove print debug statement in employee_scheduling.py
jlalbers Sep 18, 2024
2d1893a
refactor key/value assignments out of loop in utils.py
jlalbers Sep 18, 2024
0f5edb6
use `MSGS` global variable direclty in `run_cqm`
jlalbers Sep 18, 2024
6475aa2
fix bug accessing SolverType.label
jlalbers Sep 18, 2024
c9483d5
refactor consecutive shift arrays to variable
jlalbers Sep 18, 2024
a5c2146
change '==' comparison to 'is' for readability in app.py
jlalbers Sep 20, 2024
623d033
Update employee_scheduling.py
jlalbers Sep 20, 2024
2eb1a79
edit implementation of ModelParams and nl validation functions
jlalbers Sep 20, 2024
5af20b6
refactor functions to use individual parameters instead of ModelParams
jlalbers Sep 20, 2024
7536567
unpack `params` into function arguments
jlalbers Sep 20, 2024
1a024bc
add comment to clarify changing of assignment value from 2 to 100 in …
jlalbers Sep 20, 2024
95aeacd
update tests to use `asdict` with ModelParams
jlalbers Sep 20, 2024
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ __pycache__/
*.py[cod]
*$py.class
cache.db*
.venv
89 changes: 61 additions & 28 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# limitations under the License.
from __future__ import annotations

import multiprocess
k8culver marked this conversation as resolved.
Show resolved Hide resolved
from typing import TYPE_CHECKING, Any

import diskcache
Expand All @@ -21,9 +22,16 @@

import employee_scheduling
import utils
from app_configs import (APP_TITLE, DEBUG, LARGE_SCENARIO, MEDIUM_SCENARIO, MIN_MAX_EMPLOYEES,
SMALL_SCENARIO)
from app_configs import (
APP_TITLE,
DEBUG,
LARGE_SCENARIO,
MEDIUM_SCENARIO,
MIN_MAX_EMPLOYEES,
SMALL_SCENARIO,
)
from app_html import errors_list, set_html
from src.demo_enums import SolverType

if TYPE_CHECKING:
from pandas import DataFrame
Expand All @@ -33,9 +41,8 @@

# Fix for Dash background callbacks crashing on macOS 10.13+ (https://bugs.python.org/issue33725)
# See https://github.com/dwave-examples/flow-shop-scheduling/pull/4 for more details.
import multiprocess
if multiprocess.get_start_method(allow_none=True) is None:
multiprocess.set_start_method('spawn')
multiprocess.set_start_method("spawn")

app = Dash(
__name__,
Expand All @@ -47,7 +54,9 @@


@app.callback(
Output({"type": "to-collapse-class", "index": MATCH}, "className", allow_duplicate=True),
Output(
{"type": "to-collapse-class", "index": MATCH}, "className", allow_duplicate=True
),
inputs=[
Input({"type": "collapse-trigger", "index": MATCH}, "n_clicks"),
State({"type": "to-collapse-class", "index": MATCH}, "className"),
Expand Down Expand Up @@ -114,7 +123,10 @@ def set_scenario(
shifts_per_employees,
employees_per_shift,
random_seed,
False, False, False, False
False,
False,
False,
False,
)


Expand All @@ -129,7 +141,9 @@ def set_scenario(
State("employees-per-shift-select", "tooltip"),
],
)
def update_employees_per_shift(value: int, tooltip: dict[str, Any]) -> tuple[int, dict, dict]:
def update_employees_per_shift(
value: int, tooltip: dict[str, Any]
) -> tuple[int, dict, dict]:
"""Update the employees-per-shift slider max if num-employees is changed."""
marks = {
MIN_MAX_EMPLOYEES["min"]: str(MIN_MAX_EMPLOYEES["min"]),
Expand Down Expand Up @@ -222,10 +236,7 @@ def custom_random_seed(value: int, scenario: int) -> int:
Output("schedule-tab", "disabled", allow_duplicate=True),
Output("tabs", "value"),
Output({"type": "to-collapse-class", "index": 1}, "style", allow_duplicate=True),
inputs=[
Input("num-employees-select", "value"),
Input("seed-select", "value")
],
inputs=[Input("num-employees-select", "value"), Input("seed-select", "value")],
)
def disp_initial_sched(
num_employees: int, rand_seed: int
Expand Down Expand Up @@ -269,10 +280,7 @@ def update_error_sidebar(run_click: int, prev_classes) -> tuple[dict, str]:
if "collapsed" in classes:
return no_update, no_update

return (
{"display": "none"},
prev_classes + " collapsed"
)
return ({"display": "none"}, prev_classes + " collapsed")


@app.callback(
Expand All @@ -288,11 +296,20 @@ def update_error_sidebar(run_click: int, prev_classes) -> tuple[dict, str]:
State("checklist-input", "value"),
State("consecutive-shifts-select", "value"),
State("availability-content", "children"),
State("solver-select", "value"),
],
running=[
# show cancel button and hide run button, and disable and animate results tab
(Output("cancel-button", "style"), {"display": "inline-block"}, {"display": "none"}),
(Output("run-button", "style"), {"display": "none"}, {"display": "inline-block"}),
(
Output("cancel-button", "style"),
{"display": "inline-block"},
{"display": "none"},
),
(
Output("run-button", "style"),
{"display": "none"},
{"display": "inline-block"},
),
# switch to schedule tab while running
(Output("schedule-tab", "disabled"), False, False),
(Output("tabs", "value"), "schedule-tab", "schedule-tab"),
Expand All @@ -308,6 +325,7 @@ def run_optimization(
checklist: list[int],
consecutive_shifts: int,
sched_df: DataFrame,
solver: int,
) -> tuple[DataFrame, bool, dict, list]:
"""Run a job on the hybrid solver when the run button is clicked."""
if run_click == 0 or ctx.triggered_id != "run-button":
Expand All @@ -322,20 +340,35 @@ def run_optimization(
isolated_days_allowed = True if 0 in checklist else False
manager_required = True if 1 in checklist else False

cqm = employee_scheduling.build_cqm(
availability,
shifts,
*shifts_per_employee,
*employees_per_shift,
manager_required,
isolated_days_allowed,
consecutive_shifts + 1,
params = utils.ModelParams(
availability=availability,
shifts=shifts,
min_shifts=min(shifts_per_employee),
max_shifts=max(shifts_per_employee),
shift_min=min(employees_per_shift),
shift_max=max(employees_per_shift),
requires_manager=manager_required,
allow_isolated_days_off=isolated_days_allowed,
max_consecutive_shifts=consecutive_shifts
)

feasible_sampleset, errors = employee_scheduling.run_cqm(cqm)
sample = feasible_sampleset.first.sample
solver_type = SolverType(solver)

if solver_type == SolverType.CQM:
jlalbers marked this conversation as resolved.
Show resolved Hide resolved
cqm = employee_scheduling.build_cqm(params)

feasible_sampleset, errors = employee_scheduling.run_cqm(cqm)
sample = feasible_sampleset.first.sample

sched = utils.build_schedule_from_sample(sample, employees)

elif solver_type == SolverType.NL:
jlalbers marked this conversation as resolved.
Show resolved Hide resolved
model, assignments = employee_scheduling.build_nl(params)
errors = employee_scheduling.run_nl(model, assignments, params)
sched = utils.build_schedule_from_state(assignments.state(), employees, shifts)

sched = utils.build_schedule_from_sample(sample, employees)
else:
raise ValueError(f"Solver value `{solver_type.label}` is unhandled.")

return (
utils.display_schedule(sched, availability),
Expand Down
45 changes: 34 additions & 11 deletions app_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

from app_configs import (DESCRIPTION, EXAMPLE_SCENARIO, MAIN_HEADER, MAX_CONSECUTIVE_SHIFTS, MIN_MAX_EMPLOYEES,
MIN_MAX_SHIFTS, NUM_EMPLOYEES, REQUESTED_SHIFT_ICON, THUMBNAIL, UNAVAILABLE_ICON)
from src.demo_enums import SolverType


def description_card():
Expand Down Expand Up @@ -73,6 +74,29 @@ def range_slider(name: str, id: str, config: dict) -> html.Div:
)


def dropdown(label: str, id: str, options: list) -> html.Div:
"""Dropdown element for option selection.

Args:
label: The title that goes above the dropdown.
id: A unique selector for this element.
options: A list of dictionaries of labels and values.
"""
return html.Div(
className="dropdown-wrapper",
children=[
html.Label(label),
dcc.Dropdown(
id=id,
options=options,
value=options[0]["value"],
clearable=False,
searchable=False,
),
],
)


def generate_control_card() -> html.Div:
"""Generates the control card for the dashboard.

Expand All @@ -81,21 +105,20 @@ def generate_control_card() -> html.Div:
model, and solver.
"""
example_scenario = [{"label": size, "value": i} for i, size in enumerate(EXAMPLE_SCENARIO)]
solver_options = [{"label": s.label, "value": s.value} for s in SolverType]

return html.Div(
id="control-card",
children=[
html.Div(
children=[
html.Label("Scenario preset (sets sliders below)"),
dcc.Dropdown(
id="example-scenario-select",
options=example_scenario,
value=example_scenario[0]["value"],
clearable=False,
searchable=False,
),
]
dropdown(
"Solver",
"solver-select",
solver_options,
),
dropdown(
"Scenario preset (sets sliders below)",
k8culver marked this conversation as resolved.
Show resolved Hide resolved
"example-scenario-select",
example_scenario
),
# add sliders for employees and shifts
slider(
Expand Down
Loading