第一次提交

This commit is contained in:
p73692015 2022-05-31 15:09:30 +08:00
parent f1ac589d77
commit 20dbe8c8a7
47 changed files with 3191 additions and 1 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.vscode
__pycache__
TODO.md
script.py

154
MainWindow_map.py Normal file
View File

@ -0,0 +1,154 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'MainWindow_map.ui'
#
# Created by: PyQt5 UI code generator 5.15.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(1297, 900)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.centralwidget)
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
self.verticalLayout = QtWidgets.QVBoxLayout()
self.verticalLayout.setContentsMargins(-1, -1, -1, 0)
self.verticalLayout.setObjectName("verticalLayout")
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setContentsMargins(-1, -1, 0, -1)
self.horizontalLayout.setObjectName("horizontalLayout")
self.formLayout = QtWidgets.QFormLayout()
self.formLayout.setObjectName("formLayout")
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setObjectName("label")
self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label)
self.comboBox_map = QtWidgets.QComboBox(self.centralwidget)
self.comboBox_map.setObjectName("comboBox_map")
self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.comboBox_map)
self.label_2 = QtWidgets.QLabel(self.centralwidget)
self.label_2.setObjectName("label_2")
self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label_2)
self.label_3 = QtWidgets.QLabel(self.centralwidget)
self.label_3.setObjectName("label_3")
self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_3)
self.label_8 = QtWidgets.QLabel(self.centralwidget)
self.label_8.setObjectName("label_8")
self.formLayout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.label_8)
self.comboBox_axis = QtWidgets.QComboBox(self.centralwidget)
self.comboBox_axis.setObjectName("comboBox_axis")
self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.comboBox_axis)
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.lineEdit_coverr = QtWidgets.QLineEdit(self.centralwidget)
self.lineEdit_coverr.setAlignment(QtCore.Qt.AlignCenter)
self.lineEdit_coverr.setObjectName("lineEdit_coverr")
self.horizontalLayout_2.addWidget(self.lineEdit_coverr)
self.label_10 = QtWidgets.QLabel(self.centralwidget)
self.label_10.setObjectName("label_10")
self.horizontalLayout_2.addWidget(self.label_10)
self.formLayout.setLayout(1, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout_2)
self.lineEdit_shipn = QtWidgets.QLineEdit(self.centralwidget)
self.lineEdit_shipn.setAlignment(QtCore.Qt.AlignCenter)
self.lineEdit_shipn.setObjectName("lineEdit_shipn")
self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.lineEdit_shipn)
self.horizontalLayout.addLayout(self.formLayout)
self.formLayout_2 = QtWidgets.QFormLayout()
self.formLayout_2.setObjectName("formLayout_2")
self.label_6 = QtWidgets.QLabel(self.centralwidget)
self.label_6.setObjectName("label_6")
self.formLayout_2.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label_6)
self.comboBox_render = QtWidgets.QComboBox(self.centralwidget)
self.comboBox_render.setObjectName("comboBox_render")
self.formLayout_2.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.comboBox_render)
self.label_7 = QtWidgets.QLabel(self.centralwidget)
self.label_7.setObjectName("label_7")
self.formLayout_2.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_7)
self.comboBox_around = QtWidgets.QComboBox(self.centralwidget)
self.comboBox_around.setObjectName("comboBox_around")
self.formLayout_2.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.comboBox_around)
self.label_11 = QtWidgets.QLabel(self.centralwidget)
self.label_11.setObjectName("label_11")
self.formLayout_2.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.label_11)
self.comboBox_bettery = QtWidgets.QComboBox(self.centralwidget)
self.comboBox_bettery.setObjectName("comboBox_bettery")
self.formLayout_2.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.comboBox_bettery)
self.label_9 = QtWidgets.QLabel(self.centralwidget)
self.label_9.setObjectName("label_9")
self.formLayout_2.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.label_9)
self.lineEdit_bettery = QtWidgets.QLineEdit(self.centralwidget)
self.lineEdit_bettery.setAlignment(QtCore.Qt.AlignCenter)
self.lineEdit_bettery.setObjectName("lineEdit_bettery")
self.formLayout_2.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.lineEdit_bettery)
self.label_4 = QtWidgets.QLabel(self.centralwidget)
self.label_4.setObjectName("label_4")
self.formLayout_2.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label_4)
self.comboBox_algo = QtWidgets.QComboBox(self.centralwidget)
self.comboBox_algo.setObjectName("comboBox_algo")
self.formLayout_2.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.comboBox_algo)
self.horizontalLayout.addLayout(self.formLayout_2)
self.verticalLayout_2 = QtWidgets.QVBoxLayout()
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.pushButton_run = QtWidgets.QPushButton(self.centralwidget)
font = QtGui.QFont()
font.setFamily("宋体")
font.setPointSize(12)
self.pushButton_run.setFont(font)
self.pushButton_run.setObjectName("pushButton_run")
self.verticalLayout_2.addWidget(self.pushButton_run)
self.pushButton_stop = QtWidgets.QPushButton(self.centralwidget)
font = QtGui.QFont()
font.setFamily("宋体")
font.setPointSize(12)
self.pushButton_stop.setFont(font)
self.pushButton_stop.setObjectName("pushButton_stop")
self.verticalLayout_2.addWidget(self.pushButton_stop)
self.horizontalLayout.addLayout(self.verticalLayout_2)
self.horizontalLayout.setStretch(0, 1)
self.horizontalLayout.setStretch(1, 1)
self.horizontalLayout.setStretch(2, 1)
self.verticalLayout.addLayout(self.horizontalLayout)
self.label_show = QtWidgets.QLabel(self.centralwidget)
font = QtGui.QFont()
font.setFamily("微软雅黑")
font.setPointSize(12)
self.label_show.setFont(font)
self.label_show.setObjectName("label_show")
self.verticalLayout.addWidget(self.label_show)
self.verticalLayout.setStretch(0, 1)
self.verticalLayout.setStretch(1, 10)
self.horizontalLayout_3.addLayout(self.verticalLayout)
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
self.pushButton_run.clicked.connect(MainWindow.run)
self.pushButton_stop.clicked.connect(MainWindow.stop)
self.comboBox_bettery.currentTextChanged['QString'].connect(MainWindow.slot_bettery)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "SeAI Palette集智调色板"))
self.label.setText(_translate("MainWindow", "地图选择"))
self.label_2.setText(_translate("MainWindow", "最低覆盖率"))
self.label_3.setText(_translate("MainWindow", "移动节点数量"))
self.label_8.setText(_translate("MainWindow", "区域划分方向"))
self.lineEdit_coverr.setText(_translate("MainWindow", "100"))
self.label_10.setText(_translate("MainWindow", "%"))
self.lineEdit_shipn.setText(_translate("MainWindow", "1"))
self.label_6.setText(_translate("MainWindow", "是否渲染"))
self.label_7.setText(_translate("MainWindow", "考虑固定节点"))
self.label_11.setText(_translate("MainWindow", "是否需要充电"))
self.label_9.setText(_translate("MainWindow", "移动节点电池容量"))
self.lineEdit_bettery.setText(_translate("MainWindow", "100"))
self.label_4.setText(_translate("MainWindow", "算法选择"))
self.pushButton_run.setText(_translate("MainWindow", "运行"))
self.pushButton_stop.setText(_translate("MainWindow", "停止"))
self.label_show.setText(_translate("MainWindow", "<html><head/><body><p><br/></p></body></html>"))

264
MainWindow_map.ui Normal file
View File

@ -0,0 +1,264 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1297</width>
<height>900</height>
</rect>
</property>
<property name="windowTitle">
<string>SeAI Palette集智调色板</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,10">
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,1,1">
<property name="rightMargin">
<number>0</number>
</property>
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>地图选择</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="comboBox_map"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>最低覆盖率</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>移动节点数量</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>区域划分方向</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="comboBox_axis"/>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLineEdit" name="lineEdit_coverr">
<property name="text">
<string>100</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_10">
<property name="text">
<string>%</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="lineEdit_shipn">
<property name="text">
<string>1</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QFormLayout" name="formLayout_2">
<item row="1" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>是否渲染</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="comboBox_render"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>考虑固定节点</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="comboBox_around"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>是否需要充电</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="comboBox_bettery"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>移动节点电池容量</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="lineEdit_bettery">
<property name="text">
<string>100</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>算法选择</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="comboBox_algo"/>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QPushButton" name="pushButton_run">
<property name="font">
<font>
<family>宋体</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>运行</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_stop">
<property name="font">
<font>
<family>宋体</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>停止</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="label_show">
<property name="font">
<font>
<family>微软雅黑</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections>
<connection>
<sender>pushButton_run</sender>
<signal>clicked()</signal>
<receiver>MainWindow</receiver>
<slot>run()</slot>
<hints>
<hint type="sourcelabel">
<x>1186</x>
<y>39</y>
</hint>
<hint type="destinationlabel">
<x>1287</x>
<y>49</y>
</hint>
</hints>
</connection>
<connection>
<sender>pushButton_stop</sender>
<signal>clicked()</signal>
<receiver>MainWindow</receiver>
<slot>stop()</slot>
<hints>
<hint type="sourcelabel">
<x>1275</x>
<y>160</y>
</hint>
<hint type="destinationlabel">
<x>1273</x>
<y>133</y>
</hint>
</hints>
</connection>
<connection>
<sender>comboBox_bettery</sender>
<signal>currentTextChanged(QString)</signal>
<receiver>MainWindow</receiver>
<slot>slot_bettery()</slot>
<hints>
<hint type="sourcelabel">
<x>792</x>
<y>120</y>
</hint>
<hint type="destinationlabel">
<x>899</x>
<y>-8</y>
</hint>
</hints>
</connection>
</connections>
<slots>
<slot>run()</slot>
<slot>stop()</slot>
<slot>slot_bettery()</slot>
</slots>
</ui>

23
Palette/algos/__init__.py Normal file
View File

@ -0,0 +1,23 @@
from .boustrophedon import Boustrophedon
from .spiral import Spiral
from .wildfire import WildFire
from .split_area import split_area
from .multi_boustrophedon import MultiBoustrophedon
from .multi_greedy import MultiGreedy
from .multi_wildfire import MultiWildFire
from .charge import Charge
from .multi_charge import MultiCharge
from .multi_spiral import MultiSpiral
__all__ = [
"Boustrophedon",
"WildFire",
"Spiral",
"split_area",
"MultiBoustrophedon",
"MultiWildFire",
"Charge",
"MultiCharge",
"MultiSpiral",
"MultiGreedy"
]

View File

@ -0,0 +1,139 @@
import numpy as np
from Palette.constants import UP, DOWN, LEFT, RIGHT, FINISHED, BROKEN
class Boustrophedon(object):
def __init__(self):
super().__init__()
self.dest_direction = [None]
@ staticmethod
def _right_index(x, y):
return x+1, y
@ staticmethod
def _left_index(x, y):
return x-1, y
@ staticmethod
def _up_index(x, y):
return x, y-1
@ staticmethod
def _down_index(x, y):
return x, y+1
@ staticmethod
def _is_valid_index(index, an_array):
x, y = index
return 0 <= x < an_array.shape[0] and 0 <= y < an_array.shape[1]
def _count_valid_steps(self, index, direction, fields):
if direction == UP:
update_index = self._up_index
elif direction == DOWN:
update_index = self._down_index
elif direction == LEFT:
update_index = self._left_index
elif direction == RIGHT:
update_index = self._right_index
else:
raise ValueError(f'invalid direction: {direction}')
valid_steps = 0
cur_x, cur_y = index
while True:
cur_x, cur_y = update_index(cur_x, cur_y)
if not self._is_valid_index((cur_x, cur_y), fields) \
or fields[cur_x, cur_y] != 0:
break
else:
valid_steps += 1
return valid_steps
def _act(self, direction, fields, index, ship_id):
x, y = index
if direction == UP:
continue_update_index = self._up_index
reverse_update_index = self._down_index
possible_turn = [LEFT, RIGHT]
reverse_direction = DOWN
elif direction == DOWN:
continue_update_index = self._down_index
reverse_update_index = self._up_index
possible_turn = [LEFT, RIGHT]
reverse_direction = UP
elif direction == LEFT:
continue_update_index = self._left_index
reverse_update_index = self._right_index
possible_turn = [UP, DOWN]
reverse_direction = RIGHT
elif direction == RIGHT:
continue_update_index = self._right_index
reverse_update_index = self._left_index
possible_turn = [UP, DOWN]
reverse_direction = LEFT
else:
raise ValueError(f'invalid direction: {direction}')
if not self._is_valid_index(continue_update_index(x, y), fields) \
or fields[continue_update_index(x, y)] != 0:
steps0 = self._count_valid_steps((x, y), possible_turn[0], fields)
steps1 = self._count_valid_steps((x, y), possible_turn[1], fields)
if steps0 > 0 or steps1 > 0:
self.dest_direction[ship_id] = reverse_direction
if steps0 > 0 and steps1 > 0:
if steps0 < steps1:
return possible_turn[1]
else:
return possible_turn[0]
elif steps0 > 0:
return possible_turn[0]
else:
return possible_turn[1]
else:
search_x, search_y = reverse_update_index(x, y)
can_continue = False
while self._is_valid_index((search_x, search_y), fields) and fields[search_x, search_y] == 0:
search_steps0 = self._count_valid_steps(
(search_x, search_y), possible_turn[0], fields)
search_steps1 = self._count_valid_steps(
(search_x, search_y), possible_turn[1], fields)
if search_steps0 or search_steps1:
can_continue = True
break
search_x, search_y = reverse_update_index(
search_x, search_y)
if can_continue:
return reverse_direction
else:
return BROKEN
else:
return direction
def step(self, state, info):
x, y, direction = state
fields = info['fields']
if np.sum(fields == 0) == 0:
return [FINISHED] # finished
if info['redecide_direction']:
up_steps = self._count_valid_steps((x, y), UP, fields)
down_steps = self._count_valid_steps((x, y), DOWN, fields)
left_steps = self._count_valid_steps((x, y), LEFT, fields)
right_steps = self._count_valid_steps((x, y), RIGHT, fields)
direction = [UP, DOWN, LEFT, RIGHT][np.argmax(
[up_steps, down_steps, left_steps, right_steps])]
if self.dest_direction[0] is not None:
d = self.dest_direction[0]
self.dest_direction = [None]
if self._count_valid_steps((x, y), d, fields) > 0:
return [d]
else:
return [BROKEN]
return [self._act(direction, fields, (x, y), 0)]

