Skip to content

Commit 19c2591

Browse files
committed
Adds function descriptions and improved error handling to action.py
1 parent e67b260 commit 19c2591

File tree

2 files changed

+148
-32
lines changed

2 files changed

+148
-32
lines changed

famodel/irma/action.py

Lines changed: 147 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,19 @@
3232

3333

3434
def incrementer(text):
35+
'''
36+
Increments the last integer found in a string.
37+
38+
Inputs
39+
------
40+
text : str
41+
The input string to increment.
42+
43+
Returns
44+
-------
45+
str
46+
The incremented string.
47+
'''
3548
split_text = text.split()[::-1]
3649
for ind, spl in enumerate(split_text):
3750
try:
@@ -43,7 +56,19 @@ def incrementer(text):
4356

4457

4558
def increment_name(name):
46-
'''Increments an end integer after a dash in a name'''
59+
'''
60+
Increments an end integer after a dash in a name.
61+
62+
Inputs
63+
------
64+
name : str
65+
The input name string.
66+
67+
Returns
68+
-------
69+
str
70+
The incremented name string.
71+
'''
4772
name_parts = name.split(sep='-')
4873

4974
# if no numeric suffix yet, add one
@@ -72,7 +97,7 @@ def __init__(self, actionType, name, **kwargs):
7297
It must be given a name.
7398
The remaining parameters should correspond to items in the actionType dict...
7499
75-
Parameters
100+
Inputs
76101
----------
77102
actionType : dict
78103
Dictionary defining the action type (typically taken from a yaml).
@@ -83,7 +108,10 @@ def __init__(self, actionType, name, **kwargs):
83108
Additional arguments may depend on the action type and typically
84109
include a list of FAModel objects that are acted upon, or
85110
a list of dependencies (other action names/objects).
86-
111+
112+
Returns
113+
-------
114+
None
87115
'''
88116

89117
# list of things that will be controlled during this action
@@ -139,7 +167,7 @@ def __init__(self, actionType, name, **kwargs):
139167
for role, caplist in actionType['roles'].items():
140168
self.requirements[role] = {key: None for key in caplist} # each role requirment holds a dict of capabilities with values set to None for now
141169
for cap in caplist:
142-
# self.requirements[role][cap] = {} # fill in each required capacity with {'metric': 0.0}
170+
# self.requirements[role][cap] = {} # fill in each required capacity with empty dict
143171
self.requirements[role][cap] = {'area_m2': 1000, 'max_load_t': 1600} # dummy values for now, just larger than MPSV_01 values to trigger failure
144172

145173
self.assets[role] = None # placeholder for the asset assigned to this role
@@ -155,12 +183,17 @@ def __init__(self, actionType, name, **kwargs):
155183

156184

157185
def addDependency(self, dep):
158-
'''Registers other action as a dependency of this one.
186+
'''
187+
Registers other action as a dependency of this one.
159188
160189
Inputs
161190
------
162191
dep : Action
163192
The action to be added as a dependency.
193+
194+
Returns
195+
-------
196+
None
164197
'''
165198
self.dependencies[dep.name] = dep
166199
# could see if already a dependency and raise a warning if so...
@@ -169,12 +202,16 @@ def addDependency(self, dep):
169202
def assignObjects(self, objects):
170203
'''
171204
Adds a list of objects to the actions objects list,
172-
checking they are valid for the actions supported objects
205+
checking they are valid for the actions supported objects.
173206
174207
Inputs
175208
------
176209
objects : list
177210
A list of FAModel objects to be added to the action.
211+
212+
Returns
213+
-------
214+
None
178215
'''
179216

180217
for obj in objects:
@@ -189,6 +226,17 @@ def assignObjects(self, objects):
189226

190227

191228
# def setUpCapability(self):
229+
# '''
230+
# Example of what needs to happen to create a metric.
231+
#
232+
# Inputs
233+
# ------
234+
# None
235+
#
236+
# Returns
237+
# -------
238+
# None
239+
# '''
192240
# # WIP: example of what needs to happen to create a metric
193241

194242
# # figure out how to assign required metrics to capabilies in the roles based on the objects
@@ -204,6 +252,20 @@ def assignObjects(self, objects):
204252
def checkAsset(self, role_name, asset):
205253
'''Checks if a specified asset has sufficient capabilities to fulfil
206254
a specified role in this action.
255+
256+
Inputs
257+
------
258+
role_name : string
259+
The name of the role to check.
260+
asset : dict
261+
The asset to check against the role's requirements.
262+
263+
Returns
264+
-------
265+
bool
266+
True if the asset meets the role's requirements, False otherwise.
267+
str
268+
A message providing additional information about the check.
207269
'''
208270

209271
# Make sure role_name is valid for this action
@@ -212,44 +274,62 @@ def checkAsset(self, role_name, asset):
212274

213275
for capability in self.requirements[role_name].keys():
214276

