precedence-net 1.1.0

Create and analyse precedence networks
Documentation
use crate::activity;
use crate::activity::Activity;
use crate::activity_builder::ActivityBuilder;
use crate::{id, Error, Network, Result};
use crate::{DurationType, StartType};
use petgraph::graphmap::DiGraphMap;
use petgraph::Direction;
use std::collections::HashMap;

#[derive(Default)]
pub struct NetworkBuilder {
    activities: HashMap<u64, ActivityBuilder>,
    connections: DiGraphMap<u64, ()>,
}

impl NetworkBuilder {
    pub(crate) fn new(
        activities: HashMap<u64, ActivityBuilder>,
        connections: DiGraphMap<u64, ()>,
    ) -> NetworkBuilder {
        NetworkBuilder {
            activities,
            connections,
        }
    }

    /// Add an activity to the network.
    ///
    /// The `reference` and `description` of the activity will be set to `reference`. The `minimum_duration`, `expected_duration` and `maximum_duration` will all be set to `duration`. The `start_type`
    /// will be set to `StartType::Earliest` and the `duration_type` will be set to `DurationType::Expected`.
    pub fn add_activity(&mut self, reference: &str, duration: f64) -> Result<()> {
        self.add_extended_activity(
            reference,
            reference,
            duration,
            duration,
            duration,
            StartType::Earliest,
            DurationType::Expected,
        )
    }

    /// Removes an activity and all edges associated with it
    pub fn remove_activity(&mut self, reference: &str) -> Result<()> {
        self.activities
            .remove(&id(&reference))
            .ok_or_else(|| Error::UnknownReference(reference.to_string()))?;

        if !self.connections.remove_node(id(&reference)) {
            return Err(Error::UnknownReference(reference.to_string()));
        };

        Ok(())
    }

    pub fn remove_edge(&mut self, origin_reference: &str, target_reference: &str) -> Result<()> {
        let origin = self.activity(id(&origin_reference))?;
        let target = self.activity(id(&target_reference))?;

        if self
            .connections
            .remove_edge(id(&origin), id(&target))
            .is_none()
        {
            return Err(Error::UnknownEdge(
                origin_reference.to_string(),
                target_reference.to_string(),
            ));
        }

        Ok(())
    }

    /// Add a customised activity to the network.
    ///
    /// This method is for creating activities with differing durations, start types or duration
    /// types.
    ///
    /// ```ignore
    /// network_builder.add_extended_activity("develop",
    ///                                       "Develop System",
    ///                                       1.0, 3.0, 5.0,
    ///                                       StartType::Earliest,
    ///                                       DurationType::Expected)?;
    /// ```
    pub fn add_extended_activity(
        &mut self,
        reference: &str,
        description: &str,
        minimum_duration: f64,
        expected_duration: f64,
        maximum_duration: f64,
        start_type: StartType,
        duration_type: DurationType,
    ) -> Result<()> {
        let mut activity_builder = ActivityBuilder::new(
            reference,
            description,
            expected_duration,
            minimum_duration,
            maximum_duration,
        );
        activity_builder.start_type = start_type;
        activity_builder.duration_type = duration_type;

        self.activities
            .insert(id(&reference), activity_builder.clone());

        self.connections.add_node(id(&activity_builder));

        Ok(())
    }

    /// Connect two activities together
    ///
    /// ```ignore
    /// // a -> b -> c
    /// network_builder.connect("a", "b")?;
    /// network_builder.connect("b", "c")?;
    /// ```
    pub fn connect(&mut self, origin_reference: &str, target_reference: &str) -> Result<()> {
        let origin = self
            .activities
            .get(&id(&origin_reference))
            .ok_or_else(|| Error::UnknownReference(origin_reference.to_string()))?;

        let target = self
            .activities
            .get(&id(&target_reference))
            .ok_or_else(|| Error::UnknownReference(target_reference.to_string()))?;

        self.connections.add_edge(id(&origin), id(&target), ());

        Ok(())
    }

    fn mut_activity(&mut self, id: u64) -> Result<&mut ActivityBuilder> {
        self.activities.get_mut(&id).ok_or(Error::UnknownId(id))
    }

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

    fn previous_activities(&self, id: u64) -> Result<Vec<&ActivityBuilder>> {
        self.connections
            .neighbors_directed(id, Direction::Incoming)
            .map(|ref id| self.activities.get(id).ok_or(Error::UnknownId(*id)))
            .collect::<Result<Vec<&ActivityBuilder>>>()
    }

    fn next_activities(&self, id: u64) -> Result<Vec<&ActivityBuilder>> {
        self.connections
            .neighbors_directed(id, Direction::Outgoing)
            .map(|ref id| self.activities.get(id).ok_or(Error::UnknownId(*id)))
            .collect::<Result<Vec<&ActivityBuilder>>>()
    }

