Допустим, у нас есть эта волшебная рыба, которая может убрать из океана углекислый газ.
Это означает, что мы можем предотвратить закисление океана.

Для этого нам сначала нужно выяснить две вещи,

  • Как улавливать CO2 в океане
  • Как узнать, где нам нужно сделать захват

Мы знаем, что в воде есть CO2, что делает ее более кислой. Но концентрация CO2 не везде одинакова. Поэтому поиск мест с высокой концентрацией CO2 так же важен, как и сам захват.

Сегодня мы создадим симуляцию подводного робота, чтобы максимально эффективно путешествовать по морю и улавливать CO2!

Фон

Окисление океана

Представьте, что у вас есть стакан воды с несколькими каплями лимонного сока. Когда вы попробуете воду, она будет кислой из-за кислоты в лимонном соке.

Теперь давайте подумаем об океане. Океан огромен и покрывает большую часть поверхности Земли. Здесь обитают самые разные растения и животные, в том числе коралловые рифы, рыбы и многие другие организмы.

Точно так же, как лимонный сок сделал воду кислой, нечто подобное происходит и с океаном. Океан становится более кислым, и это называется подкислением океана. Но откуда берется эта дополнительная кислота? Одной из основных причин является увеличение количества углекислого газа (CO2) в атмосфере.

Видите ли, когда мы сжигаем ископаемое топливо, такое как уголь, нефть и газ, для получения энергии, мы выбрасываем в воздух много углекислого газа. Часть этого углекислого газа поглощается океаном. Океан поглощает одну треть выбросов углерода. Обычно океан может справиться с некоторыми из них, но слишком много углекислого газа вызывает проблему.

Когда углекислый газ растворяется в воде, образуется слабая кислота, называемая угольной кислотой. Эта кислота снижает уровень pH океана. Шкала pH — это способ измерить, насколько кислым или щелочным является что-либо. Более низкий pH означает большую кислотность, точно так же, как лимонный сок имеет более низкий pH, чем вода.

Почему это проблема? Ну, многие морские организмы, такие как моллюски, коралловые рифы и крошечные существа, называемые планктоном, полагаются на определенный уровень pH, чтобы выжить и построить свои раковины или скелеты. Когда океан становится более кислым, этим организмам становится все труднее создавать и поддерживать свои раковины. Их панцири могут становиться слабее и ломче, что делает их более уязвимыми для хищников и других угроз.

Последствия закисления океана могут распространяться по всей пищевой цепочке. Например, если популяция планктона сокращается из-за того, что он не может строить прочные раковины, это может повлиять на рыбу, которая питается им. Это может иметь эффект домино для других животных и, в конечном итоге, повлиять на людей, которые зависят от океана в плане еды и средств к существованию.

Поэтому нам необходимо сократить выбросы углекислого газа, защитить океан и найти способы адаптироваться к этим изменениям, чтобы сохранить наши океаны здоровыми в будущем.

Улавливание углерода

Ученые изучают несколько методов улавливания углерода из океана. Один из них называется прямой захват воздуха (DAC). DAC предполагает использование специальных машин или конструкций, которые могут извлекать CO2 непосредственно из воздуха. Эти машины используют химические процессы для улавливания CO2 и хранения его для последующего использования или постоянного хранения.

Другой метод называется улучшенное выветривание. Этот метод включает в себя измельчение определенных типов пород и распространение их по поверхности океана или добавление их в прибрежные районы. Эти камни естественным образом реагируют с углекислым газом в воде, помогая нейтрализовать кислотность и сохранять углерод в безвредной форме.

Кроме того, некоторые ученые экспериментируют с созданием искусственных сооружений в океане, таких как большие контейнеры или устройства, которые могут поглощать CO2 непосредственно из воды. Эти структуры предназначены для привлечения CO2 и его улавливания, предотвращая его дальнейшее подкисление океана.

Важно отметить, что ученые все еще работают над созданием способа эффективного и недорогого улавливания углерода из океана.

Наш робот (он же Волшебная рыбка)

Как он может улавливать CO2

Наш робот будет искусственным сооружением в океане, которое сможет притягивать CO2 и улавливать его.

Для этого предлагаются разные способы. Вот что привлекло мое внимание.

Команда Массачусетского технологического института разработала многообещающий метод эффективного и недорогого удаления двуокиси углерода (CO2) непосредственно из океанской воды.

