使用scipy.optimize.minimize函数编写目标函数

dbf7pr2w  于 2024-01-09  发布在  其他
关注(0)|答案(2)|浏览(238)

我正在尝试解决一个应用于桥梁的优化问题,在参数化设计的背景下。目标函数是尝试将预制护栏分割成最少数量的独特护栏。有两种护栏,普通护栏和带灯杆的护栏。我分享了一个3跨桥梁的图像。
参数如下:
灯杆屏障宽度等于w
灯杆屏障通过参数s均匀间隔。
灯杆起点由变量a定义。
桥梁的跨度,[l1,l2,. ln]

x是所有方程都必须被整除的宽度。

这个想法是尽可能多的障碍物的宽度x和一些剩余的长度,以填补跨度保持在最低限度。我必须得到所有的距离**[a,b,c,... f]在一个数组的方程(见图),我还在特殊屏障(s-w)之间添加了光线,并使它们都能被x**整除。我还在元素 * 数组中添加了常数,以防剩余长度不能被x整除,因此我们将使用不同的障碍进行补偿(基本上,我们使用该常数进行减法)。
下面是我正在谈论的内容的草图:x1c 0d1x

  1. from scipy.optimize import minimize
  2. import numpy as np
  3. s = 12500
  4. spans = np.array([25450, 25100, 25450])
  5. ejs = np.zeros(len(spans)-1) # expansion or separation joints . ignore for now
  6. min_bw, max_bw = 0, 1200 #light pole barrier width
  7. consts = abs((len(spans) * 2 - 1)) # Number of distance elements [b,c,d...]
  8. init = np.array([1500, 1500, 6250]) #x, w, a + w/2
  9. n = (spans - s / 2) // s # consecutive spacings of barriers for a given span. At times we have more
  10. m = np.ones(len(spans) * 2) # the length of the equation array that has to be divisible
  11. def objective(arr, n, s, spans, ejs):
  12. x = arr[0]
  13. w = arr[1]
  14. a = arr[2] # (the offset is until the mid axis of the light pole)
  15. m = np.ones(len(spans) * 2 + 1) # We make room for the last constraint h=(s-w)
  16. m[0] = a - w / 2
  17. for i in range(1, len(spans) * 2):
  18. j = (i - 1) // 2
  19. if i % 2 == 0:
  20. m[i] = (s - w) - (m[i - 1] + ejs[j] + arr[2 + i])
  21. else:
  22. m[i] = spans[j] - (m[i - 1] + n[j] * s + w + arr[2 + i])
  23. m[-1] = s - w # We also need the light distance between two poles to be divisible by x
  24. #print(m)
  25. return np.sum(m%x)
  26. #Bounds
  27. bounds = np.array([(1500, 2100), (1500, 2600), (3000, s)])
  28. extras = np.repeat([(min_bw, max_bw)], consts, axis=0) # Add the bounds for each additional element
  29. all_bounds = np.concatenate((bounds, extras), axis=0)
  30. initial = np.pad(init, (0, consts), 'constant', constant_values=min_bw)
  31. sol = minimize(fun=objective, x0=initial, bounds=all_bounds, method='Nelder-Mead', args=(n, s, spans, ejs))
  32. print(np.round(sol.x))
  33. #m = [ 5500. , 5950., 5050. ,6050. ,4950. , 6500. ,11000.] #the print m statment

字符串
我似乎不能弄清楚的问题是np.sum(m%x)不返回0!我得到了正确的距离在m数组与绘图相比。整除方程约束不满足,但函数说它成功终止。我会很感激任何见解!非常感谢!

5kgi1eie

5kgi1eie1#

