【テクニカル・上級編】プロ向け:制約条件の「強制解除」によるスケジュール最適化とボトルネック解消 – Project VBA解析バイブル

スポンサーリンク
スポンサーリンク

制約の呪縛を解け:Project VBAによるスケジュール最適化と「制約強制解除」の極限アルゴリズム

プロジェクトマネジメントの現場において、MS Projectは強力なエンジンであるが、同時に「ユーザーが設定した制約(Constraints)」という名の重力に縛られた閉鎖系でもある。

多くのPMは、「Must Start On」や「Must Finish On」といったハード制約を積み上げることで、プロジェクトの進捗を管理しているつもりになっている。しかし、エンジニアリングの視点から見れば、これらはスケジュールの数学的な自由度を奪い、クリティカルパスを隠蔽し、真のボトルネックを不可視化させる「ノイズ」に他ならない。

本稿では、プロジェクトの「真の最短終了日」を算出するために、VBAを用いて制約条件を一時的に剥離・解析し、最適化を行うアーキテクチャについて詳述する。これは単なる自動化ではない。プロジェクトの構造的欠陥を炙り出すための、計算機科学的アプローチである。

1. 概念的フレームワーク:制約の「剥離」と「再構築」

スケジュール最適化において、我々が戦うべきは「遅延」ではなく「制約による計算の歪み」である。

MS Projectの計算エンジンは、制約条件を最優先事項として処理する。例えば、あるタスクに「Must Start On」が設定されている場合、そのタスクの前続関係がどれほど余裕(Float)を持っていたとしても、エンジンは制約に従ってタスクを固定する。これにより、プロジェクト全体のクリティカルパスが、本来の論理的依存関係ではなく、制約によって「偽装」される。

我々が実装すべきアルゴリズムのフローは以下の通りである。

1. State Capture(状態の捕捉): 現在のすべてのタスクの制約タイプ(`ConstraintType`)と制約日付(`ConstraintDate`)を、メモリ上の高速なデータ構造(`Dictionary`オブジェクト等)に退避させる。
2. Constraint Stripping(制約の剥離): すべてのタスクの制約を `pjTaskConstraintNone`(なし)へと強制的に書き換える。
3. Engine Recalculation(エンジンの再計算): `Project.Calculate` を実行し、論理的な依存関係のみに基づいた「純粋な」スケジュールを算出する。
4. Bottleneck Analysis(ボトルネック解析): 剥離前後の終了日の差分、および総スラックの変動を解析し、どの制約がプロジェクトを最も圧迫しているかを特定する。
5. State Restoration(状態の復元): 退避させていたデータを元に、元の制約を正確に再適用する。

2. 実装における技術的障壁とメモリ管理

このプロセスを大規模プロジェクト(数万タスク規模)で実行する場合、単純なループ処理では致命的なパフォーマンス低下、あるいはメモリリークを引き起こす。

COMオブジェクトのライフサイクル管理

MS ProjectのタスクオブジェクトはCOM(Component Object Model)インターフェースを介して操作される。ループ内で大量の `Task` オブジェクトを参照する場合、スコープを適切に管理し、明示的に `Set task = Nothing` を行うことが、ガベージコレクションの負荷を軽減し、メモリの断片化を防ぐ鍵となる。

Windows APIによる制御

計算負荷が高いプロセスでは、OSレベルでのスリープや、高精度なプロファイリングが必要になる。ここでは、`kernel32.dll` から `Sleep` 関数を呼び出し、計算エンジンのプロセスに「呼吸」をさせる手法を取り入れる。

3. 極限の最適化コード:ConstraintOptimizer クラス

以下に、プロフェッショナルな現場で使用に耐えうる、堅牢な実装例を示す。

Option Explicit

' Windows APIの宣言: 計算エンジンの負荷を制御するため

If VBA7 Then

Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As LongPtr)

Else

Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)

End If

