Использование Python и Справочного руководства MAGTF Planner для создания более реалистичных входных данных для варгеймов.

Введение и почему

Корпус морской пехоты меняет способы ведения боевых действий. План реорганизации Корпуса морской пехоты, получивший название Force Design 2030, предусматривает разработку концепций, которые сначала будут проверены с помощью варгеймов. Как и любой экспериментальный тест, варгеймы придерживаются старой концепции GIGO (мусор на входе, мусор на выходе). Следовательно, чтобы варгеймы имели реалистичные результаты, необходимо вводить реалистичные входные данные.

Хотя существует увесистый 227-страничный авторитетный документ по планированию Корпуса морской пехоты Справочное руководство по планированию MAGTF, в него не включен метод создания быстрых расчетов для передачи варгеймерам.

В этой статье мы рассмотрим создание инструмента формирования спроса для ежедневных запросов спроса на элементы размеров взвода, роты, MLR и MEF в состояниях конкуренции, кризиса или конфликта, от проектирования до кода.

Полный код и дополнительные ресурсы, а также возможность запуска этого кода в браузере можно найти на GitHub.

Проблема, цель и задача

Первый шаг – определить, чего мы хотим достичь. Определение проблемы, цели и задачи нашего проекта поможет определить объем и дизайн проекта.

Проблема: Создание реалистичного спроса необходимо для повышения реалистичности военных игр, основанных на логистике.

Цель: Обеспечить реалистичное формирование спроса для каждого класса поставок в зависимости от размера подразделения и состояния конфликта.

Цель: Предоставить количественные данные о спросе для военных игр, основанных на логистике.

Дизайн

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

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

Размер подразделения, который мог бы охватить наиболее полную территорию, будет следующим:

  • Взвод
  • Компания
  • Морской прибрежный полк
  • Экспедиционный отряд морской пехоты

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

Три состояния континуума, которые следует учитывать:

  • Соревнование
  • Конфликт
  • Кризис

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

Классы поставок, перечисленные в Руководстве:

  • Класс первый: Еда
  • Вода
  • Класс второй: оборудование
  • Класс третий: Топливо
  • Класс четвертый: строительные и барьерные материалы.
  • Класс пятый: боеприпасы
  • Класс шестой: личные вещи
  • Класс девятый: Ремонт деталей

Имея эту информацию, мы можем нарисовать нашу диаграмму.

Теперь мы можем проверить, соответствует ли наш Дизайн нашим Проблеме, Цели и Задаче.

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

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

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

Псевдокод

Третий шаг — набросок кода. Подобно написанию плана перед началом эссе, создание структуры того, чего мы хотим достичь, может облегчить написание реального кода.

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

#IMPORTS
#FILE STRUCTURE
#VARIABLES

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

#IMPORTS
#FILE STRUCTURE
  #Outputs for CSVs
#VARIABLES
  #Unit Size Options
  #Unit State Options
  #Size of a MEF
#DATA DEFINITION
  #Unit Size
  #Unit State
    #Inflation Factor
    #Attrition Factor
  #Class One
  #Water
  #Class Two
  #Class Three
  #Class Four
  #Class Five
  #Class Six
  #Class Nine
#DATA STORAGE
  #All Data DataFrame
  #Adding Data Mechanism
#OUTPUTS
  #By Class
  #By Unit
  #By State
#DATA GENERATIONS
  #Unit Generator
#RUNNING THE PROGRAM

Теперь мы можем начать писать код.

Написание кода

Теперь мы можем использовать написанный нами псевдокод, который поможет нам в выполнении задачи Написание кода.

Импорт

Раздел импорта в начале кода добавляет модули и библиотеки, необходимые в программе. Мы знаем, что нам нужно создавать выходные данные, отслеживать данные и производить некоторые расчеты.

Библиотеки, поддерживающие это, будут:

  • ОС для взаимодействия с операционной системой для создания структуры папок для хранения наших результатов.
  • Математика — модуль математических операций.
  • Random — модуль генерации случайных чисел.
  • Numpy — пакет научных вычислений по математике.
#IMPORTS
import os
import math
import random
import numpy
import pandas

Структура файла

Теперь мы можем определить, где мы хотим выводить окончательные CSV-файлы.

#FILE STRUCTURE
"""
Purpose: Create the folder structure where we will store our outputs.
"""
#Get Current Working Directory
current_location = os.getcwd()
#All The Folders/Directories We Want To Put Outputs
directories = [
    "outputs",
    "outputs/class",
    "outputs/unit",
    "outputs/state"
]
#If That Folder/Directory Doesn't Already Exist, Make It
for directory in directories:
    if not os.path.exists(directory):
        os.makedirs(directory)

