Relationship Free Float and Float Paths in Multi-Calendar Projects (P6 MFP Free Float Option)

This is a short article about the calculation and use of Relationship Free Float in Oracle Primavera P6, a project scheduling tool.  [It amends my previous entry on Total Float and Free Float Options in P6’s Multiple Float Path Analysis]

[The following few paragraphs are cribbed (with some edits to reflect improved understanding) from my own contribution to a discussion on Planning Planet a few years ago.]

Traditional notions of Total Float and Free Float are tied to the activities in the network, but they are not sufficient for evaluating logical float paths in complex CPM schedules, especially when variable calendars and/or late constraints are imposed.  Relationship floats are needed for identifying near-driving relationships and for multiple-float-path analyses.

Documentation seems very sketchy, but based on my own observations I believe relationship floats in P6 are calculated similarly to activity floats – that is

  1. The early and late dates of relationships are computed by treating them as activities (with single FS+0 links at each end) in the forward and backward passes through the network (Duration equals lag, normally zero).
  2. Relationship total float (RelTF) = relationship late finish (RelLF)  –  relationship early finish (RelEF); 
  3. Relationship free float (RelFF) = (Early Date of Relationship Successor Activity, ES for “FS” and “SS” links, EF for “FF” and “SF” links) – RelEF

The calendars used for the calculations seem to be as follows:

  • Early dates use predecessor calendar (from the forward pass)
  • Late dates use successor calendar (from the backward pass)
  • Relationship free float and total float use the predecessor calendar.

[Apr’19 Edit: Relationship Successor Total Float and Relationship Successor Free Float are derived from the same relationship dates, but using the successor calendar rather than the predecessor calendar.  (Figures now show both measures of relationship free float.)]

With multiple calendars, the driving and near driving paths revealed by Multiple Float Path (MFP) analysis are only partly correlated to the Relationship Free Float (and Relationship Successor Free Float) values displayed by P6.  These may require careful scrutiny to avoid misinterpretation in complex, multi-calendar projects, for the following reasons:

a) A relationship is “driving” when the successor activity possesses zero working time between the lag-adjusted predecessor and successor dates.  That is, the Relationship Free Float – according to the SUCCESSOR’s calendar (“Relationship Successor Free Float”) – is zero.

b) The Relationship Free Float that P6 displays (and that P6 appears to use as the primary path-allocation parameter for Float Paths >1, using the “Free Float” option) is computed according to the PREDECESSOR’s calendar.

Consider the simple schedule illustrated below, wherein the “Assembly” activity has three predecessors whose calendars differ from Assembly’s.  Assembly utilizes a standard 5-day calendar, while Machining 1 and Machining 2 are performed using automated equipment on a 7×24 calendar.  The Assembly Plan must be prepared during a weekly resource coordination meeting that only happens on Thursdays.

As the figure shows, the Longest Path (red bars) is comprised of the two Machining activities (finishing 12 hours apart) followed by Assembly.  As expected, both machining activities are marked as driving predecessors (there is zero relationship free float for either one according to Assembly’s calendar), and the Assembly Plan predecessor relationship is marked as non-driving.

P6’s MFP algorithm (Free Float option) allocates the three predecessors of the Assembly activity to three different Float Paths:

Float Path 1 (“most critical path”):  Machining 2, a driving predecessor, with Relationship Free Float (according to its own calendar) of 1.5 days.

Float Path 2 (“1st sub-critical path”): Assembly Plan, a non-driving predecessor, with Relationship Free Float (according to its own calendar) of 0.0 days.

Float Path 3 (“2nd sub-critical path”): Machining 1, a driving predecessor, with Relationship Free Float (according to its own calendar) of 2.0 days.

This is what the P6 Help file says for the MFP Free Float option.

Free Float – Choose this option to define critical float paths based on longest path. The most critical path will be identical to the critical path that is derived when you choose to define critical activities as Longest Path in the General tab. In a multicalendar project, the longest path is calculated by identifying the activities that have an early finish equal to the latest calculated early finish for the project and tracing all driving relationships for those activities back to the project start date. After the most critical path is identified, the module will calculate the remaining sub-critical paths.

Unfortunately, the underlined portion is not consistent with the observed behavior, where the Longest Path (i.e. the driving path to project completion) is divided between Float Paths 1 and 3.

