A simple battle model on Modelica

  • Tutorial

Good day! Recently having learned about such a modeling tool as the Modelica language and its free implementation of OpenModelica , I was surprised that there was only one article on Habré about this . Since the topic is somewhat unusual, the details had to be comprehended in our own skin by some example taken from my head. This article will discuss how to build a simple battle model (for example), while understanding some of the concepts of the language (basic).



Note: for the preparation of the article, the latest OpenModelica night build (rev18625) was used. During installation, please note that you must specify the path without spaces.

Task


Imagine a war as a process in which two adversaries, possessing different forces (in terms of ratio, but not in force), achieve the same goal - to capture the largest possible enemy territory by destroying the enemy’s forces on it. For definiteness, we will assume that the territory was conquered when the infantry (ground troops) set foot on it. The sea is not included in the territory. Victory counts when the entire territory of the enemy is conquered.

Generally speaking, wars also include economics, but modeling such a thing would take up too much space. Let us now consider three types of forces: naval, land, and aviation (we will not consider nuclear weapons either). Each has resistance to attack, or health (of one unit), and also the strength of the attack itself (we will measure, say, in the number of bullets). And let every few units of time, new units come into battle.

The fighting takes place in a square across which the front line runs horizontally, and on the right - the sea, where the ships carry out the operations. The length of the side of the square is denoted by L (for definiteness, we take it as 100 kilometers). Then the area won by side A will be equal to A * L, conquered by the enemy - (LA) * L. The change in A will depend on the balance of forces. The greater the forces at the front prevail, the more A will change (in favor of those forces that are larger). Say for definiteness, an infantry unit on one side advances the front (if without a fight) by 1 kilometer per unit of time.

Concepts Modelica


The basis of modeling in Modelica are classes (and their variations in terms of declaration / application, but more on that later). The class in Modelica is somewhat different from the classes we are familiar with, since it contains not only fields and methods (in Modelica, functions), but also equations that bind variables to each other. Fields can also have different types of “variability” - a constant (and there is a constant), a parameter (does not change in the current simulation), and, in fact, a variable. A field in a class can be either an object of built-in types (Boolean, Real, etc.), or custom types. A more complex concept is the ability to "template" - replacing the type of field, its redefinition.

Classes can be inherited (including multiple, and only diamond). For Modelica, this means that all the contents of the inherited are copied to the inherited class, including equations. And here we should mention the main rule of Modelica - the number of equations for variables should correspond to the number of variables. No more and no less.

An example of solving the problem


The simplest type of class — record — cannot contain equations.
First, we define an abstract type of combat unit.
UnitData.mo
record UnitData "Abstract army unit record"
  parameter Real unitHealth;
  parameter Real unitAttack;
  parameter Real supplyTime;
  parameter Real supplyNumber;
end UnitData;


This code declares four uninitialized parameters. In quotation marks, after declaring classes and variables, you can add document strings. In general, comments in Modelica - C ++ - style, that is, // or / * * / (comments of this kind are ignored by the environment).

The next step will be to declare a class with equations, the objects of which will be some military forces, characterized by the same as military units + quantity.
Forces.mo
class Forces "Abstract army forces class" extends UnitData;
  parameter Real startNumber = 0;
  Real number (start = startNumber);
  Boolean destroyed;
equation
  destroyed = number < 0;
  when destroyed then 
    reinit(number, 0); 
  end when; 
  when sample(supplyTime, supplyTime) then
    reinit(number, pre(number) + supplyNumber);
  end when;                      
end Forces;


Using this file as an example, you can trace the inheritance, as well as working with Modelica events.
If the equation destroyed = number <0; should not cause questions, then inside the first block when the special reinit operator is used, which sets the value of the variable in the result of the expression as the second parameter. reinit, like pre (the value of the previous step), can only be used inside when blocks. pre must be used so that the equation (namely the equation) is not like this: x = x + 1. The

built-in sample function returns true if the simulation time (time) is equal to the first parameter (first call), and then at intervals equal to the second parameter (in our case they are equal - the help arrives at equal intervals starting from the moment of supplyTime).

Next, a class representing the enemy’s army will come in handy:
EnemyForces.mo
class EnemyForces "Enemy forces references"
  replaceable Forces enemyAir;
  replaceable Forces enemySea;
  replaceable Forces enemyLand;
end EnemyForces;



Specifying a variable as replaceable allows you to redefine the type of a variable when declaring an EnemyForces object.

Now we come to the most important thing. Actually the class representing the army.
Army.mo
class Army
  class LandArmy = Forces(unitHealth = 100, unitAttack = 400, supplyTime = 0.25, supplyNumber = 800);
  class AirArmy = Forces(unitHealth = 400, unitAttack = 750, supplyTime = 0.5, supplyNumber = 400);
  class SeaArmy = Forces(unitHealth = 3000, unitAttack = 7000, supplyTime = 1, supplyNumber = 20);
  SeaArmy seaArmy;
  AirArmy airArmy;
  LandArmy landArmy;
  EnemyForces enemyForces (redeclare Army.AirArmy enemyAir, redeclare Army.SeaArmy enemySea, redeclare Army.LandArmy enemyLand);
  Real armyForce;
equation
  armyForce = seaArmy.unitHealth * seaArmy.number + airArmy.unitHealth * airArmy.number + landArmy.unitHealth * landArmy.number;
  der(seaArmy.number) = if not seaArmy.destroyed then 
    -(enemyForces.enemyAir.number * enemyForces.enemyAir.unitAttack + 
      enemyForces.enemySea.number * enemyForces.enemySea.unitAttack) / seaArmy.unitHealth
  else 0;
  der(airArmy.number) = if not airArmy.destroyed then 
     -(enemyForces.enemyAir.number * enemyForces.enemyAir.unitAttack + 
       enemyForces.enemySea.number * enemyForces.enemySea.unitAttack + 
       enemyForces.enemyLand.number * enemyForces.enemyLand.unitAttack) / airArmy.unitHealth
  else 0;
  der(landArmy.number) = if not landArmy.destroyed then 
    -(enemyForces.enemyAir.number * enemyForces.enemyAir.unitAttack + 
      enemyForces.enemyLand.number * enemyForces.enemyLand.unitAttack) / landArmy.unitHealth
  else 0;