60
Palette/algos/charge.py Normal file
View File

@ -0,0 +1,60 @@
import numpy as np
from Palette.algos.wildfire import WildFire
from Palette.algos.utils import Node
from Palette.constants import FILLING_UP, FILLING_DOWN, FILLING_LEFT, FILLING_RIGHT, FINISHED, BROKEN, FILLING, FILLING_BACK
class Charge(WildFire):
def __init__(self, filling_time: int):
super().__init__()
self.filling_time = filling_time
def step(self, state, info):
if not self.empty():
# if there're actions to do
action = self.actions_to_do[0]
del self.actions_to_do[0]
return action, None
x, y, _ = state
fields = info['fields']
if np.sum(fields == 0) == 0:
return FINISHED, None
root_node = Node(x, y, None, energy=True)
buffer = [root_node]
visited = np.zeros_like(fields)
visited[x, y] = True
while len(buffer) > 0:
cur_node = buffer[0]
del buffer[0]
for dx, dy in zip(self.delta_x, self.delta_y):
cur_x, cur_y = cur_node.x, cur_node.y
next_x, next_y = cur_x+dx, cur_y+dy
if self._is_valid_index((next_x, next_y), fields) and not visited[next_x, next_y]:
visited[next_x, next_y] = True
next_node = Node(next_x, next_y, cur_node, energy=True)
if fields[next_x, next_y] == -1:
# not visited
node = next_node
while node.direction is not None:
self.actions_to_do.append(node.direction)
node = node.father
self.actions_to_do.reverse()
charge_len = len(self.actions_to_do)
self.actions_to_do.extend(
[FILLING for _ in range(self.filling_time)])
self.actions_to_do.extend(
[FILLING_BACK for _ in range(charge_len)])
action = self.actions_to_do[0]
del self.actions_to_do[0]
return action, charge_len
else:
buffer.append(next_node)
raise RuntimeError(f'Charge runtime error!')

View File

@ -0,0 +1,102 @@
import numpy as np
from Palette.algos.boustrophedon import Boustrophedon
from Palette.algos.multi_wildfire import MultiWildFire
from Palette.algos.multi_charge import MultiCharge
from Palette.algos.utils import Node
from Palette.constants import UP, DOWN, LEFT, RIGHT, FINISHED, BROKEN, \
FILLING_UP, FILLING_DOWN, FILLING_LEFT, FILLING_RIGHT
from typing import List
class MultiBoustrophedon(Boustrophedon):
def __init__(
self,
ship_num: int,
wildfires: MultiWildFire,
charges: MultiCharge
):
super().__init__()
self.ship_num = ship_num
self.dest_direction = [None for _ in range(ship_num)]
self.wildfires = wildfires
self.charges = charges
self.finished_idx = [False for _ in range(ship_num)]
#self.ship_energy = ship_energy
#self.current_ship_energy = [ship_energy for _ in range(self.ship_num)]
self.delta_x = (0, 0, 1, -1, ) # 1, 1, -1, -1)
self.delta_y = (1, -1, 0, 0, ) # 1, -1, 1, -1)
def step(self, state, info):
ret_directions = []
redecide_directions = [False for _ in range(self.ship_num)]
for i, (x, y, direction) in enumerate(state):
if self.finished_idx[i]:
ret_directions.append(FINISHED)
continue
if not self.charges[i].empty():
ret_directions.append(self.charges[i].step(state, info)[0])
if self.charges[i].empty():
redecide_directions[i] = True
continue
# Decide whether charge or not
first_charge_act, charge_len = self.charges.step(state, info, i)
if first_charge_act == FINISHED:
self.finished_idx[i] = True
ret_directions.append(FINISHED)
continue
if info['batteries'][i] < charge_len + 2:
ret_directions.append(first_charge_act)
redecide_directions[i] = True
continue
else:
self.charges[i].clear()
self.wildfires[i].clear()
if not self.wildfires[i].empty():
ret_directions.append(self.wildfires[i].step(state, info)[0])
if self.wildfires[i].empty():
redecide_directions[i] = True
continue
fields = info['fields'][i]
if np.sum(fields == 0) == 0:
self.finished_idx[i] = True
ret_directions.append(FINISHED) # finished
continue
if info['redecide_direction'][i]:
up_steps = self._count_valid_steps((x, y), UP, fields)
down_steps = self._count_valid_steps((x, y), DOWN, fields)
left_steps = self._count_valid_steps((x, y), LEFT, fields)
right_steps = self._count_valid_steps((x, y), RIGHT, fields)
direction = [UP, DOWN, LEFT, RIGHT][np.argmax(
[up_steps, down_steps, left_steps, right_steps])]
if self.dest_direction[i] is not None:
d = self.dest_direction[i]
self.dest_direction[i] = None
if self._count_valid_steps((x, y), d, fields) > 0:
ret_directions.append(d)
else:
ret_directions.append(
self.wildfires.step(state, info, i)[0])
continue
cur_act = self._act(direction, fields, (x, y), i)
if cur_act == BROKEN:
ret_directions.append(self.wildfires.step(state, info, i)[0])
redecide_directions[i] = True
else:
ret_directions.append(cur_act)
return np.asarray(ret_directions), redecide_directions

View File

@ -0,0 +1,23 @@
from Palette.algos.charge import Charge
class MultiCharge(object):
def __init__(self, n_ships: int, filling_time: int):
super().__init__()
self._n_ships = n_ships
self._filling_time = filling_time
self._charges = [Charge(self._filling_time)
for _ in range(self._n_ships)]
def __len__(self):
return self._n_ships
def __getitem__(self, idx):
return self._charges[idx]
def step(self, state, info, idx):
agent_info = {k: info[k]
for k in (info.keys()-{'fields', 'redecide_direction'})}
agent_info['fields'] = info['fields'][idx]
agent_info['redecide_direction'] = info['redecide_direction'][idx]
return self._charges[idx].step(state[idx], agent_info)

View File

@ -0,0 +1,96 @@
import numpy as np
from Palette.algos.boustrophedon import Boustrophedon
from Palette.algos.multi_wildfire import MultiWildFire
from Palette.algos.multi_charge import MultiCharge
from Palette.algos.utils import Node
from Palette.constants import UP, DOWN, LEFT, RIGHT, FINISHED, BROKEN, \
FILLING_UP, FILLING_DOWN, FILLING_LEFT, FILLING_RIGHT
from typing import List
class MultiGreedy(Boustrophedon):
def __init__(
self,
ship_num: int,
wildfires: MultiWildFire,
charges: MultiCharge
):
super().__init__()
self.ship_num = ship_num
self.dest_direction = [None for _ in range(ship_num)]
self.wildfires = wildfires
self.charges = charges
self.finished_idx = [False for _ in range(ship_num)]
#self.ship_energy = ship_energy
#self.current_ship_energy = [ship_energy for _ in range(self.ship_num)]
self.delta_x = (0, 0, 1, -1, ) # 1, 1, -1, -1)
self.delta_y = (1, -1, 0, 0, ) # 1, -1, 1, -1)
def step(self, state, info):
ret_directions = []
redecide_directions = [False for _ in range(self.ship_num)]
for i, (x, y, direction) in enumerate(state):
if self.finished_idx[i]:
ret_directions.append(FINISHED)
continue
if not self.charges[i].empty():
ret_directions.append(self.charges[i].step(state, info)[0])
if self.charges[i].empty():
redecide_directions[i] = True
continue
# Decide whether charge or not
first_charge_act, charge_len = self.charges.step(state, info, i)
if first_charge_act == FINISHED:
self.finished_idx[i] = True
ret_directions.append(FINISHED)
continue
if info['batteries'][i] < charge_len + 2:
ret_directions.append(first_charge_act)
redecide_directions[i] = True
continue
else:
self.charges[i].clear()
self.wildfires[i].clear()
if not self.wildfires[i].empty():
ret_directions.append(self.wildfires[i].step(state, info)[0])
if self.wildfires[i].empty():
redecide_directions[i] = True
continue
fields = info['fields'][i]
if np.sum(fields == 0) == 0:
self.finished_idx[i] = True
ret_directions.append(FINISHED) # finished
continue
up_steps = self._count_valid_steps((x, y), UP, fields)
down_steps = self._count_valid_steps((x, y), DOWN, fields)
left_steps = self._count_valid_steps((x, y), LEFT, fields)
right_steps = self._count_valid_steps((x, y), RIGHT, fields)
if up_steps == 0 and down_steps == 0 and left_steps == 0 and right_steps == 0:
cur_act = BROKEN
else:
ds, ss = [], []
for d, s in zip((UP, DOWN, LEFT, RIGHT), (up_steps, down_steps, left_steps, right_steps)):
if s > 0:
ds.append(d)
ss.append(s)
cur_act = ds[np.argmin(ss)]
if cur_act == BROKEN:
ret_directions.append(self.wildfires.step(state, info, i)[0])
redecide_directions[i] = True
else:
ret_directions.append(cur_act)
return np.asarray(ret_directions), redecide_directions

View File

@ -0,0 +1,93 @@
import numpy as np
from Palette.algos.spiral import Spiral
from Palette.algos.multi_wildfire import MultiWildFire
from Palette.algos.multi_charge import MultiCharge
from Palette.algos.utils import Node
from Palette.constants import UP, DOWN, LEFT, RIGHT, FINISHED, BROKEN, \
FILLING_UP, FILLING_DOWN, FILLING_LEFT, FILLING_RIGHT
from typing import List
class MultiSpiral(Spiral):
def __init__(
self,
ship_num: int,
wildfires: MultiWildFire,
charges: MultiCharge
):
super().__init__()
self.ship_num = ship_num
# self.dest_direction = [None for _ in range(ship_num)]
self.wildfires = wildfires
self.charges = charges
self.finished_idx = [False for _ in range(ship_num)]
#self.ship_energy = ship_energy
#self.current_ship_energy = [ship_energy for _ in range(self.ship_num)]
self.delta_x = [0, 0, 1, -1, 1, 1, -1, -1]
self.delta_y = [1, -1, 0, 0, 1, -1, 1, -1]
def step(self, state, info):
ret_directions = []
redecide_directions = [False for _ in range(self.ship_num)]
for i, (x, y, direction) in enumerate(state):
if self.finished_idx[i]:
ret_directions.append(FINISHED)
continue
if not self.charges[i].empty():
ret_directions.append(self.charges[i].step(state, info)[0])
if self.charges[i].empty():
redecide_directions[i] = True
continue
# Decide whether charge or not
first_charge_act, charge_len = self.charges.step(state, info, i)
if first_charge_act == FINISHED:
self.finished_idx[i] = True
ret_directions.append(FINISHED)
continue
if info['batteries'][i] < charge_len + 2:
ret_directions.append(first_charge_act)
redecide_directions[i] = True
continue
else:
self.charges[i].clear()
self.wildfires[i].clear()
if not self.wildfires[i].empty():
ret_directions.append(self.wildfires[i].step(state, info)[0])
if self.wildfires[i].empty():
redecide_directions[i] = True
continue
fields = info['fields'][i]
if np.sum(fields == 0) == 0:
self.finished_idx[i] = True
ret_directions.append(FINISHED) # finished
continue
if info['redecide_direction'][i]:
all_directions = (UP, DOWN, LEFT, RIGHT)
all_update_fn = (self._up_index, self._down_index,
self._left_index, self._right_index)
edge_directions = [self._is_edge(
update_fn(x, y), fields, direction) for update_fn in all_update_fn]
valid_steps = [self._count_valid_steps(
(x, y), d, fields) for d in all_directions]
direction = np.max([Spiral.MyTuple(e, v, d) for e, v, d in zip(
edge_directions, valid_steps, all_directions)]).direction
cur_act = self._act(direction, fields, (x, y))
if cur_act == BROKEN:
ret_directions.append(self.wildfires.step(state, info, i)[0])
redecide_directions[i] = True
else:
ret_directions.append(cur_act)
return np.asarray(ret_directions), redecide_directions