Now we modify the schedule to give both machining activities exactly the same duration (2.5 days on a 7×24 calendar), so they both finish at 8:00 PM on Saturday.  They remain driving activities for Assembly and also have exactly the same Relationship Free Float (1.5d).  But now they trade Float Paths: Machining 1 is now on Float Path 1, while Machining 2 is on Float Path 3.

After restoring the original machining schedule (finishing 12 hours apart on Saturday), now we assign a different calendar to the one that finishes first – Machining 1 is now on a 6×24 calendar, with Sunday no longer a workday.  Consequently, the Machining 1 relationship now possesses only 1.0 days of Relationship Free Float, 0.5 days less than the Machining 2 relationship.  Nevertheless, Machining 2 stays on Float Path 1, while Machining 1 is still relegated to Float Path 3.

Several tentative conclusions seem apparent from these observations:

  1. For Float Path 1 only, the Float Path is allocated to the driving predecessor which is satisfied the latest of all the driving predecessors – without regard to Relationship Free Float.  Each of the remaining predecessors will be allocated to a new (higher-numbered) Float Path.
  2. If two or more driving predecessors finish at exactly the same time, then only one of them – selected by Activity ID, Activity Name, or some other non-logic-related criteria [Early Start, then Activity ID according to others] – will be assigned to Float Path 1.
  3. For Float Paths >1, the current Float Path is allocated to the remaining predecessor (driving or not) with the lowest Relationship Free Float of all remaining predecessors.  Each of the other remaining predecessors will be allocated to a new (higher-numbered) Float Path.  As  a consequence, legitimate members of the project’s Longest Path may be relegated to non-contiguous float paths far from the “most critical” Float Path 1.   (The fact that driving predecessors are NOT prioritized is an unfortunate weakness, in my opinion, of an otherwise robust logic analysis method.)

I’ve reviewed a number of P6 schedule submittals that seem to confirm these observations in addition to a few more:

  1. For Float Paths >1, if the remaining predecessors are driving AND have the same Relationship Free Float, then the current Float Path is allocated to the remaining predecessor that finishes latest.  Each of the other remaining predecessors will be allocated to a new (higher-numbered) Float Path.
  2. Consequently, in case of parallel driving paths, FF predecessors will be preferred (i.e. be allocated to lower-numbered Float Paths) over FS predecessors.
  3. [According to a paper first presented by Mssrs Roger Nelson and Patrick Kelly at the 2018 Annual Meeting of AACE International, and later presented at the AACE SoCal chapter meeting on 19Apr’19, the next two “tie-breakers” for path assignment are the latest Early Start date and, finally, the Activity ID.]
  4. In most cases, an ALAP-constrained predecessor automatically creates a parallel (and false) driving path with Relationship Free Float = 0.  Extensive use of ALAP constraints can lead to a proliferation of false Float Paths in the MFP results.

How to Filter for Leads and Lags in Microsoft Project

Here are two macro procedures – LagFilter and LeadFilter – for creating and applying a filter to show only tasks with leads or lags above a certain threshold value.

Using Lags and/or Leads (i.e. negative lags) in Project Scheduling is discouraged for good reasons.  In most project scheduling software it is easy to identify violations by creating a filter to show only tasks with Lags (or Leads) in their predecessor relationships.

In Microsoft Project, lags are indicated by the presence of a “+” character in the task’s predecessors field.  Here is the corresponding filter specification.

You can augment the filter to show tasks on both sides of the lags:

Leads are indicated (in MSP) by the presence of a “-” character in the task’s predecessors field.  Here is the corresponding filter specification (showing both sides of the leads).

Unfortunately, these simple filters don’t help to differentiate high-lag/lead relationships from low-lag/lead relationships.  All of them are lumped together in the same filter.  It is possible to create filters for only the highest lead/lag values using a number of custom fields with complex formulas.  It is far simpler, however, to create the necessary filters using vba/macros.

Here are two macro procedures – LagFilter and LeadFilter – for creating and applying a filter to show only tasks with leads or lags above a certain threshold value.  Choosing a zero-value threshold leads to the same results as the simple filters above.  These procedures work by examining the lag of each predecessor relationship of every task in the active project, comparing it to the specified threshold value.  If the lag is high enough, then the Flag6 field of the task will be set to “yes”.  At the end, a new filter is made and applied.  Note that these macros will overwrite any values in the Flag6 field of your project, unless Flag6 is already controlled by a formula.  (In that case, the macros will crash with an error.)  You may need to edit the macros to select a different Flag field.

