Skip to content

Commit

Permalink
Merge pull request #4 from jointpoints/python
Browse files Browse the repository at this point in the history
Version 1.0.0
  • Loading branch information
jointpoints authored Aug 21, 2022
2 parents 28d6c11 + 78e3da2 commit 219b6af
Show file tree
Hide file tree
Showing 37 changed files with 29,770 additions and 5,195 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,6 @@

# Build folders
/Facility Arrangement Solver (test build for Windows, v.0.1)

# Python cache
*.pyc
44 changes: 27 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,41 @@

## Brief

A tool to arrange *subjects* (such as departments, machines, etc.) within the given
facility layout. Subjects can be categorised into *groups* or *types*. Subjects within
one type share common properties. Facility workflow is modelled via interactions between
different types of subjects. For more delails, see the manual *(will be available as soon
as time allows)*.

> The tool is in the early stage of development.
A tool to arrange *subjects* (such as departments, machines, etc.) within the given facility. Subjects are categorised into *groups*. Subjects within one group share common properties and are mutually interchangeable in the production pipeline. Workflow within the facility is modelled via interactions between different subjects. For more delails, see the Wiki pages.

## External dependencies

This tool relies on the following external packages:

* [CPLEX](https://www.ibm.com/products/ilog-cplex-optimization-studio)
* Proprietary software by IBM.
* Not supplied together with the source code in this repository, acquisition of
licensed copy on your own is required.
* Not supplied together with the source code in this repository, acquisition of a licensed copy on your own is required.

## Installation

Before you can use Facility Arrangement Solver, you will have to install:

* Python 3.8,
* `cplex` module for the respective version of Python.

##### 1. Installation of Python

Go to [the official web-site](https://www.python.org/downloads/) of Python and download the distributive of Python 3.8 that is compatible with your system. Complete the installation, add the path to the executable file to the PATH system variable.

##### 2. Installation of `cplex` module

Visit `/cplex/python/3.8/S` folder contained within your main CPLEX directory substituting `S` with your system name (will be the only folder in `/cplex/python/3.8`). In this folder, you will find a file called `setup.py`. Open a terminal in this folder and run

python setup.py install

##### 3. Installation of Facility Arrangement Solver

Download the latest release of Facility Arrangement Solver, unpack the archive into whatever folder you prefer.

## Compilation
## Usage

Unfortunately, CPLEX requires the use of
[bloody MSVC](https://community.ibm.com/community/user/datascience/communities/community-home/digestviewer/viewthread?MessageKey=efd94ad6-215c-42f2-9491-2cb9f45a93da&CommunityKey=ab7de0fd-6f43-47a9-8261-33578a231bb7)
to properly compile on Windows which might cause additional troubles to you. Install
[MSVC build tools](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2022)
if you don't want to install Visual Studio.
Open a terminal in the folder where you unpacked the archive with Facility Arrangement Solver. Run

On Linux, the use of G++ is sufficient.
python fas.py

To get more details about the compilation, navigate to `/tools` folder.
For more tips, see the Wiki section of this repository.
72 changes: 72 additions & 0 deletions benchmark/benchmark.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
'''
Facility Arrangement Solver for Python
Version: 1.0.0
Author : Andrew Eliseev (JointPoints)
'''
from sys import argv
from os import system
from time import time
from math import ceil
import random
import networkx
import json



def generate_instance(idx):
# Choose the number of subject groups
subject_group_count = random.randint(2, 10)
# Choose the number of subjects in each group
subject_count = {i : random.randint(1, 5) for i in range(subject_group_count)}
# Construct a random production pipeline
pipeline = networkx.gn_graph(subject_group_count)
total_flows = {i : {j : 0 for j in range(subject_group_count)} for i in range(subject_group_count)}
group_queue = [i for i in range(subject_group_count) if pipeline.in_degree(i) == 0]
total_input = {i : 100 if i in group_queue else 0 for i in range(subject_group_count)}
while group_queue != []:
i = group_queue[0]
for edge in pipeline.edges(i):
total_flows[i][edge[1]] = total_input[i] // pipeline.out_degree(i)
total_input[edge[1]] += total_flows[i][edge[1]]
group_queue.append(edge[1])
del group_queue[0]
# Determine the capacities and areas of the subject groups
capacities = {i : ceil(total_input[i] / subject_count[i]) for i in range(subject_group_count)}
areas = {i : random.randint(1, 2) for i in range(subject_group_count)}
# Save the instance
meta = {'created_by' : 'FAS benchmark random instance generator', 'spec' : '1.0.0', 'type' : 'fasg'}
stuff = {str(i) : {'area' : areas[i], 'input_capacity' : capacities[i], 'output_capacity' : capacities[i]} for i in range(subject_group_count)}
with open(f'benchmark_random_{idx}.fasg', 'w') as f:
json.dump({'stuff' : stuff, 'meta': meta}, f, indent='\t', sort_keys=True)
meta['type'] = 'fast'
stuff = {str(i) : {str(j) : total_flows[i][j] for j in range(subject_group_count)} for i in range(subject_group_count)}
with open(f'benchmark_random_{idx}.fast', 'w') as f:
json.dump({'stuff' : stuff, 'meta': meta}, f, indent='\t', sort_keys=True)
return



def main():
random.seed(218199)
valid_instances_count = int(argv[1])
if len(argv) == 2:
for valid_instance_i in range(valid_instances_count):
generate_instance(valid_instance_i + 1)
else:
for valid_instance_i in range(valid_instances_count):
generate_instance(0)
for a in ('linear --forcevanilla', 'cpr_linear --forcevanilla', 'linear', 'cpr_linear', 'linear_gfred', 'cpr_linear_gfred'):
for h in (5, 7, 9, 11, 13):
start_time = time()
system(f'{argv[2]} ../fas/fas.py -o arrangement.sol -f g{h}:1x{h}:1x2 -g benchmark_random_0.fasg -t benchmark_random_0.fast -d m1 -a {a}')
runtime = time() - start_time
with open(f'report_{valid_instance_i + 1}.txt', 'a') as f:
f.write(f'{a} {h} {runtime}\n')
if runtime >= 1200:
break
return



if __name__ == '__main__':
main()
154 changes: 154 additions & 0 deletions fas/fas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
'''
Facility Arrangement Solver for Python
Version: 1.0.0
Author : Andrew Eliseev (JointPoints)
'''
from tools.arrangement import *
from ui import fas_interactive
from re import match
from sys import argv



def parse_args(args):
answer = {}
arg_translate = \
{
'-o' : 'output',
'--output' : 'output',
'-f' : 'facility',
'--facility' : 'facility',
'-g' : 'groups',
'--groups' : 'groups',
'-t' : 'total_flows',
'--totalflows' : 'total_flows',
'-a' : 'algo',
'--algorithm' : 'algo',
'-d' : 'dist',
'--distance' : 'dist',
'--forcevanilla' : 'force_vanilla',
'-l' : 'log',
'--log' : 'log',
}
arg_specification = \
{
'output' : {None},
'facility' : {None},
'groups' : {None},
'total_flows' : {None},
'algo' : {'linear', 'cpr_linear', 'linear_gfred', 'cpr_linear_gfred'},
'dist' : {f'm{N}' for N in range(1, 51)} | {'moo', None},
'force_vanilla' : {0},
'log' : {None},
}
arg_expected = True
curr_arg = None
for arg in args:
if arg_expected:
if arg in arg_translate:
curr_arg = arg_translate[arg]
if 0 in arg_specification[curr_arg]:
answer[curr_arg] = True
else:
print('ERROR: Unknown command line argument. To get help execute:')
print('\tpython fas.py')
print('\thelp')
exit(1)
else:
if (arg in arg_specification[curr_arg]) or (None in arg_specification[curr_arg]):
answer[curr_arg] = arg
else:
print('ERROR: Invalid value of an argument. To get help execute:')
print('\tpython fas.py')
print('\thelp')
exit(1)
arg_expected = (not arg_expected) or (0 in arg_specification[curr_arg])
if 'algo' not in answer:
answer['algo'] = 'cpr_linear'
if 'dist' not in answer:
answer['dist'] = 'm2'
if 'force_vanilla' not in answer:
answer['force_vanilla'] = False
if 'log' not in answer:
answer['log'] = None
if ('output' not in answer) or ('facility' not in answer) or ('groups' not in answer):
print('ERROR: One or more required arguments are missing. To get help execute:')
print('\tpython fas.py')
print('\thelp')
exit(1)
return answer



def run(**kwargs):
algo = \
{
'linear' : arrange_linear,
'cpr_linear' : arrange_cpr_linear,
'linear_gfred' : arrange_linear_gfred,
'cpr_linear_gfred' : arrange_cpr_linear_gfred,
}
# Try to load data
try:
if match('g[0-9]+:[.0-9]+x[0-9]+:[.0-9]+x[0-9]+', kwargs['facility']) != None:
kwargs['facility'] = kwargs['facility'][1:]
row_count = int(kwargs['facility'][:kwargs['facility'].index(':')])
kwargs['facility'] = kwargs['facility'][kwargs['facility'].index(':')+1:]
row_step = float(kwargs['facility'][:kwargs['facility'].index('x')])
kwargs['facility'] = kwargs['facility'][kwargs['facility'].index('x')+1:]
column_count = int(kwargs['facility'][:kwargs['facility'].index(':')])
kwargs['facility'] = kwargs['facility'][kwargs['facility'].index(':')+1:]
column_step = float(kwargs['facility'][:kwargs['facility'].index('x')])
kwargs['facility'] = kwargs['facility'][kwargs['facility'].index('x')+1:]
area = int(kwargs['facility'])
if row_count * column_count * area == 0:
raise ValueError()
points = {f'({i},{j})' : Point(column_step * i, row_step * j, area) for i in range(column_count) for j in range(row_count)}
grid_size = (column_count, row_count, 1) if not kwargs['force_vanilla'] else None
else:
points = fas_load(kwargs['facility'], 'fasf')
grid_size = None
groups = fas_load(kwargs['groups'], 'fasg')
total_flows = fas_load(kwargs['total_flows'], 'fast')
except RuntimeError as e:
print(e)
exit(1)
except ValueError:
print('ERROR: Grid parameters are invalid.')
exit(1)
# Compute the distance
distance = {}
if kwargs['dist'] in {f'm{_}' for _ in range(1, 51)}:
order = int(kwargs['dist'][1:])
for point1_name in points:
for point2_name in points:
if order == 1:
distance[(point1_name, point2_name)] = abs(points[point1_name].x - points[point2_name].x) + abs(points[point1_name].y - points[point2_name].y)
else:
distance[(point1_name, point2_name)] = (abs(points[point1_name].x - points[point2_name].x)**order + abs(points[point1_name].y - points[point2_name].y)**order)**(1/order)
elif kwargs['dist'] == 'moo':
for point1_name in points:
for point2_name in points:
distance[(point1_name, point2_name)] = max(abs(points[point1_name].x - points[point2_name].x), abs(points[point1_name].y - points[point2_name].y))
# Run an arrangement algorithm
try:
algo[kwargs['algo']](points, distance, groups, total_flows, kwargs['output'], kwargs['log'], grid_size)
except RuntimeError as e:
print(e)
return



def main():
args = argv[1:]
# If there are no input arguments given, launch an interactive mode
if len(args) == 0:
fas_interactive.run()
else:
run(**parse_args(args))
return



if __name__ == '__main__':
main()
Loading

0 comments on commit 219b6af

Please sign in to comment.