1

I've been breaking my head over this for quite some time and hope to get some support here. I have an array containing multiple objects.

Every object represents a production process comprised of subprocesses made up of inputs and outputs. These processes run in line. Meaning, that the output of one process will become the input for another process. However, I have no idea in which order.

I am trying to figure out the sequence the main processes need to be run in. Our system can't tell me and now I have 5000interdependant processes that need sorting.

Here an example of how the array is formatted. As you can see Inputs have an origin flag. At least we know whether an input is taken from a warehouse or is produced somewhere upstream. If I were to sort this manually the order would be Process3, then Process 2, then Process 1. Process 1 requires the material with the id="ID3" as input, which is produced in process 2 (under subprocess 2). While Process 2 requires the material "ID5" which is produced in process 3. Anybody have an idea how to solve this nightmare?

Thank you sooo much!!!

[
  {
    processID: "1",
    subprocesses: [
      {
        subprocessID: "1",
        outputs: [
          {
            id: "ID1",
            name: "alpha",
            ratio: "1",
          },
        ],
        inputs: [
          {
            type: "rawMaterial",
            id: "ID2",
            ratio: "0.7623",
          },
          {
            type: "processOutput",
            id: "ID3",
            ratio: "0.6552",
          },
        ],
      },
    ],
  },
  {
    processID: "2",
    subprocesses: [
      {
        subprocessID: "1",
        outputs: [
          {
            id: "ID22",
            name: "beta)",
            ratio: "1",
          },
        ],
        inputs: [
          {
            type: "processOutput",
            id: "ID5",
            ratio: "0.0034",
          },
        ],
      },
      {
        subprocessID: "2",
        outputs: [
          {
            id: "ID3",
            name: "gamma",
            ratio: "1",
          },
        ],
        inputs: [
          {
            type: "rawMaterial",
            id: "ID10",
            ratio: "0.0034",
          },
        ],
      },
    ],
  },
  {
    processID: "3",
    subprocesses: [
      {
        subprocessID: "1",
        outputs: [
          {
            id: "ID5",
            name: "omega",
            ratio: "1",
          },
        ],
        inputs: [
          {
            type: "rawMaterial",
            id: "ID111",
            ratio: "0.3018",
          },
        ],
      },
    ],
  },
]
3
  • What you seem to have is a description of a directed acyclic graph (where each process is a node and the prerequisite relations of inputs/outputs form edges) and you need the Topological ordering for it. :-) Commented Jun 10, 2021 at 12:28
  • Thx for the first hint :-) Will try to understand how that works. Do you have any easy examples for me?? Commented Jun 10, 2021 at 12:30
  • If I understand the graph theory correctly, I would at least need to know the nodes to do the sorting with the proposed algorithms. I do not have those nodes. I have no idea in what relation the processes are to one another. The only thing I can do is check, whether an input is the output of another process and then sort the array accordingly.... @AKX any ideas? Commented Jun 10, 2021 at 12:41

1 Answer 1

1

As I said in the comments, what you seem to have is a graph theory problem :-)

  • The entire thingamabob is a directed acyclic graph – or in this case, basically a dependency tree.
  • Each subprocess as a node in the graph.
  • Edges point from processes outputting resources to processes requiring those resources.
  • The topological ordering of the graph is the order in which the processes need to be complete.

This solution requires the toposort package for the heavy graph lifting.

const processes = require("./processes"); // Data from original post
const toposort = require("toposort");

const processInputs = {}; // Inputs required per subprocess
const outputIdToProcessIds = {}; // Map output IDs to processes that create them
const predecessorMap = {}; // Map process IDs to the node(s) that need to run immediately before

// Step 1: massaging the input data.

// Loop over top-level processes...
processes.forEach((proc) => {
  // and each subprocess within.
  proc.subprocesses.forEach((subproc) => {
    // Compute an unique ID for the process-subprocess.
    const procId = `${proc.processID}/${subproc.subprocessID}`;
    // Add it to our map of predecessors; this way each process, whether it's needed or not,
    // is in the predecessor map.
    // This also adds a "virtual" "start" predecessor for each node; it's not strictly necessary.
    predecessorMap[procId] = ["start"];

    // Gather the required inputs for each process.
    subproc.inputs.forEach((input) => {
      (processInputs[procId] = processInputs[procId] || []).push(input);
    });

    // Gather an inverse mapping of output ids to processes that create them.
    subproc.outputs.forEach((output) => {
      (outputIdToProcessIds[output.id] = outputIdToProcessIds[output.id] || []).push(procId);
    });
  });
});

// Step 2: massaging the processed data.

// Loop over the processes that actually do need inputs.
Object.entries(processInputs).forEach(([pid, inputs]) => {
  // For each input...
  inputs.forEach((input) => {
    // ... find the process IDs that can create these outputs.
    const pidsForInput = outputIdToProcessIds[input.id] ?? [];
    if (!pidsForInput.length) {
      console.warn(`There is no process that would output ${input.id} required by ${pid}`);
    } else {
      // Push the first one of them into the predecessors list for this process.
      // This might need special handling if it's possible for a resource to be output
      // by multiple processes.
      predecessorMap[pid].push(pidsForInput[0]);
    }
  });
});

// Step 3: creating graph data.
const allEdges = []; // All edges in from-to order
// Walk over the predecessor map...
Object.entries(predecessorMap).forEach(([to, froms]) => {
  // and each predecessor of each node, and push them into the full list of edges
  froms.forEach((from) => allEdges.push([from, to]));
});

console.log(allEdges);

// Step 4: sort!
// Run the toposort and print the order!
toposort(allEdges).forEach((node) => {
  console.log(node);
});

Since the data in the original post is incomplete, the program warns you about just that, but doesn't outright fail.

The solution will be incomplete, too, of course.

The output is:

There is no process that would output ID2 required by 1/1
There is no process that would output ID10 required by 2/2
There is no process that would output ID111 required by 3/1

followed by the graph's edges

[
  [ 'start', '1/1' ],
  [ '2/2', '1/1' ],
  [ 'start', '2/1' ],
  [ '3/1', '2/1' ],
  [ 'start', '2/2' ],
  [ 'start', '3/1' ]
]

and finally the "execution order" required (with the virtual "start" node):

start
2/2
1/1
3/1
2/1

So, the order is:

  • process 2 subprocess 2 done
  • process 1 subprocess 1 done
  • process 3 subprocess 1 done
  • process 2 subprocess 1 done
Sign up to request clarification or add additional context in comments.

7 Comments

Hi @AKX, I am getting cyclic dependency errors. Any idea how to catch and resolve them?
If there are cycles, I'd imagine there's something wrong with your data or the process itself (if e.g. process A requires B which requires C which requires A)... Without knowledge of where it's okay, business-logic-wise, to break a cycle, it's hard to say.
I believe it is related to multipurpose plants where the same output is produced via different paths
Is there a way to break a cycle by saying: "try a differnt path"
If there are indeed multiple nodes that can output a material (as the "might need special handling" comment implies), yeah, you could generate different variations of the predecessor map until you find one without cycles...
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.