Их процесс включает использование безмембранных электрохимических ячеек с реактивными электродами. Клетки выделяют протоны в морскую воду, в результате чего растворенный углекислый газ выделяется в виде газа. Этот процесс цикличен: один набор ячеек подкисляет воду, чтобы преобразовать бикарбонаты в CO2, а другой набор клеток изменяет напряжение, чтобы восстановить протоны и снова превратить воду в щелочную, прежде чем снова закачать ее в море. Это потенциально может помочь противодействовать закислению океана, вызванному накоплением CO2.

Затем захваченный углекислый газ можно утилизировать, закопав его в глубокие геологические образования под морским дном или превратив в соединения, такие как этанол, для транспортного топлива или специальных химикатов. Исследователи предлагают объединить эту систему с существующей инфраструктурой, такой как опреснительные установки, чтобы сделать ее масштабируемой и рентабельной. Корабли и морские платформы также могут использовать эту технологию для компенсации выбросов. По сравнению с системами улавливания воздуха этот процесс более эффективен, поскольку морская вода имеет более высокую концентрацию CO2, что упрощает этап улавливания.

Есть еще одно исследование, которое может подойти для этой работы.

Исследователи из Калифорнийского университета в Лос-Анджелесе разработали технологию удаления углекислого газа, вдохновленную образованием морских раковин. Процесс включает в себя втягивание морской воды в машину, пропускание ее через сетку, которая придает воде электрический заряд, и запуск химических реакций, которые соединяют растворенный CO2 с кальцием и магнием в воде. Это создает известняк и магнезит, похожие на измельченные морские ракушки. Эти материалы могут быть утилизированы на суше или сброшены обратно в океан. Морская вода также может стекать обратно в океан, поглощая при этом больше CO2.

По сравнению с другими технологиями удаления углерода этот процесс имеет преимущества. Морская вода естественным образом поглощает CO2 в высокой концентрации, что делает ее концентрированной средой для работы без необходимости значительных затрат энергии. В результате это может быть дешевле, чем методы прямого захвата воздуха. Кроме того, эта технология постоянно изолирует CO2 в форме минералов, обеспечивая долгосрочное хранение без риска утечки или дополнительных шагов.

Как он может найти свой путь наиболее эффективно

Итак, теперь, когда у нас есть робот, который может удалять CO2, нам нужно иметь возможность отправлять его в места с высокой концентрацией CO2.

Вот почему мы создаем имитационную модель на Python.

Вы знаете, что такое первый шаг?

Вы правильно угадали.

Мы находим набор данных с концентрациями CO2 в разных районах наших океанов.

Мы будем использовать GLODAP.

Глобальный проект анализа данных об океане (GLODAP) представляет собой деятельность по синтезу биогеохимических данных от поверхности до дна океана, собранных посредством химического анализа проб воды. GLODAP позволяет проводить количественную оценку стока углерода в океане, закисления океана и оценки биогеохимических моделей океана.

Вы можете выбрать, на какой океан вы хотите смотреть. (Или даже пойти со всей планетой.)

Я поеду с Северным Ледовитым океаном. (потому что я люблю белых медведей :))

Хорошо, давайте сделаем шаг назад и импортируем нужные нам библиотеки.

Базовая карта. Базовая карта — отличный инструмент для простого создания карт с помощью Python. Это расширение matplotlib, поэтому оно обладает всеми функциями для создания визуализации данных и добавления географических проекций и некоторых наборов данных, позволяющих отображать береговые линии, страны и т. д. прямо из библиотеки.

Enum: эта библиотека используется для создания перечислений или набора уникальных констант. Мы используем его, чтобы указать тип робота: круг или квадрат. Это определяет, как робот представлен в анимации и как он движется.

imageio: эта библиотека предоставляет простой интерфейс для чтения и записи широкого спектра данных изображений.

IPython: мы используем его для отображения gif-анимации робота.

itertools: эта библиотека используется для создания итераторов для эффективного цикла. Мы используем его для рисования карты.

math: эта библиотека используется для выполнения математических функций. Мы используем его для расчетов, связанных с навигацией робота с использованием подхода динамического окна.

matplotlib: эта библиотека используется для построения графиков.

mpl_toolkits: эта библиотека используется как расширение возможностей matplotlib.

numpy: эта библиотека используется повсеместно в большинстве проектов по науке о данных. Он предоставляет полезную структуру данных и массивы.