尼克提出的主要问题是正确的(尽管不全面):有许多局部极小值,目标是不可微的。
但同样重要的是:你的目标根本不适合作为一个目标--它应该是一个约束;整个系统这意味着现在是整数线性规划的时候了,它比一般的minimize有更好的收敛性和准确性保证。(无论您选择哪种方法)。更具体地说,您正在进行约束编程,这里根本没有目标(或者你可能知道更好的目标应该是什么,比如最小化x的系数)。
除了x、w、a、“距离元素”和m的变量之外,还需要一个二进制选择矩阵,其x的倍数等于m。这通过使用所谓的大M约束来解决x乘以某个倍数得到m是非线性的问题。
我已经尝试让它易于理解,在任何时候你都可以调用some_constraint.A.toarray()并在PyCharm的矩阵可视化器中查看结果,看看发生了什么;但你可能还需要深入研究一些关于线性规划的介绍性材料,以更好地掌握它。

  1. import numpy as np
  2. import scipy.sparse as sp
  3. from scipy.optimize import milp, Bounds, LinearConstraint
  4. s = 12_500
  5. spans = np.array((25_450, 25_100, 25_450))
  6. ejs = np.zeros(len(spans) - 1) # expansion or separation joints
  7. min_bw, max_bw = 0, 1200 # light pole barrier width
  8. consts = spans.size*2 - 1 # Number of distance elements [b,c,d...]
  9. m_count = spans.size*2 + 1
  10. n = (spans - s/2)//s # consecutive spacings of barriers for a given span
  11. # In the old solution's objective for the initial guess, m/x ranges from 3.6 to 7.3. Ballpark 20 as a maximum.
  12. mul_count = 20
  13. multiples = np.arange(1, 1 + mul_count)[:, np.newaxis]
  14. '''
  15. Variables: x, w, a, consts, m, and then
  16. a m_count * mul_count matrix of selectors of integer multiples of m.
  17. Each selector is binary (0 or 1), and indicates times 1, times 2, ... times mul_count respectively.
  18. '''
  19. # Constraint matrix for all m equations, starting with the 'm' term itself
  20. m_A = sp.hstack((
  21. sp.csc_array((m_count, 3 + consts)),
  22. -sp.eye(m_count),
  23. sp.csc_array((m_count, m_count*mul_count)),
  24. ), format='lil')
  25. # Column positions: x, w, a, consts, m, mi
  26. # First row: a - w/2 == m0: -0.5w + a - m == 0
  27. m_A[0, 1:3] = -0.5, 1
  28. # Middle rows
  29. m_A[1:, 1] = -1 # -w (middle and last row)
  30. m_A[1:-1, 3: 3+consts] = -sp.eye(consts) # -consts
  31. m_A[1:-1, 3+consts: 3+consts+m_count - 2] -= sp.eye(m_count - 2) # -m[i - 1]
  32. m_rhs = np.empty(m_count)
  33. # First row
  34. m_rhs[0] = 0
  35. # Even rows: m[i] = s - w - m[i-1] - ejs[(i - 1)//2] - consts
  36. # -s + ejs[(i - 1)//2] = -w - m[i] - m[i-1] - consts
  37. m_rhs[2:-1:2] = ejs - s
  38. # Odd rows: m[i] = spans[(i - 1)//2] - (m[i - 1] + n[(i - 1)//2] * s + w + arr[2 + i])
  39. # -spans[(i - 1)//2] + n[(i - 1)//2]*s = -m[i] - m[i - 1] - w - consts
  40. m_rhs[1:-1:2] = n*s - spans
  41. # Last row: m[-1] = s - w
  42. # -w - m[-1] = -s
  43. m_rhs[-1] = -s
  44. constraint_m = LinearConstraint(A=m_A, lb=m_rhs, ub=m_rhs)
  45. # To see the constraint matrix: view constraint_m.A.toarray() in a matrix viewer
  46. # Exactly one selector per m-variable must be on
  47. constraint_selection = LinearConstraint(
  48. A=sp.hstack((
  49. sp.csc_array((m_count, 3 + consts + m_count)),
  50. sp.kron(
  51. A=sp.eye(m_count),
  52. B=np.ones(mul_count),
  53. format='csc',
  54. )
  55. )),
  56. lb=np.ones(m_count),
  57. ub=np.ones(m_count),
  58. )
  59. '''
  60. Enforce the multiple constraint:
  61. m >= x*i m <= x*i (if mi is selected)
  62. -i*x + m >= 0 i*x - m >= 0 (if mi is selected)
  63. -i*x + m + M(1 - mi) >= 0 i*x - m + M(1 - mi) >= 0
  64. -i*x + m - M*mi >= -M i*x - m - M*mi >= -M
  65. Column positions: x, w, a, consts, m, mi
  66. '''
  67. ix_m = sp.hstack((
  68. np.tile(multiples, (m_count, 1)),
  69. sp.csc_array((m_count * mul_count, 2 + consts)),
  70. sp.kron(
  71. -sp.eye(m_count),
  72. np.ones((mul_count, 1)),
  73. ),
  74. ))
  75. M = 1e6 # big-M constraint coefficient
  76. mmi = -M*sp.eye(m_count * mul_count)
  77. constraint_mul = LinearConstraint(
  78. A=sp.bmat((
  79. ( ix_m, mmi),
  80. (-ix_m, mmi),
  81. )),
  82. lb=np.full(shape=2*m_count*mul_count, fill_value=-M),
  83. ub=np.inf,
  84. )
  85. result = milp(
  86. c=np.zeros(3 + consts + m_count + m_count*mul_count), # There is no optimization objective
  87. integrality=np.concatenate((
  88. np.zeros(3 + consts + m_count), # x, w, a, consts and m continuous
  89. np.ones(m_count * mul_count), # mi integral
  90. )),
  91. bounds=Bounds(
  92. lb=np.concatenate((
  93. (1_500, 1_500, 3_000), # x, w, a
  94. np.full(shape=consts, fill_value=min_bw), # consts
  95. np.zeros(m_count + m_count*mul_count), # m, mi
  96. )),
  97. ub=np.concatenate((
  98. (2_100, 2_600, s), # x, w, a
  99. np.full(shape=consts, fill_value=max_bw), # consts
  100. np.full(shape=m_count, fill_value=np.inf), # m unbounded
  101. np.ones(m_count*mul_count), # mi are binary, so 0-1
  102. )),
  103. ),
  104. constraints=(
  105. constraint_m,
  106. constraint_selection,
  107. constraint_mul,
  108. ),
  109. )
  110. assert result.success, result.message
  111. (x, w, a), distances, m, mi = np.split(result.x, (3, 3+consts, 3+consts+m_count))
  112. mi = mi.reshape((m_count, mul_count))
  113. multipliers = (mi @ multiples).ravel()
  114. print(result.message)
  115. print(f'x = {x}')
  116. print(f'w = {w}')
  117. print(f'a = {a}')
  118. print(f'distance elements = {distances}')
  119. print(f'm = {m}')
  120. print(f'm/x quotients: {m/x}')
  121. print(f'multipliers = {multipliers}')
  122. print('multiply selectors =')
  123. print(mi)

