Custom Bots
The Sim Bot and Batch Bot have lots of functionality but it’s inevitable that you
have some additional process or requirement that is not currently supported. Fortunately, it’s
very easy to customise a pyqalx.Bot
to do what you need or make an additional bot and connect
it to other bots.
Subclass the BatchBot
Let us first investigate adding some additional results plots at the end of processing a batch. The code below gives an idea of how this can be achieved with comments in the code explaining the process as much as possible.
from io import BytesIO
# WE USE PYPLOT https://matplotlib.org/api/pyplot_api.html
import matplotlib.pyplot as plt
# WE USE PANDAS TO HELP PLOT DATA https://pandas.pydata.org/Pandas_Cheat_Sheet.pdf
import pandas as pd
from qalx_orcaflex.bots import BatchBot
from qalx_orcaflex.bots.batch import process_batch, post_process_batch
def make_plots(load_case_data, title):
# we know that our load_case_data will form a dataframe
th_dataframe = pd.DataFrame(**load_case_data)
th_dataframe.sort_index(inplace=True) # we want the x-axis in order
th_dataframe.plot(title=title, grid=True) # give it a title and make a grid
plot_bytes = BytesIO() # we are going to save the bytes straight to qalx
plt.savefig(plot_bytes) # save the figure into the file-like object
plot_bytes.seek(0) # before we write the file to qalx we need to go back to the
# start of the file
return plot_bytes
def extra_post_processing(job):
post_process_batch(job) # FIRST EXECUTE THE EXISTING CODE
# now we want to do our own plots
# but we only want to do this if the results have already been summarised
if job.entity['meta'].get('results_summary'):
# get the results summaries
all_results = job.session.item.get(job.entity['meta'].get(
'results_summary'))['data']
job.entity["meta"]["result_plots"] = []
# plot time histories
for result_name, result_data in all_results["Time Histories"].items():
if result_data['vs_info']: # check we have load case info
for load_case_info, load_case_data in result_data['vs_info'].items():
title = f"{result_name} vs. {load_case_info}" # give it a title
plot_bytes = make_plots(load_case_data, title) # get the plot
plot_item = job.s.item.add(
input_file=plot_bytes,
file_name=f"{title}.png",
meta={"_class": "orcaflex.batch_plot"}
) # make a new qalx item with the plot image
job.entity["meta"]["result_plots"].append(plot_item["guid"]) #
# save a reference to the plot on the original item in the meta
job.save_entity() # save the entity back to qalx
#
# We subclass BatchBot so that we know that it will do the normal process function
# as intended. We will augment the postprocess_function with our extra code to make
# plots
#
class PlotBatchBot(BatchBot):
def __init__(self, *args, **kwargs):
super(PlotBatchBot, self).__init__(*args, **kwargs)
self.process_function = process_batch
self.postprocess_function = extra_post_processing
# Instantiate the new class to pass to the command line or build in a factory
plot_th_bot = PlotBatchBot("BatchWithPlots")
The code above, if run in the place of Batch Bot will perform the same functions as
Batch Bot but will also save plots of load case info vs. time history results. References
to the saved plots will be found on the meta
of the pyqlax.Group
that represents the batch.
Make a new bot
The following code makes plots in the same way as above (although for Range Graphs this time) except it will run as a separate bot. There are good reasons why you might want to do this:
This bot doesn’t depend on OrcFxAPI and so it can be run on a linux machine saving costly windows clock cycles for the serious work of simulation
If the process here took a long time then it would happen in parallel to the processing of batches saving time over the serial approach in the previous example.
from io import BytesIO
import matplotlib.pyplot as plt
import pandas as pd
from pyqalx import Bot
from pyqalx.bot import QalxJob
plot_bot = Bot("PlottingBot")
@plot_bot.process
def make_plots(job: QalxJob):
batch = job.entity
summary = batch.meta.get("results_summary")
if summary:
job.entity["meta"]["result_plots"] = []
summary_item = job.session.item.get(summary)
data = summary_item["data"]
for result_name, range_graph in data["Range Graphs"].items():
vs_info = range_graph["vs_info"]
for load_case_info_name, load_case_data in vs_info.items():
title = f"{result_name} max vs. {load_case_info_name}"
df = pd.DataFrame(**load_case_data)
df.sort_index(inplace=True)
df.plot(title=title, grid=True)
plot_bytes = BytesIO()
plt.savefig(plot_bytes)
plot_bytes.seek(0)
plot_item = job.s.item.add(
input_file=plot_bytes, file_name=f"{title}.png"
)
job.log.debug(plot_item["file"]["url"])
job.entity["meta"]["result_plots"].append(plot_item["guid"])
job.save_entity()
The only thing we need to remember here is that we need BatchBot
to pass the job on to our
PlottingBot
. This can be done with a workflow in a factory or using BatchOptions
:
from qalx_orcaflex.core import QalxOrcaFlex, OrcaFlexBatch
import qalx_orcaflex.data_models as dm
qfx = QalxOrcaFlex()
options = dm.BatchOptions(
sim_queue="example-sim-queue",
batch_queue="example-batch-queue",
send_batch_to=['batch-plotting-queue'] # The batch will be sent here once it's
# complete
)
batch = OrcaFlexBatch(
"My Batch to Plot",
batch_options=options,
session=qfx)