Переменные и параметры

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

#VARIABLES
"""
Purpose: All the variables and parameters that are needed throughout the program (globally).
"""
unit_size = ["Platoon", "Company", "MLR", "MEF"]
unit_state = ["Competition", "Crisis", "Conflict"]
mef_size = 5300

Определение данных

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

class UnitFactors:
    #Every unit has a type (platoon, company, MLR, etc), state (competition, crisis, etc), an inflation factor, and an attrition size.
    def __init__(self, type, state):
        self.type = type
        self.state = state
        self.inflation_factor = 1
        self.attrition_size = 0

    #Set the size of the unit based on unit type.    
    def size(self):  
        if self.type == "Platoon":
            self.size = 50
        if self.type == "Company":
            self.size = 400
        if self.type == "MLR":
            self.size = 2000
        if self.type == "MEF":
            self.size = mef_size
        print("Unit Type: ", self.type)
        print("Unit Size: ", self.size)

    #Set inflation factor and attrition size based on state of conflict.
    def state(self):
        size = self.size
        if self.state == "Crisis":
            #Inflation Factors
            self.inflation_factor = 1.5
            #Attrition Factors
            attrition_factor = random.uniform(0.01, 0.1)
            attrition_size_raw = (size * attrition_factor)
            self.attrition_size = math.ceil(attrition_size_raw)

        if self.state == "Conflict":
            #Inflation Factors
            self.inflation_factor = 2
            #Attrition Factors
            attrition_factor = random.uniform(0.1, 0.4)
            attrition_size_raw = (size * attrition_factor)
            self.attrition_size = math.ceil(attrition_size_raw)

        print("Unit State: ", self.state)
        print("Inflation Factor: ", self.inflation_factor)
        print("Attrition Size: ", self.attrition_size)

    #CLASS ONE: FOOD
    def demand(self):
        #CLASS ONE: FOOD
        """
        MSTP Pamphlet 5-0.3 MAGTF Planner's Reference Manual
        196 stons daily for MEF sized element.
        """
        class_one_data = 196
        class_one_individual_mean = class_one_data / mef_size
        class_one_mean = (class_one_individual_mean * (self.size - self.attrition_size)) * self.inflation_factor
        class_one_stdev = 1
        class_one_demand_raw = abs(numpy.random.normal(class_one_mean, class_one_stdev))
        self.class_one_demand = round(class_one_demand_raw, 2)

        #WATER
        """
        MSTP Pamphlet 5-0.3 MAGTF Planner's Reference Manual
        260300 gallons daily for MEF sized element.
        """
        water_data = 196
        water_individual_mean = water_data / mef_size
        water_mean = (water_individual_mean * (self.size - self.attrition_size)) * self.inflation_factor
        water_stdev = 1
        water_demand_raw = abs(numpy.random.normal(water_mean, water_stdev))
        self.water_demand = round(water_demand_raw, 2)
        
        #CLASS TWO: EQUIPMENT
        """
        MSTP Pamphlet 5-0.3 MAGTF Planner's Reference Manual
        83 stons daily for MEF sized element.
        """
        class_two_data = 83
        class_two_individual_mean = class_two_data / mef_size
        class_two_mean = (class_two_individual_mean * (self.size - self.attrition_size)) * self.inflation_factor
        class_two_stdev = 1
        class_two_demand_raw = abs(numpy.random.normal(class_two_mean, class_two_stdev))
        self.class_two_demand = round(class_two_demand_raw, 2)
        
        #CLASS THREE: FUEL
        """
        MSTP Pamphlet 5-0.3 MAGTF Planner's Reference Manual
        950,010 gallons daily for MEF sized element.
        """
        class_three_data = 950010
        class_three_individual_mean = class_three_data / mef_size
        class_three_mean = (class_three_individual_mean * (self.size - self.attrition_size)) * self.inflation_factor
        class_three_stdev = 1
        class_three_demand_raw = abs(numpy.random.normal(class_three_mean, class_three_stdev))
        self.class_three_demand = round(class_three_demand_raw, 2)
        
        #CLASS FOUR: CONSTRUCTION AND BARRIER MATERIAL
        """
        MSTP Pamphlet 5-0.3 MAGTF Planner's Reference Manual
        139 stons daily for MEF sized element.
        """
        class_four_data = 139
        class_four_individual_mean = class_four_data / mef_size
        class_four_mean = (class_four_individual_mean * (self.size - self.attrition_size)) * self.inflation_factor
        class_four_stdev = 1
        class_four_demand_raw = abs(numpy.random.normal(class_four_mean, class_four_stdev))
        self.class_four_demand = round(class_four_demand_raw, 2)
        
        #CLASS FIVE: AMMO
        """
        MSTP Pamphlet 5-0.3 MAGTF Planner's Reference Manual
        1600 stons daily for MEF sized element.
        """
        class_five_data = 1600
        class_five_individual_mean = class_five_data / mef_size
        class_five_mean = (class_five_individual_mean * (self.size - self.attrition_size)) * self.inflation_factor
        class_five_stdev = 1
        class_five_demand_raw = abs(numpy.random.normal(class_five_mean, class_five_stdev))
        self.class_five_demand = round(class_five_demand_raw, 2)

        #CLASS SIX: PERSONAL ITEMS
        """
        MSTP Pamphlet 5-0.3 MAGTF Planner's Reference Manual
        1600 stons daily for MEF sized element.
        """
        class_six_data = 26
        class_six_individual_mean = class_six_data / mef_size
        class_six_mean = (class_six_individual_mean * (self.size - self.attrition_size)) * self.inflation_factor
        class_six_stdev = 1
        class_six_demand_raw = abs(numpy.random.normal(class_six_mean, class_six_stdev))
        self.class_six_demand = round(class_six_demand_raw, 2)

        #CLASS NINE: REPAIR PARTS
        """
        MSTP Pamphlet 5-0.3 MAGTF Planner's Reference Manual
        41 stons daily for MEF sized element.
        """
        class_nine_data = 26
        class_nine_individual_mean = class_nine_data / mef_size
        class_nine_mean = (class_nine_individual_mean * (self.size - self.attrition_size)) * self.inflation_factor
        class_nine_stdev = 1
        class_nine_demand_raw = abs(numpy.random.normal(class_nine_mean, class_nine_stdev))
        self.class_nine_demand = round(class_nine_demand_raw, 2)