[I’ve edited these macros… a) to allow the user to select whether to show both sides or only one side (the successor) of each lead/lag; b) to avoid null filters (i.e. blank screens) by applying the filter only when leads or lags matching the criterion are found; and c) to allow work-time, elapsed-time, or percentage-based lead/lag criteria.]

To apply these, simply copy and paste them into a new module in your Project Visual Basic editor (VBE).  (I typically keep these modules in the global.mpt file, though that practice is not always recommended.)  You can then run them directly from the VBE or from custom buttons that you link to the macros through the  “Customize the Ribbon” dialog.

Sub LagFilter()
'Copyright 15August2018 by T.Boyle PE, PSP
'This macro collects user input and filters the active project to display only tasks
'with dependency lags that are less than the user-specified threshold.  The threshold
'may be specified in units of working time, elapsed time, or percentage.  The filter
'is applied using the Flag6 custom field.
'FLAG6 WILL BE OVER-WRITTEN, IF POSSIBLE,OR THIS MACRO WILL CRASH.

    Dim t As Task
    Dim d As TaskDependency
    Dim LagUnits As String
    Dim ElapsedUnits As Boolean
    Dim LagThreshold As Double
    Dim LagLimit As String
    Dim SuccsOnly As Boolean
    Dim Filtername As String
    Dim Found As Boolean
    
    Found = False
    'Get lag units from user
    LagUnits = (InputBox("Enter lag units (m,h,d,w,mo,em,eh,ed,ew,emo,%):"))
    'Validate units
    Select Case LagUnits
        Case "m", "h", "d", "w", "mo", "em", "eh", "ed", "ew", "emo", "%"
            'Get the filter limit from user
            LagThreshold = (InputBox("Enter lag threshold (" & LagUnits & "):"))
            LagLimit = LagThreshold & " " & LagUnits
            If Left(LagUnits, 1) = "e" Then ElapsedUnits = True
        Case Else
            MsgBox ("Invalid lag units entered (case-sensitive). Aborting.")
            Exit Sub
    End Select
    'Convert units
    Select Case LagUnits
        Case "m"
            'proceed
        Case "h"
            LagThreshold = LagThreshold * 60
        Case "d"
            LagThreshold = LagThreshold * 60 * ActiveProject.HoursPerDay
        Case "w"
            LagThreshold = LagThreshold * 60 * ActiveProject.HoursPerWeek
        Case "mo"
            LagThreshold = LagThreshold * 60 * ActiveProject.HoursPerDay * ActiveProject.DaysPerMonth
        Case "em"
            'proceed
        Case "eh"
            LagThreshold = LagThreshold * 60
        Case "ed"
            LagThreshold = LagThreshold * 60 * 24
        Case "ew"
            LagThreshold = LagThreshold * 60 * 24 * 7
        Case "emo"
            LagThreshold = LagThreshold * 60 * 24 * 30
        Case "%"
            'proceed
    End Select
    
    If MsgBox("Display both Predecessors and Successors?" & vbCrLf & "(""Yes"" shows each lag twice. Default " _
            & "shows Successors Only)", vbQuestion + vbYesNo + vbDefaultButton2, "???") = vbYes Then
        SuccsOnly = False
        Filtername = "HasLagsAboveThreshold"
    Else
        SuccsOnly = True
        Filtername = "HasPredecessorLagsAboveThreshold"
    End If
    
    For Each t In ActiveProject.Tasks
        If Not t Is Nothing Then
            Call ClearT(t)
            For Each d In t.TaskDependencies
                If (d.To = t) Or (SuccsOnly = False) Then
                    If (d.Lag > 0 And LagThreshold = 0) Or (d.Lag >= LagThreshold And LagThreshold > 0) Then
                            If (d.LagType = 19 And LagUnits = "%") Then
                                Call MarkT(t, Found)
                            ElseIf (d.LagType Mod 2 = 1 And (Not ElapsedUnits) And (LagUnits <> "%")) Then
                                Call MarkT(t, Found)
                            ElseIf (d.LagType Mod 2 = 0 And ElapsedUnits) Then
                                Call MarkT(t, Found)
                            End If
                    End If
                End If
            Next d
        End If
    Next t
    
    If Found Then
        FilterEdit Name:=Filtername, TaskFilter:=True, Create:=True, OverwriteExisting:=True, FieldName:="Flag6", _
            Test:="equals", Value:="Yes", ShowInMenu:=True, ShowSummaryTasks:=True
        FilterApply Name:=Filtername
        MsgBox ("Filter applied: " & Filtername & vbCrLf & "Filter Threshold: " & LagLimit)
    Else
        MsgBox ("No lags found above threshold (" & LagLimit & "). No filter applied")
    End If

