Skip to content

NL Formulation #34

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

Open
wants to merge 56 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 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
482f3ca
Resolve merge conflicts
k8culver Dec 20, 2024
d8e2cbd
Add new constraints to NL formulation, fix bugs
k8culver Dec 21, 2024
149773d
Move NL option to configs
k8culver Dec 24, 2024
65fd5fa
Run black and isort
k8culver Dec 24, 2024
df98e7c
Fix tests
k8culver Dec 24, 2024
4eab876
Update circleci config
k8culver Dec 24, 2024
be4ba04
Fix objective, remove preferred shift modifier
k8culver Jan 16, 2025
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
17 changes: 0 additions & 17 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,3 @@ workflows:
- dwave/test-linux
- dwave/test-osx
- dwave/test-win

weekly:
triggers:
- schedule:
cron: "0 2 * * 6"
filters:
branches:
only:
- master
- main
jobs:
- dwave/test-linux:
integration-tests: "canary"
- dwave/test-osx:
integration-tests: "skip"
- dwave/test-win:
integration-tests: "skip"
47 changes: 29 additions & 18 deletions demo_callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
from __future__ import annotations

import math
from dataclasses import asdict
from typing import Union

import dash
import pandas as pd
Expand All @@ -29,8 +31,10 @@
REQUESTED_SHIFT_ICON,
SMALL_SCENARIO,
UNAVAILABLE_ICON,
USE_NL,
)
from demo_interface import errors_list, generate_forecast_table
from src.demo_enums import SolverType


