Skip to content

Commit

Permalink
first progress on plotting
Browse files Browse the repository at this point in the history
  • Loading branch information
dodu94 committed Jan 26, 2025
1 parent 0c783d2 commit 3e2bfb1
Show file tree
Hide file tree
Showing 10 changed files with 717 additions and 17 deletions.
6 changes: 5 additions & 1 deletion docs/source/usage/folders.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,12 @@ The following is a scheme of the JADE folder structure:
| | | |----- serpent
| | | | |----- <inputfile>.i
| | | |----- benchmark_metadata.json
| | |
| | |-----<Benchmark run 2>
| | |----- ...
| | | |----- ...
| | |
| | |----- metadata.json
| |
| |----- ...
| |----- TypicalMaterials
|
Expand Down
65 changes: 64 additions & 1 deletion src/jade/config/atlas_config.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,79 @@
from __future__ import annotations

from dataclasses import dataclass
from enum import Enum
from pathlib import Path

from jade.post.plotter import PlotType
import yaml

from jade.helper.aux_functions import PathLike


@dataclass
class ConfigAtlasProcessor:
benchmark: str
plots: list[PlotConfig]

@classmethod
def from_yaml(cls, config_file: PathLike) -> ConfigAtlasProcessor:
"""Create a ConfigExcelProcessor object from a yaml file.
Parameters
----------
config_file : PathLike
path to the configuration file
Returns
-------
ConfigAtlasProcessor
The configuration object
"""
with open(config_file) as f:
cfg = yaml.safe_load(f)

benchmark = Path(config_file).name.split(".")[0]

plots = []
for table_name, dict in cfg.items():
plots.append(PlotConfig.from_dict(dict, table_name))

return cls(benchmark=benchmark, plots=plots)


@dataclass
class PlotConfig:
name: str
results: list[int | str]
plot_type: PlotType
title: str
x_label: str
y_labels: list[str]
x: str
y: str
# optionals
expand_runs: bool = True

@classmethod
def from_dict(cls, dictionary: dict, name: str) -> PlotConfig:
return cls(
name=name,
results=dictionary["results"],
plot_type=PlotType(dictionary["plot_type"]),
title=dictionary["title"],
x_label=dictionary["x_label"],
y_labels=dictionary["y_labels"],
x=dictionary["x"],
y=dictionary["y"],
expand_runs=dictionary.get("expand_runs", True),
)


class PlotType(Enum):
BINNED = "binned"
RATIO = "ratio"
EXP = "exp points"
EXP_GROUP = "exp points group"
CE_EXP_GROUP = "exp points group CE"
DISCRETE_EXP = "discrete exp points"
GROUPED_BARS = "grouped bars"
WAVES = "waves"
220 changes: 220 additions & 0 deletions src/jade/post/atlas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
import logging
import os

# import win32com.client
# import aspose.words
import docx
import pandas as pd
from docx.enum.table import WD_ALIGN_VERTICAL
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.oxml import OxmlElement, parse_xml
from docx.oxml.ns import nsdecls, qn
from docx.shared import Inches

from jade.helper.aux_functions import PathLike


class Atlas:
def __init__(self, template: PathLike, name: str):
"""
Atlas of plots for post-processing
Parameters
----------
template : PathLike
path to the word template file.
name : str
name of the atlas file.
Returns
-------
None.
"""
self.name = name # Name of the Atlas (from libraries)
# Open The Atlas template
doc = docx.Document(template)
doc.add_heading("JADE ATLAS: " + name, level=0)
self.outname = "atlas_" + name # Name for the outfile
self.doc = doc # Word Document

def insert_img(self, img, width=Inches(7.5)):
"""Insert an image in the word document
Parameters
----------
img : _type_
_description_
width : _type_, optional
_description_, by default Inches(7.5)
"""
self.doc.add_picture(img, width=width)
last_paragraph = self.doc.paragraphs[-1]
last_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER

# def insert_df(
# self,
# df,
# caption=None,
# highlight=False,
# tablestyle=None, # , template_idx=None,
# ):
# """
# Inser a dataframe as a table in a Word file

# Parameters
# ----------
# df : pd.DataFrame
# dataframe to insert.
# caption : str, optional
# caption of the table. The default is None
# highlight : bool, optional
# Very specific for stress assessment. The default is False.
# # template_idx : int, optional
# # index of the template table to use. The default is None
# tablestyle : str, optional
# table word style to apply. The default is None

# Returns
# -------
# table : docx.Table
# table inserted.

# """
# # Create the table or inherit a template
# template_idx = None # other possibilities not implemented anymore
# if template_idx is None:
# table = self.doc.add_table(1, len(df.columns))
# else:
# template = self.template_tables[template_idx]
# table = template.insert(self.doc)

# # Assign style if provided
# if table.style is not None:
# table.style = tablestyle

# # If template is not provided, the header row must be filled
# if template_idx is None:
# # add the header rows.
# for j in range(df.shape[-1]):
# table.cell(0, j).text = df.columns[j]

# # Add the rest of the data frame
# # val = df.values
# for i, (idx, row) in enumerate(df.iterrows()):
# # Understand is safety margin is barely acceptable
# flag_almost = False
# try:
# sm = float(row["Safety Margin"])
# if sm > 1 and sm < 1.1:
# flag_almost = True
# except KeyError:
# pass
# except ValueError:
# pass
# except TypeError:
# # cannot convert to float
# pass