个字符
这个问题的规模相当小,而且稀疏性很高,因此可以快速求解。

  1. plt.imshow(
  2. np.sign(
  3. sp.vstack((
  4. constraint_m.A,
  5. constraint_selection.A,
  6. constraint_mul.A,
  7. )).toarray()
  8. ),
  9. )
  10. plt.axis('scaled')
  11. plt.show()


x1c 0d1x的数据

展开查看全部
j7dteeu8

j7dteeu82#

你现在有两个问题。

  1. m % x有一个使优化器混淆的导数。
    1.你要解决的问题有很多局部最小值。
    让我们首先为优化器提供有关如何优化函数的更多信息。

混淆导数

函数minimize()是一个局部优化器,如果有另一个最小值,它必须爬上一座山才能到达,它就不能到达它。
假设你想最小化函数f(x)= x % 100,其中x限制在50和150之间。
x1c 0d1x的数据
假设优化器从x = 90开始,它查看导数,然后沿着斜率向左移动,然后在x = 50时卡住,同样的事情也发生在你的问题中。
要解决这个问题,你可以创建一个函数,奖励优化器接近x = 100。我想出了这个函数,它是一个余弦波,当variable接近target的倍数时,它接近于零。

  1. def modular_difference(variable, target):
  2. # Move valley very slightly forward
  3. overshoot_distances_amount = 1e-8
  4. distance_to_next = variable % target / target
  5. distance_to_next -= overshoot_distances_amount
  6. ret = -np.cos(2*np.pi*distance_to_next) + 1
  7. return ret