View File

@ -0,0 +1,54 @@
import numpy as np
def multi_split_area(
fields: np.ndarray,
field_size: int,
n_areas: int,
axis: str
):
assert axis in ['x', 'y'], f"Invalid axis: {axis}"
splited_fields = np.zeros_like(fields)
blank_spaces_num = np.sum(fields == 0)
last_pos = 0
area_spaces = []
start_points = []
for area_id in range(1, n_areas+1):
p = last_pos
cur_area_spaces = 0
start_point_assigned = False
while True:
cur_spaces = np.sum(fields[p, :] == 0) if axis == 'x' else np.sum(
fields[:, p] == 0)
cur_area_spaces += cur_spaces
if axis == 'x':
for i in range(fields.shape[1]):
if fields[p, i] != -1:
splited_fields[p, i] = area_id
if not start_point_assigned and fields[p, i] == 0:
start_points.append((p, i))
start_point_assigned = True
else:
splited_fields[p, i] = -1
else:
for i in range(fields.shape[0]):
if fields[i, p] != -1:
splited_fields[i, p] = area_id
if not start_point_assigned and fields[i, p] == 0:
start_points.append((i, p))
start_point_assigned = True
else:
splited_fields[i, p] = -1
p += 1
if (axis == 'x' and p >= fields.shape[0]) \
or (axis == 'y' and p >= fields.shape[1]) \
or np.sum(area_spaces) + cur_area_spaces >= blank_spaces_num * area_id / n_areas:
break
last_pos = p
area_spaces.append(cur_area_spaces)
start_points = np.asarray(start_points)
start_points[:, 1] = fields.shape[1] - start_points[:, 1] - 1
start_points = start_points * field_size + field_size / 2
assert np.sum(splited_fields == 0) == 0
return splited_fields, np.asarray(start_points)

View File

@ -0,0 +1,21 @@
from Palette.algos.wildfire import WildFire
class MultiWildFire(object):
def __init__(self, n_ships: int):
super().__init__()
self._n_ships = n_ships
self._wildfires = [WildFire() for _ in range(self._n_ships)]
def __len__(self):
return self._n_ships
def __getitem__(self, idx):
return self._wildfires[idx]
def step(self, state, info, idx):
agent_info = {k: info[k]
for k in (info.keys()-{'fields', 'redecide_direction'})}
agent_info['fields'] = info['fields'][idx]
agent_info['redecide_direction'] = info['redecide_direction'][idx]
return self._wildfires[idx].step(state[idx], agent_info)

172
Palette/algos/spiral.py Normal file
View File

@ -0,0 +1,172 @@
import numpy as np
from Palette.constants import UP, DOWN, LEFT, RIGHT, FINISHED, BROKEN
class Spiral(object):
class MyTuple:
def __init__(self, x1, x2, direction):
self.data = (x1, x2)
self.direction = direction
def __lt__(self, other):
return self.data < other.data
def __le__(self, other):
return self.data <= other.data
def __gt__(self, other):
return self.data > other.data
def __ge__(self, other):
return self.data >= other.data
def __eq__(self, other):
return self.data == other.data
def __ne__(self, other):
return self.data != other.data
def __init__(self,):
super().__init__()
self.delta_x = [0, 0, 1, -1, 1, 1, -1, -1]
self.delta_y = [1, -1, 0, 0, 1, -1, 1, -1]
@ staticmethod
def _right_index(x, y):
return x+1, y
@ staticmethod
def _left_index(x, y):
return x-1, y
@ staticmethod
def _up_index(x, y):
return x, y-1
@ staticmethod
def _down_index(x, y):
return x, y+1
@ staticmethod
def _is_valid_index(index, an_array):
x, y = index
return 0 <= x < an_array.shape[0] and 0 <= y < an_array.shape[1]
def _count_valid_steps(self, index, direction, fields):
if direction == UP:
update_index = self._up_index
elif direction == DOWN:
update_index = self._down_index
elif direction == LEFT:
update_index = self._left_index
elif direction == RIGHT:
update_index = self._right_index
else:
raise ValueError(f'invalid direction: {direction}')
valid_steps = 0
cur_x, cur_y = index
while True:
cur_x, cur_y = update_index(cur_x, cur_y)
if not self._is_valid_index((cur_x, cur_y), fields) \
or fields[cur_x, cur_y] != 0:
break
else:
valid_steps += 1
return valid_steps
def _is_edge(self, index, fields, direction=None):
if direction is not None:
if direction == UP:
delta_x, delta_y = [self.delta_x[0]] + \
self.delta_x[2:], [self.delta_y[0]]+self.delta_y[2:]
elif direction == DOWN:
delta_x, delta_y = self.delta_x[1:], self.delta_y[1:]
elif direction == RIGHT:
delta_x, delta_y = self.delta_x[0:1] + \
self.delta_x[3:], self.delta_y[0:1] + self.delta_y[3:]
elif direction == LEFT:
delta_x, delta_y = self.delta_x[0:2] + \
self.delta_x[4:], self.delta_y[0:2] + self.delta_y[4:]
else:
delta_x, delta_y = self.delta_x, self.delta_y
x, y = index
for dx, dy in zip(delta_x, delta_y):
if self._is_valid_index((x+dx, y+dy), fields) and fields[x+dx, y+dy] != 0:
return True
return False
def _act(self, direction, fields, index):
x, y = index
if direction == UP:
continue_update_index = self._up_index
reverse_update_index = self._down_index
possible_turn = [LEFT, RIGHT]
possible_turn_update_index = [self._left_index, self._right_index]
reverse_direction = DOWN
elif direction == DOWN:
continue_update_index = self._down_index
reverse_update_index = self._up_index
possible_turn = [LEFT, RIGHT]
reverse_direction = UP
possible_turn_update_index = [self._left_index, self._right_index]
elif direction == LEFT:
continue_update_index = self._left_index
reverse_update_index = self._right_index
possible_turn = [UP, DOWN]
reverse_direction = RIGHT
possible_turn_update_index = [self._up_index, self._down_index]
elif direction == RIGHT:
continue_update_index = self._right_index
reverse_update_index = self._left_index
possible_turn = [UP, DOWN]
reverse_direction = LEFT
possible_turn_update_index = [self._up_index, self._down_index]
else:
raise ValueError(f'invalid direction: {direction}')
def _turn(cur_direction):
edge_directions = [self._is_edge(
update_fn(x, y), fields, cur_direction) for update_fn in possible_turn_update_index]
valid_steps = [self._count_valid_steps(
(x, y), d, fields) for d in possible_turn]
steps0, steps1 = valid_steps
if steps0 > 0 or steps1 > 0:
direction = np.max(
[Spiral.MyTuple(e, v, d) for e, v, d in zip(edge_directions, valid_steps, possible_turn)]).direction
return direction
else:
return BROKEN
if not self._is_valid_index(continue_update_index(x, y), fields) \
or fields[continue_update_index(x, y)] != 0:
"""
if not self._is_edge(continue_update_index(x, y), fields, direction):
print('turn')
else:
print('not turn')
"""
return _turn(direction)
else:
# print(f'not turn')
return direction
def step(self, state, info):
x, y, direction = state[0]
fields = info['fields']
if np.sum(fields == 0) == 0:
return [FINISHED]
if info['redecide_direction']:
all_directions = (UP, DOWN, LEFT, RIGHT)
all_update_fn = (self._up_index, self._down_index,
self._left_index, self._right_index)
edge_directions = [self._is_edge(
update_fn(x, y), fields, direction) for update_fn in all_update_fn]
valid_steps = [self._count_valid_steps(
(x, y), d, fields) for d in all_directions]
direction = np.max([Spiral.MyTuple(e, v, d) for e, v, d in zip(
edge_directions, valid_steps, all_directions)]).direction
return [self._act(direction, fields, (x, y))]

View File

@ -0,0 +1,54 @@
import numpy as np
def split_area(
fields: np.ndarray,
):
x_array = [0]
last_array = fields[0, :]
for i in range(1, fields.shape[0]):
cur_array = fields[i, :]
if (cur_array != last_array).any():
x_array.append(i)
last_array = cur_array
x_array.append(fields.shape[0])
"""
y_array = [0]
last_array = fields[:, 0]
for i in range(1, fields.shape[1]):
cur_array = fields[:, i]
if (cur_array != last_array).any():
y_array.append(i)
last_array = cur_array
y_array.append(fields.shape[1])
"""
splited_fields = np.zeros_like(fields)
cur_field_id = 1
"""
for i in range(len(x_array)-1):
for j in range(len(y_array)-1):
if fields[x_array[i], y_array[j]] != -1:
cur_id = cur_field_id
cur_field_id += 1
else:
cur_id = -1
print('obstacle')
splited_fields[x_array[i]:x_array[i+1],
y_array[j]:y_array[j+1]] = cur_id
"""
for i in range(len(x_array)-1):
field_id_used = False
for x in range(x_array[i], x_array[i+1]):
for y in range(fields.shape[1]):
if fields[x, y] != -1:
splited_fields[x, y] = cur_field_id
field_id_used = True
else:
splited_fields[x, y] = -1
if field_id_used:
cur_field_id += 1
assert np.sum(splited_fields == 0) == 0
return splited_fields

45
Palette/algos/utils.py Normal file
View File

@ -0,0 +1,45 @@
from Palette.constants import UP, DOWN, LEFT, RIGHT, FINISHED, BROKEN,\
FILLING_UP, FILLING_DOWN, FILLING_LEFT, FILLING_RIGHT
class Node:
def __init__(self, x, y, father, energy: bool = False):
self.x, self.y = x, y
self._energy = energy
self.direction = None
self.father = father
if self.father is not None:
father_x, father_y = self.father.x, self.father.y
delta_x, delta_y = x-father_x, y-father_y
if delta_x == 0:
if delta_y == 1:
if not self.energy:
self.direction = DOWN
else:
self.direction = FILLING_DOWN
elif delta_y == -1:
if not self.energy:
self.direction = UP
else:
self.direction = FILLING_UP
elif delta_y == 0:
if delta_x == 1:
if not self.energy:
self.direction = RIGHT
else:
self.direction = FILLING_RIGHT
elif delta_x == -1:
if not self.energy:
self.direction = LEFT
else:
self.direction = FILLING_LEFT
if self.direction is None:
raise ValueError(f'invalid delta: ({delta_x}, {delta_y})')
def is_root(self) -> bool:
return self.father is None
@property
def energy(self):
return self._energy

64
Palette/algos/wildfire.py Normal file
View File

@ -0,0 +1,64 @@
import numpy as np
from Palette.constants import UP, DOWN, LEFT, RIGHT, FINISHED, BROKEN
from Palette.algos.utils import Node
class WildFire(object):
def __init__(self):
super().__init__()
self.actions_to_do = []
self.delta_x = (0, 0, 1, -1, ) # 1, 1, -1, -1)
self.delta_y = (1, -1, 0, 0, ) # 1, -1, 1, -1)
@ staticmethod
def _is_valid_index(index, an_array):
x, y = index
return 0 <= x < an_array.shape[0] and 0 <= y < an_array.shape[1]
def empty(self):
return len(self.actions_to_do) == 0
def clear(self):
self.actions_to_do.clear()
def step(self, state, info):
if not self.empty():
# if there're actions to do
action = self.actions_to_do[0]
del self.actions_to_do[0]
return [action]
# print(state)
x, y, _ = state
fields = info['fields']
if np.sum(fields == 0) == 0:
return [FINISHED]
root_node = Node(x, y, None)
buffer = [root_node]
visited = np.zeros_like(fields)
visited[x, y] = True
while len(buffer) > 0:
cur_node = buffer[0]
del buffer[0]
for dx, dy in zip(self.delta_x, self.delta_y):
cur_x, cur_y = cur_node.x, cur_node.y
next_x, next_y = cur_x+dx, cur_y+dy
if self._is_valid_index((next_x, next_y), fields) and fields[next_x, next_y] >= 0 and not visited[next_x, next_y]:
visited[next_x, next_y] = True
next_node = Node(next_x, next_y, cur_node)
if fields[next_x, next_y] == 0:
# not visited
node = next_node
while node.direction is not None:
self.actions_to_do.append(node.direction)
node = node.father
self.actions_to_do.reverse()
action = self.actions_to_do[0]
del self.actions_to_do[0]
return [action]
else:
buffer.append(next_node)
raise RuntimeError(f'WildFire runtime error!')

37
Palette/constants.py Normal file
View File

