browsergym-workarena 0.2.0__py3-none-any.whl → 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- browsergym/workarena/__init__.py +13 -1
- browsergym/workarena/api/category.py +74 -0
- browsergym/workarena/api/change_request.py +87 -0
- browsergym/workarena/api/computer_asset.py +90 -0
- browsergym/workarena/api/cost_center.py +19 -0
- browsergym/workarena/api/expense_line.py +89 -0
- browsergym/workarena/api/incident.py +45 -0
- browsergym/workarena/api/knowledge.py +29 -0
- browsergym/workarena/api/problem.py +90 -0
- browsergym/workarena/api/report.py +183 -0
- browsergym/workarena/api/requested_items.py +63 -0
- browsergym/workarena/api/user.py +11 -8
- browsergym/workarena/api/utils.py +47 -3
- browsergym/workarena/config.py +21 -1
- browsergym/workarena/data_files/setup_files/forms/expected_incident_form_fields.json +1 -1
- browsergym/workarena/data_files/setup_files/forms/expected_request_item_form_fields.json +1 -0
- browsergym/workarena/data_files/setup_files/knowledge/protocols.json +46 -0
- browsergym/workarena/data_files/setup_files/knowledge/test.html +1 -0
- browsergym/workarena/data_files/setup_files/lists/expected_asset_list_columns.json +2 -24
- browsergym/workarena/data_files/setup_files/lists/expected_change_request_list_columns.json +4 -40
- browsergym/workarena/data_files/setup_files/lists/expected_expense_line_list_columns.json +12 -0
- browsergym/workarena/data_files/setup_files/lists/expected_hardware_list_columns.json +1 -42
- browsergym/workarena/data_files/setup_files/lists/expected_incident_list_columns.json +2 -18
- browsergym/workarena/data_files/setup_files/lists/expected_problem_list_columns.json +12 -0
- browsergym/workarena/data_files/setup_files/lists/expected_requested_items_list_columns.json +12 -0
- browsergym/workarena/data_files/setup_files/lists/expected_service_catalog_list_columns.json +2 -19
- browsergym/workarena/data_files/setup_files/lists/expected_user_list_columns.json +3 -50
- browsergym/workarena/data_files/task_configs/all_menu.json +95 -95
- browsergym/workarena/data_files/task_configs/dashboard_retrieval_minmax_task.json +1 -1
- browsergym/workarena/data_files/task_configs/dashboard_retrieval_value_task.json +1 -1
- browsergym/workarena/data_files/task_configs/filter_service_catalog_item_list_task.json +7986 -7982
- browsergym/workarena/data_files/task_configs/impersonation_users.json +3 -3
- browsergym/workarena/data_files/task_configs/report_retrieval_minmax_task.json +1 -1
- browsergym/workarena/data_files/task_configs/report_retrieval_value_task.json +1 -1
- browsergym/workarena/human_eval/console.js +176 -0
- browsergym/workarena/human_eval/tool.py +366 -0
- browsergym/workarena/install.py +81 -20
- browsergym/workarena/tasks/base.py +55 -20
- browsergym/workarena/tasks/comp_building_block.py +4 -0
- browsergym/workarena/tasks/compositional/__init__.py +76 -0
- browsergym/workarena/tasks/compositional/base.py +364 -0
- browsergym/workarena/tasks/compositional/dash_do_base.py +1366 -0
- browsergym/workarena/tasks/compositional/dash_do_catalog.py +1127 -0
- browsergym/workarena/tasks/compositional/dash_do_catalog_infeasible.py +2047 -0
- browsergym/workarena/tasks/compositional/dash_do_create_incident.py +403 -0
- browsergym/workarena/tasks/compositional/dash_do_create_incident_infeasible.py +278 -0
- browsergym/workarena/tasks/compositional/dash_do_create_problem.py +336 -0
- browsergym/workarena/tasks/compositional/dash_do_create_problem_infeasible.py +235 -0
- browsergym/workarena/tasks/compositional/dash_do_filter.py +1600 -0
- browsergym/workarena/tasks/compositional/dash_do_request_item.py +1315 -0
- browsergym/workarena/tasks/compositional/dash_do_request_item_infeasible.py +693 -0
- browsergym/workarena/tasks/compositional/delete_record.py +341 -0
- browsergym/workarena/tasks/compositional/edit_knowledge_base.py +457 -0
- browsergym/workarena/tasks/compositional/expense_management.py +598 -0
- browsergym/workarena/tasks/compositional/filter_and_do.py +139 -0
- browsergym/workarena/tasks/compositional/find_and_order_item.py +345 -0
- browsergym/workarena/tasks/compositional/manage_change_request_schedule.py +1417 -0
- browsergym/workarena/tasks/compositional/mark_duplicate_problems.py +499 -0
- browsergym/workarena/tasks/compositional/maximize_investment_return.py +1763 -0
- browsergym/workarena/tasks/compositional/navigate_and_do.py +1151 -0
- browsergym/workarena/tasks/compositional/navigate_and_do_infeasible.py +2100 -0
- browsergym/workarena/tasks/compositional/offboard_user.py +207 -0
- browsergym/workarena/tasks/compositional/onboard_user.py +226 -0
- browsergym/workarena/tasks/compositional/update_task.py +145 -0
- browsergym/workarena/tasks/compositional/utils/curriculum.py +215 -0
- browsergym/workarena/tasks/compositional/utils/infeasible_configs.py +151 -0
- browsergym/workarena/tasks/compositional/utils/knapsack.py +192 -0
- browsergym/workarena/tasks/compositional/warranty_check.py +227 -0
- browsergym/workarena/tasks/compositional/work_assignment.py +804 -0
- browsergym/workarena/tasks/compositional/workload_balancing.py +396 -0
- browsergym/workarena/tasks/dashboard.py +188 -8
- browsergym/workarena/tasks/form.py +1024 -232
- browsergym/workarena/tasks/knowledge.py +216 -25
- browsergym/workarena/tasks/list.py +519 -102
- browsergym/workarena/tasks/mark_duplicate_problem.py +171 -0
- browsergym/workarena/tasks/navigation.py +55 -13
- browsergym/workarena/tasks/scripts/extract_all_menu_items.py +9 -2
- browsergym/workarena/tasks/scripts/generate_dashboard_configs.py +6 -5
- browsergym/workarena/tasks/scripts/service_catalog.py +2 -1
- browsergym/workarena/tasks/scripts/validate.py +8 -2
- browsergym/workarena/tasks/send_chat_message.py +90 -0
- browsergym/workarena/tasks/service_catalog.py +94 -26
- browsergym/workarena/tasks/utils/form.py +1 -4
- browsergym/workarena/tasks/utils/private_tasks.py +63 -0
- browsergym/workarena/tasks/utils/utils.py +13 -0
- {browsergym_workarena-0.2.0.dist-info → browsergym_workarena-0.3.0.dist-info}/METADATA +27 -20
- browsergym_workarena-0.3.0.dist-info/RECORD +138 -0
- {browsergym_workarena-0.2.0.dist-info → browsergym_workarena-0.3.0.dist-info}/entry_points.txt +1 -0
- browsergym_workarena-0.2.0.dist-info/RECORD +0 -85
- {browsergym_workarena-0.2.0.dist-info → browsergym_workarena-0.3.0.dist-info}/WHEEL +0 -0
- {browsergym_workarena-0.2.0.dist-info → browsergym_workarena-0.3.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# from .edit_knowledge_base import __TASKS__ as EDIT_KNOWLEDGE_BASE_TASKS, __L2_TASKS__ as EDIT_KNOWLEDGE_BASE_L2_TASKS, __L3_TASKS__ as EDIT_KNOWLEDGE_BASE_L3TASKS
|
|
2
|
+
from ..dash_do_catalog import (
|
|
3
|
+
DASH_AND_ORDER,
|
|
4
|
+
DASH_COMPUTE_MEAN_AND_ORDER,
|
|
5
|
+
DASH_COMPUTE_MEDIAN_AND_ORDER,
|
|
6
|
+
DASH_COMPUTE_MODE_AND_ORDER,
|
|
7
|
+
)
|
|
8
|
+
from ..dash_do_create_incident import DASH_AND_CREATE_INCIDENT, DASH_COMPUTE_AND_CREATE_INCIDENT
|
|
9
|
+
from ..dash_do_create_problem import DASH_AND_CREATE_PROBLEM, DASH_COMPUTE_AND_CREATE_PROBLEM
|
|
10
|
+
from ..dash_do_filter import (
|
|
11
|
+
DASH_COMPUTE_MIN_FILTER_LIST,
|
|
12
|
+
DASH_COMPUTE_MAX_FILTER_LIST,
|
|
13
|
+
DASH_COMPUTE_MEAN_FILTER_LIST,
|
|
14
|
+
DASH_COMPUTE_MEDIAN_FILTER_LIST,
|
|
15
|
+
DASH_COMPUTE_MODE_FILTER_LIST,
|
|
16
|
+
)
|
|
17
|
+
from ..dash_do_request_item import (
|
|
18
|
+
DASH_AND_REQUEST,
|
|
19
|
+
DASH_COMPUTE_MEAN_AND_REQUEST,
|
|
20
|
+
DASH_COMPUTE_MEDIAN_AND_REQUEST,
|
|
21
|
+
DASH_COMPUTE_MODE_AND_REQUEST,
|
|
22
|
+
)
|
|
23
|
+
from ..expense_management import __TASKS__ as EXPENSE_MANAGEMENT_TASKS
|
|
24
|
+
from ..find_and_order_item import __TASKS__ as FIND_AND_ORDER_ITEM_TASKS
|
|
25
|
+
from ..manage_change_request_schedule import (
|
|
26
|
+
SMALL_BASE_SCHEDULING_TASKS,
|
|
27
|
+
LARGE_BASE_SCHEDULING_TASKS,
|
|
28
|
+
SMALL_TIGHT_SCHEDULING_TASKS,
|
|
29
|
+
LARGE_TIGHT_SCHEDULING_TASKS,
|
|
30
|
+
)
|
|
31
|
+
from ..mark_duplicate_problems import __TASKS__ as MARK_DUPLICATE_PROBLEMS_TASKS
|
|
32
|
+
from ..maximize_investment_return import __TASKS__ as MAXIMIZE_INVESTMENT_RETURN_TASKS
|
|
33
|
+
from ..navigate_and_do import (
|
|
34
|
+
NAVIGATE_AND_CREATE_TASKS,
|
|
35
|
+
NAVIGATE_AND_FILTER_TASKS,
|
|
36
|
+
NAVIGATE_AND_ORDER_TASKS,
|
|
37
|
+
NAVIGATE_AND_SORT_TASKS,
|
|
38
|
+
)
|
|
39
|
+
from ..navigate_and_do_infeasible import (
|
|
40
|
+
INFEASIBLE_NAVIGATE_AND_CREATE_WITH_REASON,
|
|
41
|
+
INFEASIBLE_NAVIGATE_AND_CREATE,
|
|
42
|
+
INFEASIBLE_NAVIGATE_AND_ORDER_WITH_REASON,
|
|
43
|
+
INFEASIBLE_NAVIGATE_AND_ORDER,
|
|
44
|
+
INFEASIBLE_NAVIGATE_AND_FILTER_WITH_REASON,
|
|
45
|
+
INFEASIBLE_NAVIGATE_AND_FILTER,
|
|
46
|
+
INFEASIBLE_NAVIGATE_AND_SORT_WITH_REASON,
|
|
47
|
+
INFEASIBLE_NAVIGATE_AND_SORT,
|
|
48
|
+
)
|
|
49
|
+
from ..offboard_user import __TASKS__ as OFFBOARD_USER_TASKS
|
|
50
|
+
from ..onboard_user import __TASKS__ as ONBOARD_USER_TASKS
|
|
51
|
+
from ..warranty_check import __TASKS__ as WARRANTY_CHECK_TASKS
|
|
52
|
+
from ..work_assignment import __TASKS__ as WORK_ASSIGNMENT_TASKS
|
|
53
|
+
from ..workload_balancing import __TASKS__ as WORKLOAD_BALANCING_TASKS
|
|
54
|
+
|
|
55
|
+
AGENT_CURRICULUM = {
|
|
56
|
+
"planning_and_problem_solving": {
|
|
57
|
+
"buckets": [
|
|
58
|
+
MARK_DUPLICATE_PROBLEMS_TASKS,
|
|
59
|
+
WORKLOAD_BALANCING_TASKS,
|
|
60
|
+
WORK_ASSIGNMENT_TASKS,
|
|
61
|
+
SMALL_BASE_SCHEDULING_TASKS,
|
|
62
|
+
LARGE_BASE_SCHEDULING_TASKS,
|
|
63
|
+
SMALL_TIGHT_SCHEDULING_TASKS,
|
|
64
|
+
LARGE_TIGHT_SCHEDULING_TASKS,
|
|
65
|
+
],
|
|
66
|
+
"num_seeds": 2,
|
|
67
|
+
"weights": [9, 3, 6, 1, 1, 1, 1],
|
|
68
|
+
},
|
|
69
|
+
"information_retrieval": {
|
|
70
|
+
"buckets": [
|
|
71
|
+
DASH_AND_ORDER,
|
|
72
|
+
DASH_AND_CREATE_INCIDENT,
|
|
73
|
+
DASH_AND_CREATE_PROBLEM,
|
|
74
|
+
DASH_COMPUTE_MIN_FILTER_LIST,
|
|
75
|
+
DASH_COMPUTE_MAX_FILTER_LIST,
|
|
76
|
+
DASH_AND_REQUEST,
|
|
77
|
+
WARRANTY_CHECK_TASKS,
|
|
78
|
+
FIND_AND_ORDER_ITEM_TASKS,
|
|
79
|
+
],
|
|
80
|
+
"num_seeds": 7,
|
|
81
|
+
"weights": [1, 1, 1, 1, 1, 1, 1, 1],
|
|
82
|
+
},
|
|
83
|
+
"data_driven_decision_making_and_reasoning": {
|
|
84
|
+
"buckets": [
|
|
85
|
+
EXPENSE_MANAGEMENT_TASKS,
|
|
86
|
+
MAXIMIZE_INVESTMENT_RETURN_TASKS,
|
|
87
|
+
DASH_COMPUTE_MEAN_AND_ORDER,
|
|
88
|
+
DASH_COMPUTE_MEDIAN_AND_ORDER,
|
|
89
|
+
DASH_COMPUTE_MODE_AND_ORDER,
|
|
90
|
+
DASH_COMPUTE_AND_CREATE_INCIDENT,
|
|
91
|
+
DASH_COMPUTE_AND_CREATE_PROBLEM,
|
|
92
|
+
DASH_COMPUTE_MEAN_FILTER_LIST,
|
|
93
|
+
DASH_COMPUTE_MEDIAN_FILTER_LIST,
|
|
94
|
+
DASH_COMPUTE_MODE_FILTER_LIST,
|
|
95
|
+
DASH_COMPUTE_MEAN_AND_REQUEST,
|
|
96
|
+
DASH_COMPUTE_MEDIAN_AND_REQUEST,
|
|
97
|
+
DASH_COMPUTE_MODE_AND_REQUEST,
|
|
98
|
+
],
|
|
99
|
+
"num_seeds": 1,
|
|
100
|
+
"weights": [12, 28, 1, 1, 1, 3, 3, 1, 1, 1, 1, 1, 1],
|
|
101
|
+
},
|
|
102
|
+
"sophisticated_memory": {
|
|
103
|
+
"buckets": [
|
|
104
|
+
NAVIGATE_AND_CREATE_TASKS,
|
|
105
|
+
NAVIGATE_AND_ORDER_TASKS,
|
|
106
|
+
NAVIGATE_AND_FILTER_TASKS,
|
|
107
|
+
NAVIGATE_AND_SORT_TASKS,
|
|
108
|
+
OFFBOARD_USER_TASKS,
|
|
109
|
+
ONBOARD_USER_TASKS,
|
|
110
|
+
],
|
|
111
|
+
"num_seeds": 8,
|
|
112
|
+
"weights": [1, 1, 1, 1, 1, 1],
|
|
113
|
+
},
|
|
114
|
+
"contextual_understanding_infeasible_tasks": {
|
|
115
|
+
"buckets": [
|
|
116
|
+
INFEASIBLE_NAVIGATE_AND_CREATE_WITH_REASON,
|
|
117
|
+
INFEASIBLE_NAVIGATE_AND_CREATE,
|
|
118
|
+
INFEASIBLE_NAVIGATE_AND_ORDER_WITH_REASON,
|
|
119
|
+
INFEASIBLE_NAVIGATE_AND_ORDER,
|
|
120
|
+
INFEASIBLE_NAVIGATE_AND_FILTER_WITH_REASON,
|
|
121
|
+
INFEASIBLE_NAVIGATE_AND_FILTER,
|
|
122
|
+
INFEASIBLE_NAVIGATE_AND_SORT_WITH_REASON,
|
|
123
|
+
INFEASIBLE_NAVIGATE_AND_SORT,
|
|
124
|
+
],
|
|
125
|
+
"num_seeds": 4,
|
|
126
|
+
"weights": [1, 1, 1, 1, 1, 1, 1, 1],
|
|
127
|
+
},
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
HUMAN_CURRICULUM = {
|
|
131
|
+
"planning_and_problem_solving": {
|
|
132
|
+
"buckets": [
|
|
133
|
+
MARK_DUPLICATE_PROBLEMS_TASKS,
|
|
134
|
+
WORKLOAD_BALANCING_TASKS,
|
|
135
|
+
WORK_ASSIGNMENT_TASKS,
|
|
136
|
+
SMALL_BASE_SCHEDULING_TASKS,
|
|
137
|
+
SMALL_TIGHT_SCHEDULING_TASKS,
|
|
138
|
+
],
|
|
139
|
+
"num_seeds": 1,
|
|
140
|
+
"weights": [
|
|
141
|
+
3,
|
|
142
|
+
1,
|
|
143
|
+
2,
|
|
144
|
+
1,
|
|
145
|
+
1,
|
|
146
|
+
],
|
|
147
|
+
},
|
|
148
|
+
"information_retrieval": {
|
|
149
|
+
"buckets": [
|
|
150
|
+
DASH_AND_ORDER,
|
|
151
|
+
DASH_AND_CREATE_INCIDENT,
|
|
152
|
+
DASH_AND_CREATE_PROBLEM,
|
|
153
|
+
DASH_COMPUTE_MIN_FILTER_LIST,
|
|
154
|
+
DASH_COMPUTE_MAX_FILTER_LIST,
|
|
155
|
+
DASH_AND_REQUEST,
|
|
156
|
+
WARRANTY_CHECK_TASKS,
|
|
157
|
+
FIND_AND_ORDER_ITEM_TASKS,
|
|
158
|
+
],
|
|
159
|
+
"num_seeds": 1,
|
|
160
|
+
"weights": [1, 1, 1, 1, 1, 1, 1, 1],
|
|
161
|
+
},
|
|
162
|
+
"data_driven_decision_making_and_reasoning": {
|
|
163
|
+
"buckets": [
|
|
164
|
+
EXPENSE_MANAGEMENT_TASKS,
|
|
165
|
+
MAXIMIZE_INVESTMENT_RETURN_TASKS, # Not splitting as small multiplier
|
|
166
|
+
[
|
|
167
|
+
*DASH_COMPUTE_MEAN_AND_ORDER,
|
|
168
|
+
*DASH_COMPUTE_MEDIAN_AND_ORDER,
|
|
169
|
+
*DASH_COMPUTE_MODE_AND_ORDER,
|
|
170
|
+
],
|
|
171
|
+
[
|
|
172
|
+
*DASH_COMPUTE_AND_CREATE_INCIDENT,
|
|
173
|
+
*DASH_COMPUTE_AND_CREATE_PROBLEM,
|
|
174
|
+
*DASH_COMPUTE_MEAN_AND_REQUEST,
|
|
175
|
+
],
|
|
176
|
+
DASH_COMPUTE_MEAN_FILTER_LIST,
|
|
177
|
+
[
|
|
178
|
+
*DASH_COMPUTE_MEDIAN_FILTER_LIST,
|
|
179
|
+
*DASH_COMPUTE_MODE_FILTER_LIST,
|
|
180
|
+
],
|
|
181
|
+
[
|
|
182
|
+
*DASH_COMPUTE_MEDIAN_AND_REQUEST,
|
|
183
|
+
*DASH_COMPUTE_MODE_AND_REQUEST,
|
|
184
|
+
],
|
|
185
|
+
],
|
|
186
|
+
"num_seeds": 1,
|
|
187
|
+
"weights": [2, 6, 1, 1, 1, 1, 1],
|
|
188
|
+
},
|
|
189
|
+
"sophisticated_memory": {
|
|
190
|
+
"buckets": [
|
|
191
|
+
NAVIGATE_AND_CREATE_TASKS,
|
|
192
|
+
NAVIGATE_AND_ORDER_TASKS,
|
|
193
|
+
NAVIGATE_AND_FILTER_TASKS,
|
|
194
|
+
NAVIGATE_AND_SORT_TASKS,
|
|
195
|
+
OFFBOARD_USER_TASKS,
|
|
196
|
+
ONBOARD_USER_TASKS,
|
|
197
|
+
],
|
|
198
|
+
"num_seeds": 2,
|
|
199
|
+
"weights": [1, 1, 1, 1, 1, 1],
|
|
200
|
+
},
|
|
201
|
+
"contextual_understanding_infeasible_tasks": {
|
|
202
|
+
"buckets": [
|
|
203
|
+
INFEASIBLE_NAVIGATE_AND_CREATE_WITH_REASON,
|
|
204
|
+
INFEASIBLE_NAVIGATE_AND_CREATE,
|
|
205
|
+
INFEASIBLE_NAVIGATE_AND_ORDER_WITH_REASON,
|
|
206
|
+
INFEASIBLE_NAVIGATE_AND_ORDER,
|
|
207
|
+
INFEASIBLE_NAVIGATE_AND_FILTER_WITH_REASON,
|
|
208
|
+
INFEASIBLE_NAVIGATE_AND_FILTER,
|
|
209
|
+
INFEASIBLE_NAVIGATE_AND_SORT_WITH_REASON,
|
|
210
|
+
INFEASIBLE_NAVIGATE_AND_SORT,
|
|
211
|
+
],
|
|
212
|
+
"num_seeds": 1,
|
|
213
|
+
"weights": [1, 1, 1, 1, 1, 1, 1, 1],
|
|
214
|
+
},
|
|
215
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from faker import Faker
|
|
4
|
+
|
|
5
|
+
fake = Faker()
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_infeasible_form_config(config, random: np.random, provide_reason: bool = True):
|
|
9
|
+
"""
|
|
10
|
+
Get an infeasible form config from a feasible config by replacing the name of one of the task_fields with a random word
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
--------
|
|
14
|
+
config (dict):
|
|
15
|
+
The feasible form config to be transformed into an infeasible one
|
|
16
|
+
random (np.random):
|
|
17
|
+
The random number generator to use
|
|
18
|
+
provide_reason (bool):
|
|
19
|
+
Whether to provide a reason for the infeasibility. If False, the list of reasons will be [""] so that
|
|
20
|
+
any infeasibility can be detected by the absence of a reason
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
--------
|
|
24
|
+
infeasible_config (dict):
|
|
25
|
+
The infeasible form config
|
|
26
|
+
infeasible_keywords (list[str]):
|
|
27
|
+
The name of the new field printed and its system name
|
|
28
|
+
"""
|
|
29
|
+
replaced_field = (
|
|
30
|
+
random.choice(config["infeasible_task_fields"])
|
|
31
|
+
if "infeasible_task_fields" in config
|
|
32
|
+
else random.choice(config["task_fields"])
|
|
33
|
+
)
|
|
34
|
+
new_field_printed = fake.word().capitalize() + " " + fake.word()
|
|
35
|
+
new_field_system_name = new_field_printed.lower().replace(" ", "_")
|
|
36
|
+
|
|
37
|
+
config["task_fields"].remove(replaced_field)
|
|
38
|
+
config["task_fields"].append(new_field_system_name)
|
|
39
|
+
config["fields"][new_field_system_name] = new_field_printed
|
|
40
|
+
config["template_record"][new_field_system_name] = fake.word()
|
|
41
|
+
|
|
42
|
+
infeasible_reasons = [new_field_printed, new_field_system_name] if provide_reason else [""]
|
|
43
|
+
|
|
44
|
+
return config, infeasible_reasons
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def get_infeasible_service_catalog_config(config, random: np.random, provide_reason: bool = True):
|
|
48
|
+
"""
|
|
49
|
+
Get an infeasible service catalog config from a feasible config by replacing the name of one of the additional configuration items with a random word
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
--------
|
|
53
|
+
config (dict):
|
|
54
|
+
The feasible service catalog config to be transformed into an infeasible one
|
|
55
|
+
random (np.random):
|
|
56
|
+
The random number generator to use
|
|
57
|
+
provide_reason (bool):
|
|
58
|
+
Whether to provide a reason for the infeasibility. If False, the list of reasons will be [""] so that
|
|
59
|
+
any infeasibility can be detected by the absence of a reason
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
--------
|
|
63
|
+
infeasible_config (dict):
|
|
64
|
+
The infeasible service catalog config
|
|
65
|
+
infeasible_keywords (list[str]):
|
|
66
|
+
The name of the new field printed and its system name
|
|
67
|
+
"""
|
|
68
|
+
item_configuration = list(config["configuration"].keys())
|
|
69
|
+
# if there is a configuration item, replace it with a new one; otherwise, simply add a new one
|
|
70
|
+
if item_configuration:
|
|
71
|
+
replaced_field = random.choice(item_configuration)
|
|
72
|
+
config["configuration"].pop(replaced_field)
|
|
73
|
+
new_field_printed = fake.word().capitalize() + " " + fake.word()
|
|
74
|
+
field_type = random.choice(["radio", "textarea", "checkbox", "select"])
|
|
75
|
+
field_options = [fake.word() for _ in range(random.randint(2, 5))]
|
|
76
|
+
|
|
77
|
+
config["configuration"][new_field_printed] = [field_type, ", ".join(field_options)]
|
|
78
|
+
|
|
79
|
+
infeasible_reasons = [new_field_printed, *field_options] if provide_reason else [""]
|
|
80
|
+
|
|
81
|
+
return config, infeasible_reasons
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def get_infeasible_sort_config(config, random: np.random, provide_reason: bool = True):
|
|
85
|
+
"""
|
|
86
|
+
Get an infeasible sort config from a feasible config by replacing the name of one sort_fields with a random word
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
--------
|
|
90
|
+
config (dict):
|
|
91
|
+
The feasible sort config to be transformed into an infeasible one
|
|
92
|
+
random (np.random):
|
|
93
|
+
The random number generator to use
|
|
94
|
+
provide_reason (bool):
|
|
95
|
+
Whether to provide a reason for the infeasibility. If False, the list of reasons will be [""] so that
|
|
96
|
+
any infeasibility can be detected by the absence of a reason
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
--------
|
|
100
|
+
infeasible_config (dict):
|
|
101
|
+
The infeasible sort config
|
|
102
|
+
infeasible_keywords (list[str]):
|
|
103
|
+
The name of the new sort option printed and its system name
|
|
104
|
+
"""
|
|
105
|
+
goal = config["goal"]
|
|
106
|
+
config_fields = [line[3:].split(" (")[0] for line in goal.split("\n")[1:]]
|
|
107
|
+
replaced_field_index = random.randint(0, len(config["sort_fields"]))
|
|
108
|
+
|
|
109
|
+
new_field_printed = fake.word().capitalize() + " " + fake.word()
|
|
110
|
+
new_field_system_name = new_field_printed.lower().replace(" ", "_")
|
|
111
|
+
|
|
112
|
+
config["goal"] = goal.replace(config_fields[replaced_field_index], new_field_printed)
|
|
113
|
+
config["sort_fields"][replaced_field_index] = new_field_system_name
|
|
114
|
+
|
|
115
|
+
infeasible_reasons = [new_field_printed, new_field_system_name] if provide_reason else [""]
|
|
116
|
+
|
|
117
|
+
return config, infeasible_reasons
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def get_infeasible_filter_config(config, random: np.random, provide_reason: bool = True):
|
|
121
|
+
"""
|
|
122
|
+
Get an infeasible filter config from a feasible config by replacing the name of one of the filter_columns with a random word
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
--------
|
|
126
|
+
config (dict):
|
|
127
|
+
The feasible filter config to be transformed into an infeasible one
|
|
128
|
+
random (np.random):
|
|
129
|
+
The random number generator to use
|
|
130
|
+
provide_reason (bool):
|
|
131
|
+
Whether to provide a reason for the infeasibility. If False, the list of reasons will be [""] so that
|
|
132
|
+
any infeasibility can be detected by the absence of a reason
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
--------
|
|
136
|
+
infeasible_config (dict):
|
|
137
|
+
The infeasible filter config
|
|
138
|
+
infeasible_keywords (list[str]):
|
|
139
|
+
The name of the new filter option printed and its system name
|
|
140
|
+
"""
|
|
141
|
+
replaced_field_index = random.randint(0, len(config["filter_columns"]))
|
|
142
|
+
|
|
143
|
+
new_field_printed = fake.word().capitalize() + " " + fake.word()
|
|
144
|
+
new_field_system_name = new_field_printed.lower().replace(" ", "_")
|
|
145
|
+
config["filter_columns"][replaced_field_index] = new_field_system_name
|
|
146
|
+
config["filter_values"][replaced_field_index] = fake.word().capitalize()
|
|
147
|
+
config["list_info"]["columns"][new_field_system_name] = {"label": new_field_printed}
|
|
148
|
+
|
|
149
|
+
infeasible_reasons = [new_field_printed, new_field_system_name] if provide_reason else [""]
|
|
150
|
+
|
|
151
|
+
return config, infeasible_reasons
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class KnapsackInstanceGenarator:
|
|
5
|
+
"""
|
|
6
|
+
Generates a knapsack instance with the given number of items and maximum capacity, and solves it.
|
|
7
|
+
The instance is guaranteed to have a unique optimal solution in "random" or "single_item" mode .
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
- random: Random number generator
|
|
11
|
+
- num_items: Number of items
|
|
12
|
+
- max_capacity: Maximum capacity of the knapsack
|
|
13
|
+
- mode: Mode of generation. Choice of "random", "trivial", "single_item", "single_item_uniform", "n_items"
|
|
14
|
+
- random: Randomly generate the instance and return it; guaranteed to have a unique optimal solution
|
|
15
|
+
- trivial: Generate a trivial instance with all items fitting in the knapsack; return the instance
|
|
16
|
+
- single_item: Generate an instance where the optimal solution has only one item
|
|
17
|
+
- n_items: Generate an instance with all items having uniform weight and value; n items fitting in the knapsack
|
|
18
|
+
- single_item_uniform: Generate an instance with all items having uniform weight and value; optimal solution has only one item and it can be any
|
|
19
|
+
- num_items_in_solution: Number of items in the optimal solution. Required for "n_items" mode.
|
|
20
|
+
- default_return: Default return value for investments having uniform weight and value. Required for "n_items" and "single_item_uniform" modes.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
random: np.random,
|
|
26
|
+
num_items: int,
|
|
27
|
+
max_capacity: int,
|
|
28
|
+
mode: str = "random",
|
|
29
|
+
num_items_in_solution: int = None,
|
|
30
|
+
default_return: int = 100000,
|
|
31
|
+
):
|
|
32
|
+
self.random = random
|
|
33
|
+
self.num_items = num_items
|
|
34
|
+
self.max_capacity = max_capacity
|
|
35
|
+
self.mode = mode
|
|
36
|
+
self.num_items_in_solution = num_items_in_solution
|
|
37
|
+
self.default_return = default_return
|
|
38
|
+
|
|
39
|
+
def get_instance(self):
|
|
40
|
+
if self.mode in ["random", "trivial"]:
|
|
41
|
+
return self.generate_and_solve_knapsack_instance()
|
|
42
|
+
elif self.mode == "single_item":
|
|
43
|
+
return self.generate_single_item_knapsack_instance()
|
|
44
|
+
elif self.mode in ["single_item_uniform", "n_items"]:
|
|
45
|
+
return self.generate_uniform_knapsack_instance()
|
|
46
|
+
else:
|
|
47
|
+
raise ValueError(f"Invalid mode {self.mode} for knapsack instance generation")
|
|
48
|
+
|
|
49
|
+
def generate_and_solve_knapsack_instance(self):
|
|
50
|
+
"""
|
|
51
|
+
Generates a knapsack instance with the given number of items and maximum capacity, and solves it.
|
|
52
|
+
Used to generate instances for the "random" and "trivial" mode.
|
|
53
|
+
Returns:
|
|
54
|
+
- investments: List of tuples (cost, investment_return) for each investment
|
|
55
|
+
- max_return: Maximum return achievable with optimal solution
|
|
56
|
+
- selected_indices: Indices of the investments selected in the optimal solution
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
assert self.mode in [
|
|
60
|
+
"random",
|
|
61
|
+
"trivial",
|
|
62
|
+
], f"Mode {self.mode} is invalid for instance generation with generate_and_solve_knapsack_instance"
|
|
63
|
+
|
|
64
|
+
multiple_solutions = True
|
|
65
|
+
while multiple_solutions:
|
|
66
|
+
# Generate knapsack instance...
|
|
67
|
+
investments = []
|
|
68
|
+
min_cost = self.max_capacity // (self.num_items * 2)
|
|
69
|
+
max_cost = (
|
|
70
|
+
self.max_capacity // 2
|
|
71
|
+
if self.mode == "random"
|
|
72
|
+
else self.max_capacity // self.num_items
|
|
73
|
+
)
|
|
74
|
+
for _ in range(self.num_items):
|
|
75
|
+
cost = self.random.randint(min_cost, max_cost)
|
|
76
|
+
# Ensure that investments yield positive returns
|
|
77
|
+
investment_return = self.random.randint(
|
|
78
|
+
self.max_capacity // 2, self.max_capacity // 2 + 40000
|
|
79
|
+
)
|
|
80
|
+
investments.append((cost, investment_return))
|
|
81
|
+
|
|
82
|
+
total_cost = sum([investments[i][0] for i in range(self.num_items)])
|
|
83
|
+
# Skip trivial instances where all items fit in the knapsack
|
|
84
|
+
if self.mode == "random" and total_cost <= self.max_capacity:
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
if self.mode == "random":
|
|
88
|
+
# ...Solve it...
|
|
89
|
+
max_return, num_optimal_solutions, selected_indices = self.solve_knapsack(
|
|
90
|
+
investments, self.max_capacity
|
|
91
|
+
)
|
|
92
|
+
# ...and check if there are multiple solutions
|
|
93
|
+
multiple_solutions = num_optimal_solutions > 1
|
|
94
|
+
else:
|
|
95
|
+
selected_indices = list(range(self.num_items))
|
|
96
|
+
max_return = sum([investments[i][1] for i in selected_indices])
|
|
97
|
+
multiple_solutions = False
|
|
98
|
+
|
|
99
|
+
return investments, max_return, selected_indices
|
|
100
|
+
|
|
101
|
+
def generate_single_item_knapsack_instance(self):
|
|
102
|
+
"""Generate knapsack instance where the optimal solution contains only one item
|
|
103
|
+
Returns:
|
|
104
|
+
- investments: List of tuples (cost, investment_return) for each investment
|
|
105
|
+
- max_investment_return: Investment return of the selected investment in the optimal solution
|
|
106
|
+
- selected_indices: Index of the selected investment in the optimal solution
|
|
107
|
+
"""
|
|
108
|
+
assert (
|
|
109
|
+
self.mode == "single_item"
|
|
110
|
+
), f"Mode {self.mode} is invalid for instance generation with generate_single_item_knapsack_instance"
|
|
111
|
+
|
|
112
|
+
# Ensure that the optimal solution contains only one item
|
|
113
|
+
min_cost = self.max_capacity // 2 + 1
|
|
114
|
+
max_cost = self.max_capacity - 1
|
|
115
|
+
|
|
116
|
+
max_investment_return = 0
|
|
117
|
+
max_investment_index = 0
|
|
118
|
+
|
|
119
|
+
# Generate knapsack instance...
|
|
120
|
+
investments = []
|
|
121
|
+
for i in range(self.num_items):
|
|
122
|
+
cost = self.random.randint(min_cost, max_cost)
|
|
123
|
+
investment_return = self.random.randint(max_cost, 2 * max_cost)
|
|
124
|
+
|
|
125
|
+
# Ensure that the optimal solution contains only one item
|
|
126
|
+
while investment_return == max_investment_return:
|
|
127
|
+
investment_return = self.random.randint(max_cost, 2 * max_cost)
|
|
128
|
+
|
|
129
|
+
if investment_return > max_investment_return:
|
|
130
|
+
max_investment_return = investment_return
|
|
131
|
+
max_investment_index = i
|
|
132
|
+
|
|
133
|
+
investments.append((cost, investment_return))
|
|
134
|
+
|
|
135
|
+
return investments, max_investment_return, [max_investment_index]
|
|
136
|
+
|
|
137
|
+
def generate_uniform_knapsack_instance(self):
|
|
138
|
+
"""Generate knapsack instance where all items have the same cost and return
|
|
139
|
+
Returns:
|
|
140
|
+
- investments: List of tuples (cost, investment_return) for each investment
|
|
141
|
+
- max_return: Maximum return achievable with optimal solution
|
|
142
|
+
- selected_indices=None: No need to return selected indices as all items have the same cost and return. The validation code should check that
|
|
143
|
+
the optimal solution contains a subset of the items of the right length.
|
|
144
|
+
"""
|
|
145
|
+
assert self.mode in [
|
|
146
|
+
"single_item_uniform",
|
|
147
|
+
"n_items",
|
|
148
|
+
], f"Mode {self.mode} is invalid for instance generation with generate_n_items_knapsack_instance"
|
|
149
|
+
items_in_solution = self.num_items_in_solution if self.mode == "n_items" else 1
|
|
150
|
+
|
|
151
|
+
# Ensure that the optimal solution contains the specified number of items
|
|
152
|
+
item_weight = self.max_capacity // (items_in_solution + 1) + 1
|
|
153
|
+
# Generate knapsack instance...
|
|
154
|
+
investments = [(item_weight, self.default_return) for _ in range(self.num_items)]
|
|
155
|
+
|
|
156
|
+
return investments, self.default_return * items_in_solution, None
|
|
157
|
+
|
|
158
|
+
def solve_knapsack(self, investments, max_capacity):
|
|
159
|
+
"""Solves the knapsack problem using dynamic programming"""
|
|
160
|
+
num_investments = len(investments)
|
|
161
|
+
|
|
162
|
+
# Initialize DP table for maximum return and number of ways
|
|
163
|
+
dp = [[(0, 0) for _ in range(max_capacity + 1)] for _ in range(num_investments + 1)]
|
|
164
|
+
|
|
165
|
+
for i in range(1, num_investments + 1):
|
|
166
|
+
cost, return_ = investments[i - 1]
|
|
167
|
+
for w in range(max_capacity + 1):
|
|
168
|
+
if cost <= w:
|
|
169
|
+
# If adding the current investment yields a higher return, update the cell
|
|
170
|
+
if return_ + dp[i - 1][w - cost][0] > dp[i - 1][w][0]:
|
|
171
|
+
dp[i][w] = (return_ + dp[i - 1][w - cost][0], 1)
|
|
172
|
+
# If it yields the same return, add the number of ways from the cell without the current investment
|
|
173
|
+
elif return_ + dp[i - 1][w - cost][0] == dp[i - 1][w][0]:
|
|
174
|
+
dp[i][w] = (dp[i - 1][w][0], dp[i - 1][w][1] + dp[i - 1][w - cost][1])
|
|
175
|
+
# If it yields a lower return, keep the old maximum return and number of ways
|
|
176
|
+
else:
|
|
177
|
+
dp[i][w] = dp[i - 1][w]
|
|
178
|
+
else:
|
|
179
|
+
dp[i][w] = dp[i - 1][w]
|
|
180
|
+
|
|
181
|
+
# Retrieve the maximum return and the number of ways to achieve it
|
|
182
|
+
max_return, num_ways = dp[num_investments][max_capacity]
|
|
183
|
+
|
|
184
|
+
# Retrieve the indices of the selected investments
|
|
185
|
+
selected_indices = []
|
|
186
|
+
w = max_capacity
|
|
187
|
+
for i in range(num_investments, 0, -1):
|
|
188
|
+
if dp[i][w] != dp[i - 1][w]:
|
|
189
|
+
selected_indices.append(i - 1)
|
|
190
|
+
w -= investments[i - 1][0]
|
|
191
|
+
|
|
192
|
+
return max_return, num_ways, selected_indices
|