Skip to content

Commit

Permalink
Merge pull request MaxBo#637 from MaxBo/feature/strategygraph
Browse files Browse the repository at this point in the history
Feature/strategygraph
  • Loading branch information
ChrFr authored Oct 22, 2019
2 parents 82db93c + 2bc06a5 commit 2425000
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 177 deletions.
163 changes: 83 additions & 80 deletions repair/apps/asmfa/graphs/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from repair.apps.asmfa.models import (Actor2Actor, FractionFlow, Actor,
ActorStock, Material,
StrategyFractionFlow)
from repair.apps.utils.utils import get_annotated_fractionflows
from repair.apps.changes.models import (SolutionInStrategy,
ImplementationQuantity,
AffectedFlow, Scheme,
Expand All @@ -33,7 +34,8 @@


class Formula:
def __init__(self, a=1, b=0, q=0, is_absolute=False):

def __init__(self, a=1, b=0, q=0):
'''
linear change calculation
Expand All @@ -53,18 +55,24 @@ def __init__(self, a=1, b=0, q=0, is_absolute=False):
quantity (user input), by default 0
b: float, optional
by default 0
is_absolute: bool, optional
absolute change, by default False
'''
self.a = a
self.b = b
self.q = q
self.is_absolute = is_absolute

@property
def is_absolute(self) -> bool:
"""return True if is instance of AbsoluteFormula"""
return isinstance(self, AbsoluteFormula)

@classmethod
def from_implementation(self, solution_part, implementation):
question = solution_part.question
is_absolute = solution_part.is_absolute
if is_absolute:
cls = AbsoluteFormula
else:
cls = RelativeFormula
if solution_part.scheme == Scheme.NEW and not is_absolute:
raise ValueError('new flows without reference can only be defined '
'as absolute changes')
Expand All @@ -80,25 +88,25 @@ def from_implementation(self, solution_part, implementation):
is_absolute = question.is_absolute
q = quantity.value

formula = Formula(a=a, b=b, q=q, is_absolute=is_absolute)
formula = cls(a=a, b=b, q=q)

return formula

def calculate(self, v):
'''
Parameters
----------
v: float,
value to apply formula to
class AbsoluteFormula(Formula):

Returns
-------
v': float
calculated value
'''
return self.calculate_delta(v) + v
def __init__(self, a=1, b=0, q=0):
super().__init__(a, b, q)
self.total = 0
self.n_flows = 0

def calculate_delta(self, v=None):
def set_total(self, flows):
self.total = sum(flows.values_list('strategy_amount', flat=True))
self.n_flows = len(flows)

def set_n_flows(self, n_flows: int):
self.n_flows = n_flows

def calculate_delta(self, v: float=None):
'''
Parameters
----------
Expand All @@ -110,34 +118,35 @@ def calculate_delta(self, v=None):
delta: float
signed delta
'''
if self.is_absolute:
delta = self.a * self.q + self.b
if not self.n_flows:
return 0
#raise ValueError('No Flows defined')
delta = self.a * self.q + self.b
total = getattr(self, 'total', 0)
if total and v is not None:
delta *= v / total
else:
if v is None:
raise ValueError('Value needed for calculation the delta for '
'relative changes')
delta = v * self.calculate_factor(v) - v
delta /= self.n_flows
return delta

def calculate_factor(self, v):

class RelativeFormula(Formula):

def calculate_delta(self, v):
'''
Parameters
----------
v: float, optional
needed for calculation of a
v: float,
value to apply formula to
Returns
-------
factor: float
calculated factor
delta: float
signed delta
'''
if self.is_absolute:
delta = self.calculate_delta()
v_ = v + delta
factor = v_ / v
else:
factor = self.a * self.q + self.b
return factor
factor = self.a * self.q + self.b
delta = v * factor
return delta


class BaseGraph:
Expand Down Expand Up @@ -343,22 +352,28 @@ def load(self):
self.graph = gt.load_graph(self.filename)
return self.graph

def _modify_flows(self, flows, formula, new_material=None, new_process=None,
def _modify_flows(self, flows, formula: Formula, new_material=None, new_process=None,
new_waste=-1, new_hazardous=-1):
'''
modify flows with formula
'''
deltas = []
if formula.is_absolute:
formula.set_total(flows)
if (new_material or new_process or
new_waste >= 0 or new_hazardous >= 0):
edges = self._get_edges(flows)
for i, flow in enumerate(flows):
delta = formula.calculate_delta(flow.amount)
delta = formula.calculate_delta(flow.strategy_amount)
if formula.is_absolute:
# ToDo: equal distribution or distribution depending on
# previous share on total amount?
delta /= len(flows)
# alternatively sth like that: delta *= flow.amount / total
# cut the delta to avoid negative flow amounts
delta = max(-flow.strategy_amount, delta)
# if we have a relative change (*1.5),
# then the delta is +0.5*strategy_amount
# so we have to substract the original amount
else:
delta -= flow.strategy_amount

deltas.append(delta)
if new_material:
self.graph.ep.material[edges[i]] = new_material.id
Expand All @@ -374,6 +389,8 @@ def _create_flows(self, origins, destinations, material, process, formula):
'''
create flows between all origin and destination actors
'''
if not formula.is_absolute:
raise ValueError('Formula for CreateFlows must be absolute')
total = formula.calculate_delta()
flow_count = len(origins) * len(destinations)

Expand All @@ -382,9 +399,10 @@ def _create_flows(self, origins, destinations, material, process, formula):
print('WARNING: No orgins and/or destinations found '
'while creating new flows')
return new_flows, np.empty((0, ))
formula.set_n_flows(flow_count)
# equal distribution
amount = total / flow_count
deltas = np.full((flow_count), amount)
amount_per_flow = formula.calculate_delta()
deltas = np.full((flow_count), amount_per_flow)
for origin, destination in itertools.product(origins, destinations):
new_flow = FractionFlow(
origin=origin, destination=destination,
Expand Down Expand Up @@ -437,6 +455,8 @@ def _shift_flows(self, referenced_flows, possible_new_targets,
# actors in possible new targets that are closest
closest_dict = self.find_closest_actor(actors_kept,
possible_new_targets)
if formula.is_absolute:
formula.set_total(referenced_flows)

# create new flows and add corresponding edges
for flow in referenced_flows:
Expand All @@ -452,13 +472,8 @@ def _shift_flows(self, referenced_flows, possible_new_targets,

new_vertex = self._get_vertex(new_id)

delta = formula.calculate_delta(flow.s_amount)
# ToDo: distribute total change to changes on edges
# depending on share of total or distribute equally?
if formula.is_absolute:
# equally
delta /= len(referenced_flows)
delta = flow.s_amount + delta
delta = formula.calculate_delta(flow.strategy_amount)
delta = min(delta, flow.strategy_amount)

# the edge corresponding to the referenced flow
# (the one to be shifted)
Expand Down Expand Up @@ -538,6 +553,10 @@ def _chain_flows(self, referenced_flows, possible_new_targets,
ToDo: almost the same as shift_flows(), generalize!
'''
if formula.is_absolute:
raise ValueError(
'Formula for PrependFlow and AppendFlow must be relative')

new_flows = []
deltas = []

Expand All @@ -562,12 +581,7 @@ def _chain_flows(self, referenced_flows, possible_new_targets,

new_vertex = self._get_vertex(new_id)

delta = formula.calculate_delta(flow.s_amount)
# ToDo: distribute total change to changes on edges
# depending on share of total or distribute equally?
if formula.is_absolute:
# equally
delta /= len(referenced_flows)
delta = formula.calculate_delta(flow.strategy_amount)

# the edge corresponding to the referenced flow
edges = util.find_edge(self.graph, self.graph.ep['id'], flow.id)
Expand Down Expand Up @@ -624,7 +638,7 @@ def _chain_flows(self, referenced_flows, possible_new_targets,
self.graph.ep.hazardous[new_edge] = new_flow.hazardous

new_flows.append(new_flow)
deltas.append(flow.s_amount + delta)
deltas.append(delta)

return new_flows, deltas

Expand Down Expand Up @@ -671,23 +685,24 @@ def _get_referenced_flows(self, flow_reference, implementation):
and implementation areas
'''
origins, destinations = self._get_actors(flow_reference, implementation)
flows = FractionFlow.objects.filter(
flows = get_annotated_fractionflows(self.strategy.keyflow.id,
self.strategy.id)
flows = flows.filter(
origin__in=origins,
destination__in=destinations
)
flows = self._annotate(flows)
if flow_reference.include_child_materials:
impl_materials = Material.objects.filter(id__in=descend_materials(
[flow_reference.material]))
kwargs = {
's_material__in': impl_materials
'strategy_material__in': impl_materials
}
else:
kwargs = {
's_material': flow_reference.material.id
'strategy_material': flow_reference.material.id
}
if flow_reference.process:
kwargs['s_process'] = flow_reference.process.id
kwargs['strategy_process'] = flow_reference.process.id
reference_flows = flows.filter(**kwargs)
return reference_flows

Expand All @@ -703,32 +718,20 @@ def _get_affected_flows(self, solution_part):
aff_flows = FractionFlow.objects.none()
for af in affectedflows:
#materials = descend_materials([af.material])
flows = FractionFlow.objects.filter(
flows = get_annotated_fractionflows(self.strategy.keyflow.id,
self.strategy.id)
flows = flows.filter(
origin__activity = af.origin_activity,
destination__activity = af.destination_activity
)
flows = self._annotate(flows)
kwargs = {
's_material': af.material.id
'strategy_material': af.material.id
}
if af.process:
kwargs['s_process': af.process]
kwargs['strategy_process': af.process]
aff_flows = aff_flows | flows.filter(**kwargs)
return aff_flows

def _annotate(self, flows):
''' annotate flows with strategy attributes (trailing "s_") '''
annotated = flows.annotate(
s_amount=Coalesce('f_strategyfractionflow__amount', 'amount'),
s_material=Coalesce('f_strategyfractionflow__material', 'material'),
s_waste=Coalesce('f_strategyfractionflow__waste', 'waste'),
s_hazardous=Coalesce('f_strategyfractionflow__hazardous',
'hazardous'),
s_process=Coalesce('f_strategyfractionflow__process',
'process')
)
return annotated

def _include(self, flows, do_include=True):
'''
include flows in graph, excludes all others
Expand Down
2 changes: 1 addition & 1 deletion repair/apps/asmfa/graphs/graphwalker.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def examine_edge(self, e):
for e_out in edges_out:
if not (self.visited[e_out] and self.visited[e]):
self.change[e_out] += self.amount[e_out] * amount_factor
self.visited[e] = True
self.visited[e_out] = True


def traverse_graph(g, edge, delta, upstream=True):
Expand Down
16 changes: 10 additions & 6 deletions repair/apps/asmfa/tests/flowmodeltestdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,21 +155,25 @@ def plastic_package_graph():

def plot_amounts(g, file=None):
"""Plots the graph with the 'amount' property on the edges into a file"""
vertex_ids = [f'{int(v)}' for v in g.vertices()]
vertex_text = g.new_vertex_property("string", vals=vertex_ids)
mass_text = g.new_edge_property("string",
vals=[str(round(i, 2))for i in g.ep.amount])
gt.draw.graph_draw(g, vertex_size=20, vertex_text=g.vp.id,
vprops={"text_position": 1,
"font_size": 14},
gt.draw.graph_draw(g, vertex_size=20, vertex_text=vertex_text,
vprops={"text_position": -1,
"font_size": 10},
edge_text=mass_text,
output_size=(700, 600), inline=True,
output=file)


def plot_materials(g, file=None):
"""Plots the graph with the 'material' property on the edges into a file"""
gt.draw.graph_draw(g, vertex_size=20, vertex_text=g.vp.id,
vprops={"text_position": 0,
"font_size": 14},
vertex_ids = [f'{int(v)}' for v in g.vertices()]
vertex_text = g.new_vertex_property("string", vals=vertex_ids)
gt.draw.graph_draw(g, vertex_size=20, vertex_text=vertex_text,
vprops={"text_position": -1,
"font_size": 10},
edge_text=g.ep.material,
output_size=(700, 600), inline=True,
output=file)
Expand Down
Loading

0 comments on commit 2425000

Please sign in to comment.