    fn forward_pass(&mut self) -> Result<()> {
        let mut activity_ids: Vec<u64> = Vec::new();

        for id in self
            .connections
            .neighbors_directed(id(&activity::START_REFERENCE), Direction::Outgoing)
        {
            activity_ids.push(id);
        }

        while !activity_ids.is_empty() {
            let current_activity_id = activity_ids.remove(0);
            let prev_activities = self.previous_activities(current_activity_id)?;
            if prev_activities
                .iter()
                .all(|activity| activity.earliest_finish.is_some())
            {
                let earliest_start = prev_activities
                    .iter()
                    .map(|a| a.earliest_finish.ok_or(Error::FieldNotSet))
                    .collect::<Result<Vec<f64>>>()?
                    .iter()
                    .copied()
                    .fold(0.0, f64::max);

                let depth = prev_activities
                    .iter()
                    .map(|a| a.depth)
                    .max()
                    .ok_or(Error::FieldNotSet)?;

                let activity = self.mut_activity(current_activity_id)?;
                activity.earliest_start(earliest_start);
                activity.earliest_finish(earliest_start + activity.duration()?);
                activity.depth = depth + 1;

                for next_activity in self.next_activities(current_activity_id)?.iter() {
                    activity_ids.push(id(&next_activity.reference));
                }
            } else {
                activity_ids.push(current_activity_id)
            }
        }
        Ok(())
    }

    fn backward_pass(&mut self) -> Result<()> {
        let mut activity_ids: Vec<u64> = Vec::new();

        for id in self
            .connections
            .neighbors_directed(id(&activity::FINISH_REFERENCE), Direction::Incoming)
        {
            activity_ids.push(id);
        }

        while !activity_ids.is_empty() {
            let current_activity_id = activity_ids.remove(0);
            let next_activities = self.next_activities(current_activity_id)?;
            if next_activities
                .iter()
                .all(|activity| activity.latest_start.is_some())
            {
                let latest_finish = next_activities
                    .iter()
                    .map(|a| a.latest_start.ok_or(Error::FieldNotSet))
                    .collect::<Result<Vec<f64>>>()?
                    .iter()
                    .copied()
                    .fold(f64::MAX, f64::min);

                let activity = self.mut_activity(current_activity_id)?;
                activity.latest_finish(latest_finish);
                activity.latest_start(latest_finish - activity.duration()?);

                for prev_activity in self.previous_activities(current_activity_id)?.iter() {
                    activity_ids.push(id(&prev_activity.reference));
                }
            } else {
                activity_ids.push(current_activity_id)
            }
        }

        Ok(())
    }

    /// Creates a [Network] from the [NetworkBuilder]
    ///
    /// ```ignore
    /// let network = network_builder.build();
    pub(crate) fn build(&mut self) -> Result<Network> {
        if petgraph::algo::is_cyclic_directed(&self.connections.clone().into_graph::<usize>()) {
            return Err(Error::CycleDetected);
        }

        for activity in self.activities.values_mut() {
            activity.earliest_start = None;
            activity.latest_start = None;
            activity.earliest_finish = None;
            activity.latest_finish = None;
        }

        let mut start_activity = ActivityBuilder::new(
            activity::START_REFERENCE,
            activity::START_REFERENCE,
            0.0,
            0.0,
            0.0,
        );

        start_activity.earliest_start(0.0);
        start_activity.earliest_finish(0.0);
        start_activity.latest_start(0.0);
        start_activity.latest_finish(0.0);

        let start_id = id(&activity::START_REFERENCE);

        self.connections.add_node(start_id);

        for &id in self.activities.keys() {
            if self
                .connections
                .edges_directed(id, Direction::Incoming)
                .count()
                == 0
            {
                self.connections.add_edge(start_id, id, ());
            }
        }
        self.activities.insert(start_id, start_activity);

        let finish_id = id(&activity::FINISH_REFERENCE);
        let finish_activity = ActivityBuilder::new(
            activity::FINISH_REFERENCE,
            activity::FINISH_REFERENCE,
            0.0,
            0.0,
            0.0,
        );

        self.connections.add_node(finish_id);

        for &id in self.activities.keys() {
            if id == finish_id {
                continue;
            }
            if self
                .connections
                .edges_directed(id, Direction::Outgoing)
                .count()
                == 0
            {
                self.connections.add_edge(id, finish_id, ());
            }
        }
        self.activities.insert(finish_id, finish_activity);

        self.forward_pass()?;

        let finish_activity = self.mut_activity(id(&activity::FINISH_REFERENCE))?;
        let latest_finish = finish_activity.earliest_finish.ok_or(Error::FieldNotSet)?;
        let latest_start = latest_finish - finish_activity.duration()?;

        finish_activity.latest_finish(latest_finish);
        finish_activity.latest_start(latest_start);

        self.backward_pass()?;

        let mut activities = HashMap::<u64, Activity>::new();
        let mut connections = DiGraphMap::<u64, ()>::new();

        for &id in self.activities.keys() {
            let activity = self.activity(id)?.try_into()?;
            activities.insert(id, activity);

            connections.add_node(id);
            for next_neighboud_id in self.connections.neighbors_directed(id, Direction::Outgoing) {
                connections.add_edge(id, next_neighboud_id, ());
            }
        }

        activities.remove(&start_id);
        activities.remove(&finish_id);

        self.activities.remove(&start_id);
        self.activities.remove(&finish_id);

        connections.remove_node(start_id);
        connections.remove_node(finish_id);

        self.connections.remove_node(start_id);
        self.connections.remove_node(finish_id);

        let network = Network::new(activities, connections, latest_start);

        Ok(network)
    }

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

        Ok(())
    }
}

impl From<Network> for NetworkBuilder {
    fn from(network: Network) -> NetworkBuilder {
        network.to_builder()
    }
}

impl From<&Network> for NetworkBuilder {
    fn from(network: &Network) -> NetworkBuilder {
        network.to_builder()
    }
}