os: эта библиотека предоставляет функции для взаимодействия с операционной системой.

pandas: эта библиотека используется для обработки и анализа данных.

Pillow(PIL): эта библиотека предназначена для обработки изображений, как и imageio.

random: эта библиотека используется для генерации псевдослучайных чисел.

scikit-learn(sklearn): эта библиотека используется для машинного обучения, так как она имеет различные алгоритмы классификации, регрессии и кластеризации. Мы используем его для выполнения кластеризации K-средних для создания наборов образцов для моделирования.

conda install basemap
%matplotlib inline
from enum import Enum
import imageio
from IPython.display import HTML
from itertools import chain
import math
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
import numpy as np
import os
import pandas as pd
from PIL import Image
from random import *
from sklearn.cluster import KMeans
from statistics import mean

После импорта библиотек нам нужно выполнить обработку данных.

Мы создаем карту Северного Ледовитого океана с концентрациями CO2, соответствующими широте и долготе.

def draw_map(m, scale=1.5):
    #draw a shaded-relief image
    m.shadedrelief(scale=scale)
    
    #latitudes and longitudes are returned as a dictionary
    lats = m.drawparallels(np.linspace(-90, 90, 13))
    lons = m.drawmeridians(np.linspace(-180, 180, 13))
    
    #keys contain the plt.Line2D instances
    lat_lines = chain(*(tup[1][0] for tup in lats.items()))
    lon_lines = chain(*(tup[1][0] for tup in lons.items()))
    all_lines = chain(lat_lines, lon_lines)
    
    #cycle through these lines and set the desired style
    for line in all_lines:
        line.set(linestyle='-', alpha=0.3, color='w')
        
        
data=pd.read_csv('GLODAP Arctic Ocean Dataset')
data

z_list=list(data["G2phtsinsitutp"])
zeta = list(data["G2tco2"]) # co2 concentration data
x_lis=list(data["G2longitude"])
y_lis=list(data["G2latitude"])
input_data={'G2phtsinsituetp':z_list,'G2tco2':zeta,'G2longitude':x_lis,'G2latitude':y_lis}

Затем мы выполняем кластеризацию K-средних.

Что такое кластеризация K-Means и зачем она нам нужна?

K-Means — это алгоритм машинного обучения без учителя.

Кластеризация K-средних — это способ автоматической группировки похожих объектов вместе. Это работает следующим образом:

Во-первых, вы решаете, сколько групп вы хотите создать. Допустим, вы хотите создать три группы: одну для яблок, одну для апельсинов и одну для бананов.

Затем вы случайным образом выбираете три объекта из своей коллекции в качестве начальных представителей каждой группы. Этими объектами могут быть любые фрукты, в данном случае яблоко, апельсин и банан.

Теперь вы начинаете сравнивать каждый фрукт с этими представителями. Вы смотрите на такие вещи, как их цвет, форма и размер, чтобы решить, на какого представителя они больше всего похожи.

На основе сравнений вы относите каждый фрукт к той группе представителей, на которую он больше всего похож . Итак, если фрукт больше похож на яблоко, вы помещаете его в группу яблок.

После того, как вы распределили все фрукты по группам, вы пересчитываете представителей для каждой группы. Вы находите средние характеристики всех фруктов в каждой группе и соответствующим образом обновляете представителей. Например, если у вас есть пять яблок в группе яблок, вы вычисляете средний цвет, форму и размер этих яблок, чтобы получить новое репрезентативное яблоко.

Теперь вы возвращаетесь и повторяете процесс. Вы сравниваете каждый фрукт с обновленными представителями и относите их к наиболее похожей группе. Затем вы снова пересчитываете представителей. Вы продолжаете повторять эти шаги до тех пор, пока группы не перестанут сильно меняться, а представители не стабилизируются.

В итоге вы получите три группы фруктов: одну для яблок, одну для апельсинов и одну для бананов. Фрукты в каждой группе похожи друг на друга на основе характеристик, которые вы использовали для сравнения.

Мы используем кластеризацию K-средних, потому что хотим сгруппировать места с высокой концентрацией CO2.

В кластеризации K-средних «k» означает количество групп или кластеров, которые вы хотите создать. Он представляет заранее определенное количество групп, которые вы хотите найти в своих данных. Итак, когда мы говорим «k-средних», «k» указывает, сколько кластеров мы хотим идентифицировать.

