Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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

import digital_twin.core as core 

import datetime 

import shapely 

import numpy as np 

 

 

class LevelCondition: 

"""The LevelCondition class can be used to specify the start level and stop level conditions for an Activity. 

 

container: an object which extends HasContainer, the container whose level needs to be >= or <= a certain value 

min_level: the minimum level the container is required to have 

max_level: the maximum level the container is required to have 

""" 

 

def __init__(self, container, min_level=None, max_level=None, 

*args, **kwargs): 

super().__init__(*args, **kwargs) 

"""Initialization""" 

self.container = container 

self.min_level = min_level if min_level is not None else 0 

self.max_level = max_level if max_level is not None else container.container.capacity 

 

def satisfied(self): 

current_level = self.container.container.level 

return self.min_level <= current_level <= self.max_level 

 

 

class AndCondition: 

"""The AndCondition class can be used to combine several different conditions into a single condition for an Activity. 

 

conditions: a list of condition objects that need to all be satisfied for the condition to be satisfied 

each object should have a satisfied method that returns whether the condition is satisfied or not 

""" 

 

def __init__(self, conditions, *args, **kwargs): 

super().__init__(*args, **kwargs) 

"""Initialization""" 

 

self.conditions = conditions 

 

def satisfied(self): 

for condition in self.conditions: 

if not condition.satisfied(): 

return False 

return True 

 

 

class OrCondition: 

"""The AndCondition class can be used to combine several different conditions into a single condition for an Activity. 

 

conditions: a list of condition objects, one of which needs to be satisfied for the condition to be satisfied 

each object should have a satisfied method that returns whether the condition is satisfied or not 

""" 

 

def __init__(self, conditions, *args, **kwargs): 

super().__init__(*args, **kwargs) 

"""Initialization""" 

 

self.conditions = conditions 

 

def satisfied(self): 

for condition in self.conditions: 

if condition.satisfied(): 

return True 

return False 

 

 

class TrueCondition: 

"""The TrueCondition class defines a condition which is always satisfied.""" 

 

def __init__(self, *args, **kwargs): 

super().__init__(*args, **kwargs) 

"""Initialization""" 

 

def satisfied(self): 

return True 

 

 

class Activity(core.Identifiable, core.Log): 

"""The Activity Class forms a specific class for a single activity within a simulation. 

It deals with a single origin container, destination container and a single combination of equipment 

to move substances from the origin to the destination. It will initiate and suspend processes 

according to a number of specified conditions. To run an activity after it has been initialized call env.run() 

on the Simpy environment with which it was initialized. 

 

To check when a transportation of substances can take place, the Activity class uses three different condition 

arguments: start_condition, stop_condition and condition. These condition arguments should all be given a condition 

object which has a satisfied method returning a boolean value. True if the condition is satisfied, False otherwise. 

 

start_condition: the activity will start as soon as this condition is satisfied 

by default will always be True 

stop_condition: the activity will stop (terminate) as soon as this condition is no longer satisfied after 

the activity has started 

by default will always be for the destination container to be full or the source container to be empty 

condition: after the activity has started (start_condition was satisfied), this condition will be checked as long 

as the stop_condition is not satisfied, if the condition returns True, the activity will complete exactly 

one transportation of substances, of the condition is False the activity will wait for the condition to 

be satisfied again 

by default will always be True 

origin: object inheriting from HasContainer, HasResource, Locatable, Identifiable and Log 

destination: object inheriting from HasContainer, HasResource, Locatable, Identifiable and Log 

loader: object which will get units from 'origin' Container and put them into 'mover' Container 

should inherit from Processor, HasResource, Identifiable and Log 

after the simulation is complete, its log will contain entries for each time it 

started loading and stopped loading 

mover: moves to 'origin' if it is not already there, is loaded, then moves to 'destination' and is unloaded 

should inherit from Movable, HasContainer, HasResource, Identifiable and Log 

after the simulation is complete, its log will contain entries for each time it started moving, 

stopped moving, started loading / unloading and stopped loading / unloading 

unloader: gets amount from 'mover' Container and puts it into 'destination' Container 

should inherit from Processor, HasResource, Identifiable and Log 

after the simulation is complete, its log will contain entries for each time it 

started unloading and stopped unloading 

""" 

 

# todo should loader and unloader also inherit from Locatable and Activity include checks if the loader / unloader is at the correct location? 

 

def __init__(self, 

origin, destination, 

loader, mover, unloader, 

start_condition=None, stop_condition=None, condition=None, 

show=False, 

*args, **kwargs): 

super().__init__(*args, **kwargs) 

"""Initialization""" 

 

self.start_condition = start_condition if start_condition is not None else TrueCondition() 

self.stop_condition = stop_condition if stop_condition is not None else OrCondition( 

[LevelCondition(origin, max_level=0), 

LevelCondition(destination, min_level=destination.container.capacity)]) 

self.condition = condition if condition is not None else TrueCondition() 

self.origin = origin 

self.destination = destination 

self.loader = loader 

self.mover = mover 

self.unloader = unloader 

 

self.print = show 

 

