#! /usr/bin/env python # The nomenclature: # there are 6 correlator units, each with two halves or parts # Each half has 3 paths or branches, not all are used in every configuration. """ Basic concepts (hopelessly outdated): The user request a configuration which corresponds to a list of (mode, receiver, count) tuples. Each mode is mapped to a canonical mode for distribution allocation. Each canonical mode can be allocated from a list of resources. User input = List of [Mode, Receiver(s), Count] List of [Mode, Receiver(s)] -> Canonical signature list Search the elements of the canonical signature list in a list of prebuilt lists of canonical signatures for each pair of units. The prebuilt list which are a subset of the signature list are considered candidates. Prebuilding the possible configurations for a pair of correlators: For each possible combination of canonical modes of the left and right parts of a pair of correlators: if the combination is clearly redundant or there is resource conflict for this combination, do nothing else for all valid distribution mode settings for this combination build the signature list build the sorted signature list if the same sorted signature does not already exists append the sorted signature to the list of existing signatures Mode -> Canonical -> List of pointers to resources | | v v POSSIBLE RESOURCE DESCRIPTORS DISTRIBUTION (boards+synthes) BOX SETTINGS (used to determine conflicts) | | \------> Signature <------/ #define NUNIT = 6 #define NSYNTH = (2*NUNIT) #define NBRANCHES = (3*NSYNTH) #define NBOARDS = (2*NBRANCHES) Definitions of distribution box connection for a pair: 0: unconnected, 1: connected to receiver 1, 2: connected to receiver 2, 3: connected to receiver 3, 4: connected to receiver 4, 5: connected to receivers 1 and 1, 6: connected to receivers 1 and 2, 7: connected to receivers 1 and 3, 8: connected to receivers 1 and 4, 9: connected to receivers 2 and 2, 10: connected to receivers 2 and 3, 11: connected to receivers 2 and 4, 12: connected to receivers 3 and 3, 13: connected to receivers 3 and 4, 14: connected to receivers 4 and 4, 15: connected to multibeam receiver, pixel given by hardware wiring 16 to ?: reserved for future expansion with 2 multibeam receivers Possible modes for a given band (signatures): UNC, SUL1, SUL2, SUL3, SUL4, SP41, SP42, SP43, SP44, #9 SP81, SP82, SP83, SP84, SPC1, SPC2, SPC3, SPC4, #8 DUL12, DUL13, DUL14, DUL23, DUL24, DUL34, #6 DP412, DP413, DP414, DP423, DP424, DP434, #6 PUU13, PUU24, PP113, PP124, PP213, PP224, PP413, PP424, #8 MUL1, MUL2, MP41, MP42 #4 #41 total #Distr is 1, 2, 3, 4, 12, 13, 14, 23, 24, 34, 55, 66, 56 ? #Or distr code is mode dependent ? Mode would be one of: UNC, SUL, SP4, SP8, SPC, DUL, DP4, PUU, PP1, PP2, PP4, MUL, MP4, XUU, XP1 Introduce the concept of splittable bands for 20/40kHz resolution ? No. It would be too complex... Possible modes for a board: A4, A2, A1: autocorrelation in TMF 4, 2 and 1 C4, C2, C1: cross-correlation in TMF 4, 2, and 1 XP: TMF 1, input X from backplane, Y from pass-along EP: TMF 1, input X from backplane E pins, Y from pass-along PU: TMF 1, input X from pass-along, Y from X output (U-turn) EU: TMF 1, input X from backplane E pins, Y from X output (U-turn) PP: TMF 1, both inputs from pass-alongs """ from Tkinter import * import tkMessageBox import operator from string import index # Needs a variable to keep the state of the correlator distribution # 12 bits define the state of the distribution box and 4 define # the state of the internal switching (even if implemented as 12) # For receiver setup: cursor can be gumby # Can't make the receiver area sensitive for multi receiver choices... class M: # the Modes UNC = "UNC" SM4 = "SM4" SM2 = "SM2" SM1 = "SM1" SP2 = "SP2" SUL = "SUL" SPP = "SPP" SP4 = "SP4" SP8 = "SP8" SPC = "SPC" TUL = "TUL" TP4 = "TP4" DM4 = "DM4" DM2 = "DM2" DM1 = "DM1" DP2 = "DP2" DUL = "DUL" DPP = "DPP" DP4 = "DP4" PM4 = "PM4" PM2 = "PM2" PM1 = "PM1" PUU = "PUU" PP1 = "PP1" PP2 = "PP2" PP4 = "PP4" MM4 = "MM4" MM2 = "MM2" MM1 = "MM1" MP2 = "MP2" MUL = "MUL" MPP = "MPP" MP4 = "MP4" # The canonicalized distribution mode from a given mode CANON = {SM4: SUL, SM2: SUL, SM1: SUL, SP2: SUL, SPP: SP4, DM4: DUL, DM2: DUL, DM1: DUL, DP2: DUL, DPP: DP4, MM4: MUL, MM2: MUL, MM1: MUL, MP2: MUL, MPP: MP4, PM4: PUU, PM2: PUU, PM1: PUU} # The first item is for mode setting, the second to compute # the signature when searching for a valid distribution. # The exact values are not important, but the lowest values # have to be the multibeam modes to simplify some logic and # the code is simpler if the highest value does not exceed 0x7f. SIGMODEBASES = {MUL: ( 1, 0x01), MP4: ( 2, 0x03), SUL: ( 3, 0x05), TUL: ( 4, 0x05), SP4: ( 5, 0x09), TP4: ( 6, 0x09), SP8: ( 7, 0x0d), SPC: ( 8, 0x11), DUL: ( 9, 0x15), DP4: (10, 0x1b), PUU: (11, 0x21), PP1: (12, 0x27), PP2: (13, 0x2d), PP4: (14, 0x33), UNC: ( 0, 0x39)} UNUSED = SIGMODEBASES[UNC][1] # FIXME: SIG2KIND is hardly ever used and could be removed SIG2KIND = ( "", MUL, MUL, MP4, MP4, SUL, SUL, SUL, SUL, SP4, SP4, SP4, SP4, SP8, SP8, SP8, SP8, SPC, SPC, SPC, SPC, DUL, DUL, DUL, DUL, DUL, DUL, DP4, DP4, DP4, DP4, DP4, DP4, PUU, PUU, PUU, PUU, PUU, PUU, PP1, PP1, PP1, PP1, PP1, PP1, PP2, PP2, PP2, PP2, PP2, PP2, PP4, PP4, PP4, PP4, PP4, PP4, UNC) # 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, # 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, # 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 0) # Actually DP4 also has an overhead if there are more than 6 # and SP8 if in legacy configuration mode. # Should SP8 be rather considered a kind of SPC for resource # allocation in legacy mode ? OVERHEAD = {MUL: 2, MP4: 5, SPC: 1, PP2: 1, PP4: 3} # Not necessary, just for debug #ALLMODES = (UNC, SM4, SM2, SM1, SP2, SPP, SP4, SP8, SPC, # DM4, DM2, DM1, DP2, DPP, DP4, PM4, PM2, PM1, # PP1, PP2, PP4, MM4, MM2, MM1, MP2, MPP, MP4) #for m in ALLMODES: # print m, "->", CANON.get(m,m) # There are 27 modes (including the unused)... # 27**12 is already in the range of 10**17 # Canonicalization reduces to 13 to determine the distribution... class D: # The distribution # FIXME: Define the distribution by 2 levels: # first level is multibeam or DB mapping # second level is state of DB or multibeam distribution # each part is either DB (distribution box) or MB (multi-beam) # These values make it easy to detect any attempt to # build a configuration mixing up MB and non MB modes, # they also catch invalid configurations on parallel modes. NOST = (0x80,)*15 NOMB = (0x80,)*5 DIST0 = (0x80, 0, 1, 2, 3, 0, 0, 0, 0, 1, 1, 1, 2, 2, 3) + NOMB DIST1 = (0x80,)*5 + (0, 1, 2, 3, 1, 2, 3, 2, 3, 3) + NOMB DISTP = (0x80,)*6 + (0, 1, 2, 0x80, 3, 4, 0x80, 5, 0x80) + NOMB # Only the first entry in DIMB* is currently used # the last 4 are only needed for 2 MB receivers. # FIXME: needs to include the unused case. DIMB1 = NOST + (0, 0, 0, 0, 1) DIMB2 = NOST + (0, 0, 0, 1, 1) DIMB3 = NOST + (0, 0, 1, 1, 1) DIMB4 = NOST + (0, 1, 1, 1, 1) # the final setup will be something like: # (0x80,)*1 + (0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1) # (0x80,)*3 + (0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1) # (0x80,)*6 + (0, 1, 0, 1, 0, 0, 1, 1, 1) # (0x80,)*10 + (0, 1, 1, 1, 1) # The unused case... DISTU = (0,)*(len(NOST)+len(NOMB)) del NOST, NOMB UNC = 0x000001 R1 = 0x000002 R2 = 0x000004 R3 = 0x000008 R4 = 0x000010 R11 = 0x000020 R12 = 0x000040 R13 = 0x000080 R14 = 0x000100 R22 = 0x000200 R23 = 0x000400 R24 = 0x000800 R33 = 0x001000 R34 = 0x002000 R44 = 0x004000 MB0 = 0x008000 # The following are only needed for 2 MB receivers. # MB1 = 0x010000 # MB3 = 0x020000 # MB7 = 0x040000 # MBF = 0x080000 MODES = (UNC, R1, R2, R3, R4, R11, R12, R13, R14, R22, R23, R24, R33, R34, R44, MB0)#, MB1, MB3, MB7, MBF) # Mapping from modes to RX... RX = ((0, 0), (1, 0), (2, 0), (3, 0), (4, 0), (1, 1), (1, 2), (1, 3), (1, 4), (2, 2), (2, 3), (2, 4), (3, 3), (3, 4), (4, 4), (5, 5, 5, 5), (5, 5, 5, 6), (5, 5, 6, 6), (5, 6, 6, 6), (6, 6, 6, 6)) # Change this to DUO/POL/MUL/ANY/ONE/UNC with one bit for each ? # Knowing that POL is a subset of DUO, but abstracting better # which pairs of receivers can be used for polarization. ONE = R1|R2|R3|R4 DUO = R12|R13|R14|R23|R24|R34 ANY = R11|R22|R33|R44|DUO POL = R13|R24 # Include the latter ones for 2 MB receivers, it works only by sheer # luck, not by design so beware. # Actually the whole 2 MB receivers support implemented here # is not clean and should be rewritten. Not yet needed, however... MUL = MB0#|MB1|MB3|MB7|MBF #MP0 = MB0|MB3|MB7|MBF #MP1 = MB0|MB1|MB3|MBF ALL = MUL|ANY|UNC # Changing this data would allow mixing # multi-beam and non multi-beam bands. # FIXME: for 2 MB receivers, MP4 should mask out MB1 or MB7 # depending on whether we are considering the top or bottom unit. DISTMASKS = {M.UNC: ALL, M.SUL: ANY, M.SP4: ANY, M.SP8: ANY, M.SPC: ANY, M.TUL: DUO, M.TP4: DUO, M.DUL: DUO, M.DP4: DUO, M.PUU: POL, M.PP1: POL, M.PP2: POL, M.PP4: POL, M.MUL: MUL, M.MP4: MUL} # Cost for any distribution of a band onto the 12 subbands: # 8 for each pair of correlator used, and 1 for each unit used. # Non split bands never contribute to the cost... COST = (0, 9, 9, 9, 9, 10, 10, 10, 9, 10, 10, 10, 9, 10, 10, 10) MINCOST = (0, 0, 9, 10, 10, 19, 19, 20, 20, 29, 29, 30, 30) # Hence totalcost is never higher than 68, good to know # for the minimization algorithm. MAXCOST = (0, 0, 18, 27, 28, 29, 30, 30, 30, 30, 30, 30, 30) BR2DB = (0, None, 1, None, 0, 1, 1, None, 0, None, 1, 0, 2, None, 3, None, 2, 3, 3, None, 2, None, 3, 2, 4, None, 5, None, 4, 5, 5, None, 4, None, 5, 4) HWEIGHT = (0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4) class Part: # Any of the 4 parts in a pair of correlators def __init__(self, S, P, MUL, **kw): self.S = S self.P = P self.MUL = MUL self.__dict__.update(kw) # The X order may still beed to be changed, # but the entry is only used for PP4 mode, which is not implemented # for now. FIXME: the multi parts may also only need the first 3 entries # depending on the final implementation. PARTS = (Part(( 0,), ( 0, 2), MUL = ( 0, 1, 2, 12, 13, 14, 24, 25, 26), T=( 2,), MP4 = ( 0, 4, 2, 12, 16, 14, 24, 28, 26), X=( 4, 10, 11, 5)), Part(( 4,), ( 4, 5), ( 3, 4, 5, 15, 16, 17, 27, 28, 29), D=( 4, 10)), Part(( 6,), ( 8, 6), ( 6, 7, 8, 18, 19, 20, 30, 31, 32), T=( 8,), MP4 = ( 6, 10, 8, 18, 22, 20, 30, 34, 32)), Part((10,), (11, 10), ( 9, 10, 11, 21, 22, 23, 33, 34, 35))) class HardResource: def __init__(self, kind, distmap, branches, rm): self.distmask = D.DISTMASKS[kind] self.distmap = distmap self.sigbase = M.SIGMODEBASES[kind][1] self.branches = branches # This contains the indication of whether this is # an S or T band in case it is necessary to know. self.rm = rm def __repr__(self): return "" % \ (self.rm, `self.branches`) _TYPE_M = 0x80000000 _DBOUT0 = 0x40000000 _DBOUT1 = 0x20000000 _TYPE_T = 0x10000000 _SYNTH0 = 0x08000000 _SYNTH1 = 0x04000000 _BOARDS = 0x00ffffff INCOMPAT_MASK = 0x1fffffff SYNTH_MASK = 0x0f000000 def SYN(u, n): return _SYNTH0>>(n + 2*u) def SLICE(u, n1, n2): return (_BOARDS>>(n1+12*u)) - (_BOARDS>>(n2+12*u+1)) def DIS1(u, alt): return _DBOUT0 >> (u^alt) DIS2 = _DBOUT0 | _DBOUT1 def MUL0(u): return SLICE(u, 0, 5) | SYN(u, 0) | _TYPE_M def MP40(u): return SLICE(u, 0, 11) | SYN(u, 0) | SYN(u, 1) | _TYPE_M def MUL1(u): return SLICE(u, 6, 11) | SYN(u, 1) | _TYPE_M def SUL0(u): # Right half S input in TMF 1, 2, 4[, P2] return SLICE(u, 0, 1) | SYN(u, 0) | DIS1(u, 0) def TUL0(u): # Right half T input in TMF 1, 2, 4[, P2] return SLICE(u, 4, 5) | SYN(u, 0) | DIS1(u, 1) | _TYPE_T def SP40(u): # Right half S input in modes PP and P4 return SLICE(u, 0, 3) | SYN(u, 0) | DIS1(u, 0) def TP40(u): # Right half S input in modes PP and P4 return SLICE(u, 4, 7) | SYN(u, 0) | DIS1(u, 1) | _TYPE_T def SP80(u): # Right half S input in mode P8 return SLICE(u, 0, 7) | SYN(u, 0) | DIS1(u, 0) def SPC0(u): # Right half S input in mode PC return SLICE(u, 0, 11) | SYN(u, 0) | DIS1(u, 0) def SUL1(u): # Left half S input in TMF 1, 2, 4[, P2] return SLICE(u, 8, 9) | SYN(u, 1) | DIS1(u, 0) def SP41(u): # Left half S input in TMF PP and P4 return SLICE(u, 8, 11) | SYN(u, 1) | DIS1(u, 0) def DUL0(u): # Right half dual input in TMF 1, 2, 4[, P2] return SLICE(u, 0, 1) | SLICE(u, 4, 5) | SYN(u, 0) | DIS2 def DP40(u): # Right half dual input in TMF 1, 2, 4[, P2] return SLICE(u, 0, 3) | SLICE(u, 4, 7) | SYN(u, 0) | DIS2 def DUL1(u): # Left half dual input in TMF 1, 2, 4[, P2] return SLICE(u, 8, 9) | SLICE(u, 10, 11) | SYN(u, 1) | DIS2 def PUU0(u): # Right half polarimetry in TMF 1, 2, 4 return SLICE(u, 1, 1) | SLICE(u, 4, 5) | SYN(u, 0) | DIS2 def PP10(u): # Right half polarimetry in P1 mode return SLICE(u, 0, 5) | SYN(u, 0) | DIS2 def PUU1(u): # Left half polarimetry in TMF 1, 2, 4 return SLICE(u, 8, 9) | SLICE(u, 11, 11) | SYN(u, 1) | DIS2 def PP11(u): # Left half polarimetry in P1 mode return SLICE(u, 7, 11) | SYN(u, 1) | DIS2 def PP20(u): # Polarimetry in P2 mode return SLICE(u, 1, 10) | SYN(u, 1) | DIS2 def PP40(u): # Polarimetry in P4 mode return SLICE(u, 0, 11) | SYN(u, 1) | DIS2 # Very important: the free band. BXUNC = HardResource(M.UNC, D.DISTU, (), 0) # The per band capabilities... CAPS = [{0: BXUNC}, {0: BXUNC}, {0: BXUNC}, {0: BXUNC}] def AddMode(part, kind, distmap, branches, resmask): tmp = HardResource(kind, distmap, branches, resmask) mode = M.SIGMODEBASES[kind][0] CAPS[part][mode] = tmp AddMode(0, M.MUL, D.DIMB1, PARTS[0].MUL, MUL0(0)) AddMode(0, M.MP4, D.DIMB1, PARTS[0].MP4, MP40(0)) AddMode(1, M.MUL, D.DIMB2, PARTS[1].MUL, MUL1(0)) AddMode(2, M.MUL, D.DIMB3, PARTS[2].MUL, MUL0(1)) AddMode(2, M.MP4, D.DIMB3, PARTS[2].MP4, MP40(1)) AddMode(3, M.MUL, D.DIMB4, PARTS[3].MUL, MUL1(1)) AddMode(0, M.SUL, D.DIST0, PARTS[0].S, SUL0(0)) AddMode(1, M.SUL, D.DIST0, PARTS[1].S, SUL1(0)) AddMode(2, M.SUL, D.DIST1, PARTS[2].S, SUL0(1)) AddMode(3, M.SUL, D.DIST1, PARTS[3].S, SUL1(1)) AddMode(0, M.TUL, D.DIST1, PARTS[0].T, TUL0(0)) AddMode(2, M.TUL, D.DIST0, PARTS[2].T, TUL0(1)) AddMode(0, M.SP4, D.DIST0, PARTS[0].S, SP40(0)) AddMode(1, M.SP4, D.DIST0, PARTS[1].S, SP41(0)) AddMode(2, M.SP4, D.DIST1, PARTS[2].S, SP40(1)) AddMode(3, M.SP4, D.DIST1, PARTS[3].S, SP41(1)) AddMode(0, M.TP4, D.DIST1, PARTS[0].T, TP40(0)) AddMode(2, M.TP4, D.DIST0, PARTS[2].T, TP40(1)) AddMode(0, M.SP8, D.DIST0, PARTS[0].S, SP80(0)) AddMode(2, M.SP8, D.DIST1, PARTS[2].S, SP80(1)) AddMode(0, M.SPC, D.DIST0, PARTS[0].S, SPC0(0)) AddMode(2, M.SPC, D.DIST1, PARTS[2].S, SPC0(1)) AddMode(0, M.DUL, D.DISTP, PARTS[0].P, DUL0(0)) AddMode(1, M.DUL, D.DISTP, PARTS[1].P, DUL1(0)) AddMode(2, M.DUL, D.DISTP, PARTS[2].P, DUL0(1)) AddMode(3, M.DUL, D.DISTP, PARTS[3].P, DUL1(1)) AddMode(0, M.DP4, D.DISTP, PARTS[0].P, DP40(0)) AddMode(1, M.DP4, D.DISTP, PARTS[1].D, SP41(0) | SP41(1)) AddMode(2, M.DP4, D.DISTP, PARTS[2].P, DP40(1)) AddMode(0, M.PUU, D.DISTP, PARTS[0].P, PUU0(0)) AddMode(1, M.PUU, D.DISTP, PARTS[1].P, PUU1(0)) AddMode(2, M.PUU, D.DISTP, PARTS[2].P, PUU0(1)) AddMode(3, M.PUU, D.DISTP, PARTS[3].P, PUU1(1)) AddMode(0, M.PP1, D.DISTP, PARTS[0].P, PP10(0)) AddMode(1, M.PP1, D.DISTP, PARTS[1].P, PP11(0)) AddMode(2, M.PP1, D.DISTP, PARTS[2].P, PP10(1)) AddMode(3, M.PP1, D.DISTP, PARTS[3].P, PP11(1)) # Warning: PP2 mode is considered as bands 0 and 2 and PP4 as band 0 # despite the fact that they use synthesizers and inputs from parts 1 # and 3. Not a problem since both modes are incompatible with anything # else in the same unit (pair of units for PP4). AddMode(0, M.PP2, D.DISTP, PARTS[1].P, PP20(0)) AddMode(2, M.PP2, D.DISTP, PARTS[3].P, PP20(1)) AddMode(0, M.PP4, D.DISTP, PARTS[0].X, PP40(0) | PP40(1)) #for c in range(4): # print "Part %d has %d modes." % (c, len(CAPS[c])) # keys = CAPS[c].keys() # keys.sort() # for e in keys: # print " %d: %s" % (e, `CAPS[c][e]`) #print CAPS # Now build the tables... U0CAPS=[] U1CAPS=[] for m0, p0 in CAPS[0].items(): for m1, p1 in CAPS[1].items(): if p0.rm & p1.rm & INCOMPAT_MASK: continue dm = p0.distmask & p1.distmask if dm == 0: continue U0CAPS.append((m0, m1, p0, p1, p0.rm|p1.rm, dm)) for m2, p2 in CAPS[2].items(): for m3, p3 in CAPS[3].items(): if p2.rm & p3.rm & INCOMPAT_MASK: continue dm = p2.distmask & p3.distmask if dm == 0: continue U1CAPS.append((m2, m3, p2, p3, p2.rm|p3.rm, dm)) U0CAPS = tuple(U0CAPS) U1CAPS = tuple(U1CAPS) print len(U0CAPS), len(U1CAPS) # 74, 63 # NOTE: when translating to C or Fortran, since the array # of pairconf values is large, the entries have a mode field # instead of a list of or indexes in the pairconf array. class PairConf: def __init__(self, dist, modes, sig): self.dist = dist self.modes = modes self.sig = sig def __repr__(self): return "" % \ (self.dist, `self.modes`, `self.sig`) # NOTE: when translating to C or Fortran, 4 nested loops may be # better since the incompatibilities checks are very simple and # it saves auxiliary arrays. variants=0 sigs = {} for m0, m1, p0, p1, rm01, dm01 in U0CAPS: for m2, m3, p2, p3, rm23, dm23 in U1CAPS: # This automatically removes dual T if rm01 & rm23 & INCOMPAT_MASK: continue dmask = dm01 & dm23 if dmask == 0: continue # Remove non-canonical distribution box setups dis = (rm01 | rm23) & DIS2 if dis == _DBOUT1: continue modes = m0, m1, m2, m3 bases = p0.sigbase, p1.sigbase, p2.sigbase, p3.sigbase #print ids, bases if dmask & D.UNC: dmask = D.UNC elif dis == _DBOUT0: dmask=D.ONE for i in range(len(D.MODES)): if dmask & D.MODES[i] == 0: continue variants = variants+1 #print i, bases sig = [bases[0]+p0.distmap[i], bases[1]+p1.distmap[i], bases[2]+p2.distmap[i], bases[3]+p3.distmap[i]] new = PairConf(i, modes, tuple(sig)) sig.sort() # Once sorted a repeated signature is necessarily # either in sig[1] or sig[2]. most = max(sig.count(sig[1]), sig.count(sig[2])) #haspair = (sig[0] == sig[1] != sig[2] or # sig[0] != sig[1] == sig[2] != sig[3] or # sig[1] != sig[2] == sig[3]) #print most, haspair s = (sig[0]<<24) + (sig[1]<<16) + (sig[2]<<8) + sig[3] #print "%08x" % s #print new[4:] if sigs.has_key(s): old = sigs[s] # Prefer using only 1 DB output, # makes 2 MB receivers work by luck. if new.dist != old.dist: if new.dist < old.dist: sigs[s] = new continue # Prefer grouping pairs in the same # correlator. Note that DP4 bands are # actually somewhat special since some of # them are split between two correlators. if most == 2: nd, od = new.sig, old.sig # True if distribution is good od = od[0]==od[1] or od[2]==od[3] nd = nd[0]==nd[1] or nd[2]==nd[3] # print old.sig, new.sig, od, nd # FIXME: is od old.sig: continue sigs[s] = new #c.sort() #print s, "%09x" % dmask sigset = sigs.keys() sigset.sort() #for s in sigset: # print "%08x" % s, sigs[s], D.RX[sigs[s].dist] print variants, len(sigs) # Now there are 3203 keys, even when sorted this # gives 274091205 final combinations. Quite a lot but can be # considered as searchable, especially since matching with the # requested setup will filter out the vast majority of keys. RXPOSX = (None, 50, 190, 330, 470) RXPOSY = 28 INVISIBLE = TRANSPARENT = "" PERM = "perm" BGND = "bgnd" BLACK = "black" INUSE = "gray70" INACTIVE = "gray80" RXCOLORS = (INACTIVE, "dark orange", "#00e000", "blue", "#e000e0") NOTSHOWN = (INVISIBLE,) * 10 RXLISTS = {'A+B': { 'S': ("A100", "A230", "B100", "B230"), 'D': ("A100+A230", "A100+B100", "A100+B230", "A230+B100", "A230+B230", "B100+B230"), 'P': ("", "A100+B100", "", "", "A230+B230")}, 'A+D': { 'S': ("A100", "A230", "D150", "D270"), 'D': ("A100+A230", "A100+D150", "A100+D270", "A230+D150", "A230+D270", "D150+D270"), 'P': None}, # A230 + D270 ? 'C+B': { 'S': ("C150", "C270", "B100", "B230"), 'D': ("C150+C270", "C150+B100", "C150+B230", "C270+B100", "C270+B230", "B100+B230"), 'P': None}, # C270 + B230 ? 'C+D': { 'S': ("C150", "C270", "D150", "D270"), 'D': ("C150+C270", "C150+D150", "C150+D270", "C270+D150", "C270+D270", "D150+D270"), 'P': ("", "C150+D150", "", "", "C270+D270")}, 'MB1': { 'S': ("HERA",), # FIXME: ugly kludge to translate the modes in AddBand, # this should be removed as soon as possible M.SM4: M.MM4, M.SM2: M.MM2, M.SM1: M.MM1, M.SPP: M.MP2, M.SP4: M.MP4}} ONOFF = (DISABLED, NORMAL) #The choice of colours is not very good, I'm not a specialist and #I won't lose any time looking for a better rainbow. MBCOLORS = ("black", "#ff0000", "#ff8000", "#ffc000", "#e0c000", "#a0e000", "#00ff00", "#00c0ff", "#0000ff", "#8000ff") MFTAGS = ("MF0", "MF1", "MF2", "MF3", "MF4", "MF5", "MF6", "MF7", "MF8", "MF9") MOTAGS = ("MO0", "MO1", "MO2", "MO3", "MO4", "MO5", "MO6", "MO7", "MO8", "MO9") DBTAGO = "dbo" DBTAGF = "dbf" BRANCHTAG = "branch" DBTAGSF = ("FDB0", "FDB1", "FDB2", "FDB3", "FDB4", "FDB5") DBTAGSO = ("ODB0", "ODB1", "ODB2", "ODB3", "ODB4", "ODB5") CONFTAG = "conf" TMPTAG = "tmp" # We know that the length of sub if 4. def issubseries(sub, ser): for i in sub: # No more necessary now that requests are padded # to maximum length with M.UNUSED items. #if i == M.UNUSED: break if i not in ser: return 0 j = ser.index(i) ser = ser[j+1:] return 1 def remove(sub, ser): res = [] for i in sub: ix = ser.index(i) res = res+ser[0:ix] ser = ser[ix+1:] return res+ser #bscalls = bsiters = bstries = 0 def splitcost(parts, num, bands, maxcost): # In the worst case (6 bands of 2 parts) this reduces the space # to search from 12!/64=7484400 to 11*9*7*5*3=10395 cases, # or a factor 720. Most of the cases are then avoided by # early filtering of obviously worse choices. In the precise # case of 6 bands of 2 parts, this directly reduces to about # ten early exit afte the first full recursive search. #print "splitcost:", hex(parts), num, bands, maxcost, "..." res = None if num == 1: cost = D.COST[parts>>8] + D.COST[(parts>>4)&0x0f] + \ D.COST[parts&0x0f] - D.MINCOST[bands] if costbands: skip = i^fixed skip = skip & -skip i = ((i|~parts) + skip) & parts if i == fixed: break w = HWEIGHT[i>>8] + HWEIGHT[(i>>4)&0x0f] \ + HWEIGHT[i&0x0f] if w == bands: break # if w>8] + HWEIGHT[(i>>4)&0x0f] \ + HWEIGHT[i&0x0f] if w == bands or i == fixed: break if i == fixed: break cost = D.COST[i>>8] + D.COST[(i>>4)&0x0f] + \ D.COST[i&0x0f] - D.MINCOST[bands] if cost >= maxcost: continue subsplit = splitcost(parts^i, num-1, bands, maxcost-cost) if not subsplit: continue res = cost+subsplit[0], [(cost,i)]+subsplit[1] maxcost = res[0] #print "... splitcost:", res return res def bestsplit(parts, split, maxcost): # print "BestSplit", hex(parts), split, maxcost # No band or only single part bands left end the search # for the holy grail of the perfect configuration. global bscalls, bsiters, bstries bscalls = bscalls+1 res = None #print "BestSplit:", hex(parts), split, maxcost, sol size = split[0][0] num = len(split[0][1]) need = num*size step = parts & -parts # Since need>1, the index can start at step i, w = step, 1 while 1: while 1: while w>need: bstries = bstries+1 skip = i & -i i = ((i|~parts) + skip) & parts if i == 0: break w = HWEIGHT[i>>8] + HWEIGHT[(i>>4)&0x0f] \ + HWEIGHT[i&0x0f] if w == need: break # if w>8] + HWEIGHT[(i>>4)&0x0f] \ + HWEIGHT[i&0x0f] if w == need or i == 0: break if i == 0: break bsiters = bsiters+1 sub = splitcost(i, num, size, maxcost) #print "Best split test:", hex(parts), hex(i), maxcost, cost if not sub: continue if len(split) > 1 and split[1][0] > 1: next = bestsplit(parts&~i, split[1:], maxcost-sub[0]) if not next: continue res = sub[0] + next[0], (sub[1],) + next[1] else: res = sub[0], (sub[1],) maxcost = res[0] #print "bestsplit res:", res return res # FIXME: this routine will disappear or be merged into the higher level loop def mincost(reqlist, sigsum, totcost): # FIXME: cost handling is wrong which triggers useless searches... # print "Mincost", totcost # FIXME: this search loop should be optimized, but also the # total cost minimisation search should be improved... totcost = 0 splitlist = {} best = 108 for k in reqlist.keys(): # Length 1 bands do not contribute to cost. if reqlist[k][0][0] == 1: continue split = bestsplit(sigsum[k], reqlist[k], best) totcost = totcost + split[0] splitlist[k] = split[1] #print "mincost res:", (totcost, splitlist) return totcost, splitlist def AllocBands(top, BandList): global bscalls, bsiters, bstries bscalls = bsiters = bstries = 0 reqsig = [] reqlist = {} for i in range(len(BandList)): b = BandList[i] c = M.CANON.get(b[0],b[0]) s = M.SIGMODEBASES[c][1]+b[1] reqsig = reqsig + ([s] + [M.UNUSED]*M.OVERHEAD.get(c,0))*b[2] # This can be shortened in Python 2.0 with setdefault method if not reqlist.get(s): reqlist[s] = {} reqlist[s][b[2]] = reqlist[s].get(b[2],()) + (i,) reqsig.sort() # Transform dictionary into reverse sorted tuple. for k in reqlist.keys(): tmp = reqlist[k].items() tmp.sort() tmp.reverse() reqlist[k] = tuple(tmp) print "reqlist:", reqlist cdp4 = 0 # FIXME: for legacy configurations it might be better # to map SP8 to SPC for the resource allocation pass. for i in reqsig: if M.SIG2KIND[i] == M.DP4: cdp4 = cdp4 + 1 cdp4 = max(cdp4-6, 0) reqsig = reqsig + [M.UNUSED]*max(cdp4-6, 0) if len(reqsig)>12: top.Dialogcancel() tkMessageBox.showerror("Not enough resources", "The requested setup cannot be configured since it " "would require more resources than available.") return 0 # The range can be optimized by a couple of binary searches... csig = csol = cattempts = 0 sig0 = [] possible = [] # Increase length to 12 to simplify remaining code. reqsig = reqsig + [M.UNUSED]*(12-len(reqsig)) for s in sigset: split = [s>>24, (s>>16)&0xff, (s>>8)&0xff, s&0xff] if top.UseMB: match = split[0]==M.UNUSED or split==reqsig[:4] else: match = issubseries(split, reqsig) if match: sig0 = sig0 + [split[0]] possible = possible + [s] #print "%08x" % s csig = csig + 1 print csig, "matching signatures." start0 = sig0.index(reqsig[0]) end0 = start0 + sig0.count(reqsig[0]) maxcost = 10000 slist = None for i0 in range(start0,end0): s0 = possible[i0] split0 = [s0>>24, (s0>>16)&0xff, (s0>>8)&0xff, s0&0xff] # Actually we could start from the second element of each # parameter since we know that split0[0]==reqsig[0]. rs1 = remove(split0, reqsig) start1 = sig0.index(rs1[0]) end1 = start1 + sig0.count(rs1[0]) for i1 in range(max(start1, i0), end1): s1 = possible[i1] split1 = [s1>>24, (s1>>16)&0xff, (s1>>8)&0xff, s1&0xff] cattempts = cattempts+1 # Here also we know that split1[0]==rs1[0] if not issubseries(split1, rs1): continue rs2 = remove(split1, rs1) s2 = (rs2[0]<<24)+(rs2[1]<<16)+(rs2[2]<<8)+rs2[3] if s2 not in possible or possible.index(s2)= maxcost: continue maxcost = sol[0] siglistsel = siglist bestsum = sigsum selection = sol[1] slist = s0, s1, s2 print "selection:", selection if not slist: top.Dialogcancel() tkMessageBox.showerror("Impossible configuration", "The requested setup cannot be configured because " "of signal distribution constraints.") return 0 # Fill selection with the distribution for single part bands... print "bestsum: {", for s in bestsum.keys(): print "0x%x: 0x%x" % (s, bestsum[s]), print "}" print "selection:", selection # 3 loop levels to process at most twelve elements! # But it's the most natural way nonetheless! # Something is wrong when you have 4 index levels anyway... for k in reqlist.keys(): parts = bestsum[k] req = reqlist[k] #if not selection.has_key(k): selection[k] = () for j in range(len(req)): r = req[j] #if 1 in map(lambda x: x[0], r): # selection[k] = selection[k]+([],) for i in range(len(r[1])): b = r[1][i] if r[0] > 1: p = selection[k][j][i][1] else: #selection[k][j].append((0, p)) p = parts & -parts BandList[b][3] = p parts = parts ^ p print csol, "valid solutions of ", cattempts, "attempts." print "Calls to bestsplit, iters, tries:", bscalls, bsiters, bstries #print "Selection = [", #for i in selection: print hex(i[1]), #print "]" print "selection:", selection print "BandList:", BandList # FIXME: this is wrong since the same distribution allocation # can correspond to different drawings: SPP, SP4, or MP2, MM1 #print top.Branches top.CleanScreen() # # Try to find a good configuration... # Build an array of receiver connections for each pair if reqsig[0] == M.UNUSED: return 1 for i in range(3): d = D.RX[sigs[slist[i]].dist] # FIXME: multi-beam kludge if len(d) != 2: continue top.DBConnect(i*2, d[0]) top.DBConnect(i*2+1, d[1]) itemconf = top.canvas.itemconfigure for ib in range(len(BandList)): Band = BandList[ib] mode, mask = Band[0], Band[3] isub = 1 for p in range(12): if not mask & 1<>4) | (bands>>8) else: bands = bands >> (4*i) btxt = "B%d" % (ib+1) stxt = "S%d" % isub for k in range(12): if not (bands<" % \ (self.Unit.Id+1, self.Id, self.Mode) # That one assumes that graphics is globally cleared somewhere else def Clear(self): self.Mode = M.UNC self.BandId = None self.SubId = None self.color = INVISIBLE def SetMode(self, top, BandId, SubId, mode): self.Mode = mode self.BandId = BandId self.SubId = SubId #print self #self.ct(self.x+12, self.y+24, tag=CONFTAG, # text=repr(self.BandId), anchor=N) if top.UseMB: rx = self.Id % 3 + self.Unit.Id/2*3 + 1 else: rx = top.DBConnections[self.DBIndex] color = top.RxColors[top.UseMB][rx] graphics = GRAPHICS[self.Id][Branch.CANON.get(mode, mode)] for t in graphics[VECS]: apply(self.cl, t, {'fill': color, 'tag': (CONFTAG, TMPTAG)}) t = graphics.get(DOTS) if t: apply(PutDots, (top.canvas,) + t, {'outline': color, 'tag': (CONFTAG, TMPTAG)}) top.canvas.move(TMPTAG, self.x, self.y) top.canvas.dtag(TMPTAG) seps = graphics.get(SEPS, "") for i in range(len(seps)): self.Unit.Seps[i].Set(seps[i]) class Correlator: def __init__(self, top, x, y, Id): self.x, self.y, self.Id = x, y, Id cl = top.canvas.create_line cr = top.canvas.create_rectangle ct = top.canvas.create_text co = top.canvas.create_oval cr(x, y, x+145, y+121, width=2, tag=PERM) top.BandIds.append(ct(x+96, y+24, anchor=S, # fill=INUSE, font=("New Century Schoolbook", 10, "normal"), tag=BGND)) top.BandIds.append(ct(x+24, y+24, anchor=S, # fill=INUSE, font=("New Century Schoolbook", 10, "normal"), tag = BGND)) top.SubIds.append(ct(x+120, y+24, anchor=S, # fill=INUSE, font=("New Century Schoolbook", 10, "normal"), tag=BGND)) top.SubIds.append(ct(x+48, y+24, anchor=S, # fill=INUSE, font=("New Century Schoolbook", 10, "normal"), tag = BGND)) cl(x, y+72, x+144, y+72, tag=PERM) cl(x+72, y, x+72, y+9, tag=DBTAGF) cl(x+72, y+16, x+72, y+24, tag=DBTAGF) cl(x+72, y, x+72, y+24, tag=MFTAGS[0]) ct(x+72, y+60, fill="light grey", text=repr(Id+1), tag=BGND, font=("New Century Schoolbook", 24, "bold")) cr(x+90, y+3, x+102, y+7, tag=DBTAGSO[Id]) cl(x+36, y+43, x+36, y+10, x+78, y+10, x+78, y+5, x+90, y+5, tag=DBTAGSF[Id]) cl(x+132, y+43, x+132, y+5, x+102, y+5, tag=DBTAGSF[Id]) cr(x+42, y+3, x+54, y+7, tag=DBTAGSO[Id^1]) cl(x+12, y+43, x+12, y+5, x+42, y+5, tag=DBTAGSF[Id^1]) cl(x+84, y+43, x+84, y+14, x+66, y+14, x+66, y+5, x+54, y+5, tag=DBTAGSF[Id^1]) ct(x+48, y, anchor=SW, text=' T', tag=DBTAGSF[Id^1]) ct(x+96, y, anchor=SW, text=' S', tag=DBTAGSF[Id]) self.Seps = [] for i in range(1,12): self.Seps.append(BoardSep(top, x+144-12*i, y+72)) for i in range(6): top.Branches.append(Branch(top, self, i, x+120-24*i, y+24)) # defined for a pair of correlators, this redraws the configuration # of the distribution with the correct color def CorrelPair(top, Pair): x, y = 200*Pair+28, 184 ThisPair = (Correlator(top, x, y, 2*Pair), Correlator(top, x, y+148, 2*Pair+1)) ovaltag = DBTAGSO[2*Pair] PutDots(top.canvas, x+84, y+178, x+84, y+190, x+36, y+30, x+36, y+42, x+12, y+178, x+12, y+190, x+132, y+30, x+132, y+42, tag = ovaltag) ovaltag = DBTAGSO[2*Pair+1] PutDots(top.canvas, x+84, y+30, x+84, y+42, x+36, y+178, x+36, y+190, x+12, y+30, x+12, y+42, x+132, y+178, x+132, y+190, tag=ovaltag) return ThisPair def PutDots(canvas, *coords, **kw): for i in range(0,len(coords),2): canvas.create_oval(coords[i]-1, coords[i+1]-1, coords[i]+1, coords[i+1]+1, kw) # The heterodyne receivers... def Receiver(top, n): x = RXPOSX[n] y = RXPOSY c = top.canvas return (c.create_rectangle(x, y, x+81, y+49, outline=top.RxColors[0][n], width=2), c.create_text(x+40, y+24), c.create_line(x+40, y+48, x+40, 100, fill=top.RxColors[0][n], width=3)) class VESPA(Tk): SETUPDATA = { M.SM4:("1.25 MHz resolution band setup", 160, 4), M.SM2:("320 kHz resolution band setup", 80, 8), M.SM1:("80 kHz resolution band setup", 40, 12), M.SPP:("40 kHz resolution band setup", 40, 12), M.SP4:("20 kHz resolution band setup", 20, 12), M.SP8:("10 kHz resolution band setup", 20, 6), M.SPC:("6.5 kHz resolution band setup", 20, 6), M.DM4:("Parallel 1.25 MHz resolution bands setup", 160, 4), M.DM2:("Parallel 320 kHz resolution bands setup", 80, 8), M.DM1:("Parallel 80 kHz resolution bands setup", 40, 12), M.DPP:("Parallel 40 kHz resolution bands setup", 40, 9), M.DP4:("Parallel 20 kHz resolution bands setup", 20, 9), M.MM4:("Multibeam 1.25 MHz resolution bands setup", 160, 4), M.MM2:("Multibeam 320 kHz resolution bands setup", 80, 4), M.MM1:("Multibeam 80 kHz resolution bands setup", 40, 4), M.MP2:("Multibeam 40 kHz resolution bands setup", 20, 4), M.MPP:("Multibeam 40 kHz resolution wide bands setup", 40, 2), M.MP4:("Multibeam 20 kHz resolution bands setup", 20, 2), M.PM4:("2.5 MHz resolution polarimetry band setup", 80, 8), M.PM2:("625 kHz resolution polarimetry band setup", 40, 12), M.PM1:("160 kHz resolution polarimetry band setup", 20, 12), M.PP1:("80 kHz resolution polarimetry band setup", 20, 12), M.PP2:("40 kHz resolution polarimetry band setup", 20, 6), M.PP4:("20 kHz resolution polarimetry band setup", 20, 3)} def DBConnect(self, input, rx): color = self.RxColors[0][rx] self.canvas.itemconfigure(DBTAGSF[input], fill=color) self.canvas.itemconfigure(DBTAGSO[input], outline=color) self.DBConnections[input] = rx def CleanScreen(self): for i in range(6): self.DBConnect(i,0) for i in self.Units: for j in i.Seps: j.Set('N') self.canvas.delete('conf') for Branch in self.Branches: Branch.Clear() for i in range(12): self.canvas.itemconfigure(self.BandIds[i], text='') self.canvas.itemconfigure(self.SubIds[i], text='') lift = self.canvas.lift lift(MOTAGS[0]) lift(DBTAGO) lift(DBTAGF) lift(PERM) lift(BRANCHTAG) self.canvas.lower(BGND) def Resetconfig(self): self.BandList = [] self.CleanScreen() def Reconfig(self): code = self.RxConfig.get() if (code==self.RxConfCode): return self.RxConfCode = code self.rxlists = RXLISTS[code] if (code == 'MB1'): self.RxNames = NOTSHOWN mbcol = MBCOLORS dbcol = TRANSPARENT self.RxColors[0] = NOTSHOWN self.RxColors[1] = MBCOLORS self.UseMB = 1 self.EnablePolar = 0 for i in self.NotMulti: i[0].entryconfigure(i[1], state=DISABLED) else: self.RxNames = self.rxlists['S'] mbcol = NOTSHOWN dbcol = BLACK self.RxColors[0] = RXCOLORS self.RxColors[1] = NOTSHOWN self.UseMB = 0 for i in self.NotMulti: i[0].entryconfigure(i[1], state=NORMAL) self.EnablePolar = self.rxlists.get('P') != None #print code #self.Menubar.entryconfigure(self.PolarEntry, # state=ONOFF[self.EnablePolar]) self.canvas.itemconfigure(DBTAGO, outline=dbcol) self.canvas.itemconfigure(DBTAGF, fill=dbcol) self.canvas.itemconfigure(MOTAGS[0], outline=mbcol[0]) self.canvas.itemconfigure(MFTAGS[0], fill=mbcol[0]) for i in range(1,10): self.canvas.itemconfigure(MFTAGS[i], fill=mbcol[i]) self.canvas.itemconfigure(MOTAGS[i], outline=mbcol[i]) for i in range(4): self.canvas.itemconfigure(self.Receivers[i][0], outline=self.RxColors[0][i+1]) self.canvas.itemconfigure(self.Receivers[i][2], fill=self.RxColors[0][i+1]) self.canvas.itemconfigure(self.Receivers[i][1], text=self.RxNames[i]) self.Resetconfig() def DeleteBand(self): self.BandList = self.BandList[:-1] AllocBands(self, self.BandList) def AddBand(self, kind): rxlist = self.rxlists[kind[0]] # FIXME: multibeam kludge kind = self.rxlists.get(kind, kind) self.AddBandData = kind ABD = VESPA.SETUPDATA[kind] self.Dialog=Toplevel(self) self.Dialog.title(ABD[0]) self.Dialog.parent = self self.Dialog.transient(self) self.Dialog.geometry("+%d+%d" % (self.winfo_rootx()+50, self.winfo_rooty()+50)) self.Dialog.result = None self.Dialog.focus_set() self.Dialog.resizable(0,0) self.AddBW.set(1) BWFrame = Frame(self.Dialog, borderwidth=1, relief=GROOVE) BWCount = ABD[2] if (BWCount%4): rows = 3 else: rows = 4 Label(BWFrame, text="Bandwidth (MHz):").grid( sticky=W, columnspan=3, padx=5, pady=5) for i in range(BWCount): x = ABD[1]*(i+1) r = Radiobutton(BWFrame, text=repr(x), variable=self.AddBW, anchor=W, value=i+1) r.grid(column=i/rows, row=i%rows+1, sticky=W, padx=10) Frame(BWFrame, height=5).grid(columnspan=3, row=8) BWFrame.rowconfigure(8, weight=1) BWFrame.grid(column=0, row=0, sticky='ns') if kind[0] == 'P': self.AddRx.set(1) else: self.AddRx.set(0) if len(rxlist) == 0: print "Internal errror, no receiver connections" # FIXME: Crude way to force an exception print rxlist[0] # Don't put a receiver menu if only one choice... elif len(rxlist)>1: RxFrame = Frame(self.Dialog, borderwidth=1, relief=GROOVE) if kind[0] in "SM": txt = "Receiver:" else: txt = "Receivers:" Label(RxFrame, text=txt).grid(sticky=W, padx=5, pady=5) rxsel = filter(None, rxlist) rows = len(rxsel) if rows > 4: rows = rows/2 isel = 0 for i in range(len(rxlist)): if not rxlist[i]: continue r = Radiobutton(RxFrame, text=rxlist[i], variable=self.AddRx, value=i) # Not rigorous, but happens to work... r.grid(column=isel/rows, row=isel%rows+1, sticky=W, padx=10) isel = isel+1 Frame(RxFrame, height=5).grid(columnspan=3, row=8) RxFrame.rowconfigure(8, weight=1) RxFrame.grid(column=1, row=0, sticky='ns') ButFrame=Frame(self.Dialog, borderwidth=1, relief=GROOVE) Button(ButFrame, text="OK", width=8, command=self.DoAddBand).grid(padx=5, pady=5) Button(ButFrame, text="Cancel", width=8, command=self.Dialogcancel).grid(padx=5, pady=5) ButFrame.grid(column=2, row=0, sticky='nsew') self.Dialog.grab_set() self.Dialog.protocol("WM_DELETE_WINDOW", self.Dialogcancel) self.Dialog.wait_window(self.Dialog) def Dialogcancel(self, event=None): self.focus_set() self.Dialog.destroy() def DoAddBand(self): Rx = self.AddRx.get() Bwid = self.AddBW.get() BandList = self.BandList + [[self.AddBandData, Rx, Bwid, []]] if AllocBands(self, BandList): self.BandList=BandList self.Dialogcancel() def Show(self): print self.BandList def InitMenus(self): self.Menubar = Menu(self) self.Genmenu = Menu(self.Menubar, tearoff=0) self.RxConfig = StringVar() self.AddRx = IntVar() self.AddBW= IntVar() self.NotMulti = [] self.Genmenu.add_command(label="Show details", command=self.Show) self.Genmenu.add_command(label="Delete last", command=self.DeleteBand) self.Genmenu.add_command(label="Clear", command=self.Resetconfig) self.Genmenu.add_separator() self.Configmenu = Menu(self.Genmenu, tearoff=0) r = self.Configmenu.add_radiobutton r(label="Receivers AB", variable=self.RxConfig, value='A+B', command=self.Reconfig) r(label="Receivers AD", variable=self.RxConfig, value='A+D', command=self.Reconfig) r(label="Receivers CB", variable=self.RxConfig, value='C+B', command=self.Reconfig) r(label="Receivers CD", variable=self.RxConfig, value = 'C+D', command=self.Reconfig) self.Configmenu.add_separator() r(label="Multibeam", variable=self.RxConfig, value='MB1', command=self.Reconfig) self.Genmenu.add_cascade(label='Configuration', menu=self.Configmenu) self.Genmenu.add_separator() self.Genmenu.add_command(label="Quit", command=self.quit) self.Menubar.add_cascade(label="General", menu=self.Genmenu) self.Addbandmenu = Menu(self.Menubar, tearoff=0) #self.Addmenu.add_separator() #self.Resbandmenu = Menu(self.Addbandmenu, tearoff=0) #self.Addbandmenu.add_cascade(label="Resolution", # menu=self.Resbandmenu) r = self.Addbandmenu.add_command r(label="6.5 kHz", command=lambda x=self:x.AddBand("SPC"), state=DISABLED) #self.NotMulti.append((self.Addbandmenu, # self.Addbandmenu.index("6.5 kHz"))) r(label="10 kHz", command=lambda x=self:x.AddBand("SP8")) self.NotMulti.append((self.Addbandmenu, self.Addbandmenu.index("10 kHz"))) r(label="20 kHz", command=lambda x=self:x.AddBand("SP4")) r(label="40 kHz", command=lambda x=self:x.AddBand("SPP")) r(label="80 kHz", command=lambda x=self:x.AddBand("SM1")) r(label="320 kHz", command=lambda x=self:x.AddBand("SM2")) r(label="1.25 MHz", command=lambda x=self:x.AddBand("SM4")) self.Menubar.add_cascade(label="Add band", menu=self.Addbandmenu) self.Addparamenu = Menu(self.Menubar, tearoff=0) r = self.Addparamenu.add_command r(label="20 kHz", command=lambda x=self:x.AddBand("DP4")) r(label="40 kHz", command=lambda x=self:x.AddBand("DPP")) r(label="80 kHz", command=lambda x=self:x.AddBand("DM1")) r(label="320 kHz", command=lambda x=self:x.AddBand("DM2")) r(label="1.25 MHz", command=lambda x=self:x.AddBand("DM4")) self.Menubar.add_cascade(label="Add parallel", menu=self.Addparamenu, state=DISABLED) self.NotMulti.append((self.Menubar, self.Menubar.index("Add parallel"))) self.Addpolarmenu = Menu(self.Menubar, tearoff=0) r = self.Addpolarmenu.add_command r(label="20 kHz", command=lambda x=self:x.AddBand("PP4"), state=DISABLED) r(label="40 kHz", command=lambda x=self:x.AddBand("PP2")) r(label="80 kHz", command=lambda x=self:x.AddBand("PP1")) r(label="160 kHz", command=lambda x=self:x.AddBand("PM1")) r(label="625 kHz", command=lambda x=self:x.AddBand("PM2")) r(label="2.5 MHz", command=lambda x=self:x.AddBand("PM4")) self.Menubar.add_cascade(label="Add polarimetry", menu=self.Addpolarmenu, state=DISABLED) self.PolarEntry = self.Menubar.index("Add polarimetry") # This does not work, why ? # #self.Menubar.add_command(label="Delete last", # command=self.DeleteBand) self.config(menu=self.Menubar) # This procedure is too long; it should be split def InitWidgets(self): # some shorthand symbols to make drawing bearable. cl = self.canvas.create_line cr = self.canvas.create_rectangle ct = self.canvas.create_text cr(30, 100, 571, 151, width=2, tag=DBTAGO) ct(300, 115, tag=DBTAGF, text="Distribution matrix") for i in range(6): cr(30+90*i, 130, 120+90*i, 150, tag=DBTAGO) cr(66+90*i, 137, 84+90*i, 143, tag=DBTAGSO[i]) cl(75+90*i, 137, 75+90*i, 130, tag=DBTAGSF[i]) for i in range(0, 6, 2): t = ( 60+90*i, 66+90*i, 90+90*i, 84+90*i, 150+90*i, 156+90*i, 180+90*i, 174+90*i) x = self.Units[i].x yt = self.Units[i].y yb = self.Units[i+1].y #print t0, t1, t2, t3, x, yt, yb cl(x+48, yb+4, x+48, yb, tag=DBTAGSF[i]) cl(x+48, yb, x+48, yb-16, x-8, yb-16, x-8, yt-24, t[0], yt-24, t[0], 150, width=3, tag=DBTAGSF[i]) cl(t[0], 150, t[0], 140, t[1], 140, tag=DBTAGSF[i]) cl(x+96, yt+4, x+96, yt, tag=DBTAGSF[i]) cl(x+96, yt, x+96, yt-24, t[2], yt-24, t[2], 150, width=3, tag=DBTAGSF[i]) cl(t[2], 150, t[2], 140, t[3], 140, tag=DBTAGSF[i]) cl(x+96, yb+4, x+96, yb, tag=DBTAGSF[i+1]) cl(x+96, yb, x+96, yb-16, x+152, yb-16, x+152, yt-16, t[6], yt-16, t[6], 150, width=3, tag=DBTAGSF[i+1]) cl(t[6], 150, t[6], 140, t[7], 140, tag=DBTAGSF[i+1]) cl(x+48, yt+4, x+48, yt, tag = DBTAGSF[i+1]) cl(x+48, yt, x+48, yt-16, t[4], yt-16, t[4], 150, width=3, tag = DBTAGSF[i+1]) cl(t[4], 150, t[4], 140, t[5], 140, tag = DBTAGSF[i+1]) # And now the multi-beam part cr(30, RXPOSY, 571, RXPOSY+49, width=2,tag=MOTAGS[0]) ct(300, RXPOSY+24, text='9 Pixel array receiver', tag=MFTAGS[0]) for ip in range(1, 10): x = 60*ip cr(x-24, 110, x+25, 127, width=2, tag=MOTAGS[ip]) ct(x, 119, text=`ip`, tag=MFTAGS[ip]) cl(x, RXPOSY+48, x, 110, width=3, tag=MFTAGS[ip]) for j in range(-18, 30, 12): cl(x+j, 127, x+j, 145, tag=MFTAGS[ip], arrow='last') brstart = (ip-1)%3 + (ip-1)/3*12 for ibr in range(brstart, brstart+12, 3): br = self.Branches[ibr] x, y = br.x, br.y PutDots(self.canvas, x+12, y+6, x+12, y+18, tag=MOTAGS[ip]) cl(x+12, y+19, x+12, y-30, x+12, y-29, tag=MFTAGS[ip], arrow='last') ct(x+13, y-37, anchor=S, tag=MFTAGS[ip], text=`ip`, font=('Helvetica', 10, '')) ct(x+12, y-25, anchor=SW, tag=MFTAGS[ip], text=' ' + Branch.MBINPUT[br.Id], font=('Helvetica', 10, '')) def __init__(self, master=None): self.RxConfCode = '' self.BandList = [] self.RxColors = [RXCOLORS, NOTSHOWN] Tk.__init__(self, baseName='Backend', className='VESPA') self.title("VESPA configurator") self.InitMenus() self.canvas = Canvas(self, width=600, height=480, background = 'white') self.canvas.pack() self.resizable(0,0) self.DBConnections = [0]*6 self.Receivers = [] self.Branches = [] self.BranchIds = [] self.BandIds = [] self.SubIds = [] for i in range(1,5): self.Receivers.append(Receiver(self,i)) self.Units= (CorrelPair(self, 0) + CorrelPair(self, 1) + CorrelPair(self, 2)) self.InitWidgets() # Mouse interface... self.Enter = self.canvas.tag_bind(BRANCHTAG, "", self.BranchEnter) self.Leave = self.canvas.tag_bind(BRANCHTAG, "", self.BranchLeave) self.Press = self.canvas.tag_bind(BRANCHTAG, "<1>", self.BranchPress) # Initial setup... self.Configmenu.invoke('Receivers AB') self.canvas.lift(BRANCHTAG) def BranchEnter(self, x): self.canvas.configure(cursor='question_arrow') #print "Mouse pointer entered branch ", x.__dict__ def BranchLeave(self, x): self.canvas.configure(cursor='left_ptr') #print "Mouse pointer left branch ", x.__dict__ def BranchPress(self, event): id = self.canvas.find_withtag('current')[0] id = self.BranchIds.index(id) c , i = divmod(id,6) print "Correlator", c+1, "branch", Branch.NAMES[i], if self.UseMB: print "is connected to multibeam receiver pixel %d." \ % (i%3 + c/2*3 + 1) else: dbo = BR2DB[id] if dbo== None: print "cannot be connected to any receiver." return print "is connected to distribution box output %d," \ % dbo if self.DBConnections[dbo]: print "which has been assigned to receiver", \ `self.DBConnections[dbo]`+'.' else: print "which has no assigned receiver." return if self.Branches[id].BandId: print "This branch is used for band %d subband %d." % \ (self.Branches[id].BandId, self.Branches[id].SubId) return print "This branch is unused." # And now the main program... VESPA().mainloop()