Хранилище данных

Поскольку наши подразделения генерируют спрос, мы хотим иметь возможность хранить эти данные централизованно. Для этого мы создаем DataFrame для хранения наших данных, а также способ легкого добавления к нему данных.

class Data:
    #Contains all of the base data. The demand here is amount needed for one day.
    all_data = pandas.DataFrame({
        "Unit Type": [],
        "Unit Size": [],
        "Unit State": [],
        "Inflation Factor": [],
        "Attrition Size": [],
        "Class One Demand (stons)": [],
        "Water Demand (gal)": [],
        "Class Two Demand (stons)": [],
        "Class Three Demand (gal)": [],
        "Class Four Demand (stons)": [],
        "Class Five Demand (stons)": [],
        "Class Six Demand (stons)": [],
        "Class Nine Demand (stons)": []
    })

    def add_data(unit):
        Data.all_data = Data.all_data.append({
            "Unit Type": unit.type,
            "Unit Size": unit.size,
            "Unit State": unit.state,
            "Inflation Factor": unit.inflation_factor,
            "Attrition Size": unit.attrition_size,
            "Class One Demand (stons)": unit.class_one_demand,
            "Water Demand (gal)": unit.water_demand,
            "Class Two Demand (stons)": unit.class_two_demand,
            "Class Three Demand (gal)": unit.class_three_demand,
            "Class Four Demand (stons)": unit.class_four_demand,
            "Class Five Demand (stons)": unit.class_five_demand,
            "Class Six Demand (stons)": unit.class_six_demand,
            "Class Nine Demand (stons)": unit.class_nine_demand
        }, ignore_index=True)

Выходы

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

