2016 年 3 月

第 31 卷,第 3 期

本文章是由機器翻譯。

C#-離散事件模擬 ︰ 人口成長範例

Arnaldo Perez Perez

整個記錄,模擬的能力有提供輔助開發的多個科學。醫療模型模擬人體增強研究的人為的剖析。電腦模擬遊戲,例如 「 Warcraft 世界 」 重新建立整個奇幻世界,「 模擬飛行 」 可幫助訓練試驗地面。不同的模擬程式探索恐怖份子攻擊、 pandemic 疾病和其他可能的危機的回應。即使模擬的恐龍在廣泛的應用程式的模擬和其潛在的影片 」 侏公園 」 提示中。

模擬是真實系統或處理程序所設計的模型會模擬的技術。模型封裝的所有系統的功能和行為。模擬是在執行此系統的一段時間。有幾個階段設計模擬 ︰

  • 定義系統模組化,這牽涉到學習眼前的問題,識別環境的屬性,指定目標來達成。
  • 擬定模型,其中定義所有其變數和邏輯的關聯,並建立必要的流程圖。
  • 定義資料模型所需產生所要的結果。
  • 產生模型的自訂的實作。
  • 驗證實作的模型是否符合設計。
  • 驗證透過模擬器實際上代表模擬真實系統的比較。
  • 實驗來產生想要使用模擬器的資料。
  • 分析和解譯結果從模擬器,並根據這些結果做出決策。
  • 記錄建立的模型及模擬器的工具。

模擬通常包含持續的程序或特定的事件。若要模擬的氣象系統,比方說,追蹤會發生持續經常變更的所有項目。因此,針對時間變數放溫度變數會以連續的曲線。相較之下,飛機 takeoffs 或是降落發生點的時間和,因此,模擬可以考慮精確的時間或事件,並捨棄其他項目。這種模擬稱為離散事件模擬 (DES)、,而且我將在本文中討論。

不連續事件模擬

DES 製作系統或程序為已排序的個別事件經過一段時間,也就是從一個事件時間的下一個事件的時間。因此,在 DES 模擬,時間是比即時通常較短的。

在開發目的地時,有六個主要的項目,請考慮 ︰

物件代表實際系統的項的目。擁有屬性、 關聯到事件,它們會耗用資源,並輸入及離開佇列一段時間。在飛機起飛和先前所述的平台案例中,這些物件會是飛機。在健康醫療系統中,物件可能病患或器官。在倉儲系統中,這些物件會是庫存的產品。物件應該彼此或與系統互動,您可以隨時在模擬期間建立。

屬性的功能 (大小、 登陸時間、 性別、 價格等) 的每個物件的特定儲存以判斷回應中模擬,可能發生的各種案例您可以修改這類值。

事件是飛機的到達的產品在倉庫,特定疾病等等的外觀可能發生在系統中,尤其是飛機的對等,登陸物件的項目。可以發生事件,並且以任何順序重複出現。

資源是提供服務給物件 (例如在機場,倉儲中的儲存體儲存格和醫生實習課程中的登陸區域) 的項目。當資源被佔用,需要將物件的物件必須排入佇列,而且等候直到有可用資源。

佇列是由物件組織等候目前佔用的資源版本的管道。佇列可以有最大容量,且可以有不同的呼叫方法 ︰ 後進先出 (FIFO),後進先出 (LIFO),或根據一些準則或優先順序 (疾病進展、 燃料耗用量等)。

時間 (當它發生在現實生活中) 是在模擬中不可或缺。若要測量時間,時鐘啟動模擬的開頭,然後可以用來追蹤特定一段時間 (離開或到達時間、 傳輸時間處理特定的徵狀,等所花費的時間)。這類追蹤是基本的因為它可讓您知道下一個事件時應該發生。

模擬程式設計可能很複雜,因為有許多嘗試建立只限的所有需求的模擬架構以簡化開發的語言。一種語言是程式設計的 SIMULA,在 1960 年代發明的 Ole Johan 和 Kristen Nygaard 及第一個引入物件導向 (OOP),今天的前置字元的程式設計開發架構的概念。現今,重點在於建立封裝、 架構或程式庫併入程式設計人員需要什麼建立模擬時,會有更多。這些程式庫的用意在於從一般的語言,例如 C#、 c + +、 Java 或 Python 呼叫。

在 「 歷程記錄的不連續事件模擬程式設計語言中,「 Nance 提議的六個的最低需求,應該滿足任何程式設計語言的 DES (bit.ly/1ZnvkVn):

  • 亂數產生。
  • 處理程序轉換程式,以允許非統一隨機變數的變數。
  • 清單處理程序,以便建立、 管理和刪除的物件。
  • 統計分析,以提供描述性模型行為的摘要。
  • 報告產生,要協助大型資料集的呈現方式,並有助於決策。
  • 此階段流程機制。

