Skip to content

Commit

Permalink
Merge pull request #625 from Dessia-tech/fix/reference-path
Browse files Browse the repository at this point in the history
Patch 0.14.1
  • Loading branch information
GhislainJ committed Nov 8, 2023
2 parents fbe81e0 + dfc059a commit 02fc116
Show file tree
Hide file tree
Showing 11 changed files with 113 additions and 53 deletions.
1 change: 1 addition & 0 deletions .pyenchant_dessia.dict
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ lte
Mahalanobis
matplotlib
Minkowski
Multiplot
nbv
ndarray
networkx
Expand Down
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## 0.14.0

## 0.14.1

## Changed

- Multiplot block takes selector as a string

### Fixed

- 'reference_path' is now passed on by Display block while evaluating

## 0.14.0

### Added

Expand Down
2 changes: 1 addition & 1 deletion code_pylint.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
MAX_ERROR_BY_TYPE = {
"protected-access": 48, # Highly dependant on our "private" conventions. Keeps getting raised
"arguments-differ": 1,
"too-many-locals": 7, # Reduce by dropping vectored objects
"too-many-locals": 6, # Reduce by dropping vectored objects
"too-many-branches": 10, # Huge refactor needed. Will be reduced by schema refactor
"unused-argument": 6, # Some abstract functions have unused arguments (plot_data). Hence cannot decrease
"cyclic-import": 2, # Still work to do on Specific based DessiaObject
Expand Down
43 changes: 26 additions & 17 deletions dessia_common/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,13 +261,7 @@ def __init__(self, standalone_subobject: StandaloneBuiltinsSubobject, embedded_s
self.subclass_arg = subclass_arg
self.array_arg = array_arg

self.samples = []
for i in range(500):
abscissa = round((i % 17) / 2.3, 3)
sample = {"cx": abscissa,
"cy": 100 + abscissa * i / 100,
"label": f"label{i % 5}"}
self.samples.append(plot_data.Sample(values=sample, name=f"Point{i}"))
self._samples = None

MovingObject.__init__(self, name=name)

Expand Down Expand Up @@ -389,29 +383,44 @@ def primitives(self):
return plot_data.PrimitiveGroup(primitives=[contour], name="Contour")

@plot_data_view("Scatter Plot")
def scatter_plot(self):
def scatter_plot(self, **kwargs):
""" Test plot data decorator for scatter plots. """
reference_path = kwargs.get("reference_path", "#")
attributes = ["cx", "cy"]
tooltip = plot_data.Tooltip(attributes=attributes, name="Tooltips")

return plot_data.Scatter(elements=self.samples, tooltip=tooltip, x_variable=attributes[0],
samples = self._random_samples(reference_path)
return plot_data.Scatter(elements=samples, tooltip=tooltip, x_variable=attributes[0],
y_variable=attributes[1], name="Scatter Plot")

@plot_data_view("Parallel Plot")
def parallel_plot(self):
def parallel_plot(self, reference_path: str = "#"):
""" Test plot data decorator for parallel plots. """
return plot_data.ParallelPlot(elements=self.samples, axes=["cx", "cy", "label"], name="Parallel Plot")
samples = self._random_samples(reference_path)
return plot_data.ParallelPlot(elements=samples, axes=["cx", "cy", "label"], name="Parallel Plot")

@plot_data_view("Multiplot", load_by_default=True)
def multiplot(self):
def multiplot(self, reference_path: str = "#"):
""" Test plot data decorator for multiple plots. """
scatter_plot = self.scatter_plot()
parallel_plot = self.parallel_plot()
scatter_plot = self.scatter_plot(reference_path=reference_path)
parallel_plot = self.parallel_plot(reference_path=reference_path)
objects = [scatter_plot, parallel_plot]
sizes = [plot_data.Window(width=560, height=300), plot_data.Window(width=560, height=300)]
return plot_data.MultiplePlots(elements=parallel_plot.elements, plots=objects, sizes=sizes,
samples = self._random_samples(reference_path=reference_path)
return plot_data.MultiplePlots(elements=samples, plots=objects, sizes=sizes,
coords=[(0, 0), (300, 0)], name="Multiple Plot")

def _random_samples(self, reference_path: str = "#"):
""" A dummy method to generate plot_data Samples for testing purpose. """
if self._samples is None:
samples = []
for i in range(500):
abscissa = round((i % 17) / 2.3, 3)
values = {"cx": abscissa, "cy": 100 + abscissa * i / 100, "label": f"label{i % 5}"}
samples.append(plot_data.Sample(values=values, reference_path=f"{reference_path}/path/to/element/{i}",
name=f"Point{i}"))
self._samples = samples
return self._samples

def ill_defined_method(self, arg0, arg1=1, arg2: int = 10, arg3=3):
""" Define a docstring for testing parsing purpose. """
nok_string = "This is a bad coding behavior"
Expand Down Expand Up @@ -658,7 +667,7 @@ class Generator(DessiaObject):

_standalone_in_db = True

def __init__(self, parameter: int, nb_solutions: int = 25, models: List[StandaloneObject] = None, name: str = ''):
def __init__(self, parameter: int, nb_solutions: int = 25, models: List[StandaloneObject] = None, name: str = ""):
self.parameter = parameter
self.nb_solutions = nb_solutions
self.models = models
Expand Down
7 changes: 3 additions & 4 deletions dessia_common/models/workflows/forms_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
from dessia_common.workflow.core import TypedVariable, TypedVariableWithDefaultValue, Pipe, Workflow
from dessia_common.workflow.blocks import InstantiateModel, ModelMethod, GetModelAttribute, ForEach,\
MultiPlot, Unpacker, WorkflowBlock
from dessia_common.forms import Generator, Optimizer, StandaloneObject
from dessia_common.typings import MethodType, AttributeType, PlotDataType
from dessia_common.forms import Generator, Optimizer
from dessia_common.typings import MethodType, AttributeType

instanciate_generator = InstantiateModel(model_class=Generator, name='Instantiate Generator')

Expand Down Expand Up @@ -33,8 +33,7 @@
parallel_optimization = ForEach(workflow_block=optimization_workflow_block, iter_input_index=0, name='ForEach')

multiplot_attributes = ['standalone_subobject/intarg', 'standalone_subobject/name', 'standalone_subobject/floatarg']
multiplot_selector = PlotDataType(class_=StandaloneObject, name="Multiplot")
multiplot = MultiPlot(selector=multiplot_selector, attributes=multiplot_attributes, name='Multiplot')
multiplot = MultiPlot(selector_name="Multiplot", attributes=multiplot_attributes, name='Multiplot')

unpacker = Unpacker(indices=[0], name="Unpacker")

Expand Down
5 changes: 2 additions & 3 deletions dessia_common/models/workflows/workflow_exports.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from dessia_common.workflow.blocks import InstantiateModel, ModelMethod, ModelAttribute, WorkflowBlock, ForEach,\
Export, Unpacker, Archive, MultiPlot
from dessia_common.forms import Generator, Optimizer, StandaloneObject
from dessia_common.typings import MethodType, PlotDataType
from dessia_common.typings import MethodType

inst = InstantiateModel(model_class=Generator, name='Instantiate Generator')

Expand Down Expand Up @@ -32,8 +32,7 @@
parallel_optimization = ForEach(workflow_block=optimization_workflow_block, iter_input_index=0, name='ForEach')

display_attributes = ['intarg', 'strarg', 'standalone_subobject/floatarg']
multiplot_selector = PlotDataType(class_=StandaloneObject, name="Multiplot")
display_ = MultiPlot(selector=multiplot_selector, attributes=display_attributes, name='Display')
display_ = MultiPlot(selector_name="Multiplot", attributes=display_attributes, name='Display')

unpack_results = Unpacker(indices=[0], name="Unpack Results")

Expand Down
50 changes: 41 additions & 9 deletions dessia_common/workflow/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,7 @@ class Display(Block):
""" Abstract block class for display behaviors. """

_displayable_input = 0
_non_editable_attributes = ['inputs']
_non_editable_attributes = ["inputs"]
_type = None
serialize = False

Expand Down Expand Up @@ -667,7 +667,19 @@ def evaluate(self, values, **kwargs):
""" Run method defined by selector's display_setting and compute corresponding DisplayObject. """
object_ = values[self.inputs[0]]
settings = object_._display_settings_from_selector(self.selector.name)
return [attrmethod_getter(object_, settings.method)()]
method = settings.method
if "progress_callback" in kwargs:
# User methods do not necessarily implement progress callback
del kwargs["progress_callback"]
try:
return [attrmethod_getter(object_, method)(**kwargs)]
except TypeError as exc:
arguments = list(kwargs.keys())
warnings.warn(f"Workflow : method '{method}' was called without generic arguments "
f"('{', '.join(arguments)}') because one of them is not set in method's signature.\n\n "
f"Original exception : \n{repr(exc)}")
# Cover cases where kwargs do not correspond to method signature (missing reference_path, for ex)
return [attrmethod_getter(object_, method)()]

def _to_script(self, _) -> ToScriptElement:
""" Write block config into a chunk of script. """
Expand Down Expand Up @@ -736,8 +748,9 @@ def _to_script(self, _) -> ToScriptElement:

class MultiPlot(Display):
"""
Generate a Multi plot which axes will be the given attributes.
Generate a Multiplot which axes will be the given attributes. Will show a Scatter and a Parallel Plot.
:param selector_name: Name of the selector to be displayed in object page. Must be unique throughout workflow.
:param attributes: A List of all attributes that will be shown on axes in the ParallelPlot window.
Can be deep attributes with the '/' separator.
:param name: Name of the block.
Expand All @@ -747,13 +760,17 @@ class MultiPlot(Display):
_type = "plot_data"
serialize = True

def __init__(self, selector: PlotDataType[Type], attributes: List[str], load_by_default: bool = True,
def __init__(self, selector_name: str, attributes: List[str], load_by_default: bool = True,
name: str = "", position: Tuple[float, float] = None):
self.attributes = attributes
Display.__init__(self, inputs=[TypedVariable(List[DessiaObject])], load_by_default=load_by_default,
name=name, selector=selector, position=position)
name=name, selector=PlotDataType(class_=DessiaObject, name=selector_name), position=position)
self.inputs[0].name = "Input List"

def __deepcopy__(self, memo=None):
return MultiPlot(selector_name=self.selector.name, attributes=self.attributes,
load_by_default=self.load_by_default, name=self.name, position=self.position)

def equivalent(self, other):
""" Return whether if the block is equivalent to the other given. """
same_attributes = self.attributes == other.attributes
Expand Down Expand Up @@ -792,17 +809,32 @@ def _to_script(self, _) -> ToScriptElement:
script = f"MultiPlot(attributes={self.attributes}, {self.base_script()})"
return ToScriptElement(declaration=script, imports=[self.full_classname])

def to_dict(self, use_pointers: bool = True, memo=None, path: str = '#',
id_method=True, id_memo=None) -> JsonSerializable:
""" Overwrite to_dict method in order to handle difference of behaviors about selector. """
dict_ = super().to_dict(use_pointers=use_pointers, memo=memo, path=path, id_method=id_method, id_memo=id_memo)
dict_.update({"selector_name": self.selector.name, "attributes": self.attributes, "name": self.name,
"load_by_default": self.load_by_default, "position": self.position})
return dict_

@classmethod
def dict_to_object(cls, dict_: JsonSerializable, force_generic: bool = False, global_dict=None,
pointers_memo: Dict[str, Any] = None, path: str = '#'):
""" Backward compatibility for old versions of Display blocks. """
selector = dict_.get("selector", "Multiplot")
if isinstance(selector, str):
selector_name = dict_.get("selector_name", None)
selector = dict_.get("selector", None)
if selector is None and selector_name is None:
# Backward compatibility < 0.14.0
load_by_default = dict_.get("load_by_default", False)
return DeprecatedMultiPlot(attributes=dict_["attributes"], name=dict_["name"],
load_by_default=load_by_default, position=dict_["position"])
selector = PlotDataType.dict_to_object(selector)
return MultiPlot(selector=selector, attributes=dict_["attributes"], name=dict_["name"],
if selector is not None and selector_name is None:
if isinstance(selector, str):
selector_name = selector
else:
# Backward compatibility 0.14.0 < v < 0.14.1
selector_name = selector["name"]
return MultiPlot(selector_name=selector_name, attributes=dict_["attributes"], name=dict_["name"],
load_by_default=dict_["load_by_default"], position=dict_["position"])


Expand Down
3 changes: 0 additions & 3 deletions dessia_common/workflow/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -516,9 +516,6 @@ def _equivalent_imposed_variables_values(self, other_wf) -> bool:

def __deepcopy__(self, memo=None):
""" Return the deep copy. """
if memo is None:
memo = {}

blocks = [b.__deepcopy__() for b in self.blocks]
output_adress = self.variable_indices(self.output)
if output_adress is None:
Expand Down
1 change: 0 additions & 1 deletion scripts/ci_scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@

# Workflows
"workflow/power_simulation.py",
"workflow/pipes.py",
"workflow/workflow_diverge_converge.py",
"workflow/workflow_clustering.py",
"workflow/workflow_filtering.py",
Expand Down
14 changes: 0 additions & 14 deletions scripts/workflow/pipes.py

This file was deleted.

28 changes: 28 additions & 0 deletions tests/test_workflow/test_copy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import unittest
from parameterized import parameterized
from dessia_common.models.workflows.forms_workflow import workflow_


class TestWorkflowCopy(unittest.TestCase):
def setUp(self) -> None:
self.workflow_copy = workflow_.copy()

def test_check_platform(self):
workflow_.check_platform()
self.workflow_copy.check_platform()

@parameterized.expand([
"pipes",
"nonblock_variables"
])
def test_attributes_len(self, name):
value = getattr(workflow_, name)
copied_value = getattr(self.workflow_copy, name)
self.assertEqual(len(value), len(copied_value))

def variable_names(self):
variable_names_are_equal = [p.input_variable.name == cp.input_variable.name
for p, cp in zip(workflow_.pipes, self.workflow_copy.pipes)]
self.assertTrue(all(variable_names_are_equal))


0 comments on commit 02fc116

Please sign in to comment.