ortools.sat.colab.visualization
Collection of helpers to visualize cp_model solutions in colab.
1# Copyright 2010-2024 Google LLC 2# Licensed under the Apache License, Version 2.0 (the "License"); 3# you may not use this file except in compliance with the License. 4# You may obtain a copy of the License at 5# 6# http://www.apache.org/licenses/LICENSE-2.0 7# 8# Unless required by applicable law or agreed to in writing, software 9# distributed under the License is distributed on an "AS IS" BASIS, 10# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11# See the License for the specific language governing permissions and 12# limitations under the License. 13 14"""Collection of helpers to visualize cp_model solutions in colab.""" 15 16# pylint: disable=g-import-not-at-top 17import random 18 19try: 20 from IPython.display import display 21 from IPython.display import SVG 22 import plotly.figure_factory as ff 23 import svgwrite 24 25 correct_imports = True 26except ImportError: 27 correct_imports = False 28 29 30def RunFromIPython(): 31 if not correct_imports: 32 return False 33 try: 34 return __IPYTHON__ is not None 35 except NameError: 36 return False 37 38 39def ToDate(v): 40 return "2016-01-01 6:%02i:%02i" % (v / 60, v % 60) 41 42 43class ColorManager: 44 """Utility to create colors to use in visualization.""" 45 46 def ScaledColor(self, sr, sg, sb, er, eg, eb, num_steps, step): 47 """Creates an interpolated rgb color between two rgb colors.""" 48 num_intervals = num_steps - 1 49 dr = (er - sr) / num_intervals 50 dg = (eg - sg) / num_intervals 51 db = (eb - sb) / num_intervals 52 r = sr + dr * step 53 g = sg + dg * step 54 b = sb + db * step 55 return "rgb(%i, %i, %i)" % (r, g, b) 56 57 def SeedRandomColor(self, seed=0): 58 random.seed(seed) 59 60 def RandomColor(self): 61 return "rgb(%i,%i,%i)" % ( 62 random.randint(0, 255), 63 random.randint(0, 255), 64 random.randint(0, 255), 65 ) 66 67 68def DisplayJobshop(starts, durations, machines, name): 69 """Simple function to display a jobshop solution using plotly.""" 70 71 jobs_count = len(starts) 72 machines_count = len(starts[0]) 73 all_machines = range(0, machines_count) 74 all_jobs = range(0, jobs_count) 75 df = [] 76 for i in all_jobs: 77 for j in all_machines: 78 df.append( 79 dict( 80 Task="Resource%i" % machines[i][j], 81 Start=ToDate(starts[i][j]), 82 Finish=ToDate(starts[i][j] + durations[i][j]), 83 Resource="Job%i" % i, 84 ) 85 ) 86 87 sorted_df = sorted(df, key=lambda k: k["Task"]) 88 89 colors = {} 90 cm = ColorManager() 91 cm.SeedRandomColor(0) 92 for i in all_jobs: 93 colors["Job%i" % i] = cm.RandomColor() 94 95 fig = ff.create_gantt( 96 sorted_df, 97 colors=colors, 98 index_col="Resource", 99 title=name, 100 show_colorbar=False, 101 showgrid_x=True, 102 showgrid_y=True, 103 group_tasks=True, 104 ) 105 fig.show() 106 107 108class SvgWrapper: 109 """Simple SVG wrapper to use in colab.""" 110 111 def __init__(self, sizex, sizey, scaling=20.0): 112 self.__sizex = sizex 113 self.__sizey = sizey 114 self.__scaling = scaling 115 self.__offset = scaling 116 self.__dwg = svgwrite.Drawing( 117 size=( 118 self.__sizex * self.__scaling + self.__offset, 119 self.__sizey * self.__scaling + self.__offset * 2, 120 ) 121 ) 122 123 def Display(self): 124 display(SVG(self.__dwg.tostring())) 125 126 def AddRectangle(self, x, y, dx, dy, fill, stroke="black", label=None): 127 """Draw a rectangle, dx and dy must be >= 0.""" 128 s = self.__scaling 129 o = self.__offset 130 corner = (x * s + o, (self.__sizey - y - dy) * s + o) 131 size = (dx * s - 1, dy * s - 1) 132 self.__dwg.add( 133 self.__dwg.rect(insert=corner, size=size, fill=fill, stroke=stroke) 134 ) 135 self.AddText(x + 0.5 * dx, y + 0.5 * dy, label) 136 137 def AddText(self, x, y, label): 138 text = self.__dwg.text( 139 label, 140 insert=( 141 x * self.__scaling + self.__offset, 142 (self.__sizey - y) * self.__scaling + self.__offset, 143 ), 144 text_anchor="middle", 145 font_family="sans-serif", 146 font_size="%dpx" % (self.__scaling / 2), 147 ) 148 self.__dwg.add(text) 149 150 def AddXScale(self, step=1): 151 """Add an scale on the x axis.""" 152 o = self.__offset 153 s = self.__scaling 154 y = self.__sizey * s + o / 2.0 + o 155 dy = self.__offset / 4.0 156 self.__dwg.add( 157 self.__dwg.line((o, y), (self.__sizex * s + o, y), stroke="black") 158 ) 159 for i in range(0, int(self.__sizex) + 1, step): 160 self.__dwg.add( 161 self.__dwg.line( 162 (o + i * s, y - dy), (o + i * s, y + dy), stroke="black" 163 ) 164 ) 165 166 def AddYScale(self, step=1): 167 """Add an scale on the y axis.""" 168 o = self.__offset 169 s = self.__scaling 170 x = o / 2.0 171 dx = self.__offset / 4.0 172 self.__dwg.add( 173 self.__dwg.line((x, o), (x, self.__sizey * s + o), stroke="black") 174 ) 175 for i in range(0, int(self.__sizey) + 1, step): 176 self.__dwg.add( 177 self.__dwg.line( 178 (x - dx, i * s + o), (x + dx, i * s + o), stroke="black" 179 ) 180 ) 181 182 def AddTitle(self, title): 183 """Add a title to the drawing.""" 184 text = self.__dwg.text( 185 title, 186 insert=( 187 self.__offset + self.__sizex * self.__scaling / 2.0, 188 self.__offset / 2, 189 ), 190 text_anchor="middle", 191 font_family="sans-serif", 192 font_size="%dpx" % (self.__scaling / 2), 193 ) 194 self.__dwg.add(text)
def
RunFromIPython():
def
ToDate(v):
class
ColorManager:
44class ColorManager: 45 """Utility to create colors to use in visualization.""" 46 47 def ScaledColor(self, sr, sg, sb, er, eg, eb, num_steps, step): 48 """Creates an interpolated rgb color between two rgb colors.""" 49 num_intervals = num_steps - 1 50 dr = (er - sr) / num_intervals 51 dg = (eg - sg) / num_intervals 52 db = (eb - sb) / num_intervals 53 r = sr + dr * step 54 g = sg + dg * step 55 b = sb + db * step 56 return "rgb(%i, %i, %i)" % (r, g, b) 57 58 def SeedRandomColor(self, seed=0): 59 random.seed(seed) 60 61 def RandomColor(self): 62 return "rgb(%i,%i,%i)" % ( 63 random.randint(0, 255), 64 random.randint(0, 255), 65 random.randint(0, 255), 66 )
Utility to create colors to use in visualization.
def
ScaledColor(self, sr, sg, sb, er, eg, eb, num_steps, step):
47 def ScaledColor(self, sr, sg, sb, er, eg, eb, num_steps, step): 48 """Creates an interpolated rgb color between two rgb colors.""" 49 num_intervals = num_steps - 1 50 dr = (er - sr) / num_intervals 51 dg = (eg - sg) / num_intervals 52 db = (eb - sb) / num_intervals 53 r = sr + dr * step 54 g = sg + dg * step 55 b = sb + db * step 56 return "rgb(%i, %i, %i)" % (r, g, b)
Creates an interpolated rgb color between two rgb colors.
def
DisplayJobshop(starts, durations, machines, name):
69def DisplayJobshop(starts, durations, machines, name): 70 """Simple function to display a jobshop solution using plotly.""" 71 72 jobs_count = len(starts) 73 machines_count = len(starts[0]) 74 all_machines = range(0, machines_count) 75 all_jobs = range(0, jobs_count) 76 df = [] 77 for i in all_jobs: 78 for j in all_machines: 79 df.append( 80 dict( 81 Task="Resource%i" % machines[i][j], 82 Start=ToDate(starts[i][j]), 83 Finish=ToDate(starts[i][j] + durations[i][j]), 84 Resource="Job%i" % i, 85 ) 86 ) 87 88 sorted_df = sorted(df, key=lambda k: k["Task"]) 89 90 colors = {} 91 cm = ColorManager() 92 cm.SeedRandomColor(0) 93 for i in all_jobs: 94 colors["Job%i" % i] = cm.RandomColor() 95 96 fig = ff.create_gantt( 97 sorted_df, 98 colors=colors, 99 index_col="Resource", 100 title=name, 101 show_colorbar=False, 102 showgrid_x=True, 103 showgrid_y=True, 104 group_tasks=True, 105 ) 106 fig.show()
Simple function to display a jobshop solution using plotly.
class
SvgWrapper:
109class SvgWrapper: 110 """Simple SVG wrapper to use in colab.""" 111 112 def __init__(self, sizex, sizey, scaling=20.0): 113 self.__sizex = sizex 114 self.__sizey = sizey 115 self.__scaling = scaling 116 self.__offset = scaling 117 self.__dwg = svgwrite.Drawing( 118 size=( 119 self.__sizex * self.__scaling + self.__offset, 120 self.__sizey * self.__scaling + self.__offset * 2, 121 ) 122 ) 123 124 def Display(self): 125 display(SVG(self.__dwg.tostring())) 126 127 def AddRectangle(self, x, y, dx, dy, fill, stroke="black", label=None): 128 """Draw a rectangle, dx and dy must be >= 0.""" 129 s = self.__scaling 130 o = self.__offset 131 corner = (x * s + o, (self.__sizey - y - dy) * s + o) 132 size = (dx * s - 1, dy * s - 1) 133 self.__dwg.add( 134 self.__dwg.rect(insert=corner, size=size, fill=fill, stroke=stroke) 135 ) 136 self.AddText(x + 0.5 * dx, y + 0.5 * dy, label) 137 138 def AddText(self, x, y, label): 139 text = self.__dwg.text( 140 label, 141 insert=( 142 x * self.__scaling + self.__offset, 143 (self.__sizey - y) * self.__scaling + self.__offset, 144 ), 145 text_anchor="middle", 146 font_family="sans-serif", 147 font_size="%dpx" % (self.__scaling / 2), 148 ) 149 self.__dwg.add(text) 150 151 def AddXScale(self, step=1): 152 """Add an scale on the x axis.""" 153 o = self.__offset 154 s = self.__scaling 155 y = self.__sizey * s + o / 2.0 + o 156 dy = self.__offset / 4.0 157 self.__dwg.add( 158 self.__dwg.line((o, y), (self.__sizex * s + o, y), stroke="black") 159 ) 160 for i in range(0, int(self.__sizex) + 1, step): 161 self.__dwg.add( 162 self.__dwg.line( 163 (o + i * s, y - dy), (o + i * s, y + dy), stroke="black" 164 ) 165 ) 166 167 def AddYScale(self, step=1): 168 """Add an scale on the y axis.""" 169 o = self.__offset 170 s = self.__scaling 171 x = o / 2.0 172 dx = self.__offset / 4.0 173 self.__dwg.add( 174 self.__dwg.line((x, o), (x, self.__sizey * s + o), stroke="black") 175 ) 176 for i in range(0, int(self.__sizey) + 1, step): 177 self.__dwg.add( 178 self.__dwg.line( 179 (x - dx, i * s + o), (x + dx, i * s + o), stroke="black" 180 ) 181 ) 182 183 def AddTitle(self, title): 184 """Add a title to the drawing.""" 185 text = self.__dwg.text( 186 title, 187 insert=( 188 self.__offset + self.__sizex * self.__scaling / 2.0, 189 self.__offset / 2, 190 ), 191 text_anchor="middle", 192 font_family="sans-serif", 193 font_size="%dpx" % (self.__scaling / 2), 194 ) 195 self.__dwg.add(text)
Simple SVG wrapper to use in colab.
SvgWrapper(sizex, sizey, scaling=20.0)
112 def __init__(self, sizex, sizey, scaling=20.0): 113 self.__sizex = sizex 114 self.__sizey = sizey 115 self.__scaling = scaling 116 self.__offset = scaling 117 self.__dwg = svgwrite.Drawing( 118 size=( 119 self.__sizex * self.__scaling + self.__offset, 120 self.__sizey * self.__scaling + self.__offset * 2, 121 ) 122 )
def
AddRectangle(self, x, y, dx, dy, fill, stroke='black', label=None):
127 def AddRectangle(self, x, y, dx, dy, fill, stroke="black", label=None): 128 """Draw a rectangle, dx and dy must be >= 0.""" 129 s = self.__scaling 130 o = self.__offset 131 corner = (x * s + o, (self.__sizey - y - dy) * s + o) 132 size = (dx * s - 1, dy * s - 1) 133 self.__dwg.add( 134 self.__dwg.rect(insert=corner, size=size, fill=fill, stroke=stroke) 135 ) 136 self.AddText(x + 0.5 * dx, y + 0.5 * dy, label)
Draw a rectangle, dx and dy must be >= 0.
def
AddText(self, x, y, label):
138 def AddText(self, x, y, label): 139 text = self.__dwg.text( 140 label, 141 insert=( 142 x * self.__scaling + self.__offset, 143 (self.__sizey - y) * self.__scaling + self.__offset, 144 ), 145 text_anchor="middle", 146 font_family="sans-serif", 147 font_size="%dpx" % (self.__scaling / 2), 148 ) 149 self.__dwg.add(text)
def
AddXScale(self, step=1):
151 def AddXScale(self, step=1): 152 """Add an scale on the x axis.""" 153 o = self.__offset 154 s = self.__scaling 155 y = self.__sizey * s + o / 2.0 + o 156 dy = self.__offset / 4.0 157 self.__dwg.add( 158 self.__dwg.line((o, y), (self.__sizex * s + o, y), stroke="black") 159 ) 160 for i in range(0, int(self.__sizex) + 1, step): 161 self.__dwg.add( 162 self.__dwg.line( 163 (o + i * s, y - dy), (o + i * s, y + dy), stroke="black" 164 ) 165 )
Add an scale on the x axis.
def
AddYScale(self, step=1):
167 def AddYScale(self, step=1): 168 """Add an scale on the y axis.""" 169 o = self.__offset 170 s = self.__scaling 171 x = o / 2.0 172 dx = self.__offset / 4.0 173 self.__dwg.add( 174 self.__dwg.line((x, o), (x, self.__sizey * s + o), stroke="black") 175 ) 176 for i in range(0, int(self.__sizey) + 1, step): 177 self.__dwg.add( 178 self.__dwg.line( 179 (x - dx, i * s + o), (x + dx, i * s + o), stroke="black" 180 ) 181 )
Add an scale on the y axis.
def
AddTitle(self, title):
183 def AddTitle(self, title): 184 """Add a title to the drawing.""" 185 text = self.__dwg.text( 186 title, 187 insert=( 188 self.__offset + self.__sizex * self.__scaling / 2.0, 189 self.__offset / 2, 190 ), 191 text_anchor="middle", 192 font_family="sans-serif", 193 font_size="%dpx" % (self.__scaling / 2), 194 ) 195 self.__dwg.add(text)
Add a title to the drawing.