字符串
(Note:overshoot_distances_amount的用途将在后面解释。)
这给出了下图:



这告诉优化器,如果它在x=99,它应该向x=100移动。
我用下面的方法将这个新函数集成到您的代码中。
首先,我改变了原始的目标函数,使其返回m和x,称之为objective_inner()

  1. def objective_inner(arr, n, s, spans, ejs):
  2. x = arr[0]
  3. w = arr[1]
  4. a = arr[2] # (the offset is until the mid axis of the light pole)
  5. m = np.ones(len(spans) * 2 + 1) # We make room for the last constraint h=(s-w)
  6. m[0] = a - w / 2
  7. for i in range(1, len(spans) * 2):
  8. j = (i - 1) // 2
  9. if i % 2 == 0:
  10. m[i] = (s - w) - (m[i - 1] + ejs[j] + arr[2 + i])
  11. else:
  12. m[i] = spans[j] - (m[i - 1] + n[j] * s + w + arr[2 + i])
  13. m[-1] = s - w # We also need the light distance between two poles to be divisible by x
  14. return m, x


然后,我写了两个版本的目标函数。

  1. def objective_differentiable(arr, n, s, spans, ejs):
  2. m, x = objective_inner(arr, n, s, spans, ejs)
  3. return modular_difference(m, x).sum()
  4. def objective_old(arr, n, s, spans, ejs):
  5. m, x = objective_inner(arr, n, s, spans, ejs)
  6. return np.sum(m%x)


第一个函数是你的目标的一个版本,它有很好的导数,使用正弦波。第二个函数和你原来的目标一样。
然而,修正导数本身是不够的,它仍然会陷入局部极小值。

全局优化器

有些函数不能被局部优化器优化,因为优化器会陷入局部最小值。这就是其中之一。
为了解决这个问题,我使用了basinhopping函数,它使用了一个局部优化器,但也在随机方向上采取了很大的步骤,希望找到一个更好的最小值。
我发现下面的工作很好。

  1. minimizer_kwargs = dict(bounds=all_bounds, method='Nelder-Mead', args=(n, s, spans, ejs))
  2. sol = basinhopping(objective_differentiable, x0=initial, minimizer_kwargs=minimizer_kwargs, niter=100)
  3. print("basinhopping sol", sol)
  4. sol = minimize(fun=objective_old, x0=sol.x, bounds=all_bounds, method='Nelder-Mead', args=(n, s, spans, ejs))
  5. print("minimize true sol", sol)


这将执行以下操作:

  • 首先,它使用带有Nelder-Mead的basinhopping作为局部优化器,优化目标的可微版本。
  • 然后,它将找到的解传递给你的原始目标。我这样做是为了比较它是否找到了比原始目标更好的解。(注意:这就是为什么上面的代码中存在overshoot_distances_amount。这会选择一个稍微在模循环点之后的解,这使得优化器更容易找到objective_old()的精确最小值。)

这可以找到在原始目标上获得1 e-4或更低分数的解决方案。
以下是它找到的解决方案之一:

  1. array([1501.43791458, 1989.93457535, 5499.28104559, 449.99999353,
  2. 0.000001 , 100.00001273, 0.00000361, 450.00000564])

展开查看全部

相关问题