end Army;



What's going on here. First, we create specific classes of military branches, simply equating them with the existing Forces class and initializing its parameters. We immediately declare the objects. The next thing is to create an object that will be used as a reference to the enemy army, while replacing replaceable variables with the redeclare keyword. We will need the armyForce variable purely for "analytical conclusions."
About equations - the first equation means that the strength of the army is the armor of all units that are.
The following three equations mean:
  1. If the naval army is not destroyed, then at every moment of time, the damage caused by air and sea forces decreases from all its armor,
  2. If the air army is not destroyed, then at any given time, the damage caused by any enemy troops decreases from all its armor,
  3. Similarly with ground troops, except that naval forces do not damage them


The notation der (var) is the time derivative of var (variable).

And finally, we will describe the armed conflict itself.
Conflict.mo
model Conflict "Conflict of two armies model"
  parameter Real armyASea = 200;
  parameter Real armyAAir = 1000;
  parameter Real armyALand = 4000;
  parameter Real armyBSea = 100;
  parameter Real armyBAir = 1800;
  parameter Real armyBLand = 3600;
  Army armyA (
    seaArmy.startNumber = armyASea, 
    airArmy.startNumber = armyAAir,
    landArmy.startNumber = armyALand,
    enemyForces.enemyAir.startNumber = armyBAir,
    enemyForces.enemySea.startNumber = armyBSea,
    enemyForces.enemyLand.startNumber = armyBLand
  );
  Army armyB (
    seaArmy.startNumber = armyBSea, 
    airArmy.startNumber = armyBAir,
    landArmy.startNumber = armyBLand,
    enemyForces.enemyAir.startNumber = armyAAir,
    enemyForces.enemySea.startNumber = armyASea,
    enemyForces.enemyLand.startNumber = armyALand
  );
  parameter Real L = 100;
  parameter Real landArmySpeed = 1;
  Real A (start = L / 2);
equation
  armyA.enemyForces.enemyAir.number = armyB.airArmy.number;
  armyA.enemyForces.enemySea.number = armyB.seaArmy.number;
  armyA.enemyForces.enemyLand.number = armyB.landArmy.number;
  armyB.enemyForces.enemyAir.number = armyA.airArmy.number;
  armyB.enemyForces.enemySea.number = armyA.seaArmy.number;
  armyB.enemyForces.enemyLand.number = armyA.landArmy.number;
  when A > L then terminate("Army A wins"); end when;
  when A < 0 then terminate("Army B wins"); end when;
  der (A) = (armyA.landArmy.number - armyB.landArmy.number) * landArmySpeed / L;
end Conflict;



The keyword model is used here. This is also a class, but only models can be used for optimization (about which later).
In addition to declaring warring parties, which we will not dwell on, there are equations that establish that enemy A is B, and vice versa. Two when-conditions mean terminate modeling with a mark of what happened.

Launch and Results


In order to run the model, you can use shell OpenModelica by typing the following in it:
Shell OpenModelica Commands
loadModel(Modelica)
loadFile("d:/path/UnitData.mo")
loadFile("d:/path/Forces.mo")
loadFile("d:/path/EnemyForces.mo")
loadFile("d:/path/Army.mo")
loadFile("d:/path/Conflict.mo")
simulate(Conflict, stopTime = 2)
plot({armyA.armyForce, armyB.armyForce}, xRange = {0, 2})
plot({armyA.landArmy.number, armyB.landArmy.number, armyA.seaArmy.number, armyB.seaArmy.number, armyA.airArmy.number, armyB.airArmy.number}, xRange = {0, 2})
plot(A, xRange = {0, 2})



The corresponding constructions are shown in the pictures below.

This is how the ratio of forces in time looks.

This is how the correlation of forces in time in the context of types of troops looks.

And so is the front line. It can be seen from the graphs that Army A won first, but then Army B defeated it.

Optimization with OpenModelica


In addition to what you can model, you can also find answers to questions posed by the model. For example, if you ask yourself, at what minimum initial number of ground troops does Army B win, then using the OMOptim tool included in OpenModelica you can get the following picture:


Instead of a conclusion


OpenModelica is well documented for those who are familiar with English. In general, the tool is promising. So far, the error messages are not very talking, and the debugging information does not say that it is easily interpreted. But in general, with sufficient interest and time - everything is defeated.

Still instead of a conclusion


It is clear that the battle model is very simple, and the reality, if it reflects, is only in that (in our case the minimum) superiority in forces may not guarantee victory, and the final result is determined by the structure of the system (who hits whom) no less. than the initial conditions.

useful links


Great tutorial (in English).
Web Reference (in English).

UPDATE
After some time, I realized that to call its “armor” its armor is somewhat hasty. However, light modifications:
Army.mo changes
class Army
//...
  Real armyHealth, armyStrength;
equation
  armyHealth = seaArmy.unitHealth * seaArmy.number + airArmy.unitHealth * airArmy.number + landArmy.unitHealth * landArmy.number;
  armyStrength = seaArmy.unitAttack * seaArmy.number + airArmy.unitAttack * airArmy.number + landArmy.unitAttack * landArmy.number;
//...
end Army;


you can come to a picture that will confirm the conclusions already stated.
Schedule


Also popular now: