Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add auto fading for off-diagonal blocks #50

Merged
merged 15 commits into from
Mar 21, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
sudo apt update
sudo apt-get install texlive-pictures texlive-latex-extra -y
pip install -U pip wheel
pip install testflo numpy${{ matrix.numpy-version }}
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
examples/*
!examples/*.py

*.so
*.o
*.pyc
Expand Down
14 changes: 12 additions & 2 deletions examples/kitchen_sink.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,14 @@
RIGHT,
)

x = XDSM()
x = XDSM(
auto_fade={
# "inputs": "none",
"outputs": "connected",
"connections": "outgoing",
# "processes": "none",
}
)

x.add_system("opt", OPT, r"\text{Optimizer}")
x.add_system("DOE", DOE, r"\text{DOE}")
Expand All @@ -24,7 +31,7 @@
x.add_system("D2", IFUNC, "D_2", faded=True)

x.add_system("D3", IFUNC, "D_3")
x.add_system("subopt", SUBOPT, "SubOpt")
x.add_system("subopt", SUBOPT, "SubOpt", faded=True)
x.add_system("G1", GROUP, "G_1")
x.add_system("G2", IGROUP, "G_2")
x.add_system("MM", METAMODEL, "MM")
Expand All @@ -44,9 +51,12 @@
x.connect("opt", "D2", ["z", "y_1"])
x.connect("opt", "D3", "z, y_1")
x.connect("opt", "subopt", "z, y_1")
x.connect("D3", "G1", "y_3")
x.connect("subopt", "G1", "z_2")
x.connect("subopt", "G2", "z_2")
x.connect("subopt", "MM", "z_2")
x.connect("subopt", "F", "f")
x.connect("MM", "subopt", "f")
x.connect("opt", "G2", "z")
x.connect("opt", "F", "x, z")
x.connect("opt", "F", "y_1, y_2")
Expand Down
106 changes: 87 additions & 19 deletions pyxdsm/XDSM.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,11 @@ def _label_to_spec(label, spec):
Input = namedtuple("Input", "node_name label label_width style stack faded")
Output = namedtuple("Output", "node_name label label_width style stack faded side")
Connection = namedtuple("Connection", "src target label label_width style stack faded")
Process = namedtuple("Process", "systems arrow faded")


class XDSM(object):
def __init__(self, use_sfmath=True, optional_latex_packages=None):
class XDSM:
def __init__(self, use_sfmath=True, optional_latex_packages=None, auto_fade=None):
"""Initialize XDSM object

Parameters
Expand All @@ -126,14 +127,21 @@ def __init__(self, use_sfmath=True, optional_latex_packages=None):
Whether to use the sfmath latex package, by default True
optional_latex_packages : string or list of strings, optional
Additional latex packages to use when creating the pdf and tex versions of the diagram, by default None
auto_fade : dictionary, optional
Controls the automatic fading of inputs, outputs, connections and processes based on the fading of diagonal blocks. For each key "inputs", "outputs", "connections", and "processes", the value can be one of:
- "all" : fade all blocks
- "connected" : fade all components connected to faded blocks
- "none" : do not auto-fade anything
For connections there are two additional options:
- "incoming" : Fade all connections that are incoming to faded blocks.
- "outgoing" : Fade all connections that are outgoing from faded blocks.
"""
self.systems = []
self.connections = []
self.left_outs = {}
self.right_outs = {}
self.ins = {}
self.processes = []
self.process_arrows = []

self.use_sfmath = use_sfmath
if optional_latex_packages is None:
Expand All @@ -146,6 +154,26 @@ def __init__(self, use_sfmath=True, optional_latex_packages=None):
else:
raise ValueError("optional_latex_packages must be a string or a list of strings")

self.auto_fade = {"inputs": "none", "outputs": "none", "connections": "none", "processes": "none"}
fade_options = ["all", "connected", "none"]
if auto_fade is not None:
if any([key not in self.auto_fade for key in auto_fade.keys()]):
raise ValueError(
"The supplied 'auto_fade' dictionary contains keys that are not recognized. "
+ "valid keys are 'inputs', 'outputs', 'connections', 'processes'."
)

self.auto_fade.update(auto_fade)
for key in self.auto_fade.keys():
option_is_valid = self.auto_fade[key] in fade_options or (
key == "connections" and self.auto_fade[key] in ["incoming", "outgoing"]
)
if not option_is_valid:
raise ValueError(
f"The supplied 'auto_fade' dictionary contains an invalid value: '{key}'. "
+ "valid values are 'all', 'connected', 'none', 'incoming', 'outgoing'."
)

def add_system(
self,
node_name,
Expand All @@ -156,7 +184,7 @@ def add_system(
label_width=None,
spec_name=None,
):
"""
r"""
Add a "system" block, which will be placed on the diagonal of the XDSM diagram.

Parameters
Expand Down Expand Up @@ -198,7 +226,7 @@ def add_system(
self.systems.append(sys)

def add_input(self, name, label, label_width=None, style="DataIO", stack=False, faded=False):
"""
r"""
Add an input, which will appear in the top row of the diagram.

Parameters
Expand Down Expand Up @@ -229,10 +257,17 @@ def add_input(self, name, label, label_width=None, style="DataIO", stack=False,
faded : bool
If true, the component will be faded, in order to highlight some other system.
"""
sys_faded = {}
for s in self.systems:
sys_faded[s.node_name] = s.faded
if (self.auto_fade["inputs"] == "all") or (
self.auto_fade["inputs"] == "connected" and name in sys_faded and sys_faded[name]
):
faded = True
self.ins[name] = Input("output_" + name, label, label_width, style, stack, faded)

def add_output(self, name, label, label_width=None, style="DataIO", stack=False, faded=False, side="left"):
"""
r"""
Add an output, which will appear in the left or right-most column of the diagram.

Parameters
Expand Down Expand Up @@ -267,6 +302,13 @@ def add_output(self, name, label, label_width=None, style="DataIO", stack=False,
Must be one of ``['left', 'right']``. This parameter controls whether the output
is placed on the left-most column or the right-most column of the diagram.
"""
sys_faded = {}
for s in self.systems:
sys_faded[s.node_name] = s.faded
if (self.auto_fade["outputs"] == "all") or (
self.auto_fade["outputs"] == "connected" and name in sys_faded and sys_faded[name]
):
faded = True
if side == "left":
self.left_outs[name] = Output("left_output_" + name, label, label_width, style, stack, faded, side)
elif side == "right":
Expand All @@ -284,7 +326,7 @@ def connect(
stack=False,
faded=False,
):
"""
r"""
Connects two components with a data line, and adds a label to indicate
the data being transferred.

Expand Down Expand Up @@ -325,9 +367,24 @@ def connect(
if (not isinstance(label_width, int)) and (label_width is not None):
raise ValueError("label_width argument must be an integer")

sys_faded = {}
for s in self.systems:
sys_faded[s.node_name] = s.faded

allFaded = self.auto_fade["connections"] == "all"
srcFaded = src in sys_faded and sys_faded[src]
targetFaded = target in sys_faded and sys_faded[target]
if (
allFaded
or (self.auto_fade["connections"] == "connected" and (srcFaded or targetFaded))
or (self.auto_fade["connections"] == "incoming" and targetFaded)
or (self.auto_fade["connections"] == "outgoing" and srcFaded)
):
faded = True

self.connections.append(Connection(src, target, label, label_width, style, stack, faded))

def add_process(self, systems, arrow=True):
def add_process(self, systems, arrow=True, faded=False):
"""
Add a process line between a list of systems, to indicate process flow.

Expand All @@ -341,8 +398,17 @@ def add_process(self, systems, arrow=True):
If true, arrows will be added to the process lines to indicate the direction
of the process flow.
"""
self.processes.append(systems)
self.process_arrows.append(arrow)
sys_faded = {}
for s in self.systems:
sys_faded[s.node_name] = s.faded
if (self.auto_fade["processes"] == "all") or (
self.auto_fade["processes"] == "connected"
and any(
[sys_faded[s] for s in systems if s in sys_faded.keys()]
) # sometimes a process may contain off-diagonal blocks
):
faded = True
self.processes.append(Process(systems, arrow, faded))

def _build_node_grid(self):
size = len(self.systems)
Expand Down Expand Up @@ -514,10 +580,10 @@ def _build_process_chain(self):
# node_name, style, label, stack = in_data
chain_str = ""

for proc, arrow in zip(self.processes, self.process_arrows):
for proc in self.processes:
chain_str += "{ [start chain=process]\n \\begin{pgfonlayer}{process} \n"
start_tip = False
for i, sys in enumerate(proc):
for i, sys in enumerate(proc.systems):
if sys not in sys_names and sys not in output_names:
raise ValueError(
'process includes a system named "{}" but no system with that name exists.'.format(sys)
Expand All @@ -528,21 +594,23 @@ def _build_process_chain(self):
chain_str += "\\chainin ({});\n".format(sys)
else:
if sys in output_names or (i == 1 and start_tip):
if arrow:
chain_str += "\\chainin ({}) [join=by ProcessTipA];\n".format(sys)
if proc.arrow:
style = "ProcessTipA"
else:
chain_str += "\\chainin ({}) [join=by ProcessTip];\n".format(sys)
style = "ProcessTip"
else:
if arrow:
chain_str += "\\chainin ({}) [join=by ProcessHVA];\n".format(sys)
if proc.arrow:
style = "ProcessHVA"
else:
chain_str += "\\chainin ({}) [join=by ProcessHV];\n".format(sys)
style = "ProcessHV"
if proc.faded:
style = "Faded" + style
chain_str += "\\chainin ({}) [join=by {}];\n".format(sys, style)
chain_str += "\\end{pgfonlayer}\n}"

return chain_str

def _compose_optional_package_list(self):

# Check for optional LaTeX packages
optional_packages_list = self.optional_packages
if self.use_sfmath:
Expand Down
4 changes: 4 additions & 0 deletions pyxdsm/diagram_styles.tex
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@
\tikzstyle{ProcessHVA} = [->,line width=1pt,to path={-| (\tikztotarget)}]
\tikzstyle{ProcessTip} = [-,line width=1pt]
\tikzstyle{ProcessTipA} = [->, line width=1pt]
\tikzstyle{FadedProcessHV} = [-,line width=1pt,to path={-| (\tikztotarget)},color=black!30]
\tikzstyle{FadedProcessHVA} = [->,line width=1pt,to path={-| (\tikztotarget)},color=black!30]
\tikzstyle{FadedProcessTip} = [-,line width=1pt,color=black!30]
\tikzstyle{FadedProcessTipA} = [->, line width=1pt,color=black!30]

% Matrix options
\tikzstyle{MatrixSetup} = [row sep=3mm, column sep=2mm]
Expand Down
7 changes: 0 additions & 7 deletions pyxdsm/matrix_eqn.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,6 @@ def connect(self, src, target, text="", color="tableau0"):
self._connections[src, target] = CellData(text=text, color=color, highlight="diag")

def _process_vars(self):

if self._setup:
return

Expand Down Expand Up @@ -397,7 +396,6 @@ def add_variable(self, name, size=1, text="", color="blue"):
self._total_size += size

def connect(self, src, target, text="", color=None, highlight=1):

if isinstance(target, (list, tuple)):
for t in target:
self._connections[src, t] = CellData(text=text, color=color, highlight=highlight)
Expand Down Expand Up @@ -430,7 +428,6 @@ def _process_vars(self):
self._setup = True

def jacobian(self, transpose=False):

self._process_vars()

tikz = []
Expand Down Expand Up @@ -478,7 +475,6 @@ def jacobian(self, transpose=False):
return lhs_tikz

def vector(self, base_color="red", highlight=None):

self._process_vars()

tikz = []
Expand All @@ -487,7 +483,6 @@ def vector(self, base_color="red", highlight=None):
highlight = np.ones(self._n_vars)

for i, h_light in enumerate(highlight):

color = _color(base_color, h_light)

row_size = self._ij_variables[i].size
Expand All @@ -509,7 +504,6 @@ def vector(self, base_color="red", highlight=None):
return vec_tikz

def operator(self, opperator="="):

self._process_vars()

tikz = []
Expand All @@ -528,7 +522,6 @@ def operator(self, opperator="="):
return op_tikz

def spacer(self):

self._process_vars()

tikz = []
Expand Down
2 changes: 0 additions & 2 deletions tests/test_xdsm.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ def test_connect(self):
self.fail("Expected ValueError")

def test_options(self):

filename = "xdsm_test_options"
spec_dir = filename + "_specs"

Expand Down Expand Up @@ -108,7 +107,6 @@ def test_options(self):
self.assertTrue(os.path.isfile(os.path.join(spec_dir, "G_spec.json")))

def test_stacked_system(self):

x = XDSM()

x.add_system("test", OPT, r"\text{test}", stack=True)
Expand Down