Pythonでのネットワークモデル構築の複雑さ

マネージャーがプロジェクトを管理するために使用する主なツールは何ですか? プロジェクトマネージャーの主なツールは、プロジェクトのネットワークモデルに基づくスケジュールであると見なされます。 Pythonで作業のネットワークモデルを実装したことがあります(コードと説明はこちら )。 以下は、行われた作業から学んだ教訓です。



最初に作業を作成してから、接続を作成します



ネットワークモデルを構築するとき、作業を作成し、それらの間の接続を確立するためにどのような順序で問題がしばしば発生しますか? 最も明白なのは2段階のアプローチです。最初にすべてのモデルの作業が作成され、次にそれらの間に関係が確立されます。 このアプローチは、システムがまだ作成されていない作業との通信を確立しようとするときに、これら2つの操作の並列実行中に発生するKeyError: '101'タイプのエラーを回避します。



もちろん、作業を作成し、それらの間の接続を確立するための2つの連続した操作の合計実行時間は、これらの操作が並行して実行されるアルゴリズムを使用して最適化できます。 ただし、数万のジョブがある大規模なモデルであっても、シーケンシャルアルゴリズムは非常に高速に機能します。 したがって、プロジェクトの実装に時間的な制約がある場合、従来の2段階のアプローチで十分に対応できます。



モデルを構築した後、再カウントします



ネットワークモデルを構築するときに、ジョブ間で接続が確立されるたびに詳しく説明する価値はありますか? 一方で、絶え間ない再計算により、モデルを最新の状態に保つことができます。 一方、再カウントは構築時間を増加させます。



比較のために、2つの機能が実装されました。



  1. build_model_by_method() -モデルの再計算でビルドします;
  2. build_model_by_assignment() -モデルを再カウントせずにビルドします。


その後、100、1000、10000作品のモデルで実行時間を比較しました。



network.py
from predict import Activity import xml.etree.ElementTree as ET import sys import timeit from timeit import Timer #     def get_child(child, activity_field): text = child.find(activity_field).text if text is None: return None return int(text) #       def build_model_by_method(filename): sys.setrecursionlimit(10000) f = open(filename,'r') tree = ET.parse(f) root = tree.getroot() schedule = {} next = {} for child in root.findall('Activity'): id = child.find('id').text start_date = get_child(child,'start_date') finish_date = get_child(child,'finish_date') duration = get_child(child,'duration') not_early_date = get_child(child,'not_early_date') a = Activity(id, start_date, finish_date, duration, not_early_date) schedule[id] = a next_activity = '' if child.find('next_activity').text is None else child.find('next_activity').text next[id] = next_activity for key in schedule: if next[key] != '': for next_id in next[key].split(';'): schedule[key].append_next(schedule[next_id]) sys.setrecursionlimit(1000) #       def build_model_by_assignment(filename): f = open(filename,'r') tree = ET.parse(f) root = tree.getroot() schedule = {} next = {} for child in root.findall('Activity'): id = child.find('id').text start_date = get_child(child,'start_date') finish_date = get_child(child,'finish_date') duration = get_child(child,'duration') not_early_date = get_child(child,'not_early_date') a = Activity(id, start_date, finish_date, duration, not_early_date) schedule[id] = a next_activity = '' if child.find('next_activity').text is None else child.find('next_activity').text next[id] = next_activity for key in schedule: if next[key] != '': for next_id in next[key].split(';'): schedule[key].next_activity.append(schedule[next_id]) #     print('Test for 100 activities:') t1 = Timer("build_model_by_method('data/activity_100.xml')", "from __main__ import build_model_by_method") print("build_model_by_method", t1.timeit(number = 1000)) t2 = Timer("build_model_by_assignment('data/activity_100.xml')", "from __main__ import build_model_by_assignment") print("build_model_by_assignment", t2.timeit(number = 1000)) print('Test for 1000 activities') t3 = Timer("build_model_by_method('data/activity_1000.xml')", "from __main__ import build_model_by_method") print("build_model_by_method", t3.timeit(number = 1000)) t4 = Timer("build_model_by_assignment('data/activity_1000.xml')", "from __main__ import build_model_by_assignment") print("build_model_by_assignment", t4.timeit(number = 1000)) print('Test for 10000 activities') t5 = Timer("build_model_by_method('data/activity_10000.xml')", "from __main__ import build_model_by_method") print("build_model_by_method", t5.timeit(number = 1000)) t6 = Timer("build_model_by_assignment('data/activity_10000.xml')", "from __main__ import build_model_by_assignment") print("build_model_by_assignment", t6.timeit(number = 1000))
      
      







比較結果:



 $ python network.py Test for 100 activities: build_model_by_method 1.7820062519999738 build_model_by_assignment 1.426311435999878 Test for 1000 activities build_model_by_method 18.998158786999966 build_model_by_assignment 14.216093206999858 Test for 10000 activities build_model_by_method 249.93449528199994 build_model_by_assignment 148.85600239800033
      
      







ご覧のとおり、作業量が多いほど、再計算が使用されていない関数に比べて、再計算を使用してネットワークモデルを構築する機能が遅くなります。



再帰の深さを制御する



プロジェクトのネットワークモデルは、作品と作品間の関係で構成されます。 ネットワークに関する追加情報がない場合、再帰アルゴリズムが再計算に使用されます。 このようなアルゴリズムは、ネットワークを順番に通過し、それらのパラメーター(期間、開始日、終了日など)を再計算することで構成されます。 ネットワークでの作業が多いほど、再帰の深さは大きくなります。



Pythonでは、デフォルトで再帰の深さの制限が設定されていることが知られています-再帰呼び出しは1000を超えません。 sysモジュールのgetrecursionlimit()メソッドを使用して、この制限を取得します。



 >>> import sys >>> sys.getrecursionlimit() 1000
      
      





数万単位で測定されるジョブの数である大規模なネットワークで作業している場合、通常の状況は再帰深度の過剰であり、その結果、 RecursionError:最大再帰深度を超えるタイプのエラーが発生します 。 このエラーは、モデルの構築または再計算の停止とシステムの崩壊につながります。



エラーを防止し、大規模なネットワークを構築して再集計するには、再帰の深さの制限を増やす必要があります。 また、 sysモジュールのsetrecursionlimit()メソッドはこれを支援します。



 >>> import sys >>> sys.setrecursionlimit(10000) >>> sys.getrecursionlimit() 10000
      
      





再帰の深さを増やすにはどのような価値がありますか? この質問に対する答えは、ネットワークモデルの構造によって異なります。 構造が不明または十分に複雑な場合は、ネットワーク上のジョブの数に等しい値に制限を設定することをお勧めします。 再帰アルゴリズムは、作業のすべてまたは一部で渡されます。 したがって、再帰の深さはネットワーク内のジョブの数を超えてはなりません。



そして最後に...



プロジェクト管理はファッショナブルです。 しかし、ファッションはまだ効果的ではありません。 Microsoft Projectなど、市場にあるネットワークモデルを変換するためのソフトウェアソリューションがあります。 ただし、それらに組み込まれたアルゴリズムは、対応するソフトウェアのベンダーのみが利用できます。



この記事は、ネットワークプロジェクトモデルを構築および再計算するためのオープンモジュールを開発した経験に基づいています。 この記事で学んだ教訓が、理論的および純粋に実用的な観点から読者に役立つことを願っています。 この記事に興味がある場合は、モジュールが開発されるにつれて、将来発生する新しい知識を共有します。



All Articles