precedence-net 1.1.0

Create and analyse precedence networks
Documentation
use crate::activity::Activity;
use crate::activity_builder::ActivityBuilder;
use crate::{id, Error, NetworkBuilder, Result};
use petgraph::graphmap::DiGraphMap;
use petgraph::Direction;

use std::collections::HashMap;
use std::fmt::Write;

#[derive(Default, Clone, Debug)]
pub struct Network {
    activities: HashMap<u64, Activity>,
    connections: DiGraphMap<u64, ()>,
    finish_earliest_start: f64,
}

impl Network {
    pub fn builder() -> NetworkBuilder {
        NetworkBuilder::default()
    }

    pub(crate) fn new(
        activities: HashMap<u64, Activity>,
        connections: DiGraphMap<u64, ()>,
        finish_earliest_start: f64,
    ) -> Self {
        Self {
            activities,
            connections,
            finish_earliest_start,
        }
    }

    fn id_activity(&self, id: u64) -> Result<&Activity> {
        self.activities.get(&id).ok_or(Error::UnknownId(id))
    }

    fn activity(&self, reference: &str) -> Result<&Activity> {
        self.activities
            .get(&id(&reference))
            .ok_or_else(|| Error::UnknownReference(reference.to_string()))
    }

    /// A list of all activities in the precedence network
    pub fn activities(&self) -> Result<Vec<&str>> {
        let mut depth_and_references: Vec<(usize, &str)> = self
            .activities
            .values()
            .map(|activity| (activity.depth, activity.reference.as_str()))
            .collect();

        depth_and_references.sort_by_key(|&(depth, _)| depth);
        Ok(depth_and_references
            .iter()
            .map(|(_, reference)| *reference)
            .collect())
    }

    /// A list of all edges in the precedence network
    pub fn edges(&self) -> Result<Vec<(&str, &str)>> {
        self.connections
            .all_edges()
            .map(|(origin_id, target_id, _)| {
                match (self.id_activity(origin_id), self.id_activity(target_id)) {
                    (Ok(origin), Ok(target)) => {
                        Ok((origin.reference.as_str(), target.reference.as_str()))
                    }
                    (Err(error), _) => Err(error),
                    (_, Err(error)) => Err(error),
                }
            })
            .collect::<Result<Vec<(&str, &str)>>>()
    }

    /// Test whether two activities are connected
    pub fn connected(&self, origin_reference: &str, target_reference: &str) -> Result<bool> {
        let origin = self.activity(origin_reference)?;
        let target = self.activity(target_reference)?;

        match self.connections.edge_weight(id(origin), id(target)) {
            Some(_) => Ok(true),
            None => Ok(false),
        }
    }

    /// A list of all start activities
    ///
    /// A start activity is an activity with no preceding activities.
    pub fn start_activities(&self) -> Result<Vec<&str>> {
        let mut start_activities = Vec::new();
        for id in self.activities.keys() {
            if self
                .connections
                .edges_directed(*id, Direction::Incoming)
                .count()
                == 0
            {
                start_activities.push(self.id_activity(*id)?.reference.as_str());
            }
        }
        Ok(start_activities)
    }

    /// A list of all finish activities
    ///
    /// A finish activity is an activity with no dependant activities.
    pub fn finish_activities(&self) -> Result<Vec<&str>> {
        let mut finish_activities = Vec::new();
        for id in self.activities.keys() {
            if self
                .connections
                .edges_directed(*id, Direction::Outgoing)
                .count()
                == 0
            {
                finish_activities.push(self.id_activity(*id)?.reference.as_str());
            }
        }
        Ok(finish_activities)
    }

    fn ids_to_references(&self, ids: Vec<u64>) -> Result<Vec<&str>> {
        Ok(ids
            .iter()
            .map(|id| self.id_activity(*id))
            .collect::<Result<Vec<&Activity>>>()?
            .iter()
            .map(|activity| activity.reference.as_str())
            .collect())
    }

    /// Returns a list of activities that depend on the completion of the given activity
    pub fn next_activities(&self, reference: &str) -> Result<Vec<&str>> {
        self.ids_to_references(
            self.connections
                .neighbors_directed(id(&reference), Direction::Outgoing)
                .collect(),
        )
    }

    /// Returns a list of activities that the given activity depends on
    pub fn previous_activities(&self, reference: &str) -> Result<Vec<&str>> {
        self.ids_to_references(
            self.connections
                .neighbors_directed(id(&reference), Direction::Incoming)
                .collect(),
        )
    }

    /// The earliest possible finish of an activity
    pub fn earliest_finish(&self, reference: &str) -> Result<f64> {
        Ok(self.activity(reference)?.earliest_finish)
    }

    /// The latest possible finish of an activity
    pub fn latest_finish(&self, reference: &str) -> Result<f64> {
        Ok(self.activity(reference)?.latest_finish)
    }

    /// The latest possible start of an activity
    pub fn latest_start(&self, reference: &str) -> Result<f64> {
        Ok(self.activity(reference)?.latest_start)
    }

    /// The earliest possible start of an activity
    pub fn earliest_start(&self, reference: &str) -> Result<f64> {
        Ok(self.activity(reference)?.earliest_start)
    }

    /// Returns true if the given activity is on the critical path
    pub fn on_critical_path(&self, reference: &str) -> Result<bool> {
        let activity = self.activity(reference)?;
        Ok(activity.on_critical_path())
    }

    /// A list of all activities on the critical path
    pub fn critical_path_activities(&self) -> Result<Vec<&str>> {
        Ok(self
            .activities
            .values()
            .filter(|activity| activity.on_critical_path())
            .map(|activity| activity.reference.as_str())
            .collect())
    }