Например, если вы установите k равным 3, это означает, что вы хотите найти в своих данных три отдельные группы или кластеры. Затем алгоритм будет работать, чтобы сгруппировать данные в эти три кластера на основе их сходства.

Метод локтя – это метод, используемый для определения оптимального количества кластеров (k) в кластеризации методом k-средних.

Первоначально вы выполняете кластеризацию k-средних в своем наборе данных для диапазона значений k, обычно начиная с 1 и постепенно увеличивая (в нашем случае 15259, потому что это размер набора данных). Для каждого k вы вычисляете сумму квадратов внутри кластера (WCSS), которая является мерой того, насколько разбросаны точки данных в каждом кластере.

data=''
for i in range(0,15259):
    data += str(str(input_data['G2longitude'][i])+" "+str(input_data['G2latitude'][i])+"; ")
unused=np.array(np.mat(data[:-2]))
tco2 = np.array(input_data['G2tco2'])
model = KMeans(n_clusters=280)
model.fit(unused)

model.cluster_centers_
label=np.array(model.labels_)
sse = []
for K in range(100, 1000, 100):
    model = KMeans(n_clusters=K)
    model.fit(unused)
    
    
    
       
    sse.append(model.inertia_)


plt.plot(range(100, 1000, 100), sse)

Затем вы наносите значения k на ось x и соответствующий WCSS на ось y. По мере увеличения количества кластеров (k) WCSS обычно уменьшается, поскольку большее количество кластеров позволяет лучше подгонять данные. Однако в какой-то момент скорость снижения WCSS имеет тенденцию к замедлению.

Метод локтя предлагает искать точку на графике, где скорость снижения WCSS значительно замедляется, образуя заметный изгиб или локоть. Эта точка указывает на хороший баланс между захватом структуры данных с соответствующим количеством кластеров.

Найдя наше значение k методом локтя, мы смотрим на наши кластеры на карте океана.

fig = plt.figure(figsize=(8, 6), edgecolor='w')
m = Basemap(width=15.e6,height=15.e6,projection='gnom', lon_0=-30., lat_0=60)
u_labels = np.unique(label)
m.drawmapboundary(fill_color='#76a6cc')
m.fillcontinents(color='white',lake_color='#76a6cc')
m.drawcoastlines()

#plotting the results:
for i in u_labels:
    x, y = m(unused[label == i , 0] , unused[label == i , 1])
    m.scatter(x, y , label = i)
plt.show()

Затем из наших 280кластеров мы выбираем случайный. Мы также смотрим на него на карте.

set = randint(0,280)
fig = plt.figure(figsize=(8, 6), edgecolor='w')
m = Basemap(width=15.e6,height=15.e6,projection='gnom', lon_0=-30, lat_0=60)
m.drawmapboundary(fill_color='#76a6cc')
m.fillcontinents(color='white',lake_color='#76a6cc')
m.drawcoastlines()
x, y = m(unused[label==set, 0], unused[label==set, 1])
m.scatter(x, y, marker='o', color="r")


plt.savefig('plot.png')
plt.show()

Теперь пришло время собрать нашего робота.

Помните библиотеку Enum, которая помогала нам выбирать форму нашего робота?

show_animation = True
class RobotType(Enum):
    circle = 0 # we have a circle robot!
    rectangle = 1

Хорошо, теперь нам нужно понять, как наш робот будет находить свой маршрут.

Как он будет держаться подальше от препятствий?

Насколько это будет эффективно?

Ответ: используя динамический оконный подход!

Метод динамического окна (DWA) — это метод, используемый в робототехнике для планирования движения и обхода препятствий. Он специально разработан для мобильных роботов, перемещающихся в динамичной среде, где присутствуют препятствия или другие движущиеся объекты.

Прямо как океан, да?

У робота есть «окно» допустимых скоростей и угловых скоростей, из которых он может выбирать для своего следующего движения. Это окно определяется физическими ограничениями робота, такими как максимальная скорость и возможности поворота.

Робот постоянно воспринимает окружающую среду с помощью таких датчиков, как камеры, лидары или датчики приближения. Он собирает информацию о препятствиях, их положении и скорости.

На основе текущего состояния робота и полученной информации метод динамического окна рассчитывает достижимые скорости и угловые скорости, которые робот может безопасно выполнять, не сталкиваясь с препятствиями.

