Home > Back-end >  Even distribution of directed acyclic graph with respect to edge length
Even distribution of directed acyclic graph with respect to edge length

Time:03-26

Problem

I have a set of events, some of them connected and these connections define order. Events must be held in defined order. A connection may contain a min and/or a max requirement for distance between connected events. Let the distance be in days.

I use a directed acyclic graph for a representation of my model.

I need to order this events on the given number of days respecting the defined order and min/max requirements. The distribution should tend to be even. The events should be stretched all over the given distance.

What ways or algorithms may you suggest on solving this problem? I tried to find some solution with topological sorting or constraint ordering but had little to no results.

Example

We have a set of events a, b, c with the following connections a -> b, b -> c, a -> c

The given number of days for distribution is 7.

a. without any requirements for distance between connections.

Then the best solution would be

1  2  3  4  5  6  7
a        b        c

b. with requirement where distance in days between events (a, b) is [1, 2].

Then the best solution would be

1  2  3  4  5  6  7
a     b           c

c. with requirements where distance in days between events (a, b) is [1, 2] and between events (a, c) is <= 4.

Then the best solution would be

1  2  3  4  5  6  7
a     b     c      

d. with requirements where distance in days between events (a, b) is [1, 2], between events (a, c) is <= 4, between events (b, c) is >= 3.

Then the best solution would be

1  2  3  4  5  6  7
a  b        c      

CodePudding user response:

Find R = all events that have no in edges
LOOP while R contains one or more event
   SELECT N from R with the largest number of out edges
   IF first time through
      Place N on day 1
   ELSE
      Place N in middle of largest gap between events
   LOOP M over descendants of N in required order
      Place M as far from other events as possible, within M's allowed range

CodePudding user response:

Evenness ?

If unevenness is defined by looking at all the differences between days and event_days calculated and returning the max minus the min days difference then your problem with your answer of:

1  2  3  4  5  6  7
a  b        c      

Has the values off to the left w.r.t. the days.

A better answer might be:

1  2  3  4  5  6  7
   a  b        c      

With the one shift to the left, theeir is less of a stretch from day 7 to any event day.

If you think the above is equivalent then you need to better define evenness - is it evennness over the extent of the event days perhaps?

======

Code

The source is set to run your example if you just return on each prompt.

# -*- coding: utf-8 -*-
"""
Even distribution of directed acyclic graph with respect to edge length
https://stackoverflow.com/questions/71532362/even-distribution-of-directed-acyclic-graph-with-respect-to-edge-length

Created on Sat Mar 26 08:53:19 2022

@author: paddy
"""

# https://pypi.org/project/python-constraint/
from constraint import Problem
from functools import lru_cache
from itertools import product


#%% Inputs
days = input("Days: int = ")
try:
    days = int(days.strip())
except:
    days = 7
print(f"Using {days=}")
events = input("events: space_separated = ")
events = events.strip().split()
if not events:
    events = list("abc")
print(f"Using {events=}")

constraint_funcs = []
while True:
    constr = input("constraint string_expression (. to end) = ").strip()
    if not constr:
        constraint_funcs = ["1 <= abs(b - a) <=2",
                            "abs(c - a) <= 4",
                            "abs(c - b) >= 3"]
        break
    if constr == '.':
        break
    constraint_funcs.append(constr)
print(f"\nUsing {constraint_funcs=}")


#%% Constraint Setup
print()
problem = Problem()

problem.addVariables(events, range(1, days 1))
for constr in constraint_funcs:
    constr_events = sorted( set(events) & set(compile(constr, '<input>',
                                              mode='eval').co_names))
    expr = f"lambda {', '.join(constr_events)}: {constr}"
    print(f"  Add Constraint {expr!r}, On {constr_events}")
    func = eval(expr)
    problem.addConstraint(func, constr_events)

#%% Solution optimisation for "evenness"
print()

@lru_cache
def unevenness(event_days, all_days):
    "Max - min diff between days and events"
    maxdiff, mindiff, sumdiff, n =  -1, all_days   1, 0, 0
    for day, event in product(range(1, all_days 1), event_days):
        n  = 1
        diff = abs(day - event)
        maxdiff = max(maxdiff, diff)
        mindiff = min(mindiff, diff)
        sumdiff  = diff
    return abs(maxdiff - mindiff)  # Or whatever unnevenness means??!

def printit(solution, all_days):
    drange = range(1, all_days 1)
    # print(solution)
    print('  '.join(str(i)[0] for i in drange))
    for event, day in sorted(solution.items(), key=lambda kv: kv[::-1]):
        print('   ' * (day - 1)   event )
    print()

current_best = None, 9e99
for ans in problem.getSolutionIter():
    unev = unevenness(tuple(sorted(ans.values())), days)
    if current_best[0] is None or unev < current_best[1]:
        current_best = ans, unev
        print("Best so far:")
        printit(ans, days)

Output

Sample run using another interpretation of your constraints (no use of abs(...), and using longer event names).

Days: int = 7
Using days=7

events: space_separated = arch belt card
Using events=['arch', 'belt', 'card']

constraint string_expression (. to end) = 1 <= (belt - arch) <= 2

constraint string_expression (. to end) = (card - arch) <= 4

constraint string_expression (. to end) = (card - belt) >= 3

constraint string_expression (. to end) = .

Using constraint_funcs=['1 <= (belt - arch) <= 2', '(card - arch) <= 4', '(card - belt) >= 3']

  Add Constraint 'lambda arch, belt: 1 <= (belt - arch) <= 2', On ['arch', 'belt']
  Add Constraint 'lambda arch, card: (card - arch) <= 4', On ['arch', 'card']
  Add Constraint 'lambda belt, card: (card - belt) >= 3', On ['belt', 'card']

Best so far:
1  2  3  4  5  6  7
      arch
         belt
                  card

Best so far:
1  2  3  4  5  6  7
   arch
      belt
               card

Note: The first letter of event names align with the day columns.

  • Related