browsergym-workarena 0.2.1__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.
Files changed (91) hide show
  1. browsergym/workarena/__init__.py +13 -1
  2. browsergym/workarena/api/category.py +74 -0
  3. browsergym/workarena/api/change_request.py +87 -0
  4. browsergym/workarena/api/computer_asset.py +90 -0
  5. browsergym/workarena/api/cost_center.py +19 -0
  6. browsergym/workarena/api/expense_line.py +89 -0
  7. browsergym/workarena/api/incident.py +45 -0
  8. browsergym/workarena/api/knowledge.py +29 -0
  9. browsergym/workarena/api/problem.py +90 -0
  10. browsergym/workarena/api/report.py +183 -0
  11. browsergym/workarena/api/requested_items.py +63 -0
  12. browsergym/workarena/api/user.py +11 -8
  13. browsergym/workarena/api/utils.py +47 -3
  14. browsergym/workarena/config.py +21 -1
  15. browsergym/workarena/data_files/setup_files/forms/expected_incident_form_fields.json +1 -1
  16. browsergym/workarena/data_files/setup_files/forms/expected_request_item_form_fields.json +1 -0
  17. browsergym/workarena/data_files/setup_files/knowledge/protocols.json +46 -0
  18. browsergym/workarena/data_files/setup_files/knowledge/test.html +1 -0
  19. browsergym/workarena/data_files/setup_files/lists/expected_asset_list_columns.json +2 -24
  20. browsergym/workarena/data_files/setup_files/lists/expected_change_request_list_columns.json +4 -40
  21. browsergym/workarena/data_files/setup_files/lists/expected_expense_line_list_columns.json +12 -0
  22. browsergym/workarena/data_files/setup_files/lists/expected_hardware_list_columns.json +1 -42
  23. browsergym/workarena/data_files/setup_files/lists/expected_incident_list_columns.json +2 -18
  24. browsergym/workarena/data_files/setup_files/lists/expected_problem_list_columns.json +12 -0
  25. browsergym/workarena/data_files/setup_files/lists/expected_requested_items_list_columns.json +12 -0
  26. browsergym/workarena/data_files/setup_files/lists/expected_service_catalog_list_columns.json +2 -19
  27. browsergym/workarena/data_files/setup_files/lists/expected_user_list_columns.json +3 -50
  28. browsergym/workarena/data_files/task_configs/all_menu.json +1 -1
  29. browsergym/workarena/data_files/task_configs/dashboard_retrieval_minmax_task.json +1 -1
  30. browsergym/workarena/data_files/task_configs/dashboard_retrieval_value_task.json +1 -1
  31. browsergym/workarena/data_files/task_configs/filter_service_catalog_item_list_task.json +1 -1
  32. browsergym/workarena/data_files/task_configs/impersonation_users.json +1 -1
  33. browsergym/workarena/data_files/task_configs/report_retrieval_minmax_task.json +1 -1
  34. browsergym/workarena/data_files/task_configs/report_retrieval_value_task.json +1 -1
  35. browsergym/workarena/human_eval/console.js +176 -0
  36. browsergym/workarena/human_eval/tool.py +366 -0
  37. browsergym/workarena/install.py +81 -20
  38. browsergym/workarena/tasks/base.py +55 -20
  39. browsergym/workarena/tasks/comp_building_block.py +4 -0
  40. browsergym/workarena/tasks/compositional/__init__.py +76 -0
  41. browsergym/workarena/tasks/compositional/base.py +364 -0
  42. browsergym/workarena/tasks/compositional/dash_do_base.py +1366 -0
  43. browsergym/workarena/tasks/compositional/dash_do_catalog.py +1127 -0
  44. browsergym/workarena/tasks/compositional/dash_do_catalog_infeasible.py +2047 -0
  45. browsergym/workarena/tasks/compositional/dash_do_create_incident.py +403 -0
  46. browsergym/workarena/tasks/compositional/dash_do_create_incident_infeasible.py +278 -0
  47. browsergym/workarena/tasks/compositional/dash_do_create_problem.py +336 -0
  48. browsergym/workarena/tasks/compositional/dash_do_create_problem_infeasible.py +235 -0
  49. browsergym/workarena/tasks/compositional/dash_do_filter.py +1600 -0
  50. browsergym/workarena/tasks/compositional/dash_do_request_item.py +1315 -0
  51. browsergym/workarena/tasks/compositional/dash_do_request_item_infeasible.py +693 -0
  52. browsergym/workarena/tasks/compositional/delete_record.py +341 -0
  53. browsergym/workarena/tasks/compositional/edit_knowledge_base.py +457 -0
  54. browsergym/workarena/tasks/compositional/expense_management.py +598 -0
  55. browsergym/workarena/tasks/compositional/filter_and_do.py +139 -0
  56. browsergym/workarena/tasks/compositional/find_and_order_item.py +345 -0
  57. browsergym/workarena/tasks/compositional/manage_change_request_schedule.py +1417 -0
  58. browsergym/workarena/tasks/compositional/mark_duplicate_problems.py +499 -0
  59. browsergym/workarena/tasks/compositional/maximize_investment_return.py +1763 -0
  60. browsergym/workarena/tasks/compositional/navigate_and_do.py +1151 -0
  61. browsergym/workarena/tasks/compositional/navigate_and_do_infeasible.py +2100 -0
  62. browsergym/workarena/tasks/compositional/offboard_user.py +207 -0
  63. browsergym/workarena/tasks/compositional/onboard_user.py +226 -0
  64. browsergym/workarena/tasks/compositional/update_task.py +145 -0
  65. browsergym/workarena/tasks/compositional/utils/curriculum.py +215 -0
  66. browsergym/workarena/tasks/compositional/utils/infeasible_configs.py +151 -0
  67. browsergym/workarena/tasks/compositional/utils/knapsack.py +192 -0
  68. browsergym/workarena/tasks/compositional/warranty_check.py +227 -0
  69. browsergym/workarena/tasks/compositional/work_assignment.py +804 -0
  70. browsergym/workarena/tasks/compositional/workload_balancing.py +396 -0
  71. browsergym/workarena/tasks/dashboard.py +188 -8
  72. browsergym/workarena/tasks/form.py +1024 -232
  73. browsergym/workarena/tasks/knowledge.py +216 -25
  74. browsergym/workarena/tasks/list.py +519 -102
  75. browsergym/workarena/tasks/mark_duplicate_problem.py +171 -0
  76. browsergym/workarena/tasks/navigation.py +55 -13
  77. browsergym/workarena/tasks/scripts/extract_all_menu_items.py +9 -2
  78. browsergym/workarena/tasks/scripts/generate_dashboard_configs.py +6 -5
  79. browsergym/workarena/tasks/scripts/service_catalog.py +2 -1
  80. browsergym/workarena/tasks/scripts/validate.py +8 -2
  81. browsergym/workarena/tasks/send_chat_message.py +90 -0
  82. browsergym/workarena/tasks/service_catalog.py +94 -26
  83. browsergym/workarena/tasks/utils/form.py +1 -4
  84. browsergym/workarena/tasks/utils/private_tasks.py +63 -0
  85. browsergym/workarena/tasks/utils/utils.py +13 -0
  86. {browsergym_workarena-0.2.1.dist-info → browsergym_workarena-0.3.0.dist-info}/METADATA +19 -18
  87. browsergym_workarena-0.3.0.dist-info/RECORD +138 -0
  88. {browsergym_workarena-0.2.1.dist-info → browsergym_workarena-0.3.0.dist-info}/entry_points.txt +1 -0
  89. browsergym_workarena-0.2.1.dist-info/RECORD +0 -85
  90. {browsergym_workarena-0.2.1.dist-info → browsergym_workarena-0.3.0.dist-info}/WHEEL +0 -0
  91. {browsergym_workarena-0.2.1.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