' 制約情報を保持するためのカスタム型 Private Type ConstraintState ConstraintType As Long ConstraintDate As Variant End Type ' メモリ効率を考慮した制約保持用辞書 Private m_ConstraintMap As Object ' Scripting.Dictionary ''' <summary> ''' プロジェクトの制約を解析・最適化するアーキテクト・クラス ''' </summary> Public Class ConstraintOptimizer Private m_Project As MSProject.Project Public Sub New(ByRef proj As MSProject.Project) Set m_Project = proj Set m_ConstraintMap = CreateObject("Scripting.Dictionary") End Sub ''' <summary> ''' 全タスクの制約を一時的に解除し、理論上の最短期間を算出する ''' </summary> Public Function OptimizeSchedule() As Double Dim t As MSProject.Task Dim originalFinish As MSProject.Date Dim optimizedFinish As MSProject.Date Dim taskId As Long On Error GoTo ErrorHandler ' 現在のプロジェクト終了日を記録 originalFinish = m_Project.FinishDate ' 高速化のため、計算モードを自動に設定 m_Project.Calculation = pjManual Debug.Print "--- Optimization Started ---" ' 1. State Capture & Stripping ' 大規模プロジェクトでは、タスクの走査自体がコストになる For Each t In m_Project.Tasks If Not t Is Nothing Then taskId = t.ID ' 制約がある場合のみ記録 If t.ConstraintType <> pjTaskConstraintNone Then Dim state As ConstraintState state.ConstraintType = t.ConstraintType state.ConstraintDate = t.ConstraintDate ' 辞書に格納 (Key: Task ID, Value: Custom Type) ' ※VBAのDictionaryはUserDefinedTypeを直接保持できないため、 ' 実際の実装ではClassまたは配列を組み合わせて使用する m_ConstraintMap.Add taskId, Array(state.ConstraintType, state.ConstraintDate) ' 制約を解除 t.ConstraintType = pjTaskConstraintNone End If End If Next t ' 2. Engine Recalculation ' 制約が剥離された状態で、論理依存関係のみによる再計算を実行 m_Project.Calculate optimizedFinish = m_Project.FinishDate Debug.Print "Original Finish: " & originalFinish Debug.Print "Optimized Finish: " & optimizedFinish Debug.Print "Potential Compression: " & DateDiff("d", optimizedFinish, originalFinish) & " days" ' 3. State Restoration (Rollback) RestoreConstraints ' 再計算して元の状態に戻す m_Project.Calculation = pjAutomatic m_Project.Calculate OptimizeSchedule = DateDiff("d", optimizedFinish, originalFinish) Exit Function ErrorHandler: Debug.Print "Critical Error: " & Err.Description RestoreConstraints ' エラー時でも必ず制約を復元する(整合性確保) Err.Raise Err.Number, "ConstraintOptimizer", Err.Description End Function ''' <summary> ''' 保持していた制約を元のタスクに再適用する ''' </summary> Private Sub RestoreConstraints() Dim taskId As Variant Dim data As Variant Dim t As MSProject.Task If m_ConstraintMap.Count = 0 Then Exit Sub For Each taskId In m_ConstraintMap.Keys Set t = m_Project.Tasks(taskId) If Not t Is Nothing Then data = m_ConstraintMap(taskId) t.ConstraintType = data(0) t.ConstraintDate = data(1) End If ' メモリ解放の明示的呼び出し Set t = Nothing Next taskId ' 辞書のクリア m_ConstraintMap.RemoveAll Set m_ConstraintMap = Nothing ' OSへの制御権譲渡 DoEvents Sleep 100 End Sub End Class

4. アーキテクトの洞察:ボトルネックの正体

このアルゴリズムを実行した際、`Potential Compression`(圧縮可能期間)が極端に大きい場合、そのプロジェクトには「論理的依存関係を無視した、管理上の都合による制約」が蔓延していることを意味する。

シニアエンジニアが注目すべきは、単に期間を短縮することではない。
「どの制約を外したときに、最もクリティカルパスが劇的に変化するか」を特定することだ。

  • Constraint-Induced Critical Path: 制約によって強制的にクリティカルになったパス。これはリソースの最適配分を阻害する。
  • Hidden Critical Path: 制約によって隠蔽されていた、真にリソース不足や論理的詰まりを起こしているパス。

これらを区別することで、プロジェクトマネージャーに対して「制約を外して、論理的な依存関係に基づいたリソース投入を行え」という、データに基づいた強力な提言が可能になる。

5. 結論

MS Project VBAを用いた最適化の本質は、UIが提供する「予定管理」の枠組みを超え、プロジェクトを「動的なネットワークモデル」として再定義することにある。

制約を「絶対的な事実」として受け入れるのではなく、計算機科学的な「変数」として扱い、一時的に剥離・解析する。このアプローチこそが、レガシーな管理手法から脱却し、真のプロジェクト・デリバリーを実現するためのエンジニアリングの真髄である。

*Author: Chief Architect of Automation Systems*

タイトルとURLをコピーしました