Динамическое окно определяется с учетом текущей скорости робота, максимально допустимых скоростей и ограничений, налагаемых обнаруженными препятствиями. Он определяет подмножество скоростей в пределах физических ограничений робота, которые не приводят к столкновениям.

Функция оценки применяется к каждой скорости в динамическом окне. Функция оценивает желательность каждой скорости по различным критериям, таким как близость к препятствиям, близость к цели и плавность траектории.

Скорость с наивысшим баллом выбирается в качестве следующего движения робота. Этот процесс повторяется на каждом временном шаге, что позволяет роботу динамически регулировать свою скорость и направление в зависимости от меняющейся среды.

Ниже мы устанавливаем некоторые параметры для конфигураций нашего робота.

class Config:
def __init__(self):
        # robot parameter
        self.max_speed = 2.0 # [m/s]
        self.min_speed = -2.0  # [m/s]
        self.max_yaw_rate = 40.0 * math.pi / 180.0  # [rad/s]
        self.max_accel = 0.2  # [m/ss]
        self.max_delta_yaw_rate = 40.0 * math.pi / 180.0  # [rad/ss]
        self.v_resolution = 0.01  # [m/s]
        self.yaw_rate_resolution = 0.1 * math.pi / 180.0  # [rad/s]
        self.dt = 0.1  # [s] Time tick for motion prediction
        self.predict_time = 3.0  # [s]
        self.to_goal_cost_gain = 0.15
        self.speed_cost_gain = 1.0
        self.obstacle_cost_gain = 1.0
        self.robot_stuck_flag_cons = 0.05  # constant to prevent the robot from getting stuck
        self.robot_type = RobotType.circle
        # if robot_type == RobotType.circle
        # Also used to check if goal is reached in both types
        self.robot_radius = 10.0  # [m] for collision check
        # if robot_type == RobotType.rectangle
        self.robot_width = 0.5  # [m] for collision check
        self.robot_length = 1.2  # [m] for collision check
        # obstacles [x(m) y(m), ....]
        self.ob = np.column_stack((np.random.randint(-180,high=180, size=50), np.random.randint(-90,high=90, size=50)))

    @property
    def robot_type(self):
        return self._robot_type
    @robot_type.setter
    def robot_type(self, value):
        if not isinstance(value, RobotType):
            raise TypeError("robot_type must be an instance of RobotType")
        self._robot_type = value

config = Config()
def dwa_control(x, config, goal, ob):
  dw = calc_dynamic_window(x, config) 
      u, trajectory = calc_control_and_trajectory(x, dw, config, goal, ob)
      return u, trajectory

Эта функция рассчитывает динамическое окно для робота и определяет управляющие воздействия (u) и траекторию на основе текущего состояния (x), конфигурации, положения цели (goal) и препятствий (ob).

dw = calc_dynamic_window(x, config): Эта строка вызывает функцию calc_dynamic_window с x и config в качестве аргументов для вычисления динамического окна. Динамическое окно представляет диапазон возможных скоростей и ускорений, из которых может выбирать робот.

u, trajectory = calc_control_and_trajectory(x, dw, config, goal, ob): Эта строка вызывает функцию calc_control_and_trajectory с x, dw, config, goal и ob в качестве аргументов. Эта функция вычисляет входные данные управления (u) и генерирует траекторию на основе текущего состояния x, динамического окна dw, конфигурации config, положения цели goal и препятствий ob. Управляющие входные данные определяют, как должен двигаться робот, а траектория представляет собой последовательность состояний, которым будет следовать робот.

return u, trajectory: Эта строка возвращает управляющие входные данные (u) и траекторию в качестве выходных данных функции dwa_control.

Назначение функции dwa_control состоит в том, чтобы инкапсулировать высокоуровневую логику подхода динамического окна, предоставляя удобный способ расчета управляющих воздействий и траектории на основе текущего состояния, конфигурации, цели и препятствий робота.

def motion(x, u, dt):
x[2] += u[1] * dt #updates the robot's heading angle by adding the change in angle (u[1] * dt) to the current heading angle (x[2]).
    x[0] += u[0] * math.cos(x[2]) * dt #updates the x-coordinate of the robot's position by adding the displacement in the x-direction
    x[1] += u[0] * math.sin(x[2]) * dt #updates the y-coordinate of the robot's position by adding the displacement in the y-direction 
    x[3] = u[0] #updates the linear velocity component of the robot's state to the control input u[0].
    x[4] = u[1] #updates the angular velocity component of the robot's state to the control input u[1].
    return x

