Source code for stompy.model.delft.process_diagram

from collections import defaultdict
import pandas as pd
import numpy as np
import os
import re
import glob
import six

import logging
log=logging.getLogger('stompy')

try:
    from graphviz import Digraph
except ImportError:
    log.debug("graphviz unavailable")

[docs]class ProcDiagram(object): delft_src="/home/rusty/code/delft/d3d/master" def __init__(self,waq_dir,run_name='dwaq'): self.run_name=run_name self.load_tables() self.load_run(waq_dir)
[docs] def load_tables(self): # Load the tables: table_dir=os.path.join( self.delft_src, "src/engines_gpl/waq/packages/waq_kernel/src/waq_tables" ) tab_inputs = pd.read_csv(os.path.join(table_dir,'inputs.csv')) tab_outputs= pd.read_csv(os.path.join(table_dir,'outputs.csv')) tab_proces = pd.read_csv(os.path.join(table_dir,'proces.csv'))
[docs] def load_run(self,waq_dir): self.waq_dir=waq_dir self.substances=substances=defaultdict(dict) self.sub_map=sub_map={} # map 1-based number of substance to substance name self.processes=processes=defaultdict(dict) self.constants=constants=defaultdict(dict) self.links=links=defaultdict(dict) # (process,substance,process) lsp_fn=glob.glob( os.path.join(self.waq_dir,"*.lsp"))[0] lsp = open(lsp_fn,'rt') lines=iter(lsp) try: while 1: l=six.next(lines).strip() # substance/fluxes: # -fluxes for [OXY ] m=re.match(r'-fluxes for \[(.*)\]\s*',l) if m: sub=m.group(1).strip() substances[sub]['name'] = sub sub_map[len(substances)] = sub l=six.next(lines).strip() while 1: m=re.match(r'found flux \[(.*)\].*',l) if not m: break flux_name=m.group(1).strip() l=six.next(lines).strip() m=re.match(r'from proces \[(.*)\].*',l) proc_name=m.group(1).strip() link = (proc_name,flux_name,sub) links[link]='flux' l=six.next(lines).strip() if l == 'process is switched on.': l=six.next(lines).strip() # is it the start of an input stanza? m=re.match(r'\s*Input for \[(.*)\s*\].*',l) if m: input_for_process=m.group(1).strip() processes[input_for_process]['name']=input_for_process # print("inputs for %s"%input_for_process) while 1: l=six.next(lines).strip() m=re.match(r'\s*\[(.*)\s*\].*',l) if m: input_name=m.group(1).strip() l=six.next(lines).strip() input_src = re.match(r'\s*[Uu]sing (.*)$',l).group(1).strip() # print(" %s => %s"%(input_src,input_name)) if input_src.startswith('substance'): src=('substance',int(input_src.split()[2])) # translate to name: src=sub_map[src[1]] elif input_src.startswith('segment function nr'): src=('segfn',int(input_src.split()[3])) elif input_src.startswith('parameter nr'): src=('param',int(input_src.split()[2])) elif input_src.startswith('default value'): src=('default',input_src.split(':')[1].strip()) elif input_src.startswith('constant nr'): src=('constant',input_src.split(':')[1].strip()) elif input_src.startswith('output from proces'): m=re.match(r'output from proces \[(.*)\]', input_src) src=('process',m.group(1).strip()) elif input_src.startswith('flux from proces'): m=re.match(r'flux from proces \[(.*)\]', input_src) src=('process',m.group(1).strip()) elif input_src.startswith('DELWAQ'): src=('builtin',input_src) else: raise Exception("Failed to parse '%s'"%input_src) link=( src, input_name,input_for_process ) links[link]='input' else: break except StopIteration: pass if 0: print("-="*30) # print the interesting ones: for (src,name,dest) in links: if isinstance(src,tuple): if src[0] in ('constant','default'): continue print(" %s --- %s ----> %s"%(src,name,dest))
[docs] def init_dot(self): # strict merges duplicate edges # dot is the only one which gives reasonable output # could also add in size="5,5" from graphviz import Digraph dot = Digraph(name="schematic-%s"%self.run_name, comment='WAQ Processes',strict=True,engine='dot') #dot.attr('graph',ranksep="0.4",ratio='auto') dot.attr('graph',ranksep="0.3",ratio='0.647') dot.attr('node',fontname='Helvetica',style='filled',fillcolor='gray80',fontsize="32") dot.attr('edge',penwidth="2") return dot
[docs] def create_dot(self): # results in these links, plus some more boring ones. # the idea is then that we draw edges of the graph between the first and third items. # possibly labeled with the value of the second item. # there are a bunch of constants and defaults - not that exciting. dot=self.init_dot() phys_params=['Depth','Surf','DynDepth','TotDepth','TotalDepth', 'Emersion'] nodes_output={} # track which nodes have already been output if 1: sif_fp=None # too lazy to refactor properly else: sif_fp=open('graph-%s.sif'%self.run_name,'wt') def sel_edge(a,b): if (a not in phys_params) and (b not in phys_params): for n in [a,b]: if n in self.substances: dot.node(n,n, style="filled",fillcolor='green') dot.edge(a,b) if sif_fp: sif_fp.write("%s linkto %s\n"%(a,b)) for (src,name,dest),ltype in six.iteritems(self.links): if isinstance(src,tuple): src_type,src_val = src if src_type in ('constant','default','builtin'): continue if src_type=='process': src=src_val else: src="_".join([str(s) for s in src]) else: src_type=None if ltype=='flux': # drop intermediate names like dREAROXY sel_edge(src,dest) else: if (src!=name) and (src_type not in ['segfn','param']): sel_edge(src,name) else: if src_type in ['segfn','param'] and name not in phys_params: dot.node(name,name,style="filled",fillcolor='cornflowerblue') sel_edge(name,dest) if sif_fp: sif_fp.close() sif_fp=None return dot
[docs] def view_dot(self): self.create_dot().view()
[docs] def render_dot(self): self.create_dot().render()