Использование 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, чтобы получать больше статей.