Функция motion предназначена для обновления состояния робота на основе управляющих входных данных и указанного временного шага. Он моделирует движение робота на основе заданных входных данных.

Как правило, dwa_control может использоваться для определения управляющих воздействий и траектории робота на основе подхода динамического окна, а движение может использоваться для обновления состояния робота в соответствии с заданные управляющие входы.

def calc_dynamic_window(x, config):
# defines a list Vs that represents the range of velocities and 
    #yaw rates that the robot can achieve based on the robot's specifications defined in the configuration (config).
    Vs = [config.min_speed, config.max_speed,
          -config.max_yaw_rate, config.max_yaw_rate]
    #defines a list Vd that represents the range of velocities and yaw rates 
    #based on the current state of the robot (x) and the maximum acceleration (max_accel) and maximum change in yaw rate 
    Vd = [x[3] - config.max_accel * config.dt,
          x[3] + config.max_accel * config.dt,
          x[4] - config.max_delta_yaw_rate * config.dt,
          x[4] + config.max_delta_yaw_rate * config.dt]
    #  This line calculates the intersection of the two velocity and yaw rate ranges (Vs and Vd) to obtain the dynamic window dw
    dw = [max(Vs[0], Vd[0]), min(Vs[1], Vd[1]),
          max(Vs[2], Vd[2]), min(Vs[3], Vd[3])]
    return dw

Функция `calc_dynamic_window` состоит в том, чтобы рассчитать динамическое окно для робота на основе его текущего состояния (x) и заданной конфигурации (config). Динамическое окно представляет собой диапазон возможных скоростей и скоростей рыскания (угловых скоростей), которые робот может выбирать для планирования движения.

def predict_trajectory(x_init, v, y, config):
x = np.array(x_init)
    trajectory = np.array(x)
    time = 0
    while time <= config.predict_time:
        x = motion(x, [v, y], config.dt)
        trajectory = np.vstack((trajectory, x))
        time += config.dt
    return trajectory

Целью этого кода является предсказание траектории робота в течение заданного времени прогнозирования. Он начинается с начального состояния (x_init) и итеративно обновляет состояние на основе заданных управляющих входных данных (v для скорости и y для скорости рыскания) и указанного временного шага (config.dt). Обновленные состояния сохраняются в массиве траекторий, формируя последовательность состояний, которая представляет прогнозируемую траекторию робота.

def calc_control_and_trajectory(x, dw, config, goal, ob):
    x_init = x[:]
    min_cost = float("inf")
    best_u = [0.0, 0.0]
    best_trajectory = np.array([x])
    # evaluate all trajectory with sampled input in dynamic window
    for v in np.arange(dw[0], dw[1], config.v_resolution):
        for y in np.arange(dw[2], dw[3], config.yaw_rate_resolution):
            trajectory = predict_trajectory(x_init, v, y, config)
            # calc cost
            to_goal_cost = config.to_goal_cost_gain * calc_to_goal_cost(trajectory, goal)
            speed_cost = config.speed_cost_gain * (config.max_speed - trajectory[-1, 3])
            ob_cost = config.obstacle_cost_gain * calc_obstacle_cost(trajectory, ob, config)
            final_cost = to_goal_cost + speed_cost + ob_cost
            # search trajectory with min cost
            if min_cost >= final_cost:
                min_cost = final_cost
                best_u = [v, y]
                best_trajectory = trajectory
                if abs(best_u[0]) < config.robot_stuck_flag_cons \
                        and abs(x[3]) < config.robot_stuck_flag_cons:

                    best_u[1] = -config.max_delta_yaw_rate
    return best_u, best_trajectory #the best trajectory is the output

Целью этого кода является оценка нескольких траекторий на основе различных комбинаций скоростей и углов рыскания в динамическом окне. Он рассчитывает затраты для каждой траектории на основе цели, препятствий и настроек конфигурации. Цель состоит в том, чтобы найти управляющие входные данные, которые дают траекторию с наименьшей стоимостью. Наилучшие управляющие входные данные и соответствующая наилучшая траектория возвращаются в качестве выходных данных функции.