@ -0,0 +1,37 @@
UP = 0
DOWN = 1
LEFT = 2
RIGHT = 3
FILLING_UP = 4
FILLING_DOWN = 5
FILLING_LEFT = 6
FILLING_RIGHT = 7
FILLING = 8
FILLING_BACK = 9
FINISHED = -1
BROKEN = -2
COLORS = (
(138, 43, 226),
(156, 102, 31),
(255, 64, 64),
(100, 0, 205),
(255, 97, 3),
(0, 255, 0),
(255, 20, 147),
(105, 105, 105),
(252, 230, 201),
(255, 215, 0),
(255, 106, 106),
(230, 230, 250),
(139, 137, 112),
(255, 160, 122),
(32, 178, 170),
(132, 112, 255),
(93, 71, 139),
(238, 64, 0),
(255, 192, 203),
(255, 100, 255),
)

6
Palette/env/__init__.py vendored Normal file
View File

@ -0,0 +1,6 @@
from .sea_env import make, SeaEnv
__all__ = [
'make',
'SeaEnv'
]

3
Palette/env/collide_types.py vendored Normal file
View File

@ -0,0 +1,3 @@
COLLISION_SHIP = 1
COLLISION_OBSTACLE = 2
COLLISION_DESTINATION = 3

562
Palette/env/engine.py vendored Normal file
View File