self.installation_proc = self.env.process( 

self.process_control(self.start_condition, self.stop_condition, self.condition, 

self.origin, self.destination, self.loader, self.mover, self.unloader) 

) 

 

def process_control(self, start_condition, stop_condition, condition, 

origin, destination, 

loader, mover, unloader): 

"""Installation process control""" 

 

# wait for the start condition to be satisfied 

# checking the general condition and move 

 

# stand by until the start condition is satisfied 

shown = False 

while not start_condition.satisfied(): 

if not shown: 

print('T=' + '{:06.2f}'.format(self.env.now) + ' ' + self.name + 

' to ' + destination.name + ' suspended') 

self.log_entry("suspended", self.env.now, -1, origin.geometry) 

shown = True 

yield self.env.timeout(3600) # step 3600 time units ahead 

 

# todo add nice printing to the conditions, then print them here 

print('T=' + '{:06.2f}'.format(self.env.now) + ' Start condition is satisfied, ' 

+ self.name + ' transporting from ' + origin.name + ' to ' + destination.name + ' started') 

self.log_entry("started", self.env.now, -1, origin.geometry) 

 

# keep moving substances until the stop condition is satisfied 

while not stop_condition.satisfied(): 

if condition.satisfied(): 

yield from self.installation_process(origin, destination, loader, mover, unloader) 

else: 

yield self.env.timeout(3600) 

 

print('T=' + '{:06.2f}'.format(self.env.now) + ' Stop condition is satisfied, ' 

+ self.name + ' transporting from ' + origin.name + ' to ' + destination.name + ' complete') 

self.log_entry("completed", self.env.now, -1, destination.geometry) 

 

def installation_process(self, origin, destination, 

loader, mover, unloader): 

"""Installation process""" 

# estimate amount that should be transported 

amount = min( 

mover.container.capacity - mover.container.level, 

origin.container.level, 

origin.container.capacity - origin.total_requested, 

destination.container.capacity - destination.container.level, 

destination.container.capacity - destination.total_requested) 

 

if isinstance(mover, core.HasDepthRestriction): amount = min(amount, mover.check_optimal_filling(loader, origin, destination)) 

 

if amount > 0: 

# request access to the transport_resource 

origin.total_requested += amount 

destination.total_requested += amount 

 

if self.print == True: 

print('Using ' + mover.name + ' to process ' + str(amount)) 

self.log_entry("transporting start", self.env.now, amount, mover.geometry) 

 

with mover.resource.request() as my_mover_turn: 

yield my_mover_turn 

 

# move to the origin if necessary 

if not mover.is_at(origin): 

yield from self.__move_mover__(mover, origin, 'empty') 

 

# load the mover 

loader.rate = loader.loading_func # rate is variable to loading / unloading 

yield from self.__shift_amount__(amount, loader, origin, mover, destination_resource_request=my_mover_turn) 

 

# move the mover to the destination 

yield from self.__move_mover__(mover, destination, 'full') 

 

# unload the mover 

unloader.rate = unloader.unloading_func # rate is variable to loading / unloading 

yield from self.__shift_amount__(amount, unloader, mover, destination, origin_resource_request=my_mover_turn) 

 

self.log_entry("transporting stop", self.env.now, amount, mover.geometry) 

else: 

print('Nothing to move') 

yield self.env.timeout(3600) 

 

def __shift_amount__(self, amount, processor, origin, destination, 

origin_resource_request=None, destination_resource_request=None): 

if id(origin) == id(processor) and origin_resource_request is not None or \ 

id(destination) == id(processor) and destination_resource_request is not None: 

 

yield from processor.process(origin, destination, amount, origin_resource_request=origin_resource_request, 

destination_resource_request=destination_resource_request) 

else: 

with processor.resource.request() as my_processor_turn: 

yield my_processor_turn 

 

processor.log_entry('processing start', self.env.now, amount, processor.geometry) 

yield from processor.process(origin, destination, amount, 

origin_resource_request=origin_resource_request, 

destination_resource_request=destination_resource_request) 

 

processor.log_entry('processing stop', self.env.now, amount, processor.geometry) 

 

if self.print == True: 

print('Processed {}:'.format(amount)) 

print(' from: ' + origin.name + ' contains: ' + str(origin.container.level)) 

print(' by: ' + processor.name) 

print(' to: ' + destination.name + ' contains: ' + str(destination.container.level)) 

 

def __move_mover__(self, mover, origin, status): 

old_location = mover.geometry 

 

mover.log_entry('sailing ' + status + ' start', self.env.now, mover.container.level, mover.geometry) 

yield from mover.move(origin) 

mover.log_entry('sailing ' + status + ' stop', self.env.now, mover.container.level, mover.geometry) 

 

if self.print == True: 

print('Moved:') 

print(' object: ' + mover.name + ' contains: ' + str(mover.container.level)) 

print(' from: ' + format(old_location.x, '02.5f') + ' ' + format(old_location.y, '02.5f')) 

print(' to: ' + format(mover.geometry.x, '02.5f') + ' ' + format(mover.geometry.y, '02.5f'))