AzurLaneAutoScript/dev_tools/research_optimizer.py
2023-05-30 10:28:51 +08:00

1196 lines
41 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import itertools
import re
import time
from copy import deepcopy
from dataclasses import dataclass
from functools import wraps
import numpy as np
from numba import jit
from tqdm import tqdm
from tqdm.contrib.concurrent import process_map
"""
硬编码的刷新权重和掉落数据
数据来源 https://azur-stats.lyoko.io/, 2022-01-02, 约5w6样本
默认二三期全毕业,二三期定向的权重增加到四期上
假设二三期的项目刷新和四期一样,但没有收益
"""
# 索引,期数,名称,出现权重,彩图纸掉落,彩图纸掉落,金图纸掉落,金图纸掉落,金图纸掉落,彩装备掉落
PROJECT_TABLE = """
0 4 B-4 58.42861987 0 0 0.346666667 0.346666667 0.346666667 0.0588
1 4 B-4 58.42861987 0 0 0.346666667 0.346666667 0.346666667 0.0588
2 4 B-4 58.42861987 0 0 0.346666667 0.346666667 0.346666667 0.0588
3 4 B-4 58.42861987 0 0 0.346666667 0.346666667 0.346666667 0.0588
4 4 B-4 58.42861987 0 0 0.346666667 0.346666667 0.346666667 0.0588
5 4 B-4 58.42861987 0 0 0.346666667 0.346666667 0.346666667 0.0588
6 4 B-4 58.42861987 0 0 0.346666667 0.346666667 0.346666667 0.0588
7 4 B-4 58.42861987 0 0 0.346666667 0.346666667 0.346666667 0.0588
8 4 B-4 58.42861987 0 0 0.346666667 0.346666667 0.346666667 0.0588
9 4 B-4 29.21430994 0 0 0.346666667 0.346666667 0.346666667 0.0588
10 4 B-4 29.21430994 0 0 0.346666667 0.346666667 0.346666667 0.0588
11 4 C-6 303.3660224 0 0 0 0 0 0.06
12 4 C-8 205.3100712 0.0645 0.0645 0.151 0.151 0.151 0.08
13 4 C-12 155.8916073 0.079 0.079 0.245333333 0.245333333 0.245333333 0.12
14 4 C-6 303.3660224 0 0 0 0 0 0.06
15 4 C-8 205.3100712 0.0645 0.0645 0.151 0.151 0.151 0.08
16 4 C-12 155.8916073 0.079 0.079 0.245333333 0.245333333 0.245333333 0.12
17 4 Agir-0.5 25.39955239 6 0 0 0 0 0.14
18 4 Agir-2.5 766.6697864 1.2 0 0 0 0 0.04
19 4 Agir-5 485.7871007 2.5 0 0 0 0 0.06
20 4 Agir-8 200.3313937 4 0 0 0 0 0.096
21 4 Hakuryu-0.5 25.39955239 0 6 0 0 0 0.14
22 4 Hakuryu-2.5 766.6697864 0 1.2 0 0 0 0.04
23 4 Hakuryu-5 485.7871007 0 2.5 0 0 0 0.06
24 4 Hakuryu-8 200.3313937 0 4 0 0 0 0.096
25 4 Anchorage-0.5 25.39955239 0 0 9 0 0 0.14
26 4 Anchorage-2.5 766.6697864 0 0 2.25 0 0 0.04
27 4 Anchorage-5 485.7871007 0 0 3.75 0 0 0.06
28 4 Anchorage-8 200.3313937 0 0 6 0 0 0.096
29 4 August-0.5 25.39955239 0 0 0 9 0 0.14
30 4 August-2.5 766.6697864 0 0 0 2.25 0 0.04
31 4 August-5 485.7871007 0 0 0 3.75 0 0.06
32 4 August-8 200.3313937 0 0 0 6 0 0.096
33 4 Marcopolo-0.5 25.39955239 0 0 0 0 9 0.14
34 4 Marcopolo-2.5 766.6697864 0 0 0 0 2.25 0.04
35 4 Marcopolo-5 485.7871007 0 0 0 0 3.75 0.06
36 4 Marcopolo-8 200.3313937 0 0 0 0 6 0.096
37 4 Z-2 203.9216684 0 0 0 0 0 0.024
38 4 A-2 203.9216684 0 0 0 0 0 0.06
39 4 G-1.5 582.001119 0.104 0.104 0.299 0.299 0.299 0.025
40 4 G-2.5 402.5500509 0.135 0.135 0.403333333 0.403333333 0.403333333 0.04
41 4 G-4 305.1449135 0.2585 0.2585 0.723333333 0.723333333 0.723333333 0.12
42 4 G-1.5 582.001119 0.104 0.104 0.299 0.299 0.299 0.025
43 4 G-2.5 402.5500509 0.135 0.135 0.403333333 0.403333333 0.403333333 0.04
44 4 G-4 305.1449135 0.2585 0.2585 0.723333333 0.723333333 0.723333333 0.12
45 4 H-0.5 13.18982706 0.555 0.555 1.616666667 1.616666667 1.616666667 0
46 4 H-1 608.5109359 0.33 0.33 0.97 0.97 0.97 0
47 4 H-2 394.3497965 0.4765 0.4765 1.423333333 1.423333333 1.423333333 0
48 4 H-4 151.9433367 0.665 0.665 1.906666667 1.906666667 1.906666667 0
49 4 H-0.5 13.18982706 0.555 0.555 1.616666667 1.616666667 1.616666667 0
50 4 H-1 608.5109359 0.33 0.33 0.97 0.97 0.97 0
51 4 H-2 394.3497965 0.4765 0.4765 1.423333333 1.423333333 1.423333333 0
52 4 H-4 151.9433367 0.665 0.665 1.906666667 1.906666667 1.906666667 0
53 4 Q-0.5 35.35220753 0 0 0 0 0 0.34
54 4 Q-1 204.8414852 0 0 0 0 0 0.04
55 4 Q-2 104.3558291 0 0 0 0 0 0.08
56 4 Q-4 51.26677518 0 0 0 0 0 0.16
57 4 Q-0.5 35.35220753 0 0 0 0 0 0.34
58 4 Q-1 204.8414852 0 0 0 0 0 0.04
59 4 Q-2 104.3558291 0 0 0 0 0 0.08
60 4 Q-4 51.26677518 0 0 0 0 0 0.16
61 4 Q-0.5 35.35220753 0 0 0 0 0 0.34
62 4 Q-1 204.8414852 0 0 0 0 0 0.04
63 4 Q-2 104.3558291 0 0 0 0 0 0.08
64 4 Q-4 51.26677518 0 0 0 0 0 0.16
65 4 Q-0.5 35.35220753 0 0 0 0 0 0.34
66 4 Q-1 204.8414852 0 0 0 0 0 0.04
67 4 Q-2 104.3558291 0 0 0 0 0 0.08
68 4 Q-4 51.26677518 0 0 0 0 0 0.16
69 4 Q-0.5 35.35220753 0 0 0 0 0 0.34
70 4 Q-1 204.8414852 0 0 0 0 0 0.04
71 4 Q-2 104.3558291 0 0 0 0 0 0.08
72 4 Q-4 51.26677518 0 0 0 0 0 0.16
73 4 T-3 261.8007121 0 0 0 0 0 0.045
74 4 T-4 182.1410987 0 0 0 0 0 0.06
75 4 T-6 107.3408952 0 0 0 0 0 0.09
76 2 B-4 63.85544525 0 0 0 0 0 0
77 2 B-4 63.85544525 0 0 0 0 0 0
78 2 B-4 63.85544525 0 0 0 0 0 0
79 2 B-4 63.85544525 0 0 0 0 0 0
80 2 B-4 63.85544525 0 0 0 0 0 0
81 2 B-4 63.85544525 0 0 0 0 0 0
82 2 B-4 63.85544525 0 0 0 0 0 0
83 2 B-4 63.85544525 0 0 0 0 0 0
84 2 B-4 63.85544525 0 0 0 0 0 0
85 2 B-4 31.92772262 0 0 0 0 0 0
86 2 B-4 31.92772262 0 0 0 0 0 0
87 2 C-6 331.5425296 0 0 0 0 0 0
88 2 C-8 224.3791833 0 0 0 0 0 0
89 2 C-12 170.3707535 0 0 0 0 0 0
90 2 C-6 331.5425296 0 0 0 0 0 0
91 2 C-8 224.3791833 0 0 0 0 0 0
92 2 C-12 170.3707535 0 0 0 0 0 0
93 2 Z-2 222.8618262 0 0 0 0 0 0
94 2 A-2 222.8618262 0 0 0 0 0 0
95 2 G-1.5 636.0571355 0 0 0 0 0 0
96 2 G-2.5 439.9387285 0 0 0 0 0 0
97 2 G-4 333.4866434 0 0 0 0 0 0
98 2 G-1.5 636.0571355 0 0 0 0 0 0
99 2 G-2.5 439.9387285 0 0 0 0 0 0
100 2 G-4 333.4866434 0 0 0 0 0 0
101 2 H-0.5 14.41489259 0 0 0 0 0 0
102 2 H-1 665.0291729 0 0 0 0 0 0
103 2 H-2 430.976838 0 0 0 0 0 0
104 2 H-4 166.0557692 0 0 0 0 0 0
105 2 H-0.5 14.41489259 0 0 0 0 0 0
106 2 H-1 665.0291729 0 0 0 0 0 0
107 2 H-2 430.976838 0 0 0 0 0 0
108 2 H-4 166.0557692 0 0 0 0 0 0
109 2 Q-0.5 38.63570553 0 0 0 0 0 0
110 2 Q-1 223.8670753 0 0 0 0 0 0
111 2 Q-2 114.0483541 0 0 0 0 0 0
112 2 Q-4 56.02841146 0 0 0 0 0 0
113 2 Q-0.5 38.63570553 0 0 0 0 0 0
114 2 Q-1 223.8670753 0 0 0 0 0 0
115 2 Q-2 114.0483541 0 0 0 0 0 0
116 2 Q-4 56.02841146 0 0 0 0 0 0
117 2 Q-0.5 38.63570553 0 0 0 0 0 0
118 2 Q-1 223.8670753 0 0 0 0 0 0
119 2 Q-2 114.0483541 0 0 0 0 0 0
120 2 Q-4 56.02841146 0 0 0 0 0 0
121 2 Q-0.5 38.63570553 0 0 0 0 0 0
122 2 Q-1 223.8670753 0 0 0 0 0 0
123 2 Q-2 114.0483541 0 0 0 0 0 0
124 2 Q-4 56.02841146 0 0 0 0 0 0
125 2 Q-0.5 38.63570553 0 0 0 0 0 0
126 2 Q-1 223.8670753 0 0 0 0 0 0
127 2 Q-2 114.0483541 0 0 0 0 0 0
128 2 Q-4 56.02841146 0 0 0 0 0 0
129 2 T-3 286.1166509 0 0 0 0 0 0
130 2 T-4 199.0582865 0 0 0 0 0 0
131 2 T-6 117.3106719 0 0 0 0 0 0
132 3 B-4 63.20796608 0 0 0 0 0 0
133 3 B-4 63.20796608 0 0 0 0 0 0
134 3 B-4 63.20796608 0 0 0 0 0 0
135 3 B-4 63.20796608 0 0 0 0 0 0
136 3 B-4 63.20796608 0 0 0 0 0 0
137 3 B-4 63.20796608 0 0 0 0 0 0
138 3 B-4 63.20796608 0 0 0 0 0 0
139 3 B-4 63.20796608 0 0 0 0 0 0
140 3 B-4 63.20796608 0 0 0 0 0 0
141 3 B-4 31.60398304 0 0 0 0 0 0
142 3 B-4 31.60398304 0 0 0 0 0 0
143 3 C-6 328.1807665 0 0 0 0 0 0
144 3 C-8 222.1040313 0 0 0 0 0 0
145 3 C-12 168.6432343 0 0 0 0 0 0
146 3 C-6 328.1807665 0 0 0 0 0 0
147 3 C-8 222.1040313 0 0 0 0 0 0
148 3 C-12 168.6432343 0 0 0 0 0 0
149 3 Z-2 220.6020598 0 0 0 0 0 0
150 3 A-2 220.6020598 0 0 0 0 0 0
151 3 G-1.5 629.6076661 0 0 0 0 0 0
152 3 G-2.5 435.4778534 0 0 0 0 0 0
153 3 G-4 330.1051674 0 0 0 0 0 0
154 3 G-1.5 629.6076661 0 0 0 0 0 0
155 3 G-2.5 435.4778534 0 0 0 0 0 0
156 3 G-4 330.1051674 0 0 0 0 0 0
157 3 H-0.5 14.26872898 0 0 0 0 0 0
158 3 H-1 658.2859339 0 0 0 0 0 0
159 3 H-2 426.6068344 0 0 0 0 0 0
160 3 H-4 164.3720029 0 0 0 0 0 0
161 3 H-0.5 14.26872898 0 0 0 0 0 0
162 3 H-1 658.2859339 0 0 0 0 0 0
163 3 H-2 426.6068344 0 0 0 0 0 0
164 3 H-4 164.3720029 0 0 0 0 0 0
165 3 Q-0.5 38.24394859 0 0 0 0 0 0
166 3 Q-1 221.5971159 0 0 0 0 0 0
167 3 Q-2 112.8919307 0 0 0 0 0 0
168 3 Q-4 55.46029657 0 0 0 0 0 0
169 3 Q-0.5 38.24394859 0 0 0 0 0 0
170 3 Q-1 221.5971159 0 0 0 0 0 0
171 3 Q-2 112.8919307 0 0 0 0 0 0
172 3 Q-4 55.46029657 0 0 0 0 0 0
173 3 Q-0.5 38.24394859 0 0 0 0 0 0
174 3 Q-1 221.5971159 0 0 0 0 0 0
175 3 Q-2 112.8919307 0 0 0 0 0 0
176 3 Q-4 55.46029657 0 0 0 0 0 0
177 3 Q-0.5 38.24394859 0 0 0 0 0 0
178 3 Q-1 221.5971159 0 0 0 0 0 0
179 3 Q-2 112.8919307 0 0 0 0 0 0
180 3 Q-4 55.46029657 0 0 0 0 0 0
181 3 Q-0.5 38.24394859 0 0 0 0 0 0
182 3 Q-1 221.5971159 0 0 0 0 0 0
183 3 Q-2 112.8919307 0 0 0 0 0 0
184 3 Q-4 55.46029657 0 0 0 0 0 0
185 3 T-3 283.2154955 0 0 0 0 0 0
186 3 T-4 197.0398824 0 0 0 0 0 0
187 3 T-6 116.1211694 0 0 0 0 0 0
"""
PROJECT_TABLE_S4 = """
0 4 B-4 185.4920312 0 0 0.346666667 0.346666667 0.346666667 0.0588
1 4 B-4 185.4920312 0 0 0.346666667 0.346666667 0.346666667 0.0588
2 4 B-4 185.4920312 0 0 0.346666667 0.346666667 0.346666667 0.0588
3 4 B-4 185.4920312 0 0 0.346666667 0.346666667 0.346666667 0.0588
4 4 B-4 185.4920312 0 0 0.346666667 0.346666667 0.346666667 0.0588
5 4 B-4 185.4920312 0 0 0.346666667 0.346666667 0.346666667 0.0588
6 4 B-4 185.4920312 0 0 0.346666667 0.346666667 0.346666667 0.0588
7 4 B-4 185.4920312 0 0 0.346666667 0.346666667 0.346666667 0.0588
8 4 B-4 185.4920312 0 0 0.346666667 0.346666667 0.346666667 0.0588
9 4 B-4 92.7460156 0 0 0.346666667 0.346666667 0.346666667 0.0588
10 4 B-4 92.7460156 0 0 0.346666667 0.346666667 0.346666667 0.0588
11 4 C-6 963.0893184 0 0 0 0 0 0.06
12 4 C-8 651.7932859 0.0645 0.0645 0.151 0.151 0.151 0.08
13 4 C-12 494.9055951 0.079 0.079 0.245333333 0.245333333 0.245333333 0.12
14 4 C-6 963.0893184 0 0 0 0 0 0.06
15 4 C-8 651.7932859 0.0645 0.0645 0.151 0.151 0.151 0.08
16 4 C-12 494.9055951 0.079 0.079 0.245333333 0.245333333 0.245333333 0.12
17 4 Agir-0.5 25.39955239 6 0 0 0 0 0.14
18 4 Agir-2.5 766.6697864 1.2 0 0 0 0 0.04
19 4 Agir-5 485.7871007 2.5 0 0 0 0 0.06
20 4 Agir-8 200.3313937 4 0 0 0 0 0.096
21 4 Hakuryu-0.5 25.39955239 0 6 0 0 0 0.14
22 4 Hakuryu-2.5 766.6697864 0 1.2 0 0 0 0.04
23 4 Hakuryu-5 485.7871007 0 2.5 0 0 0 0.06
24 4 Hakuryu-8 200.3313937 0 4 0 0 0 0.096
25 4 Anchorage-0.5 25.39955239 0 0 9 0 0 0.14
26 4 Anchorage-2.5 766.6697864 0 0 2.25 0 0 0.04
27 4 Anchorage-5 485.7871007 0 0 3.75 0 0 0.06
28 4 Anchorage-8 200.3313937 0 0 6 0 0 0.096
29 4 August-0.5 25.39955239 0 0 0 9 0 0.14
30 4 August-2.5 766.6697864 0 0 0 2.25 0 0.04
31 4 August-5 485.7871007 0 0 0 3.75 0 0.06
32 4 August-8 200.3313937 0 0 0 6 0 0.096
33 4 Marcopolo-0.5 25.39955239 0 0 0 0 9 0.14
34 4 Marcopolo-2.5 766.6697864 0 0 0 0 2.25 0.04
35 4 Marcopolo-5 485.7871007 0 0 0 0 3.75 0.06
36 4 Marcopolo-8 200.3313937 0 0 0 0 6 0.096
37 4 Z-2 647.3855544 0 0 0 0 0 0.024
38 4 A-2 647.3855544 0 0 0 0 0 0.06
39 4 G-1.5 1847.665921 0.104 0.104 0.299 0.299 0.299 0.025
40 4 G-2.5 1277.966633 0.135 0.135 0.403333333 0.403333333 0.403333333 0.04
41 4 G-4 968.7367243 0.2585 0.2585 0.723333333 0.723333333 0.723333333 0.12
42 4 G-1.5 1847.665921 0.104 0.104 0.299 0.299 0.299 0.025
43 4 G-2.5 1277.966633 0.135 0.135 0.403333333 0.403333333 0.403333333 0.04
44 4 G-4 968.7367243 0.2585 0.2585 0.723333333 0.723333333 0.723333333 0.12
45 4 H-0.5 41.87344863 0.555 0.555 1.616666667 1.616666667 1.616666667 0
46 4 H-1 1931.826043 0.33 0.33 0.97 0.97 0.97 0
47 4 H-2 1251.933469 0.4765 0.4765 1.423333333 1.423333333 1.423333333 0
48 4 H-4 482.3711089 0.665 0.665 1.906666667 1.906666667 1.906666667 0
49 4 H-0.5 41.87344863 0.555 0.555 1.616666667 1.616666667 1.616666667 0
50 4 H-1 1931.826043 0.33 0.33 0.97 0.97 0.97 0
51 4 H-2 1251.933469 0.4765 0.4765 1.423333333 1.423333333 1.423333333 0
52 4 H-4 482.3711089 0.665 0.665 1.906666667 1.906666667 1.906666667 0
53 4 Q-0.5 112.2318616 0 0 0 0 0 0.34
54 4 Q-1 650.3056765 0 0 0 0 0 0.04
55 4 Q-2 331.2961139 0 0 0 0 0 0.08
56 4 Q-4 162.7554832 0 0 0 0 0 0.16
57 4 Q-0.5 112.2318616 0 0 0 0 0 0.34
58 4 Q-1 650.3056765 0 0 0 0 0 0.04
59 4 Q-2 331.2961139 0 0 0 0 0 0.08
60 4 Q-4 162.7554832 0 0 0 0 0 0.16
61 4 Q-0.5 112.2318616 0 0 0 0 0 0.34
62 4 Q-1 650.3056765 0 0 0 0 0 0.04
63 4 Q-2 331.2961139 0 0 0 0 0 0.08
64 4 Q-4 162.7554832 0 0 0 0 0 0.16
65 4 Q-0.5 112.2318616 0 0 0 0 0 0.34
66 4 Q-1 650.3056765 0 0 0 0 0 0.04
67 4 Q-2 331.2961139 0 0 0 0 0 0.08
68 4 Q-4 162.7554832 0 0 0 0 0 0.16
69 4 Q-0.5 112.2318616 0 0 0 0 0 0.34
70 4 Q-1 650.3056765 0 0 0 0 0 0.04
71 4 Q-2 331.2961139 0 0 0 0 0 0.08
72 4 Q-4 162.7554832 0 0 0 0 0 0.16
73 4 T-3 831.1328586 0 0 0 0 0 0.045
74 4 T-4 578.2392675 0 0 0 0 0 0.06
75 4 T-6 340.7727365 0 0 0 0 0 0.09
"""
"""
从 Alas (https://github.com/LmeSzinc/AzurLaneAutoScript) 里复制过来的一大堆代码
只是为了能单文件运行
"""
class cached_property:
"""
cached-property from https://github.com/pydanny/cached-property
A property that is only computed once per instance and then replaces itself
with an ordinary attribute. Deleting the attribute resets the property.
Source: https://github.com/bottlepy/bottle/commit/fa7733e075da0d790d809aa3d2f53071897e6f76
"""
def __init__(self, func):
self.func = func
def __get__(self, obj, cls):
if obj is None:
return self
value = obj.__dict__[self.func.__name__] = self.func(obj)
return value
def timer(function):
@wraps(function)
def function_timer(*args, **kwargs):
t0 = time.time()
result = function(*args, **kwargs)
t1 = time.time()
print('%s: %s s' % (function.__name__, str(round(t1 - t0, 10))))
return result
return function_timer
class Filter:
def __init__(self, regex, attr, preset=()):
"""
Args:
regex: Regular expression.
attr: Attribute name.
preset: Build-in string preset.
"""
if isinstance(regex, str):
regex = re.compile(regex)
self.regex = regex
self.attr = attr
self.preset = tuple(list(p.lower() for p in preset))
self.filter_raw = []
self.filter = []
def load(self, string):
string = str(string)
self.filter_raw = [f.strip(' \t\r\n') for f in string.split('>')]
self.filter = [self.parse_filter(f) for f in self.filter_raw]
def is_preset(self, filter):
return len(filter) and filter.lower() in self.preset
def apply(self, objs, func=None):
"""
Args:
objs (list): List of objects and strings
func (callable): A function to filter object.
Function should receive an object as arguments, and return a bool.
True means add it to output.
Returns:
list: A list of objects and preset strings, such as [object, object, object, 'reset']
"""
out = []
for raw, filter in zip(self.filter_raw, self.filter):
if self.is_preset(raw):
raw = raw.lower()
if raw not in out:
out.append(raw)
else:
for index, obj in enumerate(objs):
if self.apply_filter_to_obj(obj=obj, filter=filter) and obj not in out:
out.append(obj)
if func is not None:
objs, out = out, []
for obj in objs:
if isinstance(obj, str):
out.append(obj)
elif func(obj):
out.append(obj)
else:
# Drop this object
pass
return out
def apply_filter_to_obj(self, obj, filter):
"""
Args:
obj (object):
filter (list[str]):
Returns:
bool: If an object satisfy a filter.
"""
for attr, value in zip(self.attr, filter):
if not value:
continue
if str(obj.__getattribute__(attr)).lower() != str(value):
return False
return True
def parse_filter(self, string):
"""
Args:
string (str):
Returns:
list[strNone]:
"""
string = string.replace(' ', '').lower()
result = re.search(self.regex, string)
if self.is_preset(string):
return [string]
if result and len(string) and result.span()[1]:
return [result.group(index + 1) for index, attr in enumerate(self.attr)]
else:
print(f'Invalid filter: "{string}". This selector does not match the regex, nor a preset.')
# Invalid filter will be ignored.
# Return strange things and make it impossible to match
return ['1nVa1d'] + [None] * (len(self.attr) - 1)
class SelectedGrids:
def __init__(self, grids):
self.grids = grids
def __iter__(self):
return iter(self.grids)
def __getitem__(self, item):
if isinstance(item, int):
return self.grids[item]
else:
return SelectedGrids(self.grids[item])
def __contains__(self, item):
return item in self.grids
def __str__(self):
# return str([str(grid) for grid in self])
return '[' + ', '.join([str(grid) for grid in self]) + ']'
def __len__(self):
return len(self.grids)
def __bool__(self):
return self.count > 0
# def __getattr__(self, item):
# return [grid.__getattribute__(item) for grid in self.grids]
@property
def location(self):
"""
Returns:
list[tuple]:
"""
return [grid.location for grid in self.grids]
@property
def cost(self):
"""
Returns:
list[int]:
"""
return [grid.cost for grid in self.grids]
@property
def weight(self):
"""
Returns:
list[int]:
"""
return [grid.weight for grid in self.grids]
@property
def count(self):
"""
Returns:
int:
"""
return len(self.grids)
def select(self, **kwargs):
"""
Args:
**kwargs: Attributes of Grid.
Returns:
SelectedGrids:
"""
result = []
for grid in self:
flag = True
for k, v in kwargs.items():
grid_v = grid.__getattribute__(k)
if type(grid_v) != type(v) or grid_v != v:
flag = False
if flag:
result.append(grid)
return SelectedGrids(result)
def filter(self, func):
"""
Filter grids by a function.
Args:
func (callable): Function should receive an grid as argument, and return a bool.
Returns:
SelectedGrids:
"""
return SelectedGrids([grid for grid in self if func(grid)])
def set(self, **kwargs):
"""
Set attribute to each grid.
Args:
**kwargs:
"""
for grid in self:
for key, value in kwargs.items():
grid.__setattr__(key, value)
def get(self, attr):
"""
Get an attribute from each grid.
Args:
attr: Attribute name.
Returns:
list:
"""
return [grid.__getattribute__(attr) for grid in self.grids]
def call(self, func, **kwargs):
"""
Call a function in reach grid, and get results.
Args:
func (str): Function name to call.
**kwargs:
Returns:
list:
"""
return [grid.__getattribute__(func)(**kwargs) for grid in self]
def add(self, grids):
"""
Args:
grids(SelectedGrids):
Returns:
SelectedGrids:
"""
return SelectedGrids(list(set(self.grids + grids.grids)))
def add_by_eq(self, grids):
"""
Another `add()` method, but de-duplicates with `__eq__` instead of `__hash__`.
Args:
grids(SelectedGrids):
Returns:
SelectedGrids:
"""
new = []
for grid in self.grids + grids.grids:
if grid not in new:
new.append(grid)
return SelectedGrids(new)
def intersect(self, grids):
"""
Args:
grids(SelectedGrids):
Returns:
SelectedGrids:
"""
return SelectedGrids(list(set(self.grids).intersection(set(grids.grids))))
def intersect_by_eq(self, grids):
"""
Another `intersect()` method, but de-duplicates with `__eq__` instead of `__hash__`.
Args:
grids(SelectedGrids):
Returns:
SelectedGrids:
"""
new = []
for grid in self.grids:
if grid in grids.grids:
new.append(grid)
return SelectedGrids(new)
def delete(self, grids):
"""
Args:
grids(SelectedGrids):
Returns:
SelectedGrids:
"""
g = [grid for grid in self.grids if grid not in grids]
return SelectedGrids(g)
def hr0(title):
middle = '|' + ' ' * 20 + title + ' ' * 20 + '|'
border = '+' + '-' * (len(middle) - 2) + '+'
print(border)
print(middle)
print(border)
def hr1(title):
print('=' * 20 + ' ' + title + ' ' + '=' * 20)
def hr2(title):
print('-' * 20 + ' ' + title + ' ' + '-' * 20)
def hr3(title):
print('<' * 3 + ' ' + title + ' ' + '>' * 3)
FILTER_REGEX = re.compile('([s\!][1234])?'
'-?'
'(neptune|monarch|ibuki|izumo|roon|saintlouis'
'|seattle|georgia|kitakaze|azuma|friedrich'
'|gascogne|champagne|cheshire|drake|mainz|odin'
'|anchorage|hakuryu|agir|august|marcopolo)?'
'(dr|pry)?'
'([bcdeghqtaz])?'
'-?'
'(\d.\d|\d\d?)?')
FILTER_ATTR = ('series', 'ship', 'ship_rarity', 'genre', 'duration')
FILTER_PRESET = ('shortest', 'cheapest', 'reset')
FILTER = Filter(FILTER_REGEX, FILTER_ATTR, FILTER_PRESET)
"""
科研优化器开始
"""
def parse_value(value):
if '.' in value:
try:
return float(value)
except ValueError:
pass
else:
try:
return int(value)
except ValueError:
pass
return value
def parse_text_table(string, data_class):
out = []
for row in string.split('\n'):
row = row.strip(' \r\n\t')
if not len(row):
continue
row = row.split('\t')
out.append(data_class(*row))
return SelectedGrids(out)
@dataclass()
class Research:
"""
储存每个科研项目的信息
"""
index: int
series: str
name: str
weight: float
bp_Agir: float
bp_Hakuryu: float
bp_Anchorage: float
bp_August: float
bp_Marcopolo: float
bp_Tenrai: float
def __post_init__(self):
# 转换变量类型
for k, v in self.__dict__.items():
self.__setattr__(k, parse_value(v))
# 构造科研过滤器需要的对象属性
self.genre, self.duration = self.name.split('-')
self.duration = str(self.duration)
if self.series == 4:
self.series = f'S{self.series}'
else:
self.series = f'!4'
if self.genre in ['Agir', 'Hakuryu']:
self.ship = self.genre
self.ship_rarity = 'dr'
self.genre = 'D'
elif self.genre in ['Anchorage', 'August', 'Marcopolo']:
self.ship = self.genre
self.ship_rarity = 'pry'
self.genre = 'D'
else:
self.ship = ''
self.ship_rarity = ''
def __hash__(self):
return hash(self.name)
PROJECTS = parse_text_table(PROJECT_TABLE, Research)
PROJECTS_S4 = parse_text_table(PROJECT_TABLE_S4, Research)
def product_dict(func):
out = {}
for project in PROJECTS:
out[project.index] = func(project)
return out
# 掉落加那么一点点防止过滤器写错100年都不毕业
PROJECT_DROP = product_dict(lambda project: np.array(
[project.bp_Agir, project.bp_Hakuryu, project.bp_Anchorage, project.bp_August, project.bp_Marcopolo,
project.bp_Tenrai]) + 0.000001)
PROJECT_DURATION = product_dict(lambda project: float(project.duration) / 24)
# 构造出掉落数据的数组给numba
# Shape: (project_index=188, drop_items=6)
PROJECT_DROP_ARRAY = np.array(list(PROJECT_DROP.values()))
PROJECT_DURATION_ARRAY = np.array(list(PROJECT_DURATION.values()))
class ResearchPool:
remove_projects = 'B > T > E'
all_ships = ('Agir', 'Hakuryu', 'Anchorage', 'August', 'Marcopolo')
def __init__(self, string):
FILTER.load(string)
self.filter = SelectedGrids(FILTER.apply(PROJECTS.grids))
self.reset_index = 1000
for index, project in enumerate(self.filter):
if str(project) == 'reset':
self.reset_index = index
break
@cached_property
def project_select_index(self):
"""
将过滤器字符串转换为项目选择索引越低表示越优先选择1000表示不选择需要刷新
Returns:
np.ndarray: Shape (188,), lower index means to be selected first. 1000 for not selected projects.
"""
out = np.ones((PROJECTS.count,), dtype=int) * 1000
for index, project in enumerate(self.filter):
if index != self.reset_index:
out[project.index] = index
FILTER.load(ResearchPool.remove_projects)
projects = FILTER.apply(PROJECTS.grids)
for project in projects:
out[project.index] = 1000
return out
@classmethod
def cal_project_spawn_rate(cls, projects):
"""
计算不同完成条件下的出现概率
Returns:
dict(tuple, np.ndarray): Key: Combinations of conditions, such as b'\x00\x00\x00\x00\x00\x00'
value: project appear rate.
"""
out = {}
for condition in itertools.product([False, True], repeat=len(PROJECT_DROP[0])):
ships = [ship for ship, con in zip(cls.all_ships, condition) if con]
weight = np.array(projects.get('weight'))
index = np.sum(np.array(condition) * [1, 2, 4, 8, 16, 32])
remain = len(ships)
if 0 < remain < 5:
changed = []
# 将所有四期船的概率增加到未完成的船上
for ship in ships:
for project in projects.select(ship=ship):
weight[project.index] *= 5 / remain
changed.append(project.index)
# 将已完成的科研船的定向概率归0
for project in projects.select(genre='D'):
if project.index not in changed:
weight[project.index] = 0
weight /= np.sum(weight)
out[index] = weight
return out
SPAWN_RATE = ResearchPool('reset').cal_project_spawn_rate(PROJECTS)
SPAWN_RATE_S4 = ResearchPool('reset').cal_project_spawn_rate(PROJECTS_S4)
# 构造出不同条件下的刷新概率数组给numba
# 事先累加概率,加快 random_choice()
SPAWN_RATE = np.array([np.cumsum(SPAWN_RATE[n]) for n in range(64)])
SPAWN_RATE_S4 = np.array([np.cumsum(SPAWN_RATE_S4[n]) for n in range(64)])
@jit(nopython=True, fastmath=True)
def random_choice(size, possibility_cumsum):
"""
numpy.random.choice()的土法实现
因为numba不支持numpy.random.choice()的p参数概率数组
https://numba.pydata.org/numba-doc/dev/reference/numpysupported.html
Args:
size (int): 只能生成一维数组
possibility_cumsum (np.ndarray): 经过累加后的出现概率
"""
rdm_unif = np.random.rand(size)
return np.searchsorted(possibility_cumsum, rdm_unif)
@jit(nopython=True, fastmath=True)
def sample(condition, project_select_index, reset_index):
"""
随机生成科研项目并选择
Args:
condition (np.ndarray):Shape: (6,) 各种物品的完成情况
project_select_index (np.ndarray): Shape: (188,) 项目的选择优先级越低表示越优先选择1000表示不选择需要刷新
reset_index (int): 刷新所对应的优先级数值
Returns:
int, int: 有刷新时选择的科研项目, 无刷新时选择的科研项目
"""
while 1:
# 将完成情况转换成数组索引
index = 0
for i, c in enumerate(condition):
if c:
index += 2 ** i
# 随机生成5个科研项目包含3个四期和2个任意
# np.random.seed(3)
p1, p2, p3 = random_choice(3, SPAWN_RATE_S4[index])
p4, p5 = random_choice(2, SPAWN_RATE[index])
# 去重
if p1 == p4 or p2 == p4 or p3 == p4 or p1 == p5 or p2 == p5 or p3 == p5:
continue
# 加入刷新1000表示刷新
project_list = np.array([p1, p2, p3, p4, p5, 1000])
# 将项目索引转换为过滤器索引
f1, f2, f3, f4, f5 = np.take(project_select_index, project_list[:5])
# print(filter_index)
# print(project_list)
# 无刷新时,选择的科研项目
s_index = np.array([f1, f2, f3, f4, f5, 999])
selected_no_reset = project_list[np.argmin(s_index)]
# 有刷新时,选择的科研项目
s_index = np.array([f1, f2, f3, f4, f5, reset_index])
selected_with_reset = project_list[np.argmin(s_index)]
return selected_with_reset, selected_no_reset
@jit(nopython=True, fastmath=True)
def events_add(rewards, condition):
# 活动兑换蓝图给进度最慢的,有利于提高整体速度
# 因为G系给的是随机的早毕业的就溢出了给进度最慢的不会溢出就快了
index = np.argmin(rewards[:2])
rewards[index] += 0.5 # 15 DR blueprints in each event
index = np.argmin(rewards[2:5])
rewards[index + 2] += 1 # 30 PRY blueprints in each event
return rewards
@jit(nopython=True, fastmath=True)
def simulate(project_select_index, reset_index, target, active=1., interval=0.):
"""
模拟一个玩家做科研到毕业
Args:
project_select_index (np.ndarray):Shape: (188,) 项目的选择优先级越低表示越优先选择1000表示不选择需要刷新
reset_index (int): 刷新所对应的优先级数值
target (np.ndarray): Shape: (6,) 目标物品数量
active (float): 每日活跃时间,单位 天,超出活跃时间后,仍在挂项目,但不再开始新项目
interval (float): 收菜时间,单位 天,项目完成后,过多长时间才收获
Returns:
float, np.ndarray: 消耗时间,累计获得物品 Shape: (6,)
"""
rewards = np.array([0., 0., 0., 0., 0., 0.])
condition = rewards != 0 # 每样物品是否达到目标数量True未达到False已达到
has_reset = True
day_cost = 0
while 1:
"""
做一个科研项目
"""
while 1:
index, index_no_reset = sample(condition, project_select_index, reset_index)
# print(index, index_no_reset, has_reset)
if has_reset:
if index == 1000:
has_reset = False
continue
else:
break
else:
if index_no_reset == 1000:
# 刷新次数用完,且需要刷新时,等到明天,使用明天的刷新次数
day_cost = int(day_cost) + 1
rewards = events_add(rewards, condition)
has_reset = False
continue
else:
index = index_no_reset
break
"""
收获科研项目,计算收益和消耗时间
"""
prev_day = int(day_cost)
rewards += PROJECT_DROP_ARRAY[index]
day_cost += PROJECT_DURATION_ARRAY[index] + interval
# print(rewards / day_cost)
condition = rewards < target
new_day = int(day_cost)
new_hour = day_cost - new_day
# 跨天重置刷新次数
if new_day > prev_day:
has_reset = True
rewards = events_add(rewards, condition)
else:
# 超出活跃时间
if new_hour > active:
day_cost = int(day_cost) + 1
has_reset = True
rewards = events_add(rewards, condition)
"""
达成目标物品数量
"""
if not np.any(condition):
break
return day_cost, rewards
class FilterSimulator:
active = 24 / 24
interval = 0 / 60 / 24
target = np.array([513, 513, 343, 343, 343, 150])
def __init__(self, string):
string = string.replace('E-315', 'A2')
string = string.replace('E-031', 'Z2')
self.string = string
self.pool = ResearchPool(string)
def run(self, sample_count=1000):
day_cost = 0
rewards = PROJECT_DROP[0] * 0
for _ in tqdm(range(sample_count)):
sim_day, sim_rewards = simulate(
self.pool.project_select_index,
self.pool.reset_index,
target=FilterSimulator.target,
active=FilterSimulator.active,
interval=FilterSimulator.interval
)
day_cost += sim_day
rewards += sim_rewards
day_cost /= sample_count
rewards /= sample_count
hr3('End Testing')
print(self.string)
print(f'Average time cost: {day_cost}')
print(f'Average rewards: {rewards}')
return day_cost
def split_filter(string):
if isinstance(string, list):
return string
return [f.strip(' \t\r\n') for f in string.split('>')]
def join_filter(selection):
if isinstance(selection, str):
return selection
return ' > '.join(selection)
def beautify_filter(list_filter):
if isinstance(list_filter, str):
list_filter = split_filter(list_filter)
out = []
length = 0
for selection in list_filter:
if length + len(selection) + 3 > 70:
out.append('\n')
length = 0
out.append(selection)
length += len(selection) + 3
string = ' > '.join(out).strip('\n >').replace(' > \n', '\n').replace('\n ', '\n')
return string
def position_change(string, position):
selection = split_filter(string)
selection[position], selection[position + 1] = selection[position + 1], selection[position]
return join_filter(selection)
def position_insert(string, insert, position):
selection = split_filter(string)
selection.insert(position, insert)
return join_filter(selection)
def epoch_worker(data):
index, total, sample_count, select_index, forward_index, string = data
hr3(f'Start Testing: {index}/{total}')
return FilterSimulator(string).run(sample_count)
class BruteForceOptimizer:
@timer
def optimize(self, string, diff=10):
for epoch in range(100):
hr0(f'Epoch: {epoch}')
new, diff = self.epoch(string, diff=diff)
if new == string:
break
else:
string = new
continue
def gen(self, string, look_forward):
string = split_filter(string)
yield 0, 0, join_filter(string)
for select_index, select_item in enumerate(string):
string_dropped = [s for s in string if s != select_item]
for forward_index in range(1, look_forward + 1):
forward_index = select_index - forward_index
if forward_index < 0:
continue
string_added = deepcopy(string_dropped)
string_added.insert(forward_index, select_item)
string_added = join_filter(string_added)
yield select_index, select_index - forward_index, string_added
def epoch(self, string, diff=10):
diff = min(abs(diff), 1)
level = np.log(diff) / np.log(10) + 1
sample_count = int(np.power(10, 5 - level / 2))
look_forward = int(np.power(3, level))
look_forward = max(look_forward, 1)
sample_count = min(max(sample_count, 10000), 300000)
print(f'diff: {diff}, look_forward: {look_forward}, sample_count: {sample_count}')
string_split = split_filter(string)
string_count = len(string_split)
all_tests = list(self.gen(string, look_forward=look_forward))
total = len(all_tests)
# index, total, sample_count, select_index, forward_index, string_added
tests_data = [(index, total, sample_count, *row) for index, row in enumerate(all_tests)]
results = process_map(epoch_worker, tests_data, max_workers=BruteForceOptimizer.process)
day_cost = np.ones((string_count, look_forward + 1)) * 1000
for data, result in zip(tests_data[1:], results[1:]):
day_cost[data[3]][data[4]] = result
day_cost[:, 0] = results[0]
hr2('Original filter')
print(beautify_filter(string_split))
hr2('Move forward')
forward = np.argmin(day_cost, axis=1)
if look_forward == 1:
forward[np.min(day_cost, axis=1) != np.min(day_cost)] = 0
for index, selection, forward_index in zip(range(len(forward)), string_split, forward):
if index == 0:
selection = '[Original]'
print(f'{selection.ljust(12, " ")}forward: {forward_index}, day_cost: {day_cost[index][forward_index]}')
diff = day_cost[0][0] - np.min(day_cost)
print(f'diff: {diff}')
hr2('New filter')
forward[forward > 0] += 1
forward = np.arange(forward.shape[0]) - forward
new_index = np.argsort(forward)
new_filter = beautify_filter([string_split[index] for index in new_index])
print(new_filter)
return new_filter, diff
"""
科研设置
"""
# 去除的科研项目
# 默认去除 B/T/E因为掉落数据样本小
# 切魔方:'B > T > E'
# 只做0.5h魔方:'B > T > E > H1 > H2 > H4'
# 不切魔方:'B > T > E > H'
ResearchPool.remove_projects = 'B > T > H1 > H2 > H4'
# 每日活跃时间,按天计算
# 超出活跃时间后,仍在挂项目,但不再开始新项目
FilterSimulator.active = 24 / 24
# 收菜间隔,按天计算
# 项目完成后,过多长时间才收获
FilterSimulator.interval = 0 / 60 / 24
# 科研目标
# 需要的彩图纸 彩图纸 金图纸 金图纸 金图纸 彩装备 的物品数量
# 某种图纸数量满足后,不再产生该种定向科研,图纸全满后重置
# 四期毕业np.array([513, 513, 343, 343, 343, 100])
# 仅科研船np.array([513, 513, 343, 343, 343, 0])
# 仅天雷np.array([0, 0, 0, 0, 0, 150])
FilterSimulator.target = np.array([513, 513, 343, 343, 343, 100])
# 运行的进程数
# 建议为cpu的物理进程数
BruteForceOptimizer.process = 6
if __name__ == '__main__':
"""
这个文件包含模拟器和优化器两部分,取消注释对应的代码来运行
Alas用户运行需要额外安装numba无指定版本
非Alas用户运行需要python>=3.7,安装 numba==0.45.1 llvmlite==0.29.0 numpy tqdm
过滤器与Alas内的过滤器基本相同编写参考 https://github.com/LmeSzinc/AzurLaneAutoScript/wiki/filter_string_cn
但需要注意:
- 必须有且只有一个reset
- 不能使用Alas的预设选择比如 shortest 需要展开成
0.5 > 1 > 1.5 > 2 > 2.5 > 3 > 4 > 5 > 6 > 8 > 10 > 12
- 选择数量建议为 10-24 个
选择数量不能过少,否则毕业时间过长
选择数量不能过多,否则优化太慢
- 增加了 "!" 表示""逻辑,只能用在期数上
比如 "!4" 表示非四期,详细参考正则表达式 FILTER_REGEX
如果你在Alas的目录下运行可以取消注释这些代码把过程额外输出到log中
"""
# from module.logger import logger
# import builtins
# builtins.print = logger.info
"""
模拟大量用户使用同一个过滤器的平均毕业时间和毕业时获取物品的平均数量
取消注释这些代码将你的过滤器粘贴至这里并运行在8700k上需要约4.5分钟
"""
# simulator = FilterSimulator("""
# S4-DR0.5 > S4-PRY0.5 > S4-Q0.5 > S4-H0.5 > Q0.5 > S4-DR2.5
# > S4-G1.5 > S4-Q1 > S4-DR5 > 0.5 > S4-G4 > S4-Q2 > S4-PRY2.5 > reset
# > S4-DR8 > Q1 > 1 > S4-E-315 > S4-G2.5 > G1.5 > 1.5 > S4-E-031
# > S4-Q4 > Q2 > E2 > 2 > DR2.5 > PRY2.5 > G2.5 > 2.5 > S4-PRY5
# > S4-PRY8 > Q4 > G4 > 4 > S4-C6 > DR5 > PRY5 > 5 > C6 > 6 > S4-C8
# > S4-C12 > DR8 > PRY8 > C8 > 8 > C12 > 12
# """)
# simulator.run(sample_count=300000)
"""
优化一个过滤器,尝试调整过滤器选择的顺序,找到满足目标条件的消耗时间最短的排列方式
类似于早期机器学习的实现,收敛过程中,向前尝试移动的距离变短,模拟样本量增大
取消注释这些代码并运行在8700k上需要约1-2天
已给出一个包含所有选项、顺序大体正确的过滤器作为开始,不需要修改
"""
optimizer = BruteForceOptimizer()
optimizer.optimize("""
S4-H0.5 > S4-DR0.5 > S4-PRY0.5 > S4-Q0.5 > !4-0.5 > S4-G1.5 > S4-Q1 > S4-DR2.5
> S4-G4 > S4-Q4 > S4-DR5 > S4-DR8 > S4-Q2 > S4-PRY2.5 > S4-G2.5 > !4-1
> S4-H1 > S4-H2 > S4-H4
> S4-EP2 > S4-EB2
> reset > S4-PRY8 > !4-1.5 > S4-PRY5 > !4-2.5 > !4-2 > !4-4
> S4-C6 > !4-C8 > S4-C8 > !4-C6 > S4-C12 > !4-C12
""", diff=1)