class Outputs:
    #Output complete data.
    def complete_data():
        complete_data = Data.all_data.copy()
        print(complete_data)
        complete_data.to_csv("outputs/all_data.csv")

    #Organization by class of supply.
    def by_class():
        class_one_data = Data.all_data[[
            "Unit Type",
            "Unit Size",
            "Unit State",
            "Inflation Factor",
            "Attrition Size",
            "Class One Demand (stons)",
            "Water Demand (gal)"
        ]].copy()
        print("CLASS ONE")
        print(class_one_data)
        class_one_data.to_csv("outputs/class/class_one.csv")

        class_two_data = Data.all_data[[
            "Unit Type",
            "Unit Size",
            "Unit State",
            "Inflation Factor",
            "Attrition Size",
            "Class Two Demand (stons)"
        ]].copy()
        print("CLASS TWO")
        print(class_two_data)
        class_two_data.to_csv("outputs/class/class_two.csv")

        class_three_data = Data.all_data[[
            "Unit Type",
            "Unit Size",
            "Unit State",
            "Inflation Factor",
            "Attrition Size",
            "Class Three Demand (gal)"
        ]].copy()
        print("CLASS THREE")
        print(class_three_data)
        class_three_data.to_csv("outputs/class/class_three.csv")

        class_four_data = Data.all_data[[
            "Unit Type",
            "Unit Size",
            "Unit State",
            "Inflation Factor",
            "Attrition Size",
            "Class Four Demand (stons)"
        ]].copy()
        print("CLASS FOUR")
        print(class_four_data)
        class_four_data.to_csv("outputs/class/class_four.csv")

        class_five_data = Data.all_data[[
            "Unit Type",
            "Unit Size",
            "Unit State",
            "Inflation Factor",
            "Attrition Size",
            "Class Five Demand (stons)"
        ]].copy()
        print("CLASS FIVE")
        print(class_five_data)
        class_five_data.to_csv("outputs/class/class_five.csv")

        class_six_data = Data.all_data[[
            "Unit Type",
            "Unit Size",
            "Unit State",
            "Inflation Factor",
            "Attrition Size",
            "Class Six Demand (stons)"
        ]].copy()
        print("CLASS SIX")
        print(class_six_data)
        class_six_data.to_csv("outputs/class/class_six.csv")

        class_nine_data = Data.all_data[[
            "Unit Type",
            "Unit Size",
            "Unit State",
            "Inflation Factor",
            "Attrition Size",
            "Class Nine Demand (stons)"
        ]].copy()
        print("CLASS NINE")
        print(class_nine_data)
        class_nine_data.to_csv("outputs/class/class_nine.csv")

    #Organize by unit type.
    def by_unit():
        unit_data = Data.all_data.copy()
        
        platoon_grouped = unit_data.groupby("Unit Type")
        platoon_data = platoon_grouped.get_group("Platoon")
        print("PLATOON")
        print(platoon_data)
        platoon_data.to_csv("outputs/unit/platoon.csv")

        company_grouped = unit_data.groupby("Unit Type")
        company_data = company_grouped.get_group("Company")
        print("COMPANY")
        print(company_data)
        company_data.to_csv("outputs/unit/company.csv")

        mlr_grouped = unit_data.groupby("Unit Type")
        mlr_data = mlr_grouped.get_group("MLR")
        print("MLR")
        print(mlr_data)
        mlr_data.to_csv("outputs/unit/MLR.csv")

        mef_grouped = unit_data.groupby("Unit Type")
        mef_data = mef_grouped.get_group("Platoon")
        print("MEF")
        print(mef_data)
        mef_data.to_csv("outputs/unit/MEF.csv")

    #Organize by state of conflict.
    def by_state():
        state_data = Data.all_data.copy()
        
        competition_grouped = state_data.groupby("Unit State")
        competition_data = competition_grouped.get_group("Competition")
        print("COMPETITION")
        print(competition_data)
        competition_data.to_csv("outputs/state/competition.csv")

        crisis_grouped = state_data.groupby("Unit State")
        crisis_data = crisis_grouped.get_group("Crisis")
        print("CRISIS")
        print(crisis_data)
        crisis_data.to_csv("outputs/state/crisis.csv")

        conflict_grouped = state_data.groupby("Unit State")
        conflict_data = conflict_grouped.get_group("Conflict")
        print("CONFLICT")
        print(conflict_data)
        conflict_data.to_csv("outputs/state/conflict.csv")

Генерация данных

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

def generator():
#For every size unit, for every state of conflict, generate demand.
    for size in unit_size:
        for state in unit_state:
            unit = UnitFactors(size, state)
            UnitFactors.size(unit)
            UnitFactors.state(unit)
            UnitFactors.demand(unit)
            Data.add_data(unit)

Запуск программы

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

#RUN
generator()
print("ALL DATA")
print(Outputs.complete_data())
print("BY CLASS")
print(Outputs.by_class())
print("BY UNIT")
print(Outputs.by_unit())
print("BY STATE")
print(Outputs.by_state())

Выходы

Некоторые примеры результатов показаны ниже:

Все данные в формате CSV

CSV-файл первого класса

CSV Морского прибрежного полка

CSV-файл конфликта

Заключение

Еще раз: полный исходный код и дополнительные ресурсы можно найти на GitHub.

Мне нравится писать о симуляции и науке о данных, особенно о морской пехоте. Вы можете связаться со мной в LinkedIn и подписаться на меня в Medium, чтобы получать больше статей.