This repository has been archived by the owner on May 19, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
90 changed files
with
33,327 additions
and
17 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
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 @@ | ||
''' | ||
Package for handling files and directories (folders). | ||
''' | ||
|
||
from .file_io import write_instance, read_instance, write_results | ||
from .path import list_files |
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,76 @@ | ||
''' | ||
Module for writing and reading instances to and from files. | ||
''' | ||
|
||
import os | ||
import csv | ||
from typing import List | ||
|
||
from models import PDPInstance, Point | ||
from .path import generate_filename, get_filepath | ||
|
||
def write_instance(instance: PDPInstance): | ||
''' | ||
Receives a PDP Instance and writes it to a file. | ||
''' | ||
index = 0 | ||
while True: | ||
filename = generate_filename(instance.n, instance.p, index) | ||
filepath = get_filepath(filename) | ||
if os.path.exists(filepath): | ||
index += 1 | ||
else: | ||
break | ||
|
||
folder = get_filepath('') | ||
if not os.path.exists(folder): | ||
os.makedirs(folder) | ||
|
||
try: | ||
with open(filepath, 'w') as file: | ||
file.write(str(instance)) | ||
except (IOError, OSError) as error: | ||
print('The instance could not be written:\n', error) | ||
|
||
def read_instance(filename: str) -> PDPInstance: | ||
''' | ||
Reads a file that contains a PDP instance and returns its object. | ||
''' | ||
filepath = get_filepath(filename) | ||
try: | ||
# if file is empty | ||
if os.stat(filepath).st_size == 0: | ||
print(' File %s is empty.' % filename) | ||
return None | ||
|
||
with open(filepath, 'r') as file: | ||
# read every line of the file and parse it to constructor's arguments of Point class | ||
points = [Point(*map(int, line.split())) for line in file] | ||
|
||
# get p from filename | ||
p = int(filename.split('_')[1]) | ||
# return an object of PDPInstance | ||
return PDPInstance(p, points) | ||
except FileNotFoundError as error: | ||
print(' ', error) | ||
return None | ||
except ValueError: | ||
print(' File %s has invalid format.' % filename) | ||
return None | ||
|
||
def write_results(filename: str, data: List[List[str]]): | ||
''' | ||
Writes a CSV file containing the results of an experiment. | ||
''' | ||
folder = get_filepath('', 'results') | ||
if not os.path.exists(folder): | ||
os.makedirs(folder) | ||
|
||
filepath = get_filepath(filename, 'results') | ||
try: | ||
with open(filepath, 'w', newline='') as file: | ||
writer = csv.writer(file) | ||
for row in data: | ||
writer.writerow(row) | ||
except (IOError, OSError) as error: | ||
print('The results could not be written:\n', error) |
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,54 @@ | ||
''' | ||
Module for handling directories and getting paths. | ||
''' | ||
|
||
import os | ||
import random | ||
from typing import List | ||
|
||
def generate_filename(n: int, p: int, index: int = 0) -> str: | ||
''' | ||
Generates a name for an instance's file: | ||
<n>_<p>_<index>.dat | ||
''' | ||
return str(n) + '_' + str(p) + '_' + f'{index:02d}' + '.dat' | ||
|
||
def get_filepath(filename: str, folder: str = 'instances') -> str: | ||
''' | ||
Returns the path of a file based on it's name. | ||
''' | ||
current_dir = os.path.dirname(__file__) | ||
filepath = os.path.join(current_dir, '..', folder, filename) | ||
return os.path.abspath(filepath) | ||
|
||
def list_files(size: int, number: int) -> List[str]: | ||
''' | ||
Returns a list of number .dat files in the instances/ subdirectory | ||
according to the specified size. | ||
''' | ||
current_dir = os.path.dirname(__file__) | ||
subdirectory = os.path.abspath(os.path.join(current_dir, '..', 'instances')) | ||
try: | ||
files = os.listdir(subdirectory) | ||
except FileNotFoundError: | ||
os.makedirs(subdirectory) | ||
return list_files(size, number) | ||
else: | ||
prefix = str(size) + '_' | ||
suffix = '.dat' | ||
|
||
filtered_files = [ | ||
file for file in files | ||
if file.startswith(prefix) and file.endswith(suffix) | ||
] | ||
|
||
if not filtered_files: | ||
print(f' error: there are no instances of size {size}') | ||
return [] | ||
elif len(filtered_files) == number: | ||
return filtered_files | ||
elif len(filtered_files) > number: | ||
return random.sample(filtered_files, number) | ||
else: | ||
print(f' error: there are only {len(filtered_files)} files, not {number}') |
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,8 @@ | ||
''' | ||
Generate an instance from the command line and save it to a file. | ||
''' | ||
|
||
from generator import generate_instance, parse_arguments | ||
|
||
args = parse_arguments() | ||
generate_instance(*args) |
This file was deleted.
Oops, something went wrong.
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 @@ | ||
''' | ||
Package that contains necessary functions to generate an instance. | ||
''' | ||
|
||
from .generate_instance import generate_instance | ||
from .cl_argsparse import parse_arguments |
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,113 @@ | ||
''' | ||
Command line arguments parser. | ||
Module to parse arguments from the command line. | ||
The parsed arguments are then used in other module to generate an instance. | ||
''' | ||
|
||
import sys | ||
import argparse | ||
from typing import Tuple | ||
|
||
from validations import is_valid_n, is_percentage, is_positive_int, are_valid_dimensions, is_valid_p | ||
|
||
def parse_arguments() -> Tuple[int, int, Tuple[int, int], int, int]: | ||
''' | ||
An ArgumentParser object receives arguments from the command line | ||
and returns them in a tuple of 4 elements. | ||
(n: int, p: int, dimensions: Tuple[int, int], instances: int) | ||
If no arguments are given the program will end. | ||
''' | ||
# parser's description display when --help is used | ||
description = 'Generates an random instance with the following arguments:' | ||
# instantiate argument parser | ||
parser = argparse.ArgumentParser(description=description) | ||
|
||
# remove optional arguments from parser but store it to append it at the end | ||
#* this 'hack' is used because otherwise the | ||
#* required exclusive arguments are displayed as optional when they aren't | ||
optional = parser._action_groups.pop() | ||
|
||
# required arguments | ||
# both 'n' and 'p' must be specified | ||
required = parser.add_argument_group('requiered arguments') | ||
required.add_argument( | ||
'n', | ||
type=is_valid_n, | ||
help='total number of candidate points' | ||
) | ||
required.add_argument( | ||
'p', | ||
type=is_percentage, | ||
help='percentage of points to select' | ||
) | ||
|
||
# required exclusive arguments | ||
# only one argument needs to be specified | ||
shape = required.add_mutually_exclusive_group(required=True) | ||
shape.add_argument( | ||
'-s', '--square', | ||
metavar='length', | ||
type=is_positive_int, | ||
help='locate the n points in a squared plane of dimensions length * length at most' | ||
) | ||
shape.add_argument( | ||
'-r', '--rectangle', | ||
metavar=('length', 'width'), | ||
nargs=2, | ||
type=is_positive_int, | ||
help='locate the n points in a rectangular plane of dimensions length * width at most' | ||
) | ||
|
||
# optional arguments | ||
optional.add_argument( | ||
'-n', '--number', | ||
type=is_positive_int, | ||
default=1, | ||
help='number of instances to generate, default to 1' | ||
) | ||
optional.add_argument( | ||
'-v', '--verbose', | ||
type=int, | ||
default=0, | ||
choices=(0, 1, 2), | ||
help='''increase output verbosity: | ||
0 = no output (default). | ||
1 = outputs instance generation and writing. | ||
2 = same as 1 and shows a plot.''' | ||
) | ||
# append optional arguments to parser to display at the end | ||
parser._action_groups.append(optional) | ||
|
||
# if no arguments are given, display help as if -h was used | ||
if len(sys.argv) == 1: | ||
parser.print_help() | ||
sys.exit(1) | ||
|
||
# parse arguments from command line | ||
arguments = parser.parse_args() | ||
|
||
# if chosen shape is squared | ||
if arguments.square: | ||
dimensions = (arguments.square, arguments.square) | ||
# if chosen shape is rectangular | ||
else: | ||
dimensions = tuple(arguments.rectangle) | ||
n = arguments.n | ||
percentage = arguments.p | ||
p = int(percentage * n) | ||
|
||
# validate dimensions and p | ||
msg = ' error: invalid' | ||
if not are_valid_dimensions(n, dimensions): | ||
print(f'{msg} dimensions: x*y must be greater or equal to n') | ||
sys.exit(1) | ||
elif not is_valid_p(p): | ||
print(f'{msg} p value: must be 2 or greater') | ||
sys.exit(1) | ||
else: | ||
# return parsed arguments gathered in a tuple | ||
return (arguments.n, p, dimensions, arguments.number, arguments.verbose) |
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,48 @@ | ||
''' | ||
Module to generate an instance for the PDP. | ||
''' | ||
|
||
from typing import Tuple | ||
|
||
from models import PDPInstance | ||
from models.plotter import plot_instance | ||
from file_handling import write_instance | ||
|
||
def generate_instance(n: int, p: int, dimensions: Tuple[int, int], number: int, verbose: int): | ||
''' | ||
Generates an random instance based on the arguments parsed: | ||
n: total number of candidate points. | ||
p: points to choose from n. | ||
dimensions: maximum values for coordinates (x, y). | ||
number: instances to generate. | ||
verbose: output verbosity. | ||
The generated instance is saved to a .dat file. | ||
''' | ||
x_max, y_max = dimensions | ||
|
||
if not verbose: | ||
str_gen = '' | ||
str_wr = '' | ||
str_done = '' | ||
else: | ||
str_gen = 'Generating instance... ' | ||
str_wr = 'Writing instance to file... ' | ||
str_done = 'done.' | ||
|
||
for _ in range(number): | ||
print(str_gen, end='', flush=True) | ||
instance = PDPInstance.random(n, p, x_max, y_max) | ||
print(str_done) | ||
|
||
print(str_wr, end='', flush=True) | ||
write_instance(instance) | ||
print(str_done) | ||
|
||
if verbose == 2: | ||
plot_instance(instance.points) |
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,3 @@ | ||
''' | ||
Package of the implemented algorithms of heuristics for the PDP. | ||
''' |
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,53 @@ | ||
''' | ||
Module of the implementations of constructive heuristics for the PDP. | ||
''' | ||
|
||
from models import PDPInstance, Solution | ||
from models.plotter import plot_instance_solution | ||
|
||
def greedy_construction(instance: PDPInstance, verbose: bool = False) -> Solution: | ||
''' | ||
Starting by choosing the 2 farthest points, | ||
the algorithm adds the farthest point to the current solution until p is reached. | ||
Returns a list of the p chosen points. | ||
''' | ||
# copy all the instance's points | ||
candidates = list(instance.points) | ||
# initialize the solution with the 2 farthest points | ||
solution = list(instance.get_farthest_points()) | ||
if verbose: | ||
print('Greedy Construction (GC)') | ||
print(' Solution starts with the 2 farthest points:') | ||
print(f' S = {solution}') | ||
plot_instance_solution(instance.points, solution) | ||
|
||
# remove them from the candidates | ||
candidates.remove(solution[0]) | ||
candidates.remove(solution[1]) | ||
|
||
# until solution has p points | ||
while len(solution) < instance.p: | ||
len_solution = len(solution) | ||
# add the point farthest to the current solution | ||
maximum = max( | ||
# the distance between a point and a set is | ||
# the smallest of the distances between the point and the members of the set | ||
((cp, min(instance.distances[cp.index][sp.index] for sp in solution)) | ||
for cp in candidates), | ||
key=lambda x: x[1] | ||
) | ||
chosen_point = maximum[0] | ||
# add the new point | ||
solution.append(chosen_point) | ||
# remove it from candidates | ||
candidates.remove(chosen_point) | ||
|
||
if verbose: | ||
print(f' p = {len_solution}\n') | ||
print(' Add farthest point to current solution:') | ||
print(f' x = {repr(chosen_point)}') | ||
print(f' S = {solution}') | ||
plot_instance_solution(instance.points, solution) | ||
|
||
return solution |
Oops, something went wrong.