這些需求所有可滿足在 C# 中,因此我將在本文中提供以 C# 開發的母體擴展成長不連續事件模擬。

母體擴展成長的 DES

母體擴展成長是視為母體擴展 (「 動物 」 和 「 植物 」) 如何隨著時間和空間及與其環境互動的研究中的許多層面。母體擴展的生態的同類有時住在相同的時間,在相同的空間,或進一步分隔根據特定特性。

它為何如此重要研究母體擴展成長? 進一步了解如何擴展成長或壓縮可讓科學家 biodiversity 節省中有關變更的較佳預測的可能性,資源使用,氣候變遷的行動、 侵害、 健康照護、 運輸需求等等。它也提供深入了解生態的互動方式彼此與環境,考慮母體擴展可能成功或拒絕時的重要層面。

在本文中我將會呈現 DES 母體的成長。目標是要觀察母體擴展長時間的發展,分析 (母體大小、 舊的人,年輕人等等) 的最終結果,以取得一些統計資料。母體擴展會啟動與 m 男性和 n 女性個人,有一個相關聯的時代。很明顯地,m 和 n 必須是大於零,或模擬是毫無意義。在此模型中的物件是個人。

個人就可以開始之後到達年齡會參數 λ 的波氏函式的關聯性另一部個別 = 18 歲。(您不需要完全了解波氏,現在標準或指數分佈,它們將在下一節說明)。