# row_cells = table.add_row().cells
# for j, item in enumerate(row):
# cell = row_cells[j]
# cell.text = str(item)
# cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
# for par in cell.paragraphs:
# par.style = "Table"
# if highlight is not None:
# if cell.text == "NOK":
# self._highlightCell(cell)
# elif cell.text == "OK" and flag_almost:
# self._highlightCell(cell, color="FFFF46")

# if caption is not None:
# paragraph = self.doc.add_paragraph("Table ", style="Didascalia")
# self._wrapper(paragraph, "table")
# paragraph.add_run(" - " + caption)
# # paragraph = doc.add_paragraph('Figure Text', style='Didascalia')

# return table

def save(self, outpath, pdfprint=True):
"""
Save word atlas and possibly export PDF
Parameters
----------
outpath : str/path
path to export the atlas
pdfprint : Boolean, optional
If True export also in PDF format
Returns
-------
None.
"""
outpath_word = os.path.join(outpath, self.outname + ".docx")
# outpath_pdf = os.path.join(outpath, self.outname + ".pdf")
if len(outpath_word) > 259:
logging.warning(
"The path to the word document is too long, the file will be truncated"
)
outpath_word = outpath_word[:254] + ".docx"

try:
self.doc.save(outpath_word)
except FileNotFoundError as e:
# If there is still an error it may be due to special char
# print the original exception
print(" The following is the original exception:")
print(e)
print(
"\n it may be due to invalid characters in the file name or a path too long"
)

# Remove PDF printing. If required, word document can be saved manually.
if pdfprint:
pass

@staticmethod
def _wrapper(paragraph, ptype):
"""
Wrap a paragraph in order to add cross reference
Parameters
----------
paragraph : docx.Paragraph
image to wrap.
ptype : str
type of paragraph to wrap
Returns
-------
None.
"""
if ptype == "table":
instruction = " SEQ Table \\* ARABIC"
elif ptype == "figure":
instruction = " SEQ Figure \\* ARABIC"
else:
raise ValueError(ptype + " is not a supported paragraph type")

run = run = paragraph.add_run()
r = run._r
fldChar = OxmlElement("w:fldChar")
fldChar.set(qn("w:fldCharType"), "begin")
r.append(fldChar)
instrText = OxmlElement("w:instrText")
instrText.text = instruction
r.append(instrText)
fldChar = OxmlElement("w:fldChar")
fldChar.set(qn("w:fldCharType"), "end")
r.append(fldChar)

# @staticmethod
# def _highlightCell(cell, color="FBD4B4"):
# shading_elm_1 = parse_xml(
# r'<w:shd {} w:fill="'.format(nsdecls("w")) + color + r'"/>'
# )
# cell._tc.get_or_add_tcPr().append(shading_elm_1)
77 changes: 77 additions & 0 deletions src/jade/post/atlas_processor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from __future__ import annotations

import logging
from copy import deepcopy
from pathlib import Path

from jade.config.atlas_config import ConfigAtlasProcessor
from jade.helper.aux_functions import PathLike, print_code_lib
from jade.helper.constants import CODE
from jade.post.atlas import Atlas
from jade.post.excel_processor import ExcelProcessor
from jade.post.plotter import PlotFactory


class AtlasProcessor:
def __init__(
self,
raw_root: PathLike,
atlas_folder_path: PathLike,
cfg: ConfigAtlasProcessor,
codelibs: list[tuple[str, str]],
) -> None:
"""Object responsible to produce the excel comparison results for a given
benchmark.
Parameters
----------
raw_root : PathLike
path to the raw data folder root
atlas_folder_path : PathLike
path to the atlas folder where the results will be stored
cfg : ConfigAtlasProcessor
configuration options for the excel processor
codelibs : list[tuple[str, str]]
list of code-lib results that should be compared. The first one is
interpreted as the reference data.
"""
self.atlas_folder_path = atlas_folder_path
self.raw_root = raw_root
self.cfg = cfg
self.codelibs = codelibs

def process(self) -> None:
"""Process the atlas comparison for the given benchmark. It will produce one
atlas file comparing all requested code-lib results in each plot.
"""
# instantiate the atlas
# atlas = Atlas(self.atlas_folder_path, self.cfg.benchmark)

for plot_cfg in self.cfg.plots:
dfs = []
# get global df results for each code-lib
for code_tag, lib in self.codelibs:
code = CODE(code_tag)
codelib = print_code_lib(code, lib)
logging.info("Parsing reference data")
raw_folder = Path(self.raw_root, codelib, self.cfg.benchmark)

df = ExcelProcessor._get_table_df(plot_cfg.results, raw_folder)
if plot_cfg.expand_runs:
run_dfs = []
df = df.reset_index()
for run in df["Case"].unique():
run_df = df[df["Case"] == run]
run_dfs.append((run, run_df))
dfs.append((plot_cfg.name, run_dfs))
else:
dfs.append((plot_cfg.name, df.reset_index()))

# create the plot
if plot_cfg.expand_runs: # one plot for each case/run
for case, df in dfs:
cfg = deepcopy(plot_cfg)
cfg.name = f"{cfg.name} {case}"
plot = PlotFactory.create_plot(plot_cfg, df)
else:
plot = PlotFactory.create_plot(plot_cfg, dfs)
Loading

0 comments on commit 3e2bfb1

Please sign in to comment.