@ -0,0 +1,562 @@
import pymunk
import pymunk.pygame_util
import pygame
import os
import numpy as np
import math
from copy import deepcopy
from Palette.env.utils import process_angle, asincos
from typing import List, Tuple, Union
from pygame.color import THECOLORS
from pymunk.vec2d import Vec2d
from Palette.env.collide_types import COLLISION_DESTINATION, COLLISION_OBSTACLE, COLLISION_SHIP
from Palette.constants import UP, DOWN, LEFT, RIGHT, FINISHED, BROKEN, \
FILLING_LEFT, FILLING_RIGHT, FILLING_UP, FILLING_DOWN, FILLING, FILLING_BACK,\
COLORS
class SeaEnvEngine(object):
def __init__(
self,
window_width: int,
window_height: int,
field_size: Tuple[int],
ship_radius: int,
ship_velocity: int,
sonar_spread_times: int, # the number of sonar detection points
obstacles: dict,
show_sensors: bool = False,
draw_screen: bool = True,
show_fields: bool = True,
init_ships: bool = True,
start_position=None, # [start_x, start_y]
start_r=None,
time_click: float = 1.0/10.0,
filling_time: int = 1,
cover_ratio: float = 1.0,
actual_background: bool = False,
actual_around_area: int = 2,
*args,
**kwargs
) -> None:
super().__init__()
self.max_angle, self.min_angle = None, None
# basic params
self.crashed = False # whether or not crash into an obstacle or bound
self.finished = False
self.terminate = False
self.show_sensors = show_sensors
self.draw_screen = draw_screen
self.show_fields = show_fields
self.num_steps = 0
self.window_width = window_width
self.window_height = window_height
self.sonar_spread_times = sonar_spread_times
self.ship_radius = ship_radius
self.time_click = time_click
self.filling_time = filling_time
self.cover_ratio = cover_ratio
self.actual_background = actual_background
self.actual_around_area = actual_around_area
""" 1. init pygame """
if self.draw_screen:
pygame.init()
self.screen = pygame.display.set_mode(
(window_width, window_height))
self.clock = pygame.time.Clock()
self.screen.set_alpha(None)
pygame.display.set_caption('NODE Simulation')
self.draw_options = pymunk.pygame_util.DrawOptions(self.screen)
if not actual_background:
self.ss_img = pygame.image.load(
os.path.join("pics", "sea.jpg"))
else:
self.ss_img = pygame.image.load(
os.path.join("pics", 'sea.png'))
self.ss_img = pygame.transform.scale(
self.ss_img, (self.window_width, self.window_height))
self.ship_img = pygame.image.load(os.path.join("pics", "ship.png"))
self.ship_img = pygame.transform.scale(
self.ship_img, (self.ship_radius * 4, self.ship_radius * 4))
""" 2. init gymunk """
# physics stuff
self.space = pymunk.Space()
self.space.gravity = pymunk.Vec2d(0., 0.)
# create bounds
# self.create_bounds(window_height, window_width)
# create ship
self.finished_idx = None
self.init_ships = init_ships
self.battery_capacity = None
self._ship_batteries = None
self._ship_charging = None
self._ship_charging_positions = None
self._ship_charging_time_left = None
if self.init_ships:
self.create_ship(start_position=start_position,
start_r=start_r)
self.ship_velocity = ship_velocity
assert self.window_width % field_size[0] == 0 and self.window_height % field_size[1] == 0
self.field_size = field_size
self.fields = np.zeros(
shape=(self.window_width//field_size[0], self.window_height//field_size[1]))
# create obstacles
self.obstacles = []
self.obstacles_w_h = []
for obstacle_key in obstacles.keys():
x, y, width, height = obstacles[obstacle_key]
cur_obstacle = self.create_rectangle_obstacle(x, y, width, height)
self.obstacles.append(cur_obstacle)
self.obstacles_w_h.append((width, height))
# label the position of obstacles as -1
for i in range(x//self.field_size[0], (x+width)//self.field_size[0]):
for j in range(y//self.field_size[1], (y+height)//self.field_size[1]):
self.fields[i, self.fields.shape[1]-j-1] = -1
if self.actual_background:
for i in range(
max(0, x//self.field_size[0]-self.actual_around_area),
min((x+width)//self.field_size[0] + self.actual_around_area,
self.fields.shape[0])):
for j in range(
max(0, y//self.field_size[1]-self.actual_around_area),
min((y+height)//self.field_size[1]+self.actual_around_area,
self.fields.shape[1])):
if self.fields[i, self.fields.shape[1]-j-1] != -1:
self.fields[i, self.fields.shape[1]-j-1] = 1
"""
self.obstacles_radius = []
for _ in range(n_obstacles):
cur_radius = np.random.randint(
obstacle_radius_bound[0], obstacle_radius_bound[1])
cur_init_x = np.random.randint(cur_radius, window_width-cur_radius)
cur_init_y = np.random.randint(
cur_radius, window_height-cur_radius)
cur_obstacle = self.create_obstacle(
init_x=cur_init_x, init_y=cur_init_y, obstacle_radius=cur_radius)
self.obstacles.append(cur_obstacle)
self.obstacles_radius.append(cur_radius)
"""
self.setup_collision_handler()
@property
def splited_areas(self):
return deepcopy(self._splited_areas)
@splited_areas.setter
def splited_areas(self, splited_areas):
self._splited_areas = deepcopy(splited_areas)
@property
def ship_num(self):
return len(self.ship_body)
def setup_collision_handler(self):
def post_solve_ship_obstacle(arbiter, space, data):
self.crashed = True
self.space.add_collision_handler(
COLLISION_SHIP, COLLISION_OBSTACLE).post_solve = post_solve_ship_obstacle
def create_bounds(self, window_height: float, window_width: float):
static = [
pymunk.Segment(
self.space.static_body,
(0, self.window_height-1), (0, self.window_height-window_height), 1),
pymunk.Segment(
self.space.static_body,
(1, self.window_height-window_height), (window_width, self.window_height-window_height), 1),
pymunk.Segment(
self.space.static_body,
(window_width-1, self.window_height-window_height), (window_width-1, self.window_height-1), 1),
pymunk.Segment(
self.space.static_body,
(1, self.window_height-1), (window_width, self.window_height-1), 1)
]
for s in static:
s.friction = 1.
s.group = 1
# bound is the same as obstacle for collision
s.collision_type = COLLISION_OBSTACLE
s.color = THECOLORS['blue']
self.space.add(*static)
def create_ship(self, start_position, start_r: float, battery_capacity: int):
self.ship_body, self.ship_shape = [], []
for init_xy, init_r in zip(start_position, start_r):
init_x, init_y = init_xy
inertia = pymunk.moment_for_circle(
mass=1, inner_radius=0, outer_radius=self.ship_radius, offset=(0, 0))
self.ship_body.append(pymunk.Body(1, inertia))
self.ship_body[-1].position = init_x, self.window_height - init_y
self.ship_shape.append(pymunk.Circle(
body=self.ship_body[-1], radius=self.ship_radius, offset=(0, 0)))
self.ship_shape[-1].color = THECOLORS["green"]
self.ship_shape[-1].elasticity = 1.0
self.ship_body[-1].angle = init_r
self.ship_shape[-1].collision_type = COLLISION_SHIP
self.space.add(self.ship_body[-1], self.ship_shape[-1])
self.init_ships = True
self.finished_idx = [False for _ in range(self.ship_num)]
self.battery_capacity = battery_capacity
self._ship_batteries = [
self.battery_capacity for _ in range(len(start_position))]
self._ship_charging = [False for _ in range(self.ship_num)]
self._ship_charging_positions = [[] for _ in range(self.ship_num)]
self._ship_charging_time_left = [0 for _ in range(self.ship_num)]
def create_rectangle_obstacle(self, x, y, width, height):
# points = [(0, 0), (0, height), (width, height), (width, 0)]
points = [(-width/2, -height/2), (-width/2, height/2),
(width/2, height/2), (width/2, -height/2)]
# points = [(-width, -height), (-width, 0), (0, 0), (0, -height)]
mass = 9999999999
moment = pymunk.moment_for_poly(mass, points, (0, 0))
obstacle_body = pymunk.Body(mass=mass, moment=moment)
obstacle_body.position = (x+width/2, self.window_height-y-height/2)
obstacle_shape = pymunk.Poly(obstacle_body, points)
obstacle_shape.color = THECOLORS["blue"]
obstacle_shape.collision_type = COLLISION_OBSTACLE
self.space.add(obstacle_body, obstacle_shape)
return obstacle_body
def _filling_get_last_position(self, ship_id: int):
if self._ship_charging[ship_id]:
last_pos = self._ship_charging_positions[ship_id][-1]
else:
last_pos = self.ship_body[ship_id].position
return last_pos
def get_battery(self, ship_id: int):
return self._ship_batteries[ship_id]
def frame_step(self, actions: List[int]):
"""frame update for one step
Args:
action (int): the next direction of the ship
0: up
1: down
2: left
3: right
"""
assert self.init_ships, 'Have not initialized ships!'
if self.terminate:
raise AssertionError("Cannot continue running after crash!")
for ship_id, action in enumerate(actions):
if action == UP: # up
self._ship_charging[ship_id] = False
self._ship_charging_time_left[ship_id] = 0
self.ship_body[ship_id].angle = 1.5*np.pi
elif action == DOWN: # down
self._ship_charging[ship_id] = False
self._ship_charging_time_left[ship_id] = 0
self.ship_body[ship_id].angle = 0.5*np.pi
elif action == LEFT: # left
self._ship_charging[ship_id] = False
self._ship_charging_time_left[ship_id] = 0
self.ship_body[ship_id].angle = np.pi
elif action == RIGHT: # right
self._ship_charging[ship_id] = False
self._ship_charging_time_left[ship_id] = 0
self.ship_body[ship_id].angle = 0.0
elif action == FINISHED:
self._ship_charging[ship_id] = False
self._ship_charging_time_left[ship_id] = 0
driving_direction = Vec2d(1, 0).rotated(
self.ship_body[ship_id].angle)
self.ship_body[ship_id].velocity = 0.0 * driving_direction
if not self.finished_idx[ship_id]:
self.space.remove(self.ship_body[ship_id])
self.finished_idx[ship_id] = True
elif action == FILLING_UP:
last_pos = self._filling_get_last_position(ship_id)
self._ship_charging_positions[ship_id].append(
(last_pos[0], last_pos[1]-self.ship_velocity*self.time_click))
self._ship_charging[ship_id] = True
self._ship_charging_time_left[ship_id] = self.filling_time
recent_x, recent_y = self._ship_charging_positions[ship_id][-1]
rx, ry = int(
recent_x//self.field_size[0]), int(recent_y//self.field_size[1])
if self.fields[rx, ry] >= 0:
self.fields[rx, ry] += 1
elif action == FILLING_DOWN:
last_pos = self._filling_get_last_position(ship_id)
self._ship_charging_positions[ship_id].append(
(last_pos[0], last_pos[1]+self.ship_velocity*self.time_click))
self._ship_charging[ship_id] = True
self._ship_charging_time_left[ship_id] = self.filling_time
recent_x, recent_y = self._ship_charging_positions[ship_id][-1]
rx, ry = int(
recent_x//self.field_size[0]), int(recent_y//self.field_size[1])
if self.fields[rx, ry] >= 0:
self.fields[rx, ry] += 1
elif action == FILLING_LEFT:
last_pos = self._filling_get_last_position(ship_id)
self._ship_charging_positions[ship_id].append(
(last_pos[0]-self.ship_velocity*self.time_click, last_pos[1]))
self._ship_charging[ship_id] = True
self._ship_charging_time_left[ship_id] = self.filling_time
recent_x, recent_y = self._ship_charging_positions[ship_id][-1]
rx, ry = int(
recent_x//self.field_size[0]), int(recent_y//self.field_size[1])
if self.fields[rx, ry] >= 0:
self.fields[rx, ry] += 1
elif action == FILLING_RIGHT:
last_pos = self._filling_get_last_position(ship_id)
self._ship_charging_positions[ship_id].append(
(last_pos[0]+self.ship_velocity*self.time_click, last_pos[1]))
self._ship_charging[ship_id] = True
self._ship_charging_time_left[ship_id] = self.filling_time
recent_x, recent_y = self._ship_charging_positions[ship_id][-1]
rx, ry = int(
recent_x//self.field_size[0]), int(recent_y//self.field_size[1])
if self.fields[rx, ry] >= 0:
self.fields[rx, ry] += 1
elif action == FILLING_BACK:
assert self._ship_charging[ship_id] == True
assert self._ship_charging_time_left[ship_id] == 0
self._ship_charging_positions[ship_id].pop()
if len(self._ship_charging_positions[ship_id]):
recent_x, recent_y = self._ship_charging_positions[ship_id][-1]
rx, ry = int(
recent_x//self.field_size[0]), int(recent_y//self.field_size[1])
if self.fields[rx, ry] >= 0:
self.fields[rx, ry] += 1
elif action == FILLING:
assert self._ship_charging[ship_id] == True
self._ship_charging_time_left[ship_id] -= 1
if self._ship_charging_time_left[ship_id] == 0:
self._ship_batteries[ship_id] = self.battery_capacity
else:
raise ValueError(f"invalid action: {action}")
self.ship_body[ship_id].angle = process_angle(
self.ship_body[ship_id].angle)
# calculate velocity with direction
driving_direction = Vec2d(1, 0).rotated(
self.ship_body[ship_id].angle)
if not self._ship_charging[ship_id]:
self.ship_body[ship_id].velocity = self.ship_velocity * \
driving_direction
else:
self.ship_body[ship_id].velocity = 0.0 * driving_direction
# Update the screen and stuff.
if self.draw_screen:
# self.screen.fill(THECOLORS["black"])
self.screen.blit(self.ss_img, (0, 0))
# draw(self.screen, self.space)
if self.show_fields:
for i in range(self.fields.shape[0]):
for j in range(self.fields.shape[1]):
if self.fields[i, j] > 0:
pos_x, pos_y = self.field_size[0] * i,\
self.field_size[1] * j
# the size of your rect
s = pygame.Surface(self.field_size)
# alpha level
s.set_alpha(int(96 * self.fields[i, j]))
# this fills the entire surface
color = COLORS[int(self._splited_areas[i, j] - 1)]
s.fill(color)
# (0,0) are the top-left coordinates
self.screen.blit(s, (pos_x, pos_y))
elif self.fields[i, j] == -1:
pos_x, pos_y = self.field_size[0] * i,\
self.field_size[1] * j
# the size of your rect
s = pygame.Surface(self.field_size)
# alpha level
# this fills the entire surface
if self.actual_background:
color = (0, 0, 0)
s.set_alpha(100)
else:
color = (0, 0, 255)
s.set_alpha(255)
s.fill(color)
# (0,0) are the top-left coordinates
self.screen.blit(s, (pos_x, pos_y))
for i in range(self.ship_num):
self.screen.blit(
self.ship_img, (self.ship_body[i].position[0]-2.0*self.ship_radius, self.ship_body[i].position[1]-2.0*self.ship_radius))
# draw charging trajectory
for ship_id in range(self.ship_num):
if self._ship_charging[ship_id]:
for cx, cy in self._ship_charging_positions[ship_id]:
pygame.draw.circle(
self.screen, (255, 255, 255), (cx, cy), 2)
pygame.display.flip()
self.clock.tick()
self.space.step(self.time_click)
for i in range(self.ship_num):
if not self.finished_idx[i]:
if actions[i] != FILLING:
self._ship_batteries[i] -= 1
if self._ship_batteries[i] < 0:
self.crashed = True
if self.crashed:
print('crashed!!!')
self.terminate = True
self.num_steps += 1
return self.current()
def current(self):
state = None
for ship_id in range(len(self.ship_body)):
x, y = self.ship_body[ship_id].position
if self.finished_idx[ship_id]:
direction = UP
elif self.ship_body[ship_id].angle == 1.5 * np.pi:
direction = UP
elif self.ship_body[ship_id].angle == 0.0:
direction = RIGHT
elif self.ship_body[ship_id].angle == 0.5 * np.pi:
direction = DOWN
elif self.ship_body[ship_id].angle == np.pi:
direction = LEFT
else:
raise ValueError(
f'invalid angle: {self.ship_body[ship_id].angle}')
cur_state = np.asarray([
int(x//self.field_size[0]),
int(y//self.field_size[1]),
direction])[None]
if state is None:
state = cur_state
else:
state = np.concatenate([state, cur_state], axis=0)
if not self.crashed and not self.finished_idx[ship_id] and not self._ship_charging[ship_id]:
cx, cy = int(x//self.field_size[0]), int(y//self.field_size[1])
if self.fields[cx, cy] >= 0 and 0 <= x < self.window_width \
and 0 <= y < self.window_height:
self.fields[cx, cy] += 1
else:
print(f'crash')
self.crashed = True
if np.sum(self.fields > 0) / np.sum(self.fields >= 0) >= self.cover_ratio:
self.finished = True
print('finished!!!')
self.terminate = True
info = {'fields': self.fields, 'finished': self.finished}
return state, self.terminate, info
def get_rotated_point(self, x_1, y_1, x_2, y_2, radians):
# Rotate x_2, y_2 around x_1, y_1 by angle.
x_change = (x_2 - x_1) * math.cos(radians) + \
(y_2 - y_1) * math.sin(radians)
y_change = (y_1 - y_2) * math.cos(radians) - \
(x_1 - x_2) * math.sin(radians)
new_x = x_change + x_1
# new_y = self.window_height - (y_change + y_1)
new_y = y_change + y_1
return int(new_x), int(new_y)
def get_sonar_readings(self, x, y, angle):
"""
Instead of using a grid of boolean(ish) sensors, sonar readings
simply return N "distance" readings, one for each sonar
we're simulating. The distance is a count of the first non-zero
reading starting at the object. For instance, if the fifth sensor
in a sonar "arm" is non-zero, then that arm returns a distance of 5.
"""
# Make our arms.
arm_left = self.make_sonar_arm(x, y)
# arm_middle = arm_left
# arm_right = arm_left
# Rotate them and get readings.
readings = []
for shift_angle in np.arange(-0.75, 0.75, 0.05):
readings.append(self.get_arm_distance(
arm_left, x, y, angle, shift_angle))
# print(len(readings))
# readings.append(self.get_arm_distance(arm_left, x, y, angle, 0.75))
# readings.append(self.get_arm_distance(arm_middle, x, y, angle, 0))
# readings.append(self.get_arm_distance(arm_right, x, y, angle, -0.75))
if self.draw_screen and self.show_sensors:
pygame.display.update()
return readings
def make_sonar_arm(self, x, y):
spread = 10 # Default spread.
distance = 10 # Gap before first sensor.
arm_points = []
# Make an arm. We build it flat because we'll rotate it about the
# center later.
for i in range(1, self.sonar_spread_times):
arm_points.append((distance + x + (spread * i), y))
return arm_points
def get_arm_distance(self, arm, x, y, angle, offset) -> int:
# Used to count the distance.
i = 0
# Look at each point and see if we've hit something.
for point in arm:
i += 1
# Move the point to the right spot.
rotated_p = self.get_rotated_point(
x, y, point[0], point[1], angle + offset
)
# Check if we've hit something. Return the current i (distance)
# if we did.
if rotated_p[0] <= 0 or rotated_p[1] <= 0 \
or rotated_p[0] >= self.window_width or rotated_p[1] >= self.window_height:
return i # Sensor is off the screen.
else:
for obstacle, obstacle_w_h in zip(self.obstacles, self.obstacles_w_h):
obstacle_position = obstacle.position
if 0 <= rotated_p[0] - obstacle_position[0] <= obstacle_w_h[0] \
and 0 <= rotated_p[1] - obstacle_position[1] <= obstacle_w_h[1]:
return i
if self.draw_screen and self.show_sensors:
pygame.draw.circle(
self.screen, (255, 255, 255), (rotated_p), 2)
# Return the distance for the arm.
return i

164
Palette/env/sea_env.py vendored Normal file
View File

@ -0,0 +1,164 @@
import os
import yaml
import numpy as np
from argparse import ArgumentParser
from copy import deepcopy
from easydict import EasyDict
from typing import List
from Palette.algos.multi_split_area import multi_split_area
from Palette.env.engine import SeaEnvEngine
from Palette.constants import UP, DOWN, LEFT, RIGHT, FINISHED, BROKEN
class SeaEnv(object):
def __init__(
self,
cfg: EasyDict,
draw: bool,
n_ships: int,
battery_capacity: int,
split_axis: str = 'x',
filling_time: int = 1,
cover_ratio: float = 1.0
) -> None:
super().__init__()
self.draw = draw
self.cfg = cfg
self.split_axis = split_axis
if "max_step" in self.cfg:
self.max_step = self.cfg["max_step"]
else:
self.max_step = None
self.cur_step = 0
self.init_ships = False
self.n_ships = n_ships
self.battery_capacity = battery_capacity
self.filling_time = filling_time
self.cover_ratio = cover_ratio
@property
def ship_num(self):
return self.n_ships
@property
def fields(self):
return deepcopy(self.sea_env_engine.fields)
@property
def splited_areas(self):
return self.sea_env_engine.splited_areas
def step(self, action: List[int]):
cur_frame, done, info = self.sea_env_engine.frame_step(action)
self.cur_step += 1
if self.max_step is not None and self.cur_step >= self.max_step:
done = True
return cur_frame, done, info
def reset(self):
done = True
self.cur_step = 0
while done:
self.sea_env_engine = SeaEnvEngine(
draw_screen=self.draw,
init_ships=self.init_ships,
filling_time=self.filling_time,
cover_ratio=self.cover_ratio,
**self.cfg
)
self.sea_env_engine.splited_areas, start_points = multi_split_area(
fields=self.sea_env_engine.fields,
n_areas=self.n_ships,
axis=self.split_axis,
field_size=self.cfg.field_size[0],
)
self.sea_env_engine.create_ship(
start_position=start_points,
start_r=[1.0*np.pi for _ in range(self.n_ships)],
battery_capacity=self.battery_capacity
)
state, done, info = self.sea_env_engine.current()
return state, info
def seed(self, seed: int = None) -> List[int]:
np.random.seed(seed)
return [seed]
def render(self, mode='human'):
pass
def close(self):
pass
def create_ships(self, start_position, start_r):
self.sea_env_engine.create_ship(
start_position=start_position, start_r=start_r)
def get_battery(self, ship_id: int):
return self.sea_env_engine.get_battery(ship_id)
def make(
env_config: str,
render: bool,
split_axis: str,
n_ships: int,
battery_capacity: int = np.inf,
filling_time: int = 1,
cover_ratio: float = 1.0
):
"""
if not render:
os.environ["SDL_VIDEODRIVER"] = "dummy"
else:
os.environ["DISPLAY"] = os.popen(
'printenv grep DISPLAY').read().strip()
"""
with open(os.path.join('Palette', 'maps', f'{env_config}.yaml'), 'r') as f:
config = yaml.safe_load(f)
config = EasyDict(config)
env_config = config.env
env = SeaEnv(cfg=env_config, draw=render,
split_axis=split_axis, n_ships=n_ships,
battery_capacity=battery_capacity, filling_time=filling_time,
cover_ratio=cover_ratio)
return env
if __name__ == '__main__':
def get_args():
parser = ArgumentParser()
parser.add_argument('-c', "--config_name", type=str, required=True,
help="The name of config file, e.g., map0")
parser.add_argument('-a', '--axis', type=str,
default='x', help="The axis for splitting areas.")
parser.add_argument('--render', action='store_true',
help='render or not')
return parser.parse_args()
args = get_args()
sea_env = make(env_config=args.config_name,
render=args.render, split_axis=args.axis)
import random
steps = 0
acts = []
acts.extend([[UP, DOWN] for _ in range(49)])
acts.extend([[RIGHT, LEFT] for _ in range(5)])
acts.extend([[LEFT, RIGHT] for _ in range(5)])
acts.extend([[DOWN, UP] for _ in range(20)])
acts.extend([[RIGHT, LEFT] for _ in range(200)])
while True:
s, t, _ = sea_env.step(acts[steps])
for _ in range(99999999999):
pass
steps += 1
print(s)
if t:
break
# sea_env.reset()
print(steps)

29
Palette/env/utils.py vendored Normal file
View File

@ -0,0 +1,29 @@
import numpy as np
import random
from math import acos, asin
def process_angle(_angle):
ret = _angle
if ret < 0:
ret += (abs(ret) // (2*np.pi) + 1) * 2*np.pi
if ret >= 2 * np.pi:
ret -= (abs(ret) // (2*np.pi)) * 2*np.pi
assert 0 <= ret < 2 * np.pi, f'invalid angle: {ret}'
assert abs(np.sin(ret) - np.sin(_angle)
) < 1e-5 and (np.cos(ret) - np.cos(_angle)) < 1e-5
return ret
def asincos(sin_value, cos_value):
if sin_value >= 0:
# 第一象限和第二象限
return acos(cos_value)
else:
if cos_value >= 0:
# 第四象限
return asin(sin_value)
else:
# 第三象限
return 2*np.pi - acos(cos_value)

View File

@ -0,0 +1,90 @@
import numpy as np
from argparse import ArgumentParser
from copy import deepcopy
from Palette.env import make, SeaEnv
from Palette.constants import UP, DOWN, LEFT, RIGHT, FINISHED, BROKEN
from Palette.algos import MultiGreedy, MultiWildFire, MultiCharge, multi_split_area
def calculate_repeat_steps(fields):
return int(np.sum(fields[fields > 0]) - np.sum(fields > 0))
def generate_agent_fields(fields, ship_num, splited_areas):
agents_fields = np.stack([deepcopy(fields)
for _ in range(ship_num)], axis=0)
for ship_id in range(1, ship_num+1):
cur_fields = agents_fields[ship_id-1]
# print((splited_areas != ship_id) * (splited_areas != -1))
cur_fields[(splited_areas != ship_id) * (splited_areas != -1)] += 1
return agents_fields
def main(env: SeaEnv, agent: MultiGreedy):
state, info = env.reset()
redecide_direction = [True for _ in range(env.ship_num)]
steps = 0
while True:
steps += 1
info['redecide_direction'] = redecide_direction
agents_fields = generate_agent_fields(
info['fields'], env.ship_num, env.splited_areas)
agent_info = deepcopy(info)
agent_info['fields'] = agents_fields
agent_info['batteries'] = [env.get_battery(
ship_id) for ship_id in range(env.ship_num)]
acts, redecide_direction = agent.step(state, agent_info)
assert (acts != BROKEN).all(), f'Invalid: ships output BROKEN step.'
state, terminate, info = env.step(acts)
if terminate:
if info['finished']:
print(f'Congrates! covered all the places!')
repeat_steps = calculate_repeat_steps(info['fields'])
units_num = info['fields'].size
repeat_ratio = repeat_steps / units_num
print(
f'Used steps: {steps}\nrepeat steps: {repeat_steps}\nrepeat ratio: {repeat_steps}/{units_num}={repeat_ratio*100:.2f}%')
else:
print(f'Failed!')
return
def get_args():
parser = ArgumentParser()
parser.add_argument('-c', "--config_name", type=str, required=True,
help="The name of config file, e.g., map0")
parser.add_argument('-a', '--axis', type=str, default='x',
help='The axis for splitting areas')
parser.add_argument('-n', '--n-ships', type=int,
default=3, help='The number of ships.')
parser.add_argument('-b', '--battery-capacity', type=int, default=np.inf,
help='The num of steps a full battery can support.')
parser.add_argument('-f', '--filling-time', type=int, default=1,
help='The num of time steps needed for charging.')
parser.add_argument('--render', action='store_true',
help='render or not')
parser.add_argument('--cover-ratio', type=float, default=1.0)
return parser.parse_args()
if __name__ == '__main__':
args = get_args()
sea_env = make(env_config=args.config_name,
render=args.render,
split_axis=args.axis,
n_ships=args.n_ships,
filling_time=args.filling_time,
battery_capacity=args.battery_capacity,
cover_ratio=args.cover_ratio)
wildfires = MultiWildFire(n_ships=sea_env.ship_num)
charges = MultiCharge(n_ships=args.n_ships, filling_time=args.filling_time)
algo = MultiGreedy(
ship_num=sea_env.ship_num, wildfires=wildfires, charges=charges)
main(sea_env, algo)
for _ in range(int(1e8)):
continue

View File

@ -0,0 +1,91 @@
import numpy as np
from argparse import ArgumentParser
from copy import deepcopy
from Palette.env import make, SeaEnv
from Palette.constants import UP, DOWN, LEFT, RIGHT, FINISHED, BROKEN
from Palette.algos import MultiGreedy, MultiWildFire, MultiCharge, multi_split_area
def calculate_repeat_steps(fields):
return int(np.sum(fields[fields > 0]) - np.sum(fields > 0))
def generate_agent_fields(fields, ship_num, splited_areas):
agents_fields = np.stack([deepcopy(fields)
for _ in range(ship_num)], axis=0)
for ship_id in range(1, ship_num+1):
cur_fields = agents_fields[ship_id-1]
# print((splited_areas != ship_id) * (splited_areas != -1))
cur_fields[(splited_areas != ship_id) * (splited_areas != -1)] += 1
return agents_fields
def main(env: SeaEnv, agent: MultiGreedy):
state, info = env.reset()
redecide_direction = [True for _ in range(env.ship_num)]
steps = 0
while True:
steps += 1
info['redecide_direction'] = redecide_direction
# print(np.sum(info['fields'] == 0))
agents_fields = generate_agent_fields(
info['fields'], env.ship_num, env.splited_areas)
agent_info = deepcopy(info)
agent_info['fields'] = agents_fields
agent_info['batteries'] = [env.get_battery(
ship_id) for ship_id in range(env.ship_num)]
acts, redecide_direction = agent.step(state, agent_info)
assert (acts != BROKEN).all(), f'Invalid: ships output BROKEN step.'
state, terminate, info = env.step(acts)
if terminate:
if info['finished']:
print(f'Congrates! covered all the places!')
repeat_steps = calculate_repeat_steps(info['fields'])
units_num = info['fields'].size
repeat_ratio = repeat_steps / units_num
print(
f'Used steps: {steps}\nrepeat steps: {repeat_steps}\nrepeat ratio: {repeat_steps}/{units_num}={repeat_ratio*100:.2f}%')
else:
print(f'Failed!')
return
def get_args():
parser = ArgumentParser()
parser.add_argument('-c', "--config_name", type=str, required=True,
help="The name of config file, e.g., map0")
parser.add_argument('-a', '--axis', type=str, default='x',
help='The axis for splitting areas')
parser.add_argument('-n', '--n-ships', type=int,
default=3, help='The number of ships.')
parser.add_argument('-b', '--battery-capacity', type=int, default=np.inf,
help='The num of steps a full battery can support.')
parser.add_argument('-f', '--filling-time', type=int, default=1,
help='The num of time steps needed for charging.')
parser.add_argument('--render', action='store_true',
help='render or not')
parser.add_argument('--cover-ratio', type=float, default=1.0)
return parser.parse_args()
if __name__ == '__main__':
args = get_args()
sea_env = make(env_config=args.config_name,
render=args.render,
split_axis=args.axis,
n_ships=args.n_ships,
filling_time=args.filling_time,
battery_capacity=args.battery_capacity,
cover_ratio=args.cover_ratio)
wildfires = MultiWildFire(n_ships=sea_env.ship_num)
charges = MultiCharge(n_ships=args.n_ships, filling_time=args.filling_time)
algo = MultiGreedy(
ship_num=sea_env.ship_num, wildfires=wildfires, charges=charges)
main(sea_env, algo)
for _ in range(int(1e8)):
continue

View File

@ -0,0 +1,90 @@
import numpy as np
from argparse import ArgumentParser
from copy import deepcopy
from Palette.env import make, SeaEnv
from Palette.constants import UP, DOWN, LEFT, RIGHT, FINISHED, BROKEN
from Palette.algos import MultiSpiral, MultiWildFire, MultiCharge, multi_split_area
def calculate_repeat_steps(fields):
return int(np.sum(fields[fields > 0]) - np.sum(fields > 0))
def generate_agent_fields(fields, ship_num, splited_areas):
agents_fields = np.stack([deepcopy(fields)
for _ in range(ship_num)], axis=0)
for ship_id in range(1, ship_num+1):
cur_fields = agents_fields[ship_id-1]
# print((splited_areas != ship_id) * (splited_areas != -1))
cur_fields[(splited_areas != ship_id) * (splited_areas != -1)] += 1
return agents_fields
def main(env: SeaEnv, agent: MultiSpiral):
state, info = env.reset()
redecide_direction = [True for _ in range(env.ship_num)]
steps = 0
while True:
steps += 1
info['redecide_direction'] = redecide_direction
agents_fields = generate_agent_fields(
info['fields'], env.ship_num, env.splited_areas)
agent_info = deepcopy(info)
agent_info['fields'] = agents_fields
agent_info['batteries'] = [env.get_battery(
ship_id) for ship_id in range(env.ship_num)]
acts, redecide_direction = agent.step(state, agent_info)
assert (acts != BROKEN).all(), f'Invalid: ships output BROKEN step.'
state, terminate, info = env.step(acts)
if terminate:
if info['finished']:
print(f'Congrates! covered all the places!')
repeat_steps = calculate_repeat_steps(info['fields'])
units_num = info['fields'].size
repeat_ratio = repeat_steps / units_num
print(
f'Used steps: {steps}\nrepeat steps: {repeat_steps}\nrepeat ratio: {repeat_steps}/{units_num}={repeat_ratio*100:.2f}%')
else:
print(f'Failed!')
return
def get_args():
parser = ArgumentParser()
parser.add_argument('-c', "--config_name", type=str, required=True,
help="The name of config file, e.g., map0")
parser.add_argument('-a', '--axis', type=str, default='x',
help='The axis for splitting areas')
parser.add_argument('-n', '--n-ships', type=int,
default=3, help='The number of ships.')
parser.add_argument('-b', '--battery-capacity', type=int, default=np.inf,
help='The num of steps a full battery can support.')
parser.add_argument('-f', '--filling-time', type=int, default=1,
help='The num of time steps needed for charging.')
parser.add_argument('--render', action='store_true',
help='render or not')
parser.add_argument('--cover-ratio', type=float, default=1.0)
return parser.parse_args()
if __name__ == '__main__':
args = get_args()
sea_env = make(env_config=args.config_name,
render=args.render,
split_axis=args.axis,
n_ships=args.n_ships,
filling_time=args.filling_time,
battery_capacity=args.battery_capacity,
cover_ratio=args.cover_ratio)
wildfires = MultiWildFire(n_ships=sea_env.ship_num)
charges = MultiCharge(n_ships=args.n_ships, filling_time=args.filling_time)
algo = MultiSpiral(
ship_num=sea_env.ship_num, wildfires=wildfires, charges=charges)
main(sea_env, algo)
for _ in range(int(1e8)):
continue

View File

@ -0,0 +1,45 @@
import numpy as np
from argparse import ArgumentParser
from Palette.env import make, SeaEnv
from Palette.constants import UP, DOWN, LEFT, RIGHT, FINISHED, BROKEN
from Palette.algos import MultiGreedy
def calculate_repeat_steps(fields):
assert np.sum(fields == 0) == 0, f'Not finish!'
return int(np.sum(fields[fields > 0]) - np.sum(fields > 0))
def main(env: SeaEnv, agent: MultiGreedy):
state, info = env.reset()
info['redecide_direction'] = [False for _ in range(env.ship_num())]
while True:
act = agent.step(state, info)
print(f'act: {act}')
if act == FINISHED:
print(f'Congrates! Covered all the places!')
break
elif act == BROKEN:
print('Broken!')
state, terminate, info = env.step(act)
info['redecide_direction'] = [False for _ in range(env.ship_num())]
if terminate:
print('Terminate!')
break
def get_args():
parser = ArgumentParser()
parser.add_argument('-c', "--config_name", type=str, default='multi_map0',
help="The name of config file, e.g., multi_map0")
parser.add_argument('--render', action='store_true',
help='render or not')
return parser.parse_args()
if __name__ == '__main__':
args = get_args()
sea_env = make(env_config=args.config_name, render=args.render)
algo = MultiGreedy(ship_num=sea_env.ship_num())
main(sea_env, algo)

View File

@ -0,0 +1,75 @@
import numpy as np
from argparse import ArgumentParser
from copy import deepcopy
from Palette.env import make, SeaEnv
from Palette.constants import UP, DOWN, LEFT, RIGHT, FINISHED, BROKEN
from Palette.algos import MultiGreedy, MultiWildFire, multi_split_area
def calculate_repeat_steps(fields):
assert np.sum(fields == 0) == 0, f'Not finish!'
return int(np.sum(fields[fields > 0]) - np.sum(fields > 0))
def generate_agent_fields(fields, ship_num, splited_areas):
agents_fields = np.stack([deepcopy(fields)
for _ in range(ship_num)], axis=0)
for ship_id in range(1, ship_num+1):
cur_fields = agents_fields[ship_id-1]
# print((splited_areas != ship_id) * (splited_areas != -1))
cur_fields[(splited_areas != ship_id) * (splited_areas != -1)] += 1
return agents_fields
def main(env: SeaEnv, agent: MultiGreedy):
state, info = env.reset()
redecide_direction = [True for _ in range(env.ship_num)]
steps = 0
while True:
steps += 1
info['redecide_direction'] = redecide_direction
agents_fields = generate_agent_fields(
info['fields'], env.ship_num, env.splited_areas)
agent_info = deepcopy(info)
agent_info['fields'] = agents_fields
acts, redecide_direction = agent.step(state, agent_info)
assert (acts != BROKEN).all(), f'Invalid: ships output BROKEN step.'
state, terminate, info = env.step(acts)
if terminate:
if info['finished']:
print(f'Congrates! covered all the places!')
repeat_steps = calculate_repeat_steps(info['fields'])
units_num = info['fields'].size
repeat_ratio = repeat_steps / units_num
print(
f'Used steps: {steps}\nrepeat steps: {repeat_steps}\nrepeat ratio: {repeat_steps}/{units_num}={repeat_ratio*100:.2f}%')
else:
print(f'Failed!')
return
def get_args():
parser = ArgumentParser()
parser.add_argument('-c', "--config_name", type=str, required=True,
help="The name of config file, e.g., map0")
parser.add_argument('-a', '--axis', type=str, default='x',
help='The axis for splitting areas')
parser.add_argument('-n', '--n-ships', type=int,
default=3, help='The number of ships.')
parser.add_argument('--render', action='store_true',
help='render or not')
return parser.parse_args()
if __name__ == '__main__':
args = get_args()
sea_env = make(env_config=args.config_name,
render=args.render,
split_axis=args.axis,
n_ships=args.n_ships)
wildfires = MultiWildFire(n_ships=sea_env.ship_num)
algo = MultiGreedy(ship_num=sea_env.ship_num, wildfires=wildfires)
main(sea_env, algo)

View File

@ -0,0 +1,37 @@
from argparse import ArgumentParser
from Palette.env import make, SeaEnv
from Palette.constants import UP, DOWN, LEFT, RIGHT, FINISHED, BROKEN
from Palette.algos import Boustrophedon
def main(env: SeaEnv, agent: Boustrophedon):
state, info = env.reset()
while True:
act = agent.step(state, info)
if act == FINISHED:
print(f'Congrates! Covered all the places!')
break
elif act == BROKEN:
print(f'Broken!')
break
state, terminate, info = env.step(act)
if terminate:
print(f'Terminate!')
break
def get_args():
parser = ArgumentParser()
parser.add_argument('-c', "--config_name", type=str, required=True,
help="The name of config file, e.g., map0")
parser.add_argument('--render', action='store_true',
help='render or not')
return parser.parse_args()
if __name__ == '__main__':
args = get_args()
sea_env = make(env_config=args.config_name, render=args.render)
algo = Boustrophedon()
main(sea_env, algo)

89
Palette/examples/split.py Normal file
View File

@ -0,0 +1,89 @@
import numpy as np
from argparse import ArgumentParser
from copy import deepcopy
from Palette.env import make, SeaEnv
from Palette.constants import UP, DOWN, LEFT, RIGHT, FINISHED, BROKEN
from Palette.algos import split_area, Boustrophedon, Spiral, WildFire
from typing import Union
def calculate_repeat_steps(fields):
assert np.sum(fields == 0) == 0, f'Not finish!'
return int(np.sum(fields[fields > 0]) - np.sum(fields > 0))
def main(env: SeaEnv, agent: Union[Boustrophedon, Spiral], wildfire: WildFire):
state, info = env.reset()
fields_to_split = deepcopy(info['fields'])
fields_to_split[fields_to_split > 0] = 0
splited_area = split_area(fields_to_split)
n_areas = int(np.max(splited_area))
for area_id in range(1, n_areas+1):
redecide_direction = True
cur_area_terminate = False
while True:
# for _ in range(9999999):
# continue
info['redecide_direction'] = redecide_direction
modified_fields = deepcopy(info['fields'])
modified_fields += ((splited_area != area_id)
* (splited_area != -1))
info_for_agent = deepcopy(info)
info_for_agent['fields'] = modified_fields
act = agent.step(state, info_for_agent)
redecide_direction = False
if act == FINISHED:
cur_area_terminate = True
break
elif act == BROKEN:
print(f'Broken!')
while True:
act = wildfire.step(state, info_for_agent)
state, cur_area_terminate, info = env.step(act)
if cur_area_terminate:
break
if wildfire.empty():
redecide_direction = True
break
else:
state, terminate, info = env.step(act)
if terminate:
if info['finished']:
print(f'Congrates! covered all the places!')
repeat_steps = calculate_repeat_steps(info['fields'])
units_num = info['fields'].size
repeat_ratio = repeat_steps / units_num
print(
f'repeat steps: {repeat_steps}\nrepeat ratio: {repeat_steps}/{units_num}={repeat_ratio*100:.2f}%')
else:
print(f'Failed!')
return
if cur_area_terminate:
break
def get_args():
parser = ArgumentParser()
parser.add_argument('-c', "--config_name", type=str, required=True,
help="The name of config file, e.g., map0")
parser.add_argument('-a', '--algo', type=str, choices=[
'b', 's'], default='b', help='The algorithm is Boustrophedon or Spiral.')
parser.add_argument('--render', action='store_true',
help='render or not')
return parser.parse_args()
if __name__ == '__main__':
args = get_args()
sea_env = make(env_config=args.config_name, render=args.render)
if args.algo == 'b':
algo = Boustrophedon()
elif args.algo == 's':
algo = Spiral()
else:
raise ValueError(f'invalid args.algo: {args.algo}')
wildfire = WildFire()
main(sea_env, algo, wildfire)

View File

@ -0,0 +1,77 @@
import numpy as np
from argparse import ArgumentParser
from Palette.env import make, SeaEnv
from Palette.constants import UP, DOWN, LEFT, RIGHT, FINISHED, BROKEN
from Palette.algos import Boustrophedon, WildFire
def calculate_repeat_steps(fields):
return int(np.sum(fields[fields > 0]) - np.sum(fields > 0))
def main(env: SeaEnv, agent: Boustrophedon, wildfire: WildFire):
state, info = env.reset()
redecide_direction = True
all_steps = 0
while True:
all_steps += 1
state = state[0]
info['redecide_direction'] = redecide_direction
act = agent.step(state, info)
redecide_direction = False
if act[0] == FINISHED:
print(f'Congrates! covered all the places!')
repeat_steps = calculate_repeat_steps(info['fields'])
print(f'{repeat_steps} steps repeated!')
return
elif act[0] == BROKEN:
print(f'Broken!')
while True:
all_steps += 1
act = wildfire.step(state, info)
state, terminate, info = env.step(act)
if terminate:
return
if wildfire.empty():
redecide_direction = True
break
all_steps -= 1
else:
state, terminate, info = env.step(act)
if terminate:
if info['finished']:
print(f'Congrates! covered all the places!')
print(f'steps: {all_steps}')
repeat_steps = calculate_repeat_steps(info['fields'])
units_num = info['fields'].size
repeat_ratio = repeat_steps / units_num
print(
f'repeat steps: {repeat_steps}\nrepeat ratio: {repeat_steps}/{units_num}={repeat_ratio*100:.2f}%')
else:
print(f'Failed!')
return
def get_args():
parser = ArgumentParser()
parser.add_argument('-c', "--config_name", type=str, required=True,
help="The name of config file, e.g., map0")
parser.add_argument('--render', action='store_true',
help='render or not')
parser.add_argument('--cover-ratio', type=float, default=1.0)
return parser.parse_args()
if __name__ == '__main__':
args = get_args()
sea_env = make(env_config=args.config_name,
render=args.render, n_ships=1, split_axis='x',
cover_ratio=args.cover_ratio)
algo = Boustrophedon()
wildfire = WildFire()
main(sea_env, algo, wildfire)
for _ in range(int(1e8)):
continue

View File

@ -0,0 +1,77 @@
import numpy as np
from argparse import ArgumentParser
from Palette.env import make, SeaEnv
from Palette.constants import UP, DOWN, LEFT, RIGHT, FINISHED, BROKEN
from Palette.algos import Spiral, WildFire
def calculate_repeat_steps(fields):
return int(np.sum(fields[fields > 0]) - np.sum(fields > 0))
def main(env: SeaEnv, agent: Spiral, wildfire: WildFire):
state, info = env.reset()
redecide_direction = True
all_steps = 0
while True:
all_steps += 1
info['redecide_direction'] = redecide_direction
act = agent.step(state, info)
redecide_direction = False
if act[0] == FINISHED:
print(f'Congrates! covered all the places!')
repeat_steps = calculate_repeat_steps(info['fields'])
print(f'{repeat_steps} steps repeated!')
break
elif act[0] == BROKEN:
print(f'Broken!')
while True:
all_steps += 1
act = wildfire.step(state[0], info)
state, terminate, info = env.step(act)
all_steps += 1
if terminate:
return
if wildfire.empty():
redecide_direction = True
break
all_steps -= 1
else:
state, terminate, info = env.step(act)
if terminate:
if info['finished']:
print(f'Congrates! covered all the places!')
print(f'steps: {all_steps}')
repeat_steps = calculate_repeat_steps(info['fields'])
units_num = info['fields'].size
repeat_ratio = repeat_steps / units_num
print(
f'repeat steps: {repeat_steps}\nrepeat ratio: {repeat_steps}/{units_num}={repeat_ratio*100:.2f}%')
else:
print(f'Failed!')
return
def get_args():
parser = ArgumentParser()
parser.add_argument('-c', "--config_name", type=str, required=True,
help="The name of config file, e.g., map0")
parser.add_argument('--render', action='store_true',
help='render or not')
parser.add_argument('--cover-ratio', type=float, default=1.0)
return parser.parse_args()
if __name__ == '__main__':
args = get_args()
sea_env = make(env_config=args.config_name,
render=args.render, n_ships=1, split_axis='x',
cover_ratio=args.cover_ratio)
algo = Spiral()
wildfire = WildFire()
main(sea_env, algo, wildfire)
for _ in range(int(1e8)):
continue

14
Palette/maps/map0.yaml Normal file
View File

@ -0,0 +1,14 @@
map_name: map0
env:
window_width: !!int 1000
window_height: !!int 1000
field_size: [20, 20]
start_position: [10, 10]
ship_radius: !!int 5
ship_velocity: 200
sonar_spread_times: !!int 40
obstacles: # name: [x of the left bottom, y of the left bottom, width, height]
obstacle0: [100, 100, 100, 200]
obstacle1: [300, 160, 120, 120]
obstacle2: [460, 460, 120, 120]
obstacle3: [700, 700, 160, 160]

15
Palette/maps/map1.yaml Normal file
View File

@ -0,0 +1,15 @@
map_name: map1
env:
window_width: !!int 1000
window_height: !!int 1000
field_size: [20, 20]
start_position: [10, 10]
ship_radius: !!int 5
ship_velocity: 200
sonar_spread_times: !!int 40
obstacles: # name: [x of the left bottom, y of the left bottom, width, height]
obstacle0: [100, 600, 100, 200]
obstacle1: [300, 360, 120, 120]
obstacle2: [660, 460, 120, 120]
obstacle3: [400, 700, 160, 160]
obstacle4: [460, 460, 200, 40]

17
Palette/maps/map2.yaml Normal file
View File

@ -0,0 +1,17 @@
map_name: map2
env:
window_width: !!int 1000
window_height: !!int 1000
field_size: [20, 20]
start_position: [10, 10]
ship_radius: !!int 5
ship_velocity: 200
sonar_spread_times: !!int 40
obstacles: # name: [x of the left bottom, y of the left bottom, width, height]
obstacle0: [100, 600, 100, 200]
obstacle1: [300, 260, 120, 320]
obstacle2: [660, 460, 120, 120]
obstacle3: [400, 700, 160, 160]
obstacle4: [420, 260, 200, 80]
obstacle5: [420, 500, 200, 80]
obstacle6: [580, 380, 40, 120]

View File

@ -0,0 +1,22 @@
map_name: map_actual
env:
window_width: !!int 1300
window_height: !!int 1200
field_size: [4, 4]
start_position: [2, 2]
ship_radius: !!int 1
ship_velocity: 40
sonar_spread_times: !!int 40
obstacles: # name: [x of the left bottom, y of the left bottom, width, height]
obstacle0: [40, 440, 340, 220]
obstacle1: [100, 160, 300, 180]
obstacle2: [300, 40, 100, 60]
obstacle3: [500, 340, 160, 80]
obstacle4: [980, 20, 180, 80]
obstacle5: [860, 300, 260, 160]
obstacle6: [1160, 440, 60, 40]
obstacle7: [740, 980, 180, 120]
obstacle8: [0, 1140, 140, 60]
obstacle9: [1240, 700, 60, 60]
actual_background: !!bool true
actual_around_area: !!int 0

View File

@ -0,0 +1,22 @@
map_name: map_actual_show
env:
window_width: !!int 1300
window_height: !!int 1200
field_size: [20, 20]
start_position: [10, 10]
ship_radius: !!int 5
ship_velocity: 200
sonar_spread_times: !!int 40
obstacles: # name: [x of the left bottom, y of the left bottom, width, height]
obstacle0: [40, 440, 340, 220]
obstacle1: [100, 160, 300, 180]
obstacle2: [300, 40, 100, 60]
obstacle3: [500, 340, 160, 80]
obstacle4: [980, 20, 180, 80]
obstacle5: [860, 300, 260, 160]
obstacle6: [1160, 440, 60, 40]
obstacle7: [740, 980, 180, 120]
obstacle8: [0, 1140, 140, 60]
obstacle9: [1240, 700, 60, 60]
actual_background: !!bool true
actual_around_area: !!int 0

View File

@ -0,0 +1,22 @@
map_name: map_actual_with_around
env:
window_width: !!int 1300
window_height: !!int 1200
field_size: [4, 4]
start_position: [2, 2]
ship_radius: !!int 1
ship_velocity: 40
sonar_spread_times: !!int 40
obstacles: # name: [x of the left bottom, y of the left bottom, width, height]
obstacle0: [40, 440, 340, 220]
obstacle1: [100, 160, 300, 180]
obstacle2: [300, 40, 100, 60]
obstacle3: [500, 340, 160, 80]
obstacle4: [980, 20, 180, 80]
obstacle5: [860, 300, 260, 160]
obstacle6: [1160, 440, 60, 40]
obstacle7: [740, 980, 180, 120]
obstacle8: [0, 1140, 140, 60]
obstacle9: [1240, 700, 60, 60]
actual_background: !!bool true
actual_around_area: !!int 50

View File

@ -0,0 +1,22 @@
map_name: map_actual_with_around
env:
window_width: !!int 1300
window_height: !!int 1200
field_size: [20, 20]
start_position: [10, 10]
ship_radius: !!int 5
ship_velocity: 200
sonar_spread_times: !!int 40
obstacles: # name: [x of the left bottom, y of the left bottom, width, height]
obstacle0: [40, 440, 340, 220]
obstacle1: [100, 160, 300, 180]
obstacle2: [300, 40, 100, 60]
obstacle3: [500, 340, 160, 80]
obstacle4: [980, 20, 180, 80]
obstacle5: [860, 300, 260, 160]
obstacle6: [1160, 440, 60, 40]
obstacle7: [740, 980, 180, 120]
obstacle8: [0, 1140, 140, 60]
obstacle9: [1240, 700, 60, 60]
actual_background: !!bool true
actual_around_area: !!int 5

View File

@ -0,0 +1,19 @@
map_name: multi_map0
env:
window_width: !!int 1000
window_height: !!int 1000
field_size: [20, 20]
#start_position:
# ship0: [10, 10]
# ship1: [990, 990]
#start_r:
# ship0: !!float 0.0 # pi
# ship1: !!float 1.0 # pi
ship_radius: !!int 5
ship_velocity: 200
sonar_spread_times: !!int 40
obstacles: # name: [x of the left bottom, y of the left bottom, width, height]
obstacle0: [100, 100, 100, 200]
obstacle1: [300, 160, 120, 120]
obstacle2: [460, 460, 120, 120]
obstacle3: [700, 700, 160, 160]

View File

@ -1,2 +1,44 @@
# SeAIPalette
# SeAI Palette集智调色板
## 0. 软件介绍
SeAI Palette集智调色板是面向集群网络的多节点智能协同路径规划软件。软件以面向对象的设计理念采用Python语言编程基于pyqt、pygame、pyyaml、pymunk、easydict以及ppdet等技术开发内置3个虚拟地图和多种算法牛耕法、内螺旋法、贪心法等持续更新并提供扩展接口支持地图和算法自定义。软件安装简单运行方便可选参数丰富扩展性高非常适用于相关研究领域的工程技术人员和学生掌握学习集群智能规划方法。
集智调色板软件设计了参数输入模块、算法运行模块及信息输出模块,在不同节点数量要求的前提下划分区域方向,按区域进行算法的运行。并可以综合考虑固定节点、覆盖率、电池容量等条件下,给出运行步数、重复步数和重复率等等性能指标。
软件界面简单,易学已用,包含参数的输入选择,程序的运行,算法结果的展示等,源代码公开,算法可修改。
开发人员:王凯、于化鹏、李晶、王兆琦、李慧涛、赵志允、张乐飞、陈光
## 1. 开发环境配置
运行以下命令:
```bash
conda env create -f create_env.yaml
```
该命令会创建一个名为`Palette`的conda虚拟环境用`conda activate Palette`即可激活该虚拟环境。
## 2. 软件运行
运行以下命令运行软件:
```python
python main_tt.py
```
## 3. 一些说明
1. 程序输出的说明
程序运行结束后会在命令行输出类似于下面的结果:
```
finished!!!
Congrates! covered all the places!
Used steps: 79
repeat steps: 187
repeat ratio: 187/3900=4.79%
```
分别为使用的步数,重复的步数和重复率。
2. 关于渲染结果的说明
渲染中不同移动节点负责的区域用不同颜色标记,每个区域颜色越深表示该区域被重复走的次数越多。
为了能够让人看清最后遍历的结果,我们在程序最后加了一个循环(空循环$10^8$次)以防止渲染结果立刻消失。
此外,渲染结果最后会留一格小区域没有覆盖,这个是渲染结果滞后仿真程序内核一个时间单位导致的,不会对实际测试结果造成影响。

1
SeAIPalette Submodule

@ -0,0 +1 @@
Subproject commit 82e80a5b3b1884cacc479d9918a1ff2b0ca61faa

View File

@ -0,0 +1,5 @@
pygame==2.0.1
pymunk==6.0.0
pyyaml==5.4.1
numpy==1.20.3
easydict==1.9

11
create_env.yaml Normal file
View File

@ -0,0 +1,11 @@
name: Palette
dependencies:
- python=3.7.3
- anaconda
- pip
- pip:
- pygame==2.0.1
- pymunk==6.0.0
- pyyaml==5.4.1
- numpy==1.20.3
- easydict==1.9

11
create_env.yaml.bak Normal file
View File

@ -0,0 +1,11 @@
name: Palette
dependencies:
- python=3.7.6
- anaconda
- pip
- pip:
- pygame==2.0.1
- pymunk==6.0.0
- pyyaml==5.4.1
- numpy==1.20.3
- easydict==1.9

127
main_tt.py Normal file
View File

@ -0,0 +1,127 @@
import sys, os
from MainWindow_map import Ui_MainWindow
from PyQt5 import QtWidgets, QtCore, QtSql, QtGui
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import QPixmap
import threading, time, subprocess
class mywindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self):
super(mywindow, self).__init__()
self.setupUi(self)
self.comboBox_map.addItems(['地图0', '地图1', '地图2'])
self.comboBox_algo.addItems(['野火法+牛耕法', '野火法+内螺旋法', '野火法+贪心法'])
self.comboBox_axis.addItems(['x', 'y'])
self.comboBox_render.addItems(['', ''])
self.comboBox_around.addItems(['不考虑', '考虑'])
self.comboBox_bettery.addItems(['', ''])
if self.comboBox_bettery.currentText() == '':
self.lineEdit_bettery.setEnabled(False)
def slot_bettery(self):
if self.comboBox_bettery.currentText() == '':
self.lineEdit_bettery.setEnabled(False)
if self.comboBox_bettery.currentText() == '':
self.lineEdit_bettery.setEnabled(True)
def thread_run(self):
if self.comboBox_bettery.currentText() == '':
if self.ships_num == '1':
if self.comboBox_algo.currentText() == '野火法+牛耕法':
cmd = 'python -u -m Palette.examples.wildfire_boustrophedon -c '+self.map+self.render+'--cover-ratio '+self.cover_ratio
result = subprocess.Popen(
cmd, shell=True,stdout=subprocess.PIPE).stdout
self.label_show.setText(str(result.read(),encoding="utf-8"))
elif self.comboBox_algo.currentText() == '野火法+内螺旋法':
cmd = 'python -u -m Palette.examples.wildfire_spiral -c ' + self.map + self.render + '--cover-ratio ' + self.cover_ratio
result = subprocess.Popen(
cmd, shell=True, stdout=subprocess.PIPE).stdout
self.label_show.setText(str(result.read(), encoding="utf-8"))
elif self.comboBox_algo.currentText() == '野火法+贪心法':
cmd = 'python -u -m Palette.examples.charge_multi_split_wildfire_greedy -c ' + self.map + self.render + '--cover-ratio ' + self.cover_ratio + ' -n 1'
result = subprocess.Popen(
cmd, shell=True, stdout=subprocess.PIPE).stdout
self.label_show.setText(str(result.read(), encoding="utf-8"))
elif self.ships_num != '1':
if self.comboBox_algo.currentText() == '野火法+牛耕法':
cmd = 'python -u -m Palette.examples.charge_multi_split_wildfire_boustrophedon -c '+self.map+self.render+'--cover-ratio '+self.cover_ratio+' -n '+str(self.ships_num)+' -a '+self.axis
result = subprocess.Popen(
cmd, shell=True,stdout=subprocess.PIPE).stdout
self.label_show.setText(str(result.read(),encoding="utf-8"))
elif self.comboBox_algo.currentText() == '野火法+内螺旋法':
cmd = 'python -u -m Palette.examples.charge_multi_split_wildfire_spiral -c ' + self.map + self.render + '--cover-ratio ' + self.cover_ratio+' -n '+str(self.ships_num)+' -a '+self.axis
result = subprocess.Popen(
cmd, shell=True, stdout=subprocess.PIPE).stdout
self.label_show.setText(str(result.read(), encoding="utf-8"))
elif self.comboBox_algo.currentText() == '野火法+贪心法':
cmd = 'python -u -m Palette.examples.charge_multi_split_wildfire_greedy -c ' + self.map + self.render + '--cover-ratio ' + self.cover_ratio+' -n '+str(self.ships_num)+' -a '+self.axis
result = subprocess.Popen(
cmd, shell=True, stdout=subprocess.PIPE).stdout
self.label_show.setText(str(result.read(), encoding="utf-8"))
elif self.comboBox_bettery.currentText() == '':
if self.ships_num != '1':
if self.comboBox_algo.currentText() == '野火法+牛耕法':
cmd = 'python -u -m Palette.examples.charge_multi_split_wildfire_boustrophedon -c '+self.map+self.render+'--cover-ratio '+self.cover_ratio+' -n '+str(self.ships_num)+' -a '+self.axis+' -b '+self.lineEdit_bettery.text()
result = subprocess.Popen(
cmd, shell=True,stdout=subprocess.PIPE).stdout
self.label_show.setText(str(result.read(),encoding="utf-8"))
elif self.comboBox_algo.currentText() == '野火法+内螺旋法':
cmd = 'python -u -m Palette.examples.charge_multi_split_wildfire_spiral -c ' + self.map + self.render + '--cover-ratio ' + self.cover_ratio+' -n '+str(self.ships_num)+' -a '+self.axis+' -b '+self.lineEdit_bettery.text()
result = subprocess.Popen(
cmd, shell=True, stdout=subprocess.PIPE).stdout
self.label_show.setText(str(result.read(), encoding="utf-8"))
elif self.comboBox_algo.currentText() == '野火法+贪心法':
cmd = 'python -u -m Palette.examples.charge_multi_split_wildfire_greedy -c ' + self.map + self.render + '--cover-ratio ' + self.cover_ratio+' -n '+str(self.ships_num)+' -a '+self.axis+' -b '+self.lineEdit_bettery.text()
result = subprocess.Popen(
cmd, shell=True, stdout=subprocess.PIPE).stdout
self.label_show.setText(str(result.read(), encoding="utf-8"))
elif self.ships_num == '1':
self.label_show.setText('请设置多移动节点')
def run(self):
self.cover_ratio = str(float(self.lineEdit_coverr.text()) / 100.0)
self.ships_num = self.lineEdit_shipn.text()
self.axis = self.comboBox_axis.currentText()
if self.comboBox_map.currentText() == '地图0':
self.map = 'map0'
elif self.comboBox_map.currentText() == '地图1':
self.map = 'map1'
elif self.comboBox_map.currentText() == '地图2':
self.map = 'map2'
elif self.comboBox_map.currentText() == '自定义地图':
if self.comboBox_around.currentText() == '不考虑':
self.map = 'map_actual_show'
elif self.comboBox_around.currentText() == '考虑':
self.map = 'map_actual_with_around_show'
if self.comboBox_render.currentText() == '':
self.render = ' --render '
elif self.comboBox_render.currentText() == '':
self.render = ' '
threading.Thread(target=self.thread_run).start()
self.label_show.setText('程序正在运行,请稍后!')
def stop(self):
print('2')
if __name__ == '__main__':
app = QApplication(sys.argv)
window = mywindow()
# window.showMaximized()
window.show()
sys.exit(app.exec_())

BIN
pics/sea.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
pics/ship.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 936 KiB