沒有小於 50%機率為單一且能夠與彼此,甚至更吸引人的相反性別個人就會發生只有時代差異時,不超過 5 年。[圖 1 示範兩個個人結束之間的關聯性的機率。

[圖 1 的機率的關聯性結束

平均年齡 機率
14-20 0.7
21-28 0.5
29+ 0.2

個人版之關聯性中可以有子系會與參數 λ 指數函式的時間之後 = 8 年。

女人可以取得懷孕她必須遵循常態 (鐘型) 分配的函式參數的 µ age = 28 和 σ2= 8 年。每一位具有會與參數的 µ 正常的函式的子系數目 = 2and σ2= 6 年。(參數的 µ 代表平均年齡 σ 時2是年齡變化的量值)。

每個人擁有的期望與參數 λ 函式波氏分配的生命 = 70 年來,其中 λ 代表平均期望生命。

在前面的描述中可能會找出數個事件 ︰

  • 正在啟動關聯性。
  • 結束關聯性。
  • 取得那些項目。
  • 具有子系。
  • 超載。

每個事件會伴隨決定的那一刻起,事件就會發生不連續隨機變數。

機率分佈

是離散的隨機變數的值的組合很有限或無限 countably。也就是可以列出值,做為值 1、 2、 3 的有限或無限序列...為不連續的隨機變數,其機率分佈是任何圖形、 表格或公式,將指派給每個可能值的機率。所有機率的總和必須為 1,以及每個個別的機率必須介於 0 和 1 之間。比方說,擲回公平骰子 (各邊有同等可能性),請將不連續的隨機變數 X 代表可能的結果會有機率分佈 X(1) = 1/6、 X(2) = 1/6,...,X(6) = 1/6。所有側邊都有同等可能性的因此每個指派的機率為 1/6。

參數 l 和的 µ 指出平均值 (預期的值) 在其對應的散發套件。平均值代表平均會隨機變數的值。換句話說,這是總和 E = [(each possible outcome) × (該結果的機率)],其中 E 代表平均值。如果骰子,平均值為 E = 1/6 + 2/6 + 3/6 + 4/6 + 5/6 + 6/6 = 3.5。請注意,結果 3.5 實際偶數所有可能的值,可能需要骰子。骰子復原大量的時間時,它是預期的值。

參數 σ2 表示分佈的變異數。變異數代表隨機變數的可能值的離散,隨時都是非負數。小型的變異數 (接近 0) 傾向於指示的值是接近彼此和平均值。高變異數 (接近 1) 表示距離太長,以及平均值之間的值。

波氏是離散分佈的機率相關的事件,每個時間單位數 (請參閱 [圖 2)。它通常會套用事件的機率小,而發生這個問題的機會數目很大時。在書中,客戶抵達企業中心,汽車抵達號誌燈和每年指定年齡群組中的半毀 misprints 數目是波氏分配的應用程式的所有範例。

波氏分配、 參數 λ = 18
[圖 2 波氏分配、 參數 λ = 18

指數分佈表示波氏程序中事件之間的時間 (請參閱 [圖 3)。比方說,如果您處理波氏處理程序描述的一段時間抵達企業中心的客戶數目時,您可能感興趣的隨機變數可指出多少時間傳遞第一個客戶送達之前。指數分佈可以達到這個目的。它也可套用至物理條件的處理序,例如若要表示的物件,其中 λ 會指出的速率的年齡物件存留期。

指數分佈,參數 λ = 8
[圖 3 指數分佈,參數 λ = 8

常態分佈描述中所示,傾向於中央值沒有偏差向左或右周圍的機率 [圖 4。常態分佈是對稱的而且擁有與單一的高峰平均值鐘形密度曲線。50%的散發就平均值和 50%的左到右。標準差表示模式或 girth 的鐘形曲線。小標準差愈集中的資料。必須指定平均數和標準差,做為常態分佈的參數。許多自然現象確實遵循常態分佈 ︰ 血壓力、 人的高度、 中測量的錯誤和更多。

NormalDistribution,參數的 µ = 28 和 σ2 = 8 年
[圖 4 NormalDistribution,參數的 µ = 28 和 σ2 = 8 年

現在我將示範如何在受歡迎、 複雜和簡潔的語言,例如 C# 中實作建議的 DES。

 

實作

若要開發此模擬中,我會利用 OOP 範例的所有優點。其概念是要取得可能最容易閱讀的程式碼。科學應用程式通常會很複雜而難以了解,而且務必要試著讓它們盡可能清楚,讓其他人可以了解您的程式碼。我會開發以 C# 主控台應用程式的應用程式中所顯示的結構與 [圖 5

模擬應用程式的結構
[圖 5 模擬應用程式的結構

邏輯路徑很重要;請注意命名空間的結構如何維護它自己的常識 ︰ Simulation.DES.PopulationGrowth。

[事件] 資料夾包含 Event.cs 檔,定義列舉型別代表所有可能的事件,在模擬中 ︰

namespace DES.PopulationGrowth.Events
{
public enum Event
  {
Capable Engaging,
Birth EngageDisengage,
GetPregnant,
ChildrenCount,
TimeChildren,
    Die
  }
}

[物件] 資料夾包含每個類別相關的個人。這些是將受益良 OOP 的程式碼片段。有三個與個人相關的類別 ︰ 個人、 男性和女性。第一個是抽象類別和其他繼承。也就是說,男性和女性是個人。大部分的程式碼會在個別的類別。[圖 6 顯示其屬性和方法。

個別的類別的屬性和方法
[圖 6 屬性和方法的個別類別

以下是每個屬性的描述 ︰

  • 存留期 ︰ 個別的年齡。
  • 幾項 ︰ 如果個別 null 是單一的否則其幾,另一個個人。
  • 執行 ︰ 如果個別否則涉及關聯性,則為 false,則為 true。
  • 存留期 ︰ 年齡的個別故障了。
  • RelationAge: 個別可以開始關聯性的年齡。
  • TimeChildren: 時間以模擬 (不是天數) 的個別可以有子系。

其方法的詳細說明 ︰

  • 卸除 ︰ 結束兩個人之間的關聯性。
  • EndRelation: 決定是否應該結束作業關聯,請根據機率表中 [圖 1
  • FindPartner: 尋找可用的合作夥伴的個人。
  • SuitablePartner: 判斷個人是否適用於啟動的關聯性 (age 差異和相反性別)。
  • SuitableRelation: 決定是否某些個人可以開始關聯性。亦即是單一和啟動關聯的年齡。
  • ToString: 覆寫個別資訊表示為字串。

類別一開始會定義所有屬性和更新版本的建構函式,只是在設定期限 ︰

public abstract class Individual
  {
public int Age { get; set; }
public int RelationAge{ get; set; }
public int LifeTime{ get; set; }
public double TimeChildren{ get; set; }
public Individual Couple { get; set; }
protected Individual(int age)
{
  Age = age;
}

是只是邏輯測試,相當直接了當的 SuitableRelation 和 SuitablePartner 方法和 Engaged 屬性 ︰

public bool SuitableRelation()
{
return Age >= RelationAge&& Couple == null;
}
public bool SuitablePartner(Individual individual)
{
return ((individual is Male && this is Female) ||
(individual is Female && this is Male)) &&Math.Abs(individual.Age - Age) <= 5;
}  
public bool Engaged
{
get { return Couple != null; }
}

打開方法會將兩個欄位設定為 null,幾和更新版本上個別的中斷關聯性。它也會設定其有子系為 0,因為他們不再從事的時間。

public void Disengage()
{
Couple.Couple = null;
Couple = null;
TimeChildren = 0;
}

EndRelation 方法基本上是機率資料表,以判斷關聯性結尾的機率的翻譯。它會使用統一的隨機變數,以產生隨機值在範圍 [0,1] 相當於產生可接受的百分比。字典中模擬類別 (稍後說明) 會建立,並且保存組事件 (機率分配),因此散發關聯其散發至每個事件 ︰

public bool EndRelation(Dictionary<Event, IDistribution> distributions)
{
var sample =
  ((ContinuousUniform) distributions[Event.BirthEngageDisengage]).Sample();
if (Age >= 14 && Age <= 20 && sample <= 0.7)
return true;
if (Age >= 21 && Age <= 28 && sample <= 0.5)
return true;
if (Age >= 29 && sample <= 0.2)
return true;
return false;
}

中顯示的 FindPartner 方法 [圖 7, ,尋找可用的協力廠商的執行個體。它會收到做為輸入母體擴展] 清單中,模擬及散發字典的目前時間。

圖 7 FindPartner 方法

public void FindPartner(IEnumerable<Individual> population, int currentTime,
  Dictionary<Event, IDistribution> distributions)
{
foreach (var candidate in population)
if (SuitablePartner(candidate) &&
candidate.SuitableRelation() &&
((ContinuousUniform) distributions[Event.BirthEngageDisengage]).Sample() <= 0.5)
{
// Relate them
candidate.Couple = this;
Couple = candidate;
// Set time for having child
var childTime = ((Exponential)  distributions[Event.TimeChildren]).Sample()*100;
// They can have children on the simulated year: 'currentTime + childTime'.
candidate.TimeChildren = currentTime + childTime;
TimeChildren = currentTime + childTime;
break;
}
}

最後,ToString 方法定義個別的字串表示 ︰

public override string ToString()
{
Return string.Format("Age: {0} Lifetime {1}", Age, LifeTime);
}

男性類別是非常簡單 ︰

public class Male: Individual
{
public Male(int age) : base(age)
{
}
public override string ToString()
{
return base.ToString() + " Male";
}
}

女性類別是較為複雜,因為它包含的 pregnancy、 生日等等。類別定義會從所有屬性和建構函式宣告 ︰

public class Female :Individual
{
public bool IsPregnant{ get; set; }
public double PregnancyAge{ get; set; }
public double ChildrenCount{ get; set; }
public Female(int age) : base(age)
{
}
}

以下是女性類別的屬性 ︰

  • IsPregnant: 判斷是否懷孕此女性。
  • PregnancyAge: 決定的女人可以取得懷孕的年齡。
  • ChildrenCount: 表示要讓 birth 的子系數目。

而這裡是它所包含的方法 ︰

  • SuitablePregnancy: 判斷這個女人是否可以取得懷孕。
  • GiveBirth: 表示讓 birth 女人母體加入新的人員。
  • ToString:override: 用來代表女性做為字串的資訊。

SuitablePregnancy 是執行個體女人符合用來取得那些每個條件時,會輸出為 true 的測試方法 ︰

public bool SuitablePregnancy(intcurrentTime)
  {
return Age >= PregnancyAge && currentTime <= TimeChildren && ChildrenCount > 0;
}

中顯示的 GiveBirth 方法 [圖 8, ,程式碼會將切入初始化新的個人。

[圖 8 TheGiveBirth 方法

public Individual GiveBirth(Dictionary<Event, IDistribution> distributions, int currentTime)
  {
var sample =
  ((ContinuousUniform) distributions[Event.BirthEngageDisengage]).Sample();
var child = sample > 0.5 ? (Individual) newMale(0) : newFemale(0);
// One less child to give birth to
ChildrenCount--;
child.LifeTime = ((Poisson)distributions[Event.Die]).Sample();
child.RelationAge = ((Poisson)distributions[Event.CapableEngaging]).Sample();
if (child is Female)
{
(child as Female).PregnancyAge =
  ((Normal)distributions[Event.GetPregnant]).Sample();
(child as Female).ChildrenCount =
  ((Normal)distributions[Event.ChildrenCount]).Sample();
}
if (Engaged && ChildrenCount> 0)
{
TimeChildren =
  currentTime + ((Exponential)distributions[Event.TimeChildren]).Sample();
Couple.TimeChildren = TimeChildren;
}
else
TimeChildren = 0;
IsPregnant = false;
return child;
  }

統一的範例會產生以先判斷新的人員會性別,各有 50%(0.5) 的機率。ChildrenCount 值是 1,指出這個女人有一個較少子系給予 birth 來遞減。其餘程式碼與個別的初始化和重新設定一些變數產生關聯。

ToString 方法變更女性表示為字串 ︰

public override string ToString()
  {
return base.ToString() + " Female";
}

與個人相關的所有函式放在另外的類別,因為模擬類別現在會更簡單。屬性和建構函式是在程式碼的開頭 ︰

public class Simulation
  {
public List<Individual> Population { get; set; }
public int Time { get; set; }
private int _currentTime;
private readonly Dictionary<Event, IDistribution> _distributions ;

屬性和變數自述性且有些先前所述。模擬會持續時間表示的時間 (年),和 _currentTime 代表模擬的目前年份。在此例中所示的建構函式 [圖 9, ,是更複雜,因為它包含每個個別的隨機變數的初始化。

[圖 9 模擬類別的建構函式

public Simulation(IEnumerable<Individual> population, int time)
{
Population = new List<Individual>(population);
Time = time;
_distributions = new Dictionary<Event, IDistribution>
{
{ Event.CapableEngaging, new Poisson(18) },
{ Event.BirthEngageDisengage, new ContinuousUniform() },
{ Event.GetPregnant, new Normal(28, 8) },
{ Event.ChildrenCount, new Normal(2, 6) },
{ Event.TimeChildren, new Exponential(8) },
{ Event.Die, new Poisson(70) },
                                 };
foreach (var individual in Population)
{
// LifeTime
individual.LifeTime = ((Poisson) _distributions[Event.Die]).Sample();
// Ready to start having relations
individual.RelationAge = ((Poisson)_distributions[Event.CapableEngaging]).Sample();
// Pregnancy Age (only women)
if (individual is Female)
                {
(individual as Female).PregnancyAge = ((Normal) _distributions[Event.GetPregnant]).Sample();
(individual as Female).ChildrenCount = ((Normal)_distributions[Event.ChildrenCount]).Sample();
}
}
}

最後,Execute 方法中,顯示 圖 10, ,都是模擬的所有邏輯。

[圖 10 Execute 方法

public void Execute()
{
while (_currentTime< Time)
{
// Check what happens to every individual this year
for (vari = 0; i<Population.Count; i++)
{
var individual = Population[i];
// Event -> Birth
if (individual is Female&& (individual as Female).IsPregnant)
Population.Add((individual as Female).GiveBirth(_distributions,
  _currentTime));
// Event -> Check whether someone starts a relationship this year
if (individual.SuitableRelation())
individual.FindPartner(Population, _currentTime, _distributions);
// Events where having an engaged individual represents a prerequisite
if (individual.Engaged)
{
// Event -> Check whether arelationship ends this year
if (individual.EndRelation(_distributions))
individual.Disengage();
// Event -> Check whether a couple can have a child now
if (individual is Female &&
(individual as Female).SuitablePregnancy(_currentTime))
(individual as Female).IsPregnant = true;
}
// Event -> Check whether someone dies this year
if (individual.Age.Equals(individual.LifeTime))
{
// Case: Individual in relationship (break relation)
if (individual.Engaged)
individual.Disengage();
Population.RemoveAt(i);
}
individual.Age++;
_currentTime++;
}
}

外部迴圈中的反覆項目代表模擬中經過的年份。內部迴圈會逐一查看在該特定年度內部人員可能發生的事件。

母體擴展長時間的發展,請設定新的主控台應用程式,顯示 [圖 11

[圖 11 檢視一段時間的母體擴展

static void main()
{
var population = new List<Individual>
{
new Male(2),
new Female(2),
new Male(3),
new Female(4),
new Male(5),
new Female(3)
};
var sim = new Simulation(population, 1000);
sim.Execute();
// Print population after simulation
foreach (var individual in sim.Population)
Console.WriteLine("Individual {0}", individual);
Console.ReadLine();
}

圖 12 顯示 1000 年後的母體擴展。

1000 年後的母體擴展
[圖 12 1000 年後的母體擴展

在本文中我開發離散事件模擬若要查看母體擴展時間的變化。物件導向的方法,而獲得證明來取得可讀取、 簡潔的程式碼,讀者可以試著改善必要時非常有用。


Arnaldo Pérez Castaño是電腦科學家古巴基礎。他是一系列的程式設計書籍的作者 — 「 JavaScript Fácil 」,「 HTML y CSS Fácil,"和"Python Fácil 」 (Marcombo s.a 跳)。他的專長包括 Visual Basic、 C#、.NET Framework 和人工智慧、 和他服務提供透過 freelancer nubelo.com。影片和音樂都他的最愛。他的連絡 arnaldo.skywalker@gmail.com。

感謝下列 Microsoft 技術專家人員檢閱這份文件 ︰ James McCaffrey