-
Notifications
You must be signed in to change notification settings - Fork 0
/
objects.py
2631 lines (2199 loc) · 88.7 KB
/
objects.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import sys, arcpy, operator, numpy
from collections import defaultdict, deque
sys.path.append('.')
import common, colors
MASS_TO_STRG = 1e-10
ID_SORTER = operator.methodcaller('getID')
MASS_SORTER = operator.methodcaller('getMass')
SECONDARY_MASS_SORTER = operator.methodcaller('getSecondaryMass')
GAIN_SORTER = operator.methodcaller('getGain')
class BaseInteractions(defaultdict):
'''An "abstract" base class that stores interaction targets and strengths.
A sibling of collections.defaultdict(int) storing pairs target : strength, where target should be RegionalUnit and strength either a numeric value or an InteractionVector.
Provides basic vector arithmetic methods (i. e. addition of two instances results in one with strengths to identical targets summed).
May contain a "raw" attribute that corresponds to strengths of interactions to undefined targets.
Caches a sum of all its values.'''
def __init__(self, factory, *args, **kwargs):
defaultdict.__init__(self, factory, *args, **kwargs)
self.raw = factory()
self._sum = 0
self._summed = False
def __add__(self, plusinter):
ret = self.copy()
ret += plusinter
return ret
def __mul__(self, factor):
new = self.new()
for zone in self:
new[zone] = self[zone] * factor
new.raw += self.raw * factor
self._summed = False
return new
def __iadd__(self, inter):
for zone in inter:
self[zone] += inter[zone]
self.raw += inter.raw
self._summed = False
return self
def __isub__(self, inter):
for zone in inter:
self[zone] -= inter[zone]
self.raw -= inter.raw
self._summed = False
return self
def __div__(self, divisor):
new = self.new()
divisor = float(divisor)
for target in self:
new[target] = self[target] / divisor
new.raw += self.raw / divisor
return new
@classmethod
def new(cls):
return cls()
def copy(self):
cp = self.new()
for target in self:
cp[target] = self[target]
cp.raw = self.raw
return cp
# def restrict(self, restricted):
# '''Removes all targets that are not contained in restricted from the interactions.'''
# todel = []
# for target in self:
# if target not in restricted:
# todel.append(target) # to prevent RuntimeError from changing dict size during iteration
# self._summed = False
# for target in todel:
# del self[target]
# return self
# def restrictToRegions(self):
# '''Removes all targets that are not regions from the interactions.'''
# todel = []
# for target in self:
# if not isinstance(target, Region):
# todel.append(target)
# for target in todel:
# del self[target]
# self._summed = False
# return self
# {k: d1[k] for k in (d1.viewkeys() & l1)}
def restrict(self, restricted):
'''Removes all targets that are not contained in the provided sequence from the interactions.'''
# print self, restricted, set(self).difference(restricted)
for target in set(self).difference(restricted):
del self[target]
self._summed = False
return self
def restrictToRegions(self):
'''Removes all targets that are not regions from the interactions.'''
todel = []
for target in self:
if not isinstance(target, Region):
todel.append(target)
for target in todel:
del self[target]
self._summed = False
return self
def exclude(self, excluded):
'''Removes all targets that are contained in the provided sequence from the interactions.'''
for exc in excluded:
if exc in self:
del self[exc]
self._summed = False
return self
def onlyClassSum(self, targetClass):
'''Returns its copy with only flows targetting the specified class retained.'''
total = self.default_factory()
for target in self:
if isinstance(target, targetClass):
total += self[target]
return total
@classmethod
def inflowsTo(cls, sources):
'''Given a list of RegionalUnits, returns a sum of their inflows.'''
# if len(sources) == 1: return sources[0].getInflows()
sources.sort(key=MASS_SORTER)
flows = sources[0].getInflows().copy()
for zone in sources[1:]:
flows += zone.getInflows()
return flows
@classmethod
def outflowsFrom(cls, sources):
'''Given a list of RegionalUnits, returns a sum of their outflows.'''
# common.debug(sources)
# common.debug([(x, x.getOutflows()) for x in sources])
# if len(sources) == 1: return sources[0].getOutflows()
# flows = cls.new()
# for zone in sources:
# flows += zone.getOutflows()
# return flows
sources.sort(key=MASS_SORTER)
flows = sources[0].getOutflows().copy()
for zone in sources[1:]:
flows += zone.getOutflows()
return flows
def sum(self):
'''Returns a sum of its values (strengths) including raw.'''
if not self._summed:
# print self
# print sum(self.itervalues()), self.default_factory(), self.raw
# common.debug(self.values())
# common.debug(self.raw)
# common.debug(sum(self.itervalues()))
self._sum = (sum(self.itervalues()) if len(self) > 0 else self.default_factory()) + self.raw
self._summed = True
# print self._sum, len(self), sum(self.itervalues()), self.default_factory(), self.raw, self.default_factory() + self.raw
return self._sum
def toCore(self):
'''Returns its copy. If any of its targets is a zone that is a core of a region, its value is added to that region's value instead.'''
regional = self.new()
for target in self:
if isinstance(target, Region):
region = target
else:
region = target.getCore()
if region is None:
regional[target] += self[target]
else:
regional[region] += self[target]
regional.raw = self.raw
return regional
def toRegional(self):
'''Returns its copy. If any of its targets is a zone that is inside a region, its value is added to that region's value instead.'''
regional = self.new()
for target in self:
if isinstance(target, Region):
region = target
else:
region = target.getRegion()
if region is None:
regional[target] += self[target]
else:
regional[region] += self[target]
regional.raw = self.raw
return regional
def sumToCoreOf(self, region):
'''Returns a sum of strengths to all its targets that are zones of a specified region's core.'''
total = self.default_factory()
for target, strength in self.iteritems():
if target.isCoreOf(region):
total += strength
return total
def sumToRegion(self, region):
'''Returns a sum of strengths to all its targets that are zones of a specified region.'''
total = self.default_factory()
for target, strength in self.iteritems():
if target.isInRegion(region):
total += strength
return total
def sumOutOf(self, region):
'''Returns a sum of strengths to all its targets that are zones outside the specified region.'''
total = self.default_factory()
for target, strength in self.iteritems():
if not target.isInRegion(region):
total += strength
return total
def sumsByCore(self, region):
'''Returns results of sumToCoreOf(region) and sumOutOf(region) in a two-member tuple. Should be a little faster.'''
inside = self.default_factory()
outside = self.default_factory()
for target, strength in self.iteritems():
if target.isCoreOf(region):
inside += strength
elif not target.isInRegion(region):
outside += strength
return (inside, outside)
def sumsByRegion(self, region):
'''Returns results of sumToRegion(region) and sumOutOf(region) in a two-member tuple. Should be a little faster.'''
inside = self.default_factory()
outside = self.default_factory()
for target, strength in self.iteritems():
if target.isInRegion(region):
inside += strength
else:
outside += strength
return (inside, outside)
def addRaw(self, toAdd):
'''Adds to raw interaction value (with unspecified targets).'''
# common.debug(repr(toAdd))
self.raw += toAdd
def subRaw(self, toSub):
'''Subtracts from raw interaction value (with unspecified targets).'''
self.raw -= toSub
def getRaw(self):
'''Returns a raw interaction value (with unspecified targets).'''
return self.raw
class Interactions(BaseInteractions):
'''Simple interactions containing only a single strength value (int or float).'''
def __init__(self, *args, **kwargs):
BaseInteractions.__init__(self, int, *args, **kwargs)
self.raw = 0
def allOver(self, strength, exclude=[]):
'''Returns all its interactions that have strength exceeding the given value and their target (or its region) is not contained in the exclude list.'''
over = self.new()
for target in self:
if self[target] >= strength and (not exclude or (target not in exclude and target.getRegion() not in exclude)):
over[target] = self[target]
return over
def max(self):
'''Returns a maximum of its values.'''
return max(self.itervalues()) if self else 0
def strongest(self):
'''Returns a target with the highest corresponding strength.'''
return common.maxKey(self)
def significant(self):
'''Returns only significant flows according to Van Nuffel's algorithm.
Compares the flows' strengths normalised by the largest one to the theoretical sequence of [1 / n] * n + [0] * (len(self) - n) for n starting at 1 and increasing as long as the correlation keeps growing. When it no longer grows, stops and declares the first n largest flows as significant.'''
if not self: return self
maxFlow = self.max()
real = [(target, strength / maxFlow) for target, strength in self.iteritems()]
real.sort(key=operator.itemgetter(1), reverse=True)
expected = [0] * len(real) # expected flows: first num is 1/num, others are 0
num = 0
prevResVar = len(real) + 2
resVar = prevResVar - 1
while resVar < prevResVar: # as long as the residual variance keeps diminishing
if num == len(real): break
num += 1
factor = 1 / float(num)
for i in range(num):
expected[i] = factor # first n expected values
prevResVar = resVar
# calculate the determination coefficient (only residual variance; the only thing needed to compare)
resVar = 0
for i in range(len(real)):
resVar += (real[i][1] - expected[i]) ** 2
# create the significant flows instance
over = self.new()
for item in real[:(num-1)]: # the last one was bad, do not include it
over[item[0]] = item[1]
return over
def sortedTargets(self):
'''Returns a list of its targets sorted by their corresponding interaction strengths descending (strongest interaction target first).'''
return sorted(self, key=self.get, reverse=True)
def orders(self):
valords = sorted(self.itervalues(), reverse=True)
ords = self.new()
for target, val in self.iteritems():
# average of first and last index of value found
ords[target] = (valords.index(val) + len(valords) - valords[::-1].index(val)) / 2.0 + 0.5
return ords
def relativeStrengths(self):
maxFlow = self.max()
if not maxFlow: maxFlow = 1
return self / float(maxFlow)
@staticmethod
def transform(inter, callerName):
caller = operator.methodcaller(callerName)
trans = {}
for item, rels in inter.iteritems():
trans[item] = (caller(rels[0]), caller(rels[1]))
return trans
class InteractionVector(list):
'''A vector that stores values of MultiInteractions strengths.
A sibling of list that adds a few methods to implement vector arithmetic.
Overrides boolean checking - evaluates to True iff any of its items is nonzero.'''
@classmethod
def zeros(cls, length):
return cls([0] * length)
def __add__(self, other):
both = self.new()
for i in range(len(self)):
both[i] = self[i] + other[i]
return both
def __iadd__(self, other):
for i in range(len(self)):
self[i] += other[i]
return self
def __mul__(self, factor):
fac = self.new()
for item in self:
fac.append(item * factor)
return fac
def __isub__(self, other):
for i in range(len(self)):
self[i] -= other[i]
return self
def __div__(self, divisor):
fac = self.new()
divisor = float(divisor)
for item in self:
fac.append(item / divisor)
return fac
def __nonzero__(self):
return bool(sum(self))
def __repr__(self):
return 'I' + list.__repr__(self)
@classmethod
def new(cls):
return cls()
class MultiInteractions(BaseInteractions):
'''Interactions with more than one strength. Using InteractionVector to store their values.'''
defaultLength = None
def __init__(self, length=None, *args, **kwargs):
if length is None:
if self.defaultLength is None:
raise ValueError, 'must provide length for multiinteractions'
else:
length = self.defaultLength
lamb = lambda: numpy.zeros(length)
# lamb = lambda: InteractionVector.zeros(length)
BaseInteractions.__init__(self, lamb, *args, **kwargs)
def copy(self):
cp = self.new()
for target in self:
cp[target] = numpy.copy(self[target])
cp.raw = self.raw
return cp
@classmethod
def setDefaultLength(cls, length):
cls.defaultLength = length
class RegionalUnit:
'''A measurable regional unit - a pseudoabstract superclass of Zone and Region allowing both of them to provide IDs.'''
id = None
def __init__(self, id):
self.id = id
def getID(self):
return self.id
# for interaction calculations (excluding zones by their region)
def getRegion(self):
return None
class Neighbour:
'''A simple superclass allowing neighbourhood formalization.'''
def __init__(self):
self.neighbours = set()
def addNeighbour(self, zone):
self.neighbours.add(zone)
def hasNeighbour(self, zone):
return (zone in self.neighbours)
def setNeighbours(self, neighs):
if neighs:
self.neighbours.update(neighs)
def getNeighbours(self):
return self.neighbours
class Exterior(Neighbour):
def getID(self):
return -1
def __repr__(self):
return '<Exterior>'
exterior = Exterior()
class GeometricalZone(Neighbour):
'''A zone that implements neighbourhood by geometry intersection.'''
def __init__(self, id, geometry=None):
self.id = id
if geometry is not None:
geometry = self.prepareGeometry(geometry)
self.geometry = geometry
def intersects(self, zone):
# tried to test minimum bounding rectangles... no speedup obvious (probably is done already)
return not self.geometry.disjoint(zone.getGeometry())
def getGeometry(self):
return self.geometry
def getID(self):
return self.id
# copies geometry... the uncopied one didn't seem to work
@staticmethod
def prepareGeometry(polygon):
array = arcpy.Array()
for part in polygon:
array.add(part)
return arcpy.Polygon(array)
# zone (a basic territorial unit for which data is provided, usually a settlement)
class RegionalZone(RegionalUnit, Neighbour):
delegation = 'region'
coreable = True
def __init__(self, id, mass=None, unitID=None):
'''Initialize the zone with a given ID and mass.'''
RegionalUnit.__init__(self, id)
Neighbour.__init__(self)
self.mass = mass
self.assignment = None
self.removeAssignment()
def getMass(self):
return self.mass
def getRawMass(self):
return self.mass
def addAssignment(self, ass):
self.assignment = ass
self._region = ass.getRegion()
def removeAssignment(self, ass=None):
if ass is None or ass is self.assignment:
self.assignment = None
self._region = None
def getAssignments(self):
return [self.assignment] if self.assignment is not None else []
def getAssignmentTo(self, region):
if self._region is region:
return self.assignment
else:
return None
def deassign(self):
if self._region is not None:
self.assignment.erase()
def getRegion(self):
'''Returns its region if it is in one, None otherwise.'''
return self._region
def getRegionID(self):
if self._region is None:
return None
else:
return self._region.getID()
def getRegions(self):
return [self._region] if self._region is not None else []
def isInRegion(self, region):
return self._region is region
def isAssigned(self):
return self._region is not None
def getContiguousRegions(self):
# oscillation assignments are included...
contig = set()
for zone in self.neighbours:
if zone is not exterior:
contig.add(zone.getRegion())
contig.discard(None)
return contig
def hasContiguousRegion(self, region):
for zone in self.neighbours:
if zone is not exterior and region is zone.getRegion():
return True
return False
def isInContiguousRegion(self, region):
return self._region is region and not self.assignment.isExclave()
def __repr__(self):
return '<Zone %s (%i)>' % (self.getID(), self.getMass())
def transferExclaveFlag(self):
pass
class NoFlowZone(RegionalZone):
def __init__(self, id, mass, mass2=0, rigidUnitID=None, flexUnitID=None, location=None):
'''Initialize the zone with a given ID and masses. Stores the administrative unit IDs to be able to be assigned to units created later.'''
RegionalZone.__init__(self, id, mass)
self.secondaryMass = mass2
self.rigidUnitID = rigidUnitID
self.flexUnitID = flexUnitID
self.location = location
def getRigidUnitID(self):
return self.rigidUnitID
def getFlexibleUnitID(self):
return self.flexUnitID
def setRigidUnit(self, unit):
self.rigidUnit = unit
def getRigidUnit(self):
return self.rigidUnit
def setFlexibleUnit(self, unit):
self.flexibleUnit = unit
def getFlexibleUnit(self):
return self.flexibleUnit
def getSecondaryMass(self):
return self.secondaryMass
def setLocation(self, loc):
self.location = loc
def getLocation(self):
return self.location
class FlowZone(RegionalZone):
delegation = 'region'
penalization = 1
interactionClass = Interactions
coreable = True
def __init__(self, id, mass=None, coop=None, assign=None, color=None, coreable=True):
'''Initialize the zone with a given ID, mass, color and regional setup.
coop signals that the zone should be merged to that region as a core,
assign signals that the zone should be added to that region's hinterland.'''
RegionalZone.__init__(self, id, (0 if mass is None else mass))
self.inflows = self.interactionClass()
self.outflows = self.interactionClass()
self.mutualFlows = self.interactionClass()
self.regionPreset = assign
self.coop = coop
self.coreable = coreable
self.color = colors.hexToRGB(color) if color is not None and color.strip() else colors.WHITE_RGB
self.exclaveFlag = 0
def addInflow(self, source, strength):
self.inflows[source] += strength
self.mutualFlows[source] += strength
def addOutflow(self, target, strength):
self.outflows[target] += strength
self.mutualFlows[target] += strength
def addRawInflow(self, strength):
self.inflows.addRaw(strength)
self.mutualFlows.addRaw(strength)
def addRawOutflow(self, strength):
self.outflows.addRaw(strength)
self.mutualFlows.addRaw(strength)
def setInflows(self, inflows):
self.inflows = inflows
self.mutualFlows = self.inflows + self.outflows
def setOutflows(self, outflows):
self.outflows = outflows
self.mutualFlows = self.inflows + self.outflows
def getOutflow(self, target):
return self.outflows[target]
def sortOutflows(self):
self.sortedTargets = self.outflows.sortedTargets()
def relativizeOutflows(self):
'''Changes its outflow values to the multiples of its largest flow.'''
maxFlow = self.outflows.max()
if not maxFlow: maxFlow = 1
self.relativizedOutflows = self.outflows / float(maxFlow)
def getRegionPreset(self):
return self.regionPreset
def getCoop(self):
return self.coop
def setColor(self, color):
self.color = color
def getColor(self):
return self.color
def getColorHex(self):
return colors.rgbToHex(self.color)
# deprecated, do not use
def calcFuzzyColor(self, memName=None):
'''Calculates a membership color using the provided membership function.'''
if memFunc:
self.color = self.membershipColor(self.membershipFlows(memName))
else:
reg = self.getRegion()
if reg:
self.color = reg.getColor()
else:
self.color = RGBColor.makeWhite()
def getMaxOutflowTarget(self):
tgt = common.maxKey(self.outflows)
return tgt, self.outflows[tgt] / self.outflows.sum()
def getOutflowIndex(self, target):
return self.sortedOutflows.index(target)
def getRelativeOutflow(self, target):
return self.relativizedOutflows[target]
def getRawOutflowPercent(self):
return self.relativizedOutflows.getRaw()
def getInflows(self):
return self.inflows
def getOutflows(self):
return self.outflows
def getMutualFlows(self):
return self.inflows + self.outflows
def getExclaveFlag(self):
return self.exclaveFlag
def transferExclaveFlag(self):
'''Freezes the zone's current exclave status to the exclaveFlag variable.'''
self.exclaveFlag = (1 if self.isExclave() else 0)
def hamplMembership(self, region, penal=1):
'''Returns Hampl membership function value for the given region.'''
try:
if self.isCoreOf(region): # core cannot be exclave
iR, dR = self.mutualFlows.sumsByRegion(region)
penal = 1
else:
iR, dR = self.mutualFlows.sumsByCore(region)
# if self.id == '544256': common.debug((region, iR, dR))
return penal * iR / float(iR + dR)
except ZeroDivisionError:
return 0
def membershipMass(self, memName, region, penal=1):
'''Returns the fraction of its mass contributed to its region via its specified membership function.'''
return self.mass * getattr(self, memName)(region, penal=penal)
def hamplMembershipMass(self, region, penal=1):
return self.membershipMass('hamplMembership', region, penal=penal)
def membershipFlows(self, memName, core=True, excl=False):
'''Returns membership function values of the zone in all the regions it has any binding to.'''
srcFlows = self.mutualFlows.toCore() if core else self.mutualFlows.toRegional()
memFlows = self.interactionClass()
excl = excl and bool(self.penalization != 1)
if excl: regs = self.getContiguousRegions()
for target in srcFlows:
if isinstance(target, Region):
memFlows[target] = getattr(self, memName)(target, penal=(self.penalization if excl and target not in regs else 1))
return memFlows
def getLesserCoreID(self):
'''Returns its region ID if it is its core but the IDs are unequal.'''
core = self.getCore()
if core and core.getID() != self.id:
return core.getID()
else:
return None
# @staticmethod
# def membershipColor(memberFlows):
# '''Returns a membership color.
# The color is computed as the average of the colors of the regions the zone has any binding to, weighted by the membership function in that region.'''
# myColor = RGBColor()
# sumdeg = 0.0
# for reg in memberFlows:
# myColor += reg.getColor() * memberFlows[reg]
# sumdeg += memberFlows[reg]
# rest = (1.0 - sumdeg) # fill up to 1 with white - should not be necessary
# if abs(rest) > 0.001: # TODO: warn if using
# myColor += RGBColor.makeWhite() * rest
# return myColor
def maxMembership(self, memFunc):
regOutflows = self.mutualFlows.toCore()
myReg = self.getCore()
if myReg:
regOutflows[myReg] = self.outflows.sumToRegion(myReg)
for target, flow in sorted(regOutflows.items(), key=operator.itemgetter(1), reverse=True):
if isinstance(target, Region):
return memFunc(target, flow, self.getPenalizationDiff(target))
return 0
def sumFlows(self, out=True):
return self.outflows.sum() if out else self.inflows.sum()
def getPenalization(self, isExclave=None):
if isExclave is None: isExclave = self.isExclave()
return (self.penalization if isExclave else 1)
def getPenalizationDiff(self, region):
if self.penalization == 1:
return 1 # no need to check anything, does not matter
else:
return self.getPenalization(not self.hasContiguousRegion(region))
@classmethod
def setExclavePenalization(cls, penal):
cls.penalization = penal
class MonoZone(FlowZone):
oscillationRatio = 1
def __init__(self, *args, **kwargs):
FlowZone.__init__(self, *args, **kwargs)
self.removeAssignment()
def isExclave(self): # if the zone is an exclave
if self.assignment is not None:
return self.assignment.isExclave()
else:
return False
def addAssignment(self, ass):
FlowZone.addAssignment(self, ass)
self._iscore = ass.isCore()
def removeAssignment(self, ass=None):
FlowZone.removeAssignment(self, ass)
if ass is None or ass is self.assignment:
self._iscore = False
def setAssignmentExclave(self, region=None):
if self._region is not None:
self.assignment.setExclave(True)
def isCoreOf(self, region):
return self._iscore and self._region is region
def isInRegion(self, region):
return self._region is region
def getCore(self):
'''Returns its region if it is its core, None otherwise.'''
return self._region if self._iscore else None
def getRegionColor(self):
'''Transfers the color of the zone's region to the zone.'''
if self._region is None:
return colors.BLACK_RGB
else:
return self._region.getColor()
class MultiZone(FlowZone):
def __init__(self, *args, **kwargs):
RegionalZone.__init__(self, *args, **kwargs)
self.assignments = []
def isExclave(self): # if the zone is an exclave
for ass in self.assignments:
if not ass.isExclave():
return False
return True
def addAssignment(self, ass):
# common.debug('adding %s' % ass)
self.assignments.append(ass)
def removeAssignment(self, ass=None):
# common.debug('removing %s' % ass)
if ass in self.assignments:
self.assignments.remove(ass)
if len(self.assignments) == 1 and not self.assignments[0]:
self.assignments[0].setDegree(1) # solidify the remaining assignment from 0 to 1
else:
pass
def getAssignments(self):
return self.assignments
def getAssignmentTo(self, region):
for ass in self.assignments:
if ass.getRegion() == region:
return ass
return None
def deassign(self):
for ass in self.assignments:
ass.erase()
def setAssignmentExclave(self, region):
for ass in self.assignments:
if ass.getRegion() is region:
ass.setExclave(True)
def isCoreOf(self, region):
for ass in self.assignments:
if ass.getRegion() is region and ass.isCore():
return True
return False
def isInRegion(self, region):
for ass in self.assignments:
if ass.getRegion() is region:
return True
return False
def isInContiguousRegion(self, region):
for ass in self.assignments:
if ass.getRegion() is region and not ass.isExclave():
return True
return False
def getRegion(self):
'''Returns its region if it is in one, None otherwise.'''
if not self.assignments:
return None
elif len(self.assignments) == 1:
return self.assignments[0].getRegion()
else:
maxAss = max(self.assignments)
if maxAss:
return maxAss.getRegion()
else:
return None
def getCore(self):
'''Returns its region if it is its core, None otherwise.'''
if self.assignments:
if len(self.assignments) == 1:
if self.assignments[0].isCore():
return self.assignments[0].getRegion()
else:
return None
else:
# common.warning('multiassign: %s' % self.assignments) # TODO
coreAss = [ass for ass in self.assignments if ass.isCore()]
if coreAss:
return max(coreAss).getRegion() # no nonzero check; core assignments never oscillate
else:
return None
return None
def getRegions(self):
return [ass.getRegion() for ass in self.assignments]
def isAssigned(self):
return bool(self.assignments)
def getRegionColor(self):
'''Transfers the color of the zone's region to the zone.'''
regs = self.getRegions()
if len(regs) == 1:
return regs[0].getColor()
elif regs:
return colors.WHITE_RGB
else:
return colors.BLACK_RGB
class SimpleAssignment:
'''An assignment of a zone to a region.'''
def __init__(self, zone, region):
self.zone = zone
self.region = region
def tangle(self): # binds the relationship to both sides
# common.debug('assigning %s to %s' % (self.zone, self.region))
self.zone.addAssignment(self)
self.region.addAssignment(self)
def erase(self): # erases the relationship from both sides
self.zone.removeAssignment(self)
self.region.removeAssignment(self)
def dissolve(self): # erases the relationship only from the zone side (used when region is dissolved)
self.zone.removeAssignment(self)
def getZone(self):
return self.zone
def getRegion(self):
return self.region
def getMass(self):
return self.zone.getMass()
def getSecondaryMass(self):
return self.zone.getSecondaryMass()
def isExclave(self):
return False
def isOnlyConnection(self, diffZone): # PERFORMANCE BOTTLENECK
'''Returns True if diffZone would change exclave status of this assignment, False otherwise.'''
artic = self.region.getArticulation(diffZone)
if artic: # if diffZone is an articulation point of self.region
cores = len(self.region.getCoreZones())
myZone = False
for subtree in artic: # for each biconnected component separated by diffZone
coreZone = False
for zone in subtree:
if zone == self.zone:
myZone = True
elif zone.getCore():
coreZone = True
if myZone: # self.zone is inside, OK if there is a core in the same component
return (not coreZone)
elif coreZone: # one core less to be in the same component with self.zone
cores -= 1