def calc_obstacle_cost(trajectory, ob, config):
ox = ob[:, 0]
    oy = ob[:, 1]
    dx = trajectory[:, 0] - ox[:, None]
    dy = trajectory[:, 1] - oy[:, None]
    r = np.hypot(dx, dy)
    if config.robot_type == RobotType.rectangle:
        yaw = trajectory[:, 2]
        rot = np.array([[np.cos(yaw), -np.sin(yaw)], [np.sin(yaw), np.cos(yaw)]])
        rot = np.transpose(rot, [2, 0, 1])
        local_ob = ob[:, None] - trajectory[:, 0:2]
        local_ob = local_ob.reshape(-1, local_ob.shape[-1])
        local_ob = np.array([local_ob @ x for x in rot])
        local_ob = local_ob.reshape(-1, local_ob.shape[-1])
        upper_check = local_ob[:, 0] <= config.robot_length / 2
        right_check = local_ob[:, 1] <= config.robot_width / 2
        bottom_check = local_ob[:, 0] >= -config.robot_length / 2
        left_check = local_ob[:, 1] >= -config.robot_width / 2
        if (np.logical_and(np.logical_and(upper_check, right_check),
                           np.logical_and(bottom_check, left_check))).any():
            return float("Inf")
    elif config.robot_type == RobotType.circle:
        if np.array(r <= config.robot_radius).any():
            return float("Inf")
    min_r = np.min(r)
    return 1.0 / min_r 

Цель этого кода — рассчитать стоимость препятствия на основе заданной траектории и положения препятствий. Стоимость определяется близостью траектории к препятствиям с учетом типа робота, указанного в настройках конфигурации. Стоимость препятствия используется в подходе динамического окна для оценки качества различных траекторий и выбора траектории с наименьшей стоимостью как лучшей.

def calc_to_goal_cost(trajectory, goal):
dx = goal[0] - trajectory[-1, 0]
    dy = goal[1] - trajectory[-1, 1]
    error_angle = math.atan2(dy, dx)
    cost_angle = error_angle - trajectory[-1, 2]
    cost = abs(math.atan2(math.sin(cost_angle), math.cos(cost_angle)))
    return cost

calc_to_goal_costвычисляет стоимость достижения заданной целевой позиции из последней точки траектории. Он использует концепцию углов для определения стоимости достижения цели из текущего состояния робота (представленного траекторией).

def plot_robot(x, y, yaw, config):  # pragma: no cover
    if config.robot_type == RobotType.rectangle:
        outline = np.array([[-config.robot_length / 2, config.robot_length / 2,
                             (config.robot_length / 2), -config.robot_length / 2,
                             -config.robot_length / 2],
                            [config.robot_width / 2, config.robot_width / 2,
                             - config.robot_width / 2, -config.robot_width / 2,
                             config.robot_width / 2]])
        Rot1 = np.array([[math.cos(yaw), math.sin(yaw)],
                         [-math.sin(yaw), math.cos(yaw)]])
        outline = (outline.T.dot(Rot1)).T
        outline[0, :] += x
        outline[1, :] += y
        plt.plot(np.array(outline[0, :]).flatten(),
                 np.array(outline[1, :]).flatten(), "-k")
    elif config.robot_type == RobotType.circle:
        circle = plt.Circle((x, y), config.robot_radius, color="b")
        plt.gcf().gca().add_artist(circle)
        out_x, out_y = (np.array([x, y]) +
                        np.array([np.cos(yaw), np.sin(yaw)]) * config.robot_radius)
        plt.plot([x, out_x], [y, out_y], marker="_",color="g")

plot_robot это функция визуализации, используемая для отображения положения и ориентации робота на двухмерном графике. Функция принимает координаты x и y робота, его рыскание (ориентацию) и объект конфигурации в качестве входных данных.

def plot_arrow(x, y, yaw, length=5, width=1):  # pragma: no cover
    plt.arrow(x, y, length * math.cos(yaw), length * math.sin(yaw),
              head_length=width, head_width=width)
    plt.plot(x, y)
directory = ""
parent_dir=''
path = os.path.join(parent_dir, directory)
os.mkdir(path)
names=[]

plot_arrow — это функция визуализации, используемая для построения стрелки на двумерном графике. Стрелка представляет собой вектор, указывающий в определенном направлении из заданной точки (x, y) с заданной длиной и шириной. Когда вы вызываете эту функцию с соответствующими значениями для x, y, yaw, length и width, она нарисует стрелку на графике, начинающуюся с точки (x, y) и указывающую в направлении, указанном углом yaw. Длина и ширина стрелки будут определяться предоставленными значениями length и width.

