# Implementation of module {seam} # Last edited on 2021-10-02 17:08:06 by stolfi import seam import contact import move import move_parms import path import block import hacks import rn import pyx from math import nan, inf, sqrt import sys class Seam_IMP: # A block contact {sm} is between{Block} objects {bc0=sm.blocks[0]} and {bc1=sm.blocks[1]}, # The field {sm.cts} is a tuple of {Contact} objects (trace-trace contacts) # where side {i} of each contact is a trace in some choice of block {sm.blocks[i]} # # The field {sm.drc} field is a boolean that indicates whether the # chosen alternative for block {bc0} must be executed before the # chosen alternative of {bc1}, whatever those choices may be. # def __init__(self, bc0, bc1, CTS, drc): # Read-only fields: self.blocks = (bc0, bc1) self.CTS = tuple(CTS) self.drc = drc self.name = None # Mutable fields for the {hotpath} heuristic: self.iscov = [False, False] # Status of block contact rel to current {Q}. def make(bc0, bc1, CTS, drc): assert isinstance(bc0, block.Block) assert isinstance(bc1, block.Block) assert type(CTS) is tuple or type(CTS) is list assert type(drc) is bool assert bc0 != bc1 return seam.Seam(bc0, bc1, CTS, drc) # ---------------------------------------------------------------------- def get_contacts(sm): assert isinstance(sm, seam.Seam) return sm.CTS # ---------------------------------------------------------------------- def side(sm, i): return sm.blocks[i] # ---------------------------------------------------------------------- def which_side(bc, sm): assert isinstance(bc, block.Block) assert isinstance(sm, seam.Seam) for i in range(2): if sm.blocks[i] == bc: return i return None def is_directed(sm): assert isinstance(sm, seam.Seam) return sm.drc # ---------------------------------------------------------------------- def bbox(SMS): B = None for sm in SMS: B = rn.box_join(B, contact.bbox(sm.CTS)) return B # ---------------------------------------------------------------------- # def min_max_rcool(sm, mp_jump): # bc0 = sm.blocks[0] # bc1 = sm.blocks[1] # rc_max_min = +inf # for ich0 in range(block.nchoices(bc0)): # oph0 = block.choice(bc0,ich0) # omv0 = path.elem(oph0, path.nelems(oph0)-1) # Last move of {oph0}. # for ich1 in range(block.nchoices(bc1)): # oph1 = block.choice(bc1,ich1) # omv0 = path.elem(oph0, 0) # First move of {oph1}. # use_jump = False # use_link = False # tconn = connector_fabtime(omv0, omv1, use_jump, use_link, mp_jump) # # Compute max cooling time among contacts of {sm} that # # are closed by {oph0} and {oph1}: # rc_max = pair_max_rcool(oph0, tconn, oph1, sm.CTS) # if rc_max < rc_max_min: rc_max_min = rc_max # return rc_max_min # # ---------------------------------------------------------------------- def min_max_rcool_list(SMS, mp_link, mp_jump): rcmax = 0 for sm in SMS: rc_min = min_max_rcool(sm, mp_link, mp_jump) if rc_min > rcmax: rcmax = rc_min return rcmax # ---------------------------------------------------------------------- def print_contact_table(wr, BCS, SMS, MVS): nbc = len(BCS) nsm = len(SMS) nmv = (0 if MVS == None else len(MVS)) # Find max number {mct} of contacts per seam: mct = 1; for sm in SMS: mct = max(mct, len(seam.get_contacts(sm))) # Find max number {mch} of choices in each block: mch = 1 for bc in BCS: mch = max(mch, block.nchoices(bc)) def dfmt(n): # Outputs a "d" format suitable for printing integers # in {0..n-1}. fmt = "%0" + ("1" if n <= 10 else "2" if n <= 100 else "3" if n <= 1000 else "4") + "d" return fmt # Formats for moves, block choices, contact sides: dfmt_mv = "M" + dfmt(nmv) dfmt_bc = "B" + dfmt(nbc) + ":" + dfmt(mch) dfmt_sm = "S" + dfmt(nsm) + ":" + dfmt(mct) + ":" + "%d" # Blank entries of the proper size: space_mv = " "*len(dfmt_mv % 0) dash_bc = "-"*len(dfmt_bc % (0,0)) space_sm = " "*len(dfmt_sm % (0,0,0)) def print_table_row(ct, ism, kct, j): # Prints a row of the table, for side {j} of the contact {ct}, # that is the contact indexed {kct} of seam indexed {ism}. ctID = (dfmt_sm % (ism, kct, j)) mvij = contact.side(ct,j) if MVS == None or not mvij in MVS: mvID = space_mv else: r = MVS.index(mvij) mvID = (dfmt_mv % r) wr.write("%s %s " % (ctID,mvID)) for ibc in range(nbc): bci = BCS[ibc] nchi = block.nchoices(bci) for jch in range(nchi): ophkl = block.choice(bci, jch) t = path.find_move(ophkl, mvij) if t == None: wr.write(" " + dash_bc) else: wr.write(" %*d" % (len(dash_bc), t)) wr.write("\n") return # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - wr.write("%s %s " % (space_sm,space_mv)) for ib in range(nbc): bci = BCS[ib] nchi = block.nchoices(bci) for jc in range(nchi): wr.write(" " + (dfmt_bc % (ib, jc))) wr.write("\n") # Contact entries: for ism in range(nsm): smi = SMS[ism] CTSi = seam.get_contacts(sm) for kct in range(len(CTSi)): ctik = CTSi[kct] for j in range(2): print_table_row(ctik, ism, kct, j) wr.write("\n") return # ---------------------------------------------------------------------- # PLOTTING def plot_to_files(fname, SMS, clr_ct, wd_ct, BCS, CLRS, rwd, wd_axes, matter): nsm = len(SMS) nbc = len(BCS) B = seam.bbox(SMS) B = rn.box_join(B, block.bbox(BCS)) if CLRS == None: CLRS = hacks.trace_colors(nbc) PLTS = define_subplots(SMS, BCS) npl = len(PLTS) c, szx,szy = hacks.make_canvas(B, None, True, True, 1, npl) for k in range(npl): dpi = (0, k*szy) ICTSk, IPHSk, = PLTS[k] plot_subplot(c, dpi, SMS, ICTSk, clr_ct, wd_ct, BCS, IPHSk, CLRS, rwd, wd_axes, matter) hacks.write_plot(c, fname) return # ---------------------------------------------------------------------- def define_subplots(SMS, BCS): # Used by {plot_to_files}. Takes a list of {Seam} objects {SMS} and a # list of {Block} objects {BCS}. Outputs a list {PLTS} whose elements # describe the subplots needed to show all those seams and blocks. # # Each element {PLT} of {PLTS} is a /subplot spec/ consisting of a # pair {(ICTS, IPHS)} where {ICTS} is a list of seam and contact # indices and {IPHS} is a list of block and choice indices. # # Namely, each element of the list {ICTS} is a pair {(ism,ict)} # where {ism} is an index into the seam list {SMS} and {ict} is the # index of some contact of seam {SMS[ism]}. # # Similarly, each element of the list {IPHS} is a pair {(ibc,ich)} # where {ibc} is an index into the block list {BCS} and {ich} is the # index of some choice of block {BCS[ibc]}. # # The subplot spec says that the corresponding sub-plot must show the # contacts specified in {ICTS} and the block choices (paths) specified # in {IPHS}. Every element of {ICTS} is distinct, but may include two # or more contacts from the same seam. The block indices in {IPHS} are # all distinct. # # Every contact from every relevant seam will appear in at least one # sub-plot spec {(ICTS,IPHS)}. For each block {bc} in {BCS} and each # sub-plot spec {(ICTS,IPHS)}, at mosy one choice of {bc} will appear # in {IPHS}. If a contact {ct} from a seam {sm} appears in the list # {CTS} of a sub-plot spec {(CTS,IPHS)}, then, for each block {bc} of # {BCS} that is a side of {sm}, there will be a pair {(ibc,ich)} in # {IPHS} where {bc = BCS[ibc]} and choice {ich} of {bc} will include # the trace that is the corresponding side of {ct}. # # On the other hand, not every choice of every block of {BCS} will # appear in the sub-plot specs {(CTS,IPHS)}. # nbc = len(BCS) nsm = len(SMS) # Make lists # # {IBSMS} of pairs {(ibc0, ibc1)} such that {BCS[IBSMS[ism][i]]} is # the block that is side {i} of the seam {SMS[ism]} # # {ISCTS} of pairs {(ism, ict)} where {ism} is the index of a # seam in {SMS} and {ict} is the index of a contact in {SMS[ism]}: # IBSMS = [] ISCTS = [] for ism in range(len(SMS)): sm = SMS[ism] # Get the blocks that are the sides of the seam: ibc = [None, None] for i in range(2): bc = side(sm, i) if bc in BCS: ibc[i] = BCS.index(bc) IBSMS.append(tuple(ibc)) if ibc[0] == None and ibc[1] == None: sys.stderr.write("seam {SMS[%d]} is not relevant to {BCS}, will not be plotted" % ism) else: SMSi = get_contacts(sm) for ict in range(len(SMSi)): ISCTS.append((ism,ict,)) nct = len(ISCTS) # Number of contacts to plot. # Determine the list of sub-plots {PLTS}: PLTS = [] while len(ISCTS) > 0: # Need at least one more sub-plot to show the contacts in {ISCTS}. # Decide which contacts and which choices of which blocks to plot: ICTS, IPHS, ISCTS_rest = select_items_in_subplot(SMS, BCS, IBSMS, ISCTS) assert len(ICTS) > 0 # Must show at least one contact assert len(ICTS) + len(ISCTS_rest) == len(ISCTS) assert len(IPHS) <= nbc PLTS.append((ICTS, IPHS,)) ISCTS = ISCTS_rest return PLTS # ---------------------------------------------------------------------- def select_items_in_subplot(SMS, BCS, IBSMS, ISCTS): # Used by {define_subplots}. # Selects which block choices and contacts to show in the next sub-plot. Receives # # {SMS} a list of {Seam} objects. # # {BCS} a list of {Block} objects. # # {IBSMS} a list of pairs such that {IBSMS[ism]} is {(ibc0,ibc1)} if sides 0 and 1 of seam # {SMS[ism]} are the blocks {BCS[ibc0]} and {BCS[ibc1]}, respecctively. # # {ISCTS} a list of contacts that have not been plotted yet, each representd by a pair {ism,ict} # meaning the contact with index {ict} in seam {SMS[ism]}. # # Returns the two lists {ICTS} and {ICHS} that define what will be shown in the next sub-plot, # and the list {ISCTS_rest} of the pairs of {ISCTS} not included in{ICTS}. # nbc = len(BCS) # Number of blocks. ich_show = [None]*nbc # {ich_show[ibc]} is which choice was chosen for block {BCS[ibc]}, or {None} ICTS = [] # Elements of {ISCTS} that will be shown in this sub-plot. ISCTS_rest = [] # Elements of {ISCTS} that will not be shown in this sub-plot. for ism, ict in ISCTS: sm = SMS[ism] ct = get_contacts(sm)[ict] ibc = IBSMS[ism] # Indices in {BCS} of blocks that are the two sides of {sm}, or {None}. ich_ct_needs = contact_is_plottable(ct, BCS, ibc, ich_show) if ich_ct_needs == None: # Contact {ct} cannot be shown in this sub-plot. Leave it for later: ISCTS_rest.append((ism,ict)) else: # Contact {ct} can be shown in this sub-plot: assert ich_ct_needs[0] != None or ich_ct_needs[1] != None # Mark those choices of the two blocks (if not {None}) as the ones to be plotted: for i in range(2): if ich_ct_needs[i] != None: assert ibc[i] != None # Contact needs a specific choice of {BCS[ibc[i]]}: if ich_show[ibc[i]] != None: # Must be asking for what has been chosen already: assert ich_ct_needs[i] == ich_show[ibc[i]] else: # No choice has been defined for the block, define it: ich_show[ibc[i]] = ich_ct_needs[i] # Add contact to list to be shown: ICTS.append((ism,ict)) IPHS = [ (ibc,ich_show[ibc],) for ibc in range(nbc) if ich_show[ibc] != None ] return ICTS, IPHS, ISCTS_rest # ---------------------------------------------------------------------- def contact_is_plottable(ct, BCS, ibc, ich_show): # Decides whether a contact {ct} can be shown in a sub-plot of {plot_standard}. # # The parameter {ibc} must be a pair, such that {BCS[ibc[i]]} is the {Block} object # that contains side {i} of the contact {ct}, or {None} if that side is not in {BCS}. # At most one of {ibc[0],ibc[1]} may be {None}. # # The parameter {ich_show} must be a list such that, for each block index {ibc}, # {ich_show[ibc]} is the index of the choice of {BCS[ibc]} that has been chosen # to be shown in the next sub-plot; of {None} if that choice has no been made yet. # # If the contact {ct} can be shown in the next-sub-plot, the procedure returns a pair # {ich_ct_needs} such that {ich_ct_needs[i]} is the choice # of block {BCS[ibc[i]]} that should be painted in order to show the move that is side {i} of {ct}. # It is either {None}, if {ibc[i]} is {None}, or some {ich} in {0..block.nchoices(BCS[ibc[i]]}. # In the latter case, {block.choice(BCS[ibc[i]], ich)} must include the trace that is side {i} # of {ct}; and, if {ich_show[ibc[i]]} is not {None}, it must be equal to {ich}. # # If it is impossible to satisfy these conditions -- namely, {ich_show[ibc[i]]} is not {None} # but that choice does not include side {i} of {ct} -- then the procedure returns {None}, # to signify that the contact {ct} cannot be shown in this sub-plot. showct = True # Shall we show contact {ct} on this sub-plot? assert ibc[0] != None or ibc[1] != None ich_ct_needs = [None, None] # To show {ct} we need to plot these choices of blocks {ibc[0..1]} for i in range(2): if ibc[i] != None: bc = BCS[ibc[i]] # The block that is side {i} of {sm}. mv = contact.side(ct, i) # The trace that is side {i} of {ct} chosen = ich_show[ibc[i]] if chosen != None: # We already decided to show choice {chosen} of block {ibc[i]}. # See if that choice contains side {i} of {ct}: assert chosen < block.nchoices(bc) oph = block.choice(bc, chosen) imv = path.find_move(oph, mv) if imv == None: # Nope -- can't show this contact now: showct = False else: # So far,contact {ct} could be shown now: ich_ct_needs[i] = chosen else: # We have not selected a choice for this block yet. # Find some choice that contains side {i} of {ct}: chosen = block.find_choice_with_move(bc, mv) if chosen == None: sys.stderr.write \ ( "block {BCS[%d]} has no choice with side %d of contact %d of seam {SMS[%d]}" % (ibc[i],i,ict,ism) ) assert False # Ask to show choice {chosenk} of block {BCS[ibc[i]]}: ich_ct_needs[i] = chosen if showct: for i in range(2): assert (ich_ct_needs[i] != None) == (ibc[i] != None) ich_ct_needs = tuple(ich_ct_needs) # Make read-only. else: ich_ct_needs = None return ich_ct_needs # ---------------------------------------------------------------------- def plot_subplot(c, dp, SMS, ICTS, clr_ct, wd_ct, BCS, IPHS, CLRS, rwd, wd_axes, matter): # Used by {plot_to_files} nsm = len(SMS) nct = len(ICTS) nbc = len(BCS) nph = len(IPHS) assert CLRS != None # Plot the matter shadows of all blocks: for bc in BCS: block.plot_matter_shadow(c, bc, dp) if nph > 0: # Plot the block choices specified by {IPHS}: axes = True dots = True arrows_tr = True matter = False OPHS = [] CLRS_ph = [] for ibc, ich in IPHS: bc = BCS[ibc] oph = block.choice(bc, ich) OPHS.append(oph) clr = CLRS[0] if len(CLRS) == 1 else CLRS[ibc] CLRS_ph.append(clr) path.plot_standard(c, OPHS, dp, None, CLRS_ph, rwd, wd_axes, axes, dots, arrows_tr, matter) # Plot the contacts: for ism, ict in ICTS: sm = SMS[ism] ct = seam.get_contacts(sm)[ict] arrow_ct = seam.is_directed(sm) sz_tic_ct = 0 if arrow_ct else wd_ct contact.plot_single(c, ct, dp, clr_ct, wd_ct, sz_tic_ct, arrow_ct) return # ---------------------------------------------------------------------- def plot_single(c, sm, dp, clr, wd, oph0, oph1, tics): arrow = is_directed(sm) sz_tic = 0 if arrow or not tics else wd for ct in get_contacts(sm): ok = True for i in range(2): ophi = oph0 if i == 0 else oph1 if ophi != None: # Demand that side {i} of {ct} is in {ophi}: mvi = contact.side(ct, i) k = path.find_move(ophi, mvi) if k == None: ok = False if ok: contact.plot_single(c, ct, dp, clr, wd, sz_tic, arrow) return # ---------------------------------------------------------------------- # DEBUGGING AND VALIDATING def get_name(sm): assert isinstance(sm, seam.Seam) name = sm.name if name == None: name = "S?" return name # ---------------------------------------------------------------------- def set_name(sm, name): assert type(name) is str sm.name = name return # ---------------------------------------------------------------------- def tag_names(SMS, tag): if tag != None and tag != "": assert type(tag) is str for sm in SMS: sm.name = tag + get_name(sm) return # ---------------------------------------------------------------------- def show(wr, sm, contacts, ind, wna, wnc): xind = " "*ind wr.write(xind) wr.write("%-*s" % (wna, get_name(sm))) wr.write(" %d" % int(seam.is_directed(sm))) wr.write(" sides: ") for i in range(2): if i > 0: wr.write(",") wr.write(block.get_name(sm.blocks[i])) if contacts: wr.write(" contacts: ") for i in range(len(sm.CTS)): cti = sm.CTS[i] if i > 0 : wr.write(",") wr.write(contact.get_name(cti)) return # ---------------------------------------------------------------------- def show_list(wr, SMS, contacts, ind): xind = " "*ind assert type(SMS) is list or type (SMS) is tuple nsm = len(SMS) if nsm == 0: return wna = 4 # Width of "name" column; min 4 because of the header. wnc = 1 # Width of "number of contacts" column. for sm in SMS: wna = max(wna, len(get_name(sm))) wnc = max(wnc, len(str(len(seam.get_contacts(sm))))) wix = len(str(nsm-1)) # Num digits in index. wr.write("\n") # Write header: wr.write("%s%*s %-*s %*s contacts \n" % (xind,wix,"k",wna,"name",wnc,"n")) wr.write("%s%s %s %s --------------\n" % (xind,"-"*wix,"-"*wna,"-"*wnc)) # Write seams: for k in range(len(SMS)): sm = SMS[k] wr.write("%s%*d " % (xind,wix,k)) show(wr, sm, contacts, 0, wna, wnc) wr.write("\n") wr.write("\n") return # ----------------------------------------------------------------------