    /// The total float (or slack) of an activity
    ///
    /// Slack is how much the activity can be delayed to not cause
    /// the final completion time of all activities to be delayed.
    pub fn total_float(&self, reference: &str) -> Result<f64> {
        Ok(self.activity(reference)?.total_float())
    }

    /// The free (or early) float of an activity
    ///
    /// The free float is how much the activity can be delayed to not cause
    /// a delay in the earliest start of any subsequent activity.
    pub fn free_float(&self, reference: &str) -> Result<f64> {
        let activity = self.activity(reference)?;
        let min_earliest_start = self
            .next_activities(reference)?
            .iter()
            .map(|reference| self.activity(reference))
            .collect::<Result<Vec<&Activity>>>()?
            .iter()
            .map(|activity| activity.earliest_start)
            .fold(self.finish_earliest_start, f64::min);

        let max_earliest_finish = self
            .previous_activities(reference)?
            .iter()
            .map(|reference| self.activity(reference))
            .collect::<Result<Vec<&Activity>>>()?
            .iter()
            .map(|activity| activity.earliest_finish)
            .fold(0.0, f64::max);

        Ok(min_earliest_start - max_earliest_finish - activity.duration())
    }

    /// The start of an activity, which can vary depending on how `activity_type` was set
    pub fn start(&self, reference: &str) -> Result<f64> {
        Ok(self.activity(reference)?.start())
    }

    /// The finish time of an activity, which can vary depending on how `activity_type` was set
    pub fn finish(&self, reference: &str) -> Result<f64> {
        Ok(self.activity(reference)?.finish())
    }

    /// Whether an activity is active at a certain time
    pub fn active_at(&self, reference: &str, time: f64) -> Result<bool> {
        Ok(self.activity(reference)?.active_on(time))
    }

    /// Whether an activity is active during a certain time range
    pub fn active_during(&self, reference: &str, range: std::ops::Range<f64>) -> Result<bool> {
        Ok(self.activity(reference)?.active_during(range))
    }

    /// The mean duration of the activity
    ///
    /// The mean is calculated as: `((4 * expected_duration) + minimum_duration + maximum_duration) / 6`
    pub fn mean_duration(&self, reference: &str) -> Result<f64> {
        Ok(self.activity(reference)?.mean_duration())
    }

    /// The variance of the duration of the activity
    ///
    /// The variance is calculated as: `variance ** 2`
    pub fn variance(&self, reference: &str) -> Result<f64> {
        Ok(self.activity(reference)?.variance())
    }

    /// The standard deviation of the duration of the activity
    ///
    /// This is calculated as: `(maximum_duration - minimum_duration) / 6`
    pub fn standard_deviation(&self, reference: &str) -> Result<f64> {
        Ok(self.activity(reference)?.standard_deviation())
    }

    /// The depth is a measure of how far the activity is from the start
    ///
    /// All start activities have depth 1 and all other activities have depth of the maximum depth
    /// of their preceding activities + 1.
    pub fn depth(&self, reference: &str) -> Result<usize> {
        Ok(self.activity(reference)?.depth)
    }

    /// Returns a string containing a representation suitable for `dot` and other programs in
    /// the GraphViz package.
    pub fn to_dot(&self) -> Result<String> {
        let mut s = String::new();

        writeln!(&mut s, "digraph {{")?;
        writeln!(&mut s, "rankdir=LR;")?;
        writeln!(&mut s, "node [shape=Mrecord];")?;
        for activity in self.activities.values() {
            write!(&mut s, "\"{}\" ", activity.reference)?;
            write!(&mut s, "[label=\"{}|", activity.description)?;
            write!(&mut s, "{{")?;
            write!(
                &mut s,
                "{{{}|{}}}",
                activity.earliest_start, activity.latest_start,
            )?;
            write!(
                &mut s,
                "|{{{}|{{{}|{}}}}}",
                activity.reference,
                activity.total_float(),
                self.free_float(&activity.reference)?
            )?;
            write!(
                &mut s,
                "|{{{}|{}}}}}",
                activity.earliest_finish, activity.latest_finish
            )?;
            writeln!(&mut s, "|{}\"];", activity.duration())?;
        }

        for (from, to) in self.edges()?.iter() {
            write!(&mut s, "\"{}\" -> \"{}\"", from, to)?;
            if self.on_critical_path(from)? && self.on_critical_path(to)? {
                write!(&mut s, "[style=bold]")?;
            }
            writeln!(&mut s, ";")?;
        }
        write!(&mut s, "}}")?;
        Ok(s)
    }

    /// Returns a [NetworkBuilder] representing the current structure of the Network.
    pub(crate) fn to_builder(&self) -> NetworkBuilder {
        let mut activities = HashMap::<u64, ActivityBuilder>::new();
        for (&id, activity) in self.activities.iter() {
            activities.insert(id, activity.into());
        }

        NetworkBuilder::new(activities, self.connections.clone())
    }

    pub fn update_activity<F>(self, reference: &str, with_activity_builder: F) -> Result<Network>
    where
        F: FnOnce(&mut ActivityBuilder),
    {
        let mut network_builder = self.to_builder();
        network_builder.update_activity(reference, with_activity_builder)?;

        Self::try_from(network_builder)
    }
}

impl TryFrom<NetworkBuilder> for Network {
    type Error = crate::Error;

    fn try_from(mut network_builder: NetworkBuilder) -> std::result::Result<Network, Self::Error> {
        network_builder.build()
    }
}

impl TryFrom<&mut NetworkBuilder> for Network {
    type Error = crate::Error;

    fn try_from(network_builder: &mut NetworkBuilder) -> std::result::Result<Network, Self::Error> {
        network_builder.build()
    }
}