-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added LandscapeConfiguration - structure that could response on request about environment (the environment remains static over time), and LandGraph, that describes a graph of environment: vertices - platforms, resource holders edges - roads with own weight (length) and bandwidth Developed algorithm of unrenewable resource delivery before the start of a work. SupplyTimeline was developed to take into account the resources' of: holders: materials (there is no way to make up for them), vehicles (renewable resources, because they could leave their holder and come back after delivery), roads: vehicles (this is represents of road bandwidth, it is renewable resource) PlatformTimeline is presented to avoid implementation difficult platforms' behaviour. The state of platform, that distributed over the time, has follow rules: timestamp has resources that are left AFTER the work, that started at the time, obtain necessary materials, the work can be scheduled at the timestamp A if ALL timestamps after 'A' can provide necessary materials (not other way). Moreover, SimpleSynthetic was extended by LandscapeConfiguration generator based on obtained WorkGraph. In addition, tests include flag about MaterialReq generation for each GraphNode in WorkGraph. PlatformTimeline is the beginning of separation SupplyTimeline into individual timelines to consider the complex behaviour of each participant in evironment (holders, roads, vehicles, platforms). Added examples that demonstrate the work with landscape generator and without it.
- Loading branch information
Showing
34 changed files
with
2,369 additions
and
395 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
from sampo.generator import SimpleSynthetic | ||
from sampo.generator.environment import get_contractor_by_wg | ||
from sampo.pipeline.default import DefaultInputPipeline | ||
from sampo.scheduler import GeneticScheduler | ||
from sampo.utilities.visualization import VisualizationMode | ||
|
||
if __name__ == '__main__': | ||
|
||
# Set up scheduling algorithm and project's start date | ||
start_date = "2023-01-01" | ||
|
||
# Set up visualization mode (ShowFig or SaveFig) and the gant chart file's name (if SaveFig mode is chosen) | ||
visualization_mode = VisualizationMode.ShowFig | ||
gant_chart_filename = './output/synth_schedule_gant_chart.png' | ||
|
||
# Generate synthetic graph with material requirements for | ||
# number of unique works names and number of unique resources | ||
ss = SimpleSynthetic(rand=31) | ||
wg = ss.small_work_graph() | ||
wg = ss.set_materials_for_wg(wg) | ||
landscape = ss.synthetic_landscape(wg) | ||
|
||
# Be careful with the high number of generations and size of population | ||
# It can lead to a long time of the scheduling process because of landscape complexity | ||
scheduler = GeneticScheduler(number_of_generation=1, | ||
mutate_order=0.05, | ||
mutate_resources=0.005, | ||
size_of_population=10) | ||
|
||
# Get information about created LandscapeConfiguration | ||
platform_number = len(landscape.platforms) | ||
is_all_nodes_have_materials = all([node.work_unit.need_materials() for node in wg.nodes]) | ||
print(f'LandscapeConfiguration: {platform_number} platforms, ' | ||
f'All nodes have materials: {is_all_nodes_have_materials}') | ||
|
||
# Get list with the Contractor object, which can satisfy the created WorkGraph's resources requirements | ||
contractors = [get_contractor_by_wg(wg)] | ||
|
||
project = DefaultInputPipeline() \ | ||
.wg(wg) \ | ||
.contractors(contractors) \ | ||
.landscape(landscape) \ | ||
.schedule(scheduler) \ | ||
.visualization('2023-01-01')[0] \ | ||
.show_gant_chart() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import random | ||
|
||
import pandas as pd | ||
|
||
from sampo.generator import SimpleSynthetic | ||
from sampo.generator.environment import get_contractor_by_wg | ||
from sampo.pipeline import DefaultInputPipeline | ||
from sampo.scheduler import GeneticScheduler | ||
from sampo.schemas.time_estimator import DefaultWorkEstimator | ||
|
||
work_time_estimator = DefaultWorkEstimator() | ||
|
||
|
||
def run_test(args): | ||
graph_size, iterations = args | ||
# global seed | ||
|
||
result = [] | ||
for i in range(iterations): | ||
rand = random.Random() | ||
ss = SimpleSynthetic(rand=rand) | ||
if graph_size < 100: | ||
wg = ss.small_work_graph() | ||
else: | ||
wg = ss.work_graph(top_border=graph_size) | ||
|
||
wg = ss.set_materials_for_wg(wg) | ||
contractors = [get_contractor_by_wg(wg, contractor_id=str(i), contractor_name='Contractor' + ' ' + str(i + 1)) | ||
for i in range(1)] | ||
|
||
landscape = ss.synthetic_landscape(wg) | ||
scheduler = GeneticScheduler(number_of_generation=1, | ||
mutate_order=0.05, | ||
mutate_resources=0.005, | ||
size_of_population=1, | ||
work_estimator=work_time_estimator, | ||
rand=rand) | ||
schedule = DefaultInputPipeline() \ | ||
.wg(wg) \ | ||
.contractors(contractors) \ | ||
.work_estimator(work_time_estimator) \ | ||
.landscape(landscape) \ | ||
.schedule(scheduler) \ | ||
.finish() | ||
result.append(schedule[0].schedule.execution_time) | ||
|
||
# seed += 1 | ||
|
||
return result | ||
|
||
|
||
# Number of iterations for each graph size | ||
total_iters = 1 | ||
# Number of graph sizes | ||
graphs = 1 | ||
# Graph sizes | ||
sizes = [100 * i for i in range(1, graphs + 1)] | ||
total_results = [] | ||
# Seed for random number generator can be specified here | ||
# seed = 1 | ||
|
||
# Iterate over graph sizes and receive results | ||
for size in sizes: | ||
results_by_size = run_test((size, total_iters)) | ||
total_results.append(results_by_size) | ||
print(size) | ||
|
||
# Save results to the DataFrame | ||
result_df = {'size': [], 'makespan': []} | ||
for i, results_by_size in enumerate(total_results): | ||
result = results_by_size[0] | ||
|
||
result_df['size'].append(sizes[i]) | ||
result_df['makespan'].append(result) | ||
|
||
pd.DataFrame(result_df).to_csv('landscape_genetic_results.csv', index=False) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,13 @@ | ||
[tool.poetry] | ||
name = "sampo" | ||
version = "0.1.1.304" | ||
version = "0.1.1.341" | ||
description = "Open-source framework for adaptive manufacturing processes scheduling" | ||
authors = ["iAirLab <[email protected]>"] | ||
license = "BSD-3-Clause" | ||
# readme = "README.rst" | ||
# readme = "README.md" | ||
# build = "build.py" | ||
#log_cli = 1 | ||
|
||
[tool.poetry.dependencies] | ||
python = ">=3.10,<3.11" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
import math | ||
import random | ||
import uuid | ||
from collections import defaultdict | ||
|
||
from sampo.schemas import Material, WorkGraph | ||
from sampo.schemas.landscape import ResourceHolder, Vehicle, LandscapeConfiguration | ||
from sampo.schemas.landscape_graph import LandGraphNode, ResourceStorageUnit, LandGraph | ||
|
||
|
||
def setup_landscape(platforms_info: dict[str, dict[str, int]], | ||
warehouses_info: dict[str, list[dict[str, int], list[tuple[str, dict[str, int]]]]], | ||
roads_info: dict[str, list[tuple[str, float, int]]]) -> LandscapeConfiguration: | ||
""" | ||
Build landscape configuration based on the provided information with structure as below. | ||
Attributes: | ||
Platform_info structure: | ||
{platform_name: | ||
{material_name: material_count} | ||
} | ||
Warehouse_info structure: | ||
{holder_name: | ||
[ | ||
{material_name: material_count}, | ||
[(vehicle_name, {vehicle_material_name: vehicle_material_count})] | ||
] | ||
} | ||
Roads_info structure: | ||
{platform_name: | ||
[(neighbour_name, road_length, road_workload)] | ||
} | ||
:return: landscape configuration | ||
""" | ||
name2platform: dict[str, LandGraphNode] = {} | ||
holders: list[ResourceHolder] = [] | ||
for platform, platform_info in platforms_info.items(): | ||
node = LandGraphNode(str(uuid.uuid4()), platform, ResourceStorageUnit( | ||
{name: count for name, count in platform_info.items()} | ||
)) | ||
name2platform[platform] = node | ||
|
||
for holder_name, holder_info in warehouses_info.items(): | ||
materials = holder_info[0] | ||
vehicles = holder_info[1] | ||
holder_node = LandGraphNode( | ||
str(uuid.uuid4()), holder_name, ResourceStorageUnit( | ||
{name: count for name, count in materials.items()} | ||
)) | ||
name2platform[holder_name] = holder_node | ||
holders.append(ResourceHolder( | ||
str(uuid.uuid4()), holder_name, | ||
[ | ||
Vehicle(str(uuid.uuid4()), name, [ | ||
Material(str(uuid.uuid4()), mat_name, mat_count) | ||
for mat_name, mat_count in vehicle_mat_info.items() | ||
]) | ||
for name, vehicle_mat_info in vehicles | ||
], | ||
holder_node | ||
)) | ||
|
||
for from_node, adj_list in roads_info.items(): | ||
name2platform[from_node].add_neighbours([(name2platform[node], length, workload) | ||
for node, length, workload in adj_list]) | ||
|
||
platforms: list[LandGraphNode] = list(name2platform.values()) | ||
|
||
return LandscapeConfiguration( | ||
holders=holders, | ||
lg=LandGraph(nodes=platforms) | ||
) | ||
|
||
|
||
def get_landscape_by_wg(wg: WorkGraph, rnd: random.Random) -> LandscapeConfiguration: | ||
nodes = wg.nodes | ||
max_materials = defaultdict(int) | ||
|
||
for node in nodes: | ||
for mat in node.work_unit.need_materials(): | ||
if mat.name not in max_materials: | ||
max_materials[mat.name] = mat.count | ||
else: | ||
max_materials[mat.name] = max(max_materials[mat.name], mat.count) | ||
|
||
platforms_number = math.ceil(math.log(wg.vertex_count)) | ||
platforms = [] | ||
materials_name = list(max_materials.keys()) | ||
|
||
for i in range(platforms_number): | ||
platforms.append(LandGraphNode(str(uuid.uuid4()), f'platform{i}', | ||
ResourceStorageUnit( | ||
{ | ||
# name: rnd.randint(max(max_materials[name], 1), | ||
# 2 * max(max_materials[name], 1)) | ||
name: max(max_materials[name], 1) | ||
for name in materials_name | ||
} | ||
))) | ||
|
||
for i, platform in enumerate(platforms): | ||
if i == platforms_number - 1: | ||
continue | ||
neighbour_platforms = rnd.choices(platforms[i + 1:], k=rnd.randint(1, math.ceil(len(platforms[i + 1:]) / 3))) | ||
|
||
neighbour_platforms_tmp = neighbour_platforms.copy() | ||
for neighbour in neighbour_platforms: | ||
if neighbour in platform.neighbours: | ||
neighbour_platforms_tmp.remove(neighbour) | ||
neighbour_platforms = neighbour_platforms_tmp | ||
|
||
# neighbour_edges = [(neighbour, rnd.uniform(1.0, 10.0), rnd.randint(wg.vertex_count, wg.vertex_count * 2)) | ||
# for neighbour in neighbour_platforms] | ||
lengths = [i * 50 for i in range(1, len(neighbour_platforms) + 1)] | ||
neighbour_edges = [(neighbour, lengths[i], wg.vertex_count) | ||
for i, neighbour in enumerate(neighbour_platforms)] | ||
platform.add_neighbours(neighbour_edges) | ||
|
||
inseparable_heads = [node for node in nodes if not node.is_inseparable_son()] | ||
|
||
platforms_tmp = ((len(inseparable_heads) // platforms_number) * platforms + | ||
platforms[:len(inseparable_heads) % platforms_number]) | ||
rnd.shuffle(platforms_tmp) | ||
|
||
for node, platform in zip(inseparable_heads, platforms_tmp): | ||
if not node.work_unit.is_service_unit: | ||
for ins_child in node.get_inseparable_chain_with_self(): | ||
platform.add_works(ins_child) | ||
|
||
holders_number = math.ceil(math.sqrt(math.log(wg.vertex_count))) | ||
holders_node = [] | ||
holders = [] | ||
|
||
sample_materials_for_holders = materials_name * holders_number | ||
# random.shuffle(sample_materials_for_holders) | ||
materials_number = len(materials_name) | ||
|
||
for i in range(holders_number): | ||
if not max_materials: | ||
materials_name_for_holder = [] | ||
else: | ||
materials_name_for_holder = sample_materials_for_holders[i * materials_number: (i + 1) * materials_number] | ||
holders_node.append(LandGraphNode(str(uuid.uuid4()), f'holder{i}', | ||
ResourceStorageUnit( | ||
{ | ||
name: max(max_materials[name], 1) * wg.vertex_count | ||
for name in materials_name_for_holder | ||
} | ||
))) | ||
neighbour_platforms = rnd.choices(holders_node[:-1] + platforms, k=rnd.randint(1, len(holders_node[:-1] + platforms))) | ||
|
||
neighbour_platforms_tmp = neighbour_platforms.copy() | ||
for neighbour in neighbour_platforms: | ||
if neighbour in holders_node[-1].neighbours: | ||
neighbour_platforms_tmp.remove(neighbour) | ||
neighbour_platforms = neighbour_platforms_tmp | ||
|
||
# neighbour_edges = [(neighbour, rnd.uniform(1.0, 10.0), rnd.randint(wg.vertex_count, wg.vertex_count * 2)) | ||
# for neighbour in neighbour_platforms] | ||
lengths = [i * 50 for i in range(1, len(neighbour_platforms) + 1)] | ||
neighbour_edges = [(neighbour, lengths[i], wg.vertex_count * 2) | ||
for i, neighbour in enumerate(neighbour_platforms)] | ||
holders_node[-1].add_neighbours(neighbour_edges) | ||
|
||
# vehicles_number = rnd.randint(7, 20) | ||
vehicles_number = 20 | ||
holders.append(ResourceHolder(str(uuid.uuid4()), holders_node[-1].name, | ||
vehicles=[ | ||
Vehicle(str(uuid.uuid4()), f'vehicle{j}', | ||
[Material(name, name, count // 2) | ||
for name, count in max_materials.items()]) | ||
for j in range(vehicles_number) | ||
], node=holders_node[-1])) | ||
|
||
lg = LandGraph(nodes=platforms + holders_node) | ||
return LandscapeConfiguration(holders, lg) |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
def max_fill(mat_max: int, mat_available: int): | ||
return mat_max - mat_available | ||
|
||
|
||
def necessary_fill(mat_count: int, mat_available: int, mat_max: int): | ||
return mat_count + mat_available - mat_max |
Oops, something went wrong.