End Sub

Sub LeadFilter()
'Copyright 15August2018 by T.Boyle PE, PSP
'This macro collects user input and filters the active project to display only tasks
'with dependency leads (i.e. negative lags) that are less than the user-specified threshold.
'The threshold may be specified in units of working time, elapsed time, or percentage.
'The filter is applied using the Flag6 custom field.
'FLAG6 WILL BE OVER-WRITTEN, IF POSSIBLE,OR THIS MACRO WILL CRASH.

    Dim t As Task
    Dim d As TaskDependency
    Dim LeadUnits As String
    Dim ElapsedUnits As Boolean
    Dim LeadThreshold As Double
    Dim LeadLimit As String
    Dim SuccsOnly As Boolean
    Dim Filtername As String
    Dim Found As Boolean
    
    Found = False
    'Get Lead units from user
    LeadUnits = (InputBox("Enter Lead units (m,h,d,w,mo,em,eh,ed,ew,emo,%):"))
    'Validate units
    Select Case LeadUnits
        Case "m", "h", "d", "w", "mo", "em", "eh", "ed", "ew", "emo", "%"
            'Get the filter limit from user
            LeadThreshold = (InputBox("Enter Lead threshold (" & LeadUnits & "):"))
            LeadLimit = LeadThreshold & " " & LeadUnits
            If Left(LeadUnits, 1) = "e" Then ElapsedUnits = True
        Case Else
            MsgBox ("Invalid Lead units entered (case-sensitive). Aborting.")
            Exit Sub
    End Select
    'Convert units
    Select Case LeadUnits
        Case "m"
            'proceed
        Case "h"
            LeadThreshold = LeadThreshold * 60
        Case "d"
            LeadThreshold = LeadThreshold * 60 * ActiveProject.HoursPerDay
        Case "w"
            LeadThreshold = LeadThreshold * 60 * ActiveProject.HoursPerWeek
        Case "mo"
            LeadThreshold = LeadThreshold * 60 * ActiveProject.HoursPerDay * ActiveProject.DaysPerMonth
        Case "em"
            'proceed
        Case "eh"
            LeadThreshold = LeadThreshold * 60
        Case "ed"
            LeadThreshold = LeadThreshold * 60 * 24
        Case "ew"
            LeadThreshold = LeadThreshold * 60 * 24 * 7
        Case "emo"
            LeadThreshold = LeadThreshold * 60 * 24 * 30
        Case "%"
            'proceed
    End Select
    
    If MsgBox("Display both Predecessors and Successors?" & vbCrLf & "(""Yes"" shows each Lead twice. Default " _
            & "shows Successors Only)", vbQuestion + vbYesNo + vbDefaultButton2, "???") = vbYes Then
        SuccsOnly = False
        Filtername = "HasLeadsAboveThreshold"
    Else
        SuccsOnly = True
        Filtername = "HasPredecessorLeadsAboveThreshold"
    End If
    
    For Each t In ActiveProject.Tasks
        If Not t Is Nothing Then
            Call ClearT(t)
            For Each d In t.TaskDependencies
                If (d.To = t) Or (SuccsOnly = False) Then
                    If (d.Lag < 0 And LeadThreshold = 0) Or (d.Lag <= -1 * LeadThreshold And LeadThreshold > 0) Then
                            If (d.LagType = 19 And LeadUnits = "%") Then
                                Call MarkT(t, Found)
                            ElseIf (d.LagType Mod 2 = 1 And (Not ElapsedUnits) And (LeadUnits <> "%")) Then
                                Call MarkT(t, Found)
                            ElseIf (d.LagType Mod 2 = 0 And ElapsedUnits) Then
                                Call MarkT(t, Found)
                            End If
                    End If
                End If
            Next d
        End If
    Next t
    
    If Found Then
        FilterEdit Name:=Filtername, TaskFilter:=True, Create:=True, OverwriteExisting:=True, FieldName:="Flag6", _
            Test:="equals", Value:="Yes", ShowInMenu:=True, ShowSummaryTasks:=True
        FilterApply Name:=Filtername
        MsgBox ("Filter applied: " & Filtername & vbCrLf & "Filter Threshold: " & LeadLimit)
    Else
        MsgBox ("No Leads found above threshold (" & LeadLimit & "). No filter applied")
    End If