Затем мы создаем пустой каталог в новом каталоге.

def main(initial, goal_x, goal_y, n_pts, robot_type=RobotType.circle):
    print("START")
    num=0
    
    for i in range(n_pts):
        
        x = np.array([initial[0], initial[1], math.pi / 12.0, 0.0, 0.0])
        
        goal = np.array([goal_x[i], goal_y[i]])

        config.robot_type = robot_type
        trajectory = np.array(x)
        ob = config.ob
        while True:
            u, predicted_trajectory = dwa_control(x, config, goal, ob)
            x = motion(x, u, config.dt)  #simulate
            final_trajectory = np.vstack((trajectory, x))  #store history
    

            
            if show_animation:
                plt.plot(predicted_trajectory[:, 0], predicted_trajectory[:, 1],"-g")
                plt.plot(x[0], x[1],"xr")
                plt.plot(goal_x, goal_y, "xb")
                #plt.plot(previous[:, 0], previous[:, 1], "Dg")
                plt.plot(ob[:, 0], ob[:, 1], "ok")
                plt.plot(final_trajectory[:, 0], final_trajectory[:, 1],"-r")
                plot_robot(x[0], x[1], x[2], config)
                plot_arrow(x[0], x[1], x[2])
                plt.axis("equal")
                plt.grid(True)
                plt.xlabel('Boylam')
                plt.ylabel('Enlem')
                plt.savefig('Pictures/plot_{}'.format(num))
                
                plt.pause(1)
                
            
            dist_to_goal = math.hypot(x[0] - goal[0], x[1] - goal[1])
            names.append(Image.open('.png'.format(num)))
            
            num+=1
            if dist_to_goal <= config.robot_radius:
                PHB_yield+=predict_PHB_yield(tco2[x_lis.index(goal[0])])
                print(f"DESTINATION")
                initial=goal
                break

    print("Done")
    
    plt.show()

main планирует и моделирует движение нашего робота в 2D-среде с помощью DWA. Он использует функции dwa_control и motion, которые мы закодировали для управления движениями робота. Эта функция также включает функции визуализации с использованием библиотеки matplotlib для построения траектории робота, положения целей и препятствий в окружающей среде.

# ensure that main runs only when the script is executed directly and not when it's imported as a module in another script.
if __name__ == '__main__':
    main([randint(
        math.floor(np.amin(unused[label==set], axis=0)[0]), 
        math.ceil(np.amax(unused[label==set], axis=0)[0])), randint(
        math.floor(np.amin(unused[label==set], axis=0)[1]),
        math.ceil(np.amax(unused[label==set], axis=0)[1]))],unused[label==0, 0],unused[label==0, 1],1, robot_type=RobotType.circle)

names[0].save('output.gif', save_all=True, append_images=names)
gif_original = 'output.gif'
gif_speed_up = 'out.gif'
gif = imageio.mimread(gif_original, memtest=False)
imageio.mimsave(gif_speed_up, gif, fps=1200)
#Clean unnecessary files
for directory,_,filenames in os.walk(path):
    for filename in filenames:
        os.remove(os.path.join(path,filename))
os.rmdir(path)
os.remove('output.gif')
HTML('<img src="./out.gif" />')

Выше приведен GIF-файл, показывающий, как наш робот использует DWA, чтобы найти путь в океане.

Заключение

В этой статье я хотел изучить идею использования искусственных устройств для улавливания углекислого газа в океане, чтобы смягчить закисление океана.

Сначала мы рассмотрели, как наш робот может улавливать CO2, изучив два исследования.

Тогда я поднял этот вопрос. 'Что теперь? Как наш робот найдет путь через океан, чтобы улавливать CO2?»

Океан неоднороден, когда дело доходит до углекислоты. Итак, нам нужно было найти места с высокой концентрацией CO2, используя кластеризацию K-средних по данным GLODAP.

После этого мы использовали DWA для планирования движения нашего робота.

В конце концов, мы смоделировали ВОЛШЕБНУЮ РЫБУ, которая плывет по морю и захватывает CO2!

Надеюсь, вам было интересно прочитать о моем проекте.

Следуйте за мной, чтобы увидеть больше статей и проектов, подобных этому!