215-
if capability in asset['capabilities'].keys(): # check capability
216-
# does this work if there are no metrics in a capability? This should be possible, as not all capabilities will require a constraint.
277+
if capability in asset['capabilities'].keys(): # check capability is in asset
278+
279+
# TODO: does this work if there are no metrics in a capability? This should be possible, as not all capabilities will require a constraint.
217280
for metric in self.requirements[role_name][capability].keys(): # loop over the capacity requirements for the capability (if more than one)
218-
if self.requirements[role_name][capability][metric] > asset['capabilities'][capability][metric]: # check capacity
219-
# TODO: can we throw a message here that explains what metric is violated?
220-
return False # the asset does not have the capacity for this capability
221-
return True
281+
282+
if metric not in asset['capabilities'][capability].keys(): # value error because capabilities are defined in capabilities.yaml. This should only be triggered if something has gone wrong (i.e. overwriting values somewhere)
283+
raise ValueError(f"The '{capability}' capability does not have metric: '{metric}'.")
284+
285+
if self.requirements[role_name][capability][metric] > asset['capabilities'][capability][metric]: # check requirement is met
286+
return False, f"The asset does not have sufficient '{metric}' for '{capability}' capability in '{role_name}' role of '{self.name}' action."
287+
288+
return True, 'All capabilities in role met'
222289

223290
else:
224-
return False # at least one capability is not met
225-
226-
return True
227-
291+
return False, f"The asset does not have the '{capability}' capability for '{role_name}' role of '{self.name}' action." # a capability is not met
292+
228293

229294
def assignAsset(self, role_name, asset):
230-
'''Assigns a vessel or port to a certain role in the action.
231-
232-
Parameters
233-
----------
295+
'''
296+
Assigns a vessel or port to a certain role in the action.
297+
298+
Inputs
299+
------
234300
role_name : string
235301
Name of the asset role being filled (must be in the action's list)
236302
asset : Vessel or Port object
237303
The asset to be registered with the class.
304+
305+
Returns
306+
-------
307+
None
238308
'''
239309

240310
# Make sure role_name is valid for this action
241311
if not role_name in self.assets.keys():
242312
raise Exception(f"The specified role name '{role_name}' is not a named asset role in this action.")
243313

244-
if self.checkAsset(role_name, asset):
314+
assignable, message = self.checkAsset(role_name, asset)
315+
if assignable:
245316
self.assets[role_name] = asset
246317
else:
247-
raise Exception(f"The asset does not have the capabilities for role '{role_name}'.")
318+
raise Exception(message) # throw error message
248319

249320

250321
def calcDurationAndCost(self):
251-
'''The structure here is dependent on actions.yaml.
322+
'''
323+
Calculates duration and cost for the action. The structure here is dependent on actions.yaml.
252324
TODO: finish description
325+
326+
Inputs
327+
------
328+
None
329+
330+
Returns
331+
-------
332+
None
253333
'''
254334

255335
print('Calculating duration and cost for action:', self.name)
@@ -305,13 +385,19 @@ def calcDurationAndCost(self):
305385

306386

307387
def evaluateAssets(self, assets):
308-
'''Check whether an asset can perform the task, and if so calculate
388+
'''
389+
Check whether an asset can perform the task, and if so calculate
309390
the time and cost associated with using those assets.
310-
391+
392+
Inputs
393+
------
311394
assets : dict
312-
Dictionary of role_name, asset object pairs for assignment to the action.
313-
Each asset is a vessel or port object.
314-
395+
Dictionary of {role_name: asset} pairs for assignment of the
396+
assets to the roles in the action.
397+
398+
Returns
399+
-------
400+
None
315401
'''
316402

317403
# error check that assets is a dict of {role_name, asset dict}, and not just an asset dict?
@@ -330,23 +416,53 @@ def evaluateAssets(self, assets):
330416
# ----- Below are drafts of methods for use by the engine -----
331417
"""
332418
def begin(self):
333-
'''Take control of all objects'''
419+
'''
420+
Take control of all objects.
421+
422+
Inputs
423+
------
424+
None
425+
426+
Returns
427+
-------
428+
None
429+
'''
334430
for vessel in self.vesselList:
335431
vessel._attach_to(self)
336432
for object in self.objectList:
337433
object._attach_to(self)
338434
339435
340436
def end(self):
341-
'''Release all objects'''
437+
'''
438+
Release all objects.
439+
440+
Inputs
441+
------
442+
None
443+
444+
Returns
445+
-------
446+
None
447+
'''
342448
for vessel in self.vesselList:
343449
vessel._detach_from()
344450
for object in self.objectList:
345451
object._detach_from()
346452
"""
347453

348454
def timestep(self):
349-
'''Advance the simulation of this action forward one step in time.'''
455+
'''
456+
Advance the simulation of this action forward one step in time.
457+
458+
Inputs
459+
------
460+
None
461+
462+
Returns
463+
-------
464+
None
465+
'''
350466

351467
# (this is just documenting an idea for possible future implementation)
352468
# Perform the hourly action of the task

famodel/irma/irma.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ def findCompatibleVessels(self):
330330

331331
# add and register anchor install action(s)
332332
a1 = sc.addAction('install_anchor', 'install_anchor-1', objects=[anchor])
333-
a1.evaluateAssets({'carrier' : sc.vessels["MPSV_01"]})
333+
a1.evaluateAssets({'operator' : sc.vessels["MPSV_01"]}) # example assignment to test the code.
334334

335335
# register the actions as necessary for the anchor <<< do this for all objects??
336336
anchor.install_dependencies = [a1]

0 commit comments

Comments
 (0)