@dash.callback(
Expand Down Expand Up @@ -208,10 +212,10 @@ def display_initial_schedule(
{"display": "none"},
count,
count,
[num_employees]*len(count),
[num_employees] * len(count),
new_full_time_max,
full_time_marks,
num_full_time
num_full_time,
)


Expand Down Expand Up @@ -309,34 +313,41 @@ def run_optimization(
if run_click == 0 or ctx.triggered_id != "run-button":
raise PreventUpdate

solver_type = SolverType.NL if USE_NL else SolverType.CQM
shifts = list(sched_df["props"]["data"][0].keys())
shifts.remove("Employee")

availability = utils.availability_to_dict(sched_df["props"]["data"])
employees = list(availability.keys())

isolated_days_allowed = True if 0 in checklist else False

forecast = [
val if isinstance(val, int)
else forecast_placeholder[i]
for i, val in enumerate(forecast)
val if isinstance(val, int) else forecast_placeholder[i] for i, val in enumerate(forecast)
]

cqm = employee_scheduling.build_cqm(
availability,
shifts,
*shifts_per_employee,
forecast,
isolated_days_allowed,
consecutive_shifts + 1,
num_full_time,
params = utils.ModelParams(
availability=availability,
shifts=shifts,
min_shifts=min(shifts_per_employee),
max_shifts=max(shifts_per_employee),
shift_forecast=forecast,
allow_isolated_days_off=0 in checklist,
max_consecutive_shifts=consecutive_shifts,
num_full_time=num_full_time,
)

sampleset, errors = employee_scheduling.run_cqm(cqm)
sample = sampleset.first.sample
if solver_type is SolverType.NL:
model, assignments = employee_scheduling.build_nl(**asdict(params))
errors = employee_scheduling.run_nl(model, assignments, **asdict(params))
sched = utils.build_schedule_from_state(assignments.state(), employees, shifts)

else: # CQM
cqm = employee_scheduling.build_cqm(**asdict(params))

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

sched = utils.build_schedule_from_sample(sample, employees)

sched = utils.build_schedule_from_sample(sample, employees)
scheduled_count = sched.map(lambda cell: UNAVAILABLE_ICON not in cell).sum()[1:].to_dict()

return (
Expand Down
3 changes: 2 additions & 1 deletion demo_configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
# Optional: None or an integer
RANDOM_SEED = None

USE_NL = False # Uses NL if True, otherwise uses CQM

#######################################
# Sliders, buttons and option entries #
Expand All @@ -58,7 +59,7 @@
"value": 12,
}

# number of full time employees slider (value means default)
# number of full-time employees slider (value means default)
NUM_FULL_TIME = {
"min": 0,
"max": 9,
Expand Down
144 changes: 96 additions & 48 deletions demo_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,28 @@ def range_slider(label: 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_options(options_list: list) -> list[dict]:
"""Generates options for dropdowns, checklists, radios, etc."""
return [{"label": label, "value": i} for i, label in enumerate(options_list)]
Expand All @@ -100,17 +122,10 @@ def generate_settings_form() -> html.Div:
className="settings",
id="control-card",
children=[
html.Div(
children=[
html.Label("Presets (sets sliders below)"),
dcc.Dropdown(
id="example-scenario-select",
options=example_scenario,
value=example_scenario[0]["value"],
clearable=False,
searchable=False,
),
]
dropdown(
"Presets (sets sliders below)",
"example-scenario-select",
example_scenario,
),
slider(
"Employees",
Expand Down Expand Up @@ -186,11 +201,20 @@ def generate_run_buttons() -> html.Div:

def generate_forecast_table(forecast: list, scheduled: dict) -> html.Div:
"""Generate the forecasted vs scheduled table"""
forecast = {scheduled_key: forecast_value for scheduled_key, forecast_value in zip(scheduled.keys(), forecast)}
forecast = {
scheduled_key: forecast_value
for scheduled_key, forecast_value in zip(scheduled.keys(), forecast)
}
return html.Div(
className="schedule-forecast",
children=[
html.Div([html.Label("Forecasted Need:"), html.Label("Scheduled Employees:"), html.Label("Difference:")]),
html.Div(
[
html.Label("Forecasted Need:"),
html.Label("Scheduled Employees:"),
html.Label("Difference:"),
]
),
dash_table.DataTable(
id="forecast-output",
columns=([{"id": p, "name": p} for p in forecast.keys()]),
Expand Down Expand Up @@ -227,7 +251,7 @@ def create_interface():
"num-full-time-select": NUM_FULL_TIME["value"],
"consecutive-shifts-select": MAX_CONSECUTIVE_SHIFTS["value"],
"shifts-per-employee-select": MIN_MAX_SHIFTS["value"],
}
},
),
dcc.Store(id="submission_indicator"),
# Header brand banner
Expand Down Expand Up @@ -289,42 +313,54 @@ def create_interface():
html.Div(
className="schedule-forecast schedule-forecast-input",
children=[
html.Label("Staffing Requirements:"),
html.Label(
"Staffing Requirements:"
),
html.Div(
[
dcc.Input(
id={"index": param, "type": "forecast"},
id={
"index": param,
"type": "forecast",
},
type="number",
debounce=True,
value=0,
min=0,
max=100,
required=True,
) for param in COL_IDS
)
for param in COL_IDS
],
id="forecast-input"
)
id="forecast-input",
),
],
),
],
),
html.Div(
className="legend",
children=[
html.Div([
html.Div(
className="requested-shifts",
children=[REQUESTED_SHIFT_ICON],
),
html.Label("Requested"),
]),
html.Div([
html.Div(
className="unavailable-shifts",
children=[UNAVAILABLE_ICON],
),
html.Label("Unavailable"),
]),
html.Div(
[
html.Div(
className="requested-shifts",
children=[
REQUESTED_SHIFT_ICON
],
),
html.Label("Requested"),
]
),
html.Div(
[
html.Div(
className="unavailable-shifts",
children=[UNAVAILABLE_ICON],
),
html.Label("Unavailable"),
]
),
],
),
],
Expand Down Expand Up @@ -358,21 +394,33 @@ def create_interface():
html.Div(
className="legend",
children=[
html.Div([
html.Div(className="scheduled-shifts"),
html.Label("Scheduled"),
]),
html.Div([
html.Div(
className="unscheduled-requested-shifts",
children=[REQUESTED_SHIFT_ICON],
),
html.Label("Unscheduled requested"),
]),
html.Div([
html.Div(UNAVAILABLE_ICON),
html.Label("Unavailable"),
]),
html.Div(
[
html.Div(
className="scheduled-shifts"
),
html.Label("Scheduled"),
]
),
html.Div(
[
html.Div(
className="unscheduled-requested-shifts",
children=[
REQUESTED_SHIFT_ICON
],
),
html.Label(
"Unscheduled requested"
),
]
),
html.Div(
[
html.Div(UNAVAILABLE_ICON),
html.Label("Unavailable"),
]
),
],
),
],
Expand Down
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
dash[diskcache]==2.16.1
dash-bootstrap-components==1.6.0
dwave-ocean-sdk>=6.7
dwave-ocean-sdk>=7.0.0
dwave-optimization>=0.4.2
Faker==21.0.0
pandas>=2.0
29 changes: 29 additions & 0 deletions src/demo_enums.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Copyright 2024 D-Wave
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from enum import Enum


class SolverType(Enum):
"""Enum class representing the solver types used in the demo."""

CQM = 0 # Default value for application dropdown
NL = 1

@property
def label(self):
return {
SolverType.CQM: "CQM",
SolverType.NL: "Nonlinear",
}[self]
Loading