"""
We plot the end results of an experiment with 2 algorithms on 3 problems.
End results plots are violin plots, which show the distribution of the
data samples for values, overlaid with Box Plots, which show the raw
statistical information in a straightforward way. Both plots are vertical.
A violin plot shows you the approximate distribution of the data.
It is aligned vertically, meaning, e.g., that the vertical axis of the plot is
the best achieved objective value.
For any objective value along the vertical axis, the width of violin plot then
corresponds to how often the value was encountered.
A box plot shows similar information, but boiled down to quantiles and
outliers.
It is therefore more concrete and easier to compare visually. However, it does
not really show you whether the distribution of values is uni- or multi-modal,
which, in turn, is visible in the violin plots.
We here therefore conduct a small example experiment, collect its end results,
and plot them into such a violin-box plot.
See Also
- https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.violinplot.html
- https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.boxplot.html
"""
from time import sleep
from webbrowser import open_new_tab
from pycommons.io.temp import temp_dir
from pycommons.processes.caller import is_build
from moptipy.algorithms.so.hill_climber import HillClimber
from moptipy.algorithms.so.rls import RLS
from moptipy.api.execution import Execution
from moptipy.api.experiment import run_experiment
from moptipy.evaluation.end_results import from_logs
from moptipy.evaluation.plot_end_results import plot_end_results
from moptipy.examples.jssp.gantt_space import GanttSpace
from moptipy.examples.jssp.instance import Instance
from moptipy.examples.jssp.makespan import Makespan
from moptipy.examples.jssp.ob_encoding import OperationBasedEncoding
from moptipy.operators.permutations.op0_shuffle import Op0Shuffle
from moptipy.operators.permutations.op1_swap2 import Op1Swap2
from moptipy.spaces.permutations import Permutations
from moptipy.utils.plot_utils import create_figure, save_figure
# The three JSSP instances we want to try to solve:
problems = [lambda: Instance.from_resource("ft06"),
lambda: Instance.from_resource("la24"),
lambda: Instance.from_resource("dmu23")]
def make_rls(problem: Instance) -> Execution:
"""
Create an RLS Execution.
:param problem: the JSSP instance
:returns: the execution
"""
ex = Execution() # create execution context
perms = Permutations.with_repetitions( # create search space as
problem.jobs, problem.machines) # permutations with repetitions
ex.set_search_space(perms) # set search space
ex.set_encoding(OperationBasedEncoding(problem)) # set encoding
ex.set_solution_space(GanttSpace(problem)) # solution space: Gantt charts
ex.set_objective(Makespan(problem)) # objective function is makespan
ex.set_algorithm(RLS( # create RLS that
Op0Shuffle(perms), # create random permutation
Op1Swap2())) # swap two jobs
ex.set_max_fes(200) # permit 200 FEs
return ex
def make_hill_climber(problem: Instance) -> Execution:
"""
Create a hill climber Execution.
:param problem: the JSSP instance
:returns: the execution
"""
ex = Execution() # create execution context
perms = Permutations.with_repetitions( # create search space as
problem.jobs, problem.machines) # permutations with repetitions
ex.set_search_space(perms) # set search space
ex.set_encoding(OperationBasedEncoding(problem)) # set encoding
ex.set_solution_space(GanttSpace(problem)) # solution space: Gantt charts
ex.set_objective(Makespan(problem)) # objective function is makespan
ex.set_algorithm( # now construct algorithm
HillClimber( # create hill climber that
Op0Shuffle(perms), # create random permutation
Op1Swap2())) # swap two jobs
ex.set_max_fes(200) # permit 200 FEs
return ex
# We execute the whole experiment in a temp directory.
# For a real experiment, you would put an existing directory path into `td` by
# doing `from pycommons.io.path import Path; td = Path("mydir")` and not use
# the `with` block.
with temp_dir() as td: # create temporary directory `td`
run_experiment(base_dir=td, # set the base directory for log files
instances=problems, # define the problem instances
setups=[make_rls, # provide RLS run creator
make_hill_climber], # provide hill climber
n_runs=31) # we will execute 31 runs per setup
# Once we arrived here, the experiment with 2*3*31=186 runs has completed.
data = [] # we will load the data into this list
from_logs(td, data.append) # load all end results
# Plot the end results in a scaled fashion: All objective values are
# divided by the lower bound. Therefore, 1 is optimal.
fig = create_figure(width=4) # create an empty, 4"-wide figure
plot_end_results(end_results=data,
figure=fig,
dimension="scaledF")
# Notice that save_figure returns a list of files that has been generated.
# You can specify multiple formats, e.g., ("svg", "pdf", "png") and get
# multiple files.
# Below, we only generate one svg image and remember the list (containing
# the single generated file) as `files`. We will add all other files we
# generate to this list and, in the end, display all of them in the web
# browser.
files = save_figure(fig=fig, # store fig to a file
file_name="end_results_scaled", # base name
dir_name=td, # store graphic in temporary directory
formats="svg") # file type = svg
del fig # dispose figure
# Plot the end results in an un-scaled fashion: All objective values are
# used as is. This makes the problems less comparable.
fig = create_figure(width=4) # create an empty, 4"-wide figure
plot_end_results(end_results=data, figure=fig, dimension="plainF")
files.extend(save_figure(fig=fig, # store fig to a file
file_name="end_results", # base name
dir_name=td, # store graphic in temp directory
formats="svg")) # file type = svg
del fig # dispose figure
# Now we compare the FE when the last improvement happened.
fig = create_figure(width=4) # create an empty, 4"-wide figure
plot_end_results(end_results=data, figure=fig,
dimension="lastImprovementFE")
files.extend(save_figure(fig=fig, # store fig to a file
file_name="last_improv_fe", # base name
dir_name=td, # store graphic in temp directory
formats="svg")) # file type = svg
del fig # dispose figure
# OK, we have now plotted a set of different end results plots.
# We will open them in the web browser if we are not in a make build.
if not is_build():
for file in files: # for each file we generated
open_new_tab(f"file://{file}") # open a browser tab
sleep(10) # sleep 10 seconds (enough time for the browser to load)
# The temp directory is deleted as soon as we leave the `with` block.
# Hence, all the figures generated above as well as the experimental results
# now have disappeared.