End Sub

Sub ClearT(t As Task)
    t.Flag6 = "No"
End Sub
Sub MarkT(ByRef t As Task, ByRef Found As Boolean)
    t.Flag6 = "Yes"
    Found = True
End Sub

Neither the simple filter nor the macro provided here implements the algorithm used by the Project Logic Checker in our BPC Logic Filter Add-In, which incorporates a slightly different premise.  That is: a relationship (or combination of relationships) with (positive or negative) lag may be the most effective and efficient method for modeling the true sequential restraints of the work, but only when the lag represents a relatively small proportion of the durations of the related tasks.  Thus, the Project Logic Checker flags tasks where the relationship lead/lag exceeds a certain percentage of the associated task durations.

BPC Logic Filter in Microsoft Project 2013/2016

During its development, we targeted BPC Logic Filter – our Add-In for analyzing project schedules – for use with Microsoft Project (MSP) 2010.  After all, we developed the Add-In essentially for our own use, and MSP 2010 has been a regular tool for us (in Windows 7 boxes) since its inception.  Our most recent computer purchase brought with it necessary upgrades to 2016 versions of MS Office and MSP, all running the 64-bit flavor on a Windows 10 Workstation.

Now that I’ve had a chance to directly test BPC Logic Filter in an MSP 2016 environment, I must apologize to those users of our software who have suffered in silence with their MSP 2016 (and also MSP 2013) installations.  My initial testing experience with the filter functions was horribly slow, and I was finally able to repeat some crashing behavior – not encountered in MSP 2010 – that had been reported by a lone user.  No wonder the representative feedback from users on MSP 2013 and 2016 has been, love the Task Logic Inspector! (but silent on the other stuff).

With recent updates, we’ve managed to speed up the filter functions while completely eliminating the particular crashing issue.  As a result, with bar-coloring disabled, the new machine can complete a comprehensive Near-Longest Path Filter of a typical ~1000-task schedule in under 8 seconds.  This compares to an 11-second analysis of the same schedule on the old machine; I attribute the improvement primarily to the increased processing speed of the new machine.

Bar-coloring, however, remains sub optimal.  This is already time-consuming – manipulating Gantt bars and bar styles using essentially “foreground” processes.   As a result, the time to generate our comprehensive Near-Longest Path Filter on the old (MSP 2010) machine increases from 11 seconds to 33 seconds when bar-coloring with auto-ranging is selected.  Such an increase is justified by the improved communication that bar coloring allows.  Unfortunately, the time to perform the same task on the new (MSP 2016) machine increased from 8 seconds to 46 seconds, even after our optimizations and adjustments.  I would expect users with slower computers to have much worse experience.  It seems that manipulating graphic display objects involves substantially more processing power in MSP 2016 than in MSP 2010.  This is ironic in light of the general degradation in graphical output beginning with MSP 2013.  Unfortunately, we have not yet found a way around this problem.

Finally, there seems to be a bug in MSP 2016’s handling of the GanttBarFormat method when a) the method originates in a VSTO (Visual Studio Tools for Office) Add-In rather rather than in a native VBA (Visual Basic for Applications) procedure; and b) there is actual progress on the task.  (The GanttBarFormat method is used to apply format exceptions to a particular bar style of a particular task; like right-clicking on a bar and choosing “format bar”.)  Unfortunately, MSP 2016 ignores the selected bar style and applies the exception to the “Task Progress” bar if one exists.  This makes for some odd-looking outputs from our Add-In for schedules showing actual progress.  I’ll have to figure out a way to raise this issue and get it fixed.