Skip to content

Commit cfbdf4f

Browse files
committed
Updates to Action.py for checking valid asset-action capability and capability capacity
1 parent 89f8afd commit cfbdf4f

File tree

3 files changed

+149
-28
lines changed

3 files changed

+149
-28
lines changed

famodel/irma/action.py

Lines changed: 129 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def __init__(self, actionType, name, **kwargs):
8888

8989
# list of things that will be controlled during this action
9090
self.assets = {} # dict of named roles for the vessel(s) or port required to perform the action
91-
self.requirements = {} # the capabilities required of each asset role assets (same keys as self.assets)
91+
self.requirements = {} # the capabilities required of each role (same keys as self.assets)
9292
self.objectList = [] # all objects that could be acted on
9393
self.dependencies = {} # list of other actions this one depends on
9494

@@ -97,6 +97,8 @@ def __init__(self, actionType, name, **kwargs):
9797
self.status = 0 # 0, waiting; 1=running; 2=finished
9898

9999
self.duration = getFromDict(actionType, 'duration', default=3)
100+
101+
self.supported_objects = [] # list of FAModel object types supported by the action
100102

101103
'''
102104
# Create a dictionary of supported object types (with empty entries)
@@ -119,30 +121,29 @@ def __init__(self, actionType, name, **kwargs):
119121
# make list of supported object type names
120122
if 'objects' in actionType:
121123
if isinstance(actionType['objects'], list):
122-
supported_objects = actionType['objects']
124+
self.supported_objects = actionType['objects']
123125
elif isinstance(actionType['objects'], dict):
124-
supported_objects = list(actionType['objects'].keys())
125-
else:
126-
supported_objects = []
126+
self.supported_objects = list(actionType['objects'].keys())
127127

128128
# Add objects to the action's object list as long as they're supported
129129
if 'objects' in kwargs:
130130
for obj in kwargs['objects']:
131131
objType = obj.__class__.__name__.lower() # object class name
132-
if objType in supported_objects:
132+
if objType in self.supported_objects:
133133
self.objectList.append(obj)
134134
else:
135135
raise Exception(f"Object type '{objType}' is not in the action's supported list.")
136136

137137
# Create placeholders for asset roles based on the "requirements"
138138
if 'roles' in actionType:
139-
for asset, caplist in actionType['roles'].items():
140-
self.assets[asset] = None # each asset role holds a None value until assigned
141-
self.requirements[asset] = {}
139+
for role, caplist in actionType['roles'].items():
140+
self.requirements[role] = {key: None for key in caplist} # each role requirment holds a dict of capabilities with values set to None for now
142141
for cap in caplist:
143-
self.requirements[asset][cap] = 0 # fill in each required metric with zero to start with?
144-
145-
142+
# self.requirements[role][cap] = {} # fill in each required capacity with {'metric': 0.0}
143+
self.requirements[role][cap] = {'area_m2': 1000, 'max_load_t': 1600} # dummy values for now, just larger than MPSV_01 values to trigger failure
144+
145+
self.assets[role] = None # placeholder for the asset assigned to this role
146+
146147
# Process dependencies
147148
if 'dependencies' in kwargs:
148149
for dep in kwargs['dependencies']:
@@ -154,26 +155,69 @@ def __init__(self, actionType, name, **kwargs):
154155

155156

156157
def addDependency(self, dep):
157-
'''Registers other action as a dependency of this one.'''
158+
'''Registers other action as a dependency of this one.
159+
160+
Inputs
161+
------
162+
dep : Action
163+
The action to be added as a dependency.
164+
'''
158165
self.dependencies[dep.name] = dep
159166
# could see if already a dependency and raise a warning if so...
160167

168+
169+
def assignObjects(self, objects):
170+
'''
171+
Adds a list of objects to the actions objects list,
172+
checking they are valid for the actions supported objects
173+
174+
Inputs
175+
------
176+
objects : list
177+
A list of FAModel objects to be added to the action.
178+
'''
179+
180+
for obj in objects:
181+
objType = obj.__class__.__name__.lower() # object class name
182+
if objType in self.supported_objects:
183+
if obj in self.objectList:
184+
print(f"Warning: Object '{obj}' is already in the action's object list.")
185+
self.objectList.append(obj)
186+
else:
187+
raise Exception(f"Object type '{objType}' is not in the action's supported list.")
188+
189+
190+
191+
# def setUpCapability(self):
192+
# # WIP: example of what needs to happen to create a metric
193+
194+
# # figure out how to assign required metrics to capabilies in the roles based on the objects
195+
# for role, caps in self.requirements.items():
196+
# for cap, metrics in caps.items():
197+
# for obj in self.objectList:
198+
# # this is for the deck_space capability
199+
# metrics = {'area_m2': obj.area, 'max_load_t': obj.mass / 1000} # / 1000 to convert kg to T
200+
# metrics.update(obj.get_capability_metrics(cap))
201+
# pass
202+
161203

162204
def checkAsset(self, role_name, asset):
163205
'''Checks if a specified asset has sufficient capabilities to fulfil
164206
a specified role in this action.
165-
'''
166-
167-
# Make sure role_name is valid for this action
168-
if not role_name in self.assets.keys():
169-
raise Exception(f"The specified role name '{role_name}' is not a named asset role in this action.")
170-
171-
for cap, req in self.requirements['role_name']:
172-
if asset.capabilities[cap] >= req: # <<< this is pseudocode. Needs to look at capability numbers! <<<
173-
pass
174-
# so far so good
207+
'''
208+
209+
for capability in self.requirements[role_name].keys():
210+
211+
if capability in asset['capabilities'].keys(): # check capability
212+
# does this work if there are no metrics in a capability? This should be possible, as not all capabilities will require a constraint.
213+
for metric in self.requirements[role_name][capability].keys(): # loop over the capacity requirements for the capability (if more than one)
214+
if self.requirements[role_name][capability][metric] > asset['capabilities'][capability][metric]: # check capacity
215+
# TODO: can we throw a message here that explains what metric is violated?
216+
return False # the asset does not have the capacity for this capability
217+
return True
218+
175219
else:
176-
return False # at least on capability is not met, so return False
220+
return False # at least one capability is not met
177221

178222
return True
179223

@@ -192,12 +236,68 @@ def assignAsset(self, role_name, asset):
192236
# Make sure role_name is valid for this action
193237
if not role_name in self.assets.keys():
194238
raise Exception(f"The specified role name '{role_name}' is not a named asset role in this action.")
195-
196-
self.assets[role_name] = asset
239+
240+
if self.checkAsset(role_name, asset):
241+
self.assets[role_name] = asset
242+
else:
243+
raise Exception(f"The asset does not have the capabilities for role '{role_name}'.")
197244

198245

199246
def calcDurationAndCost(self):
200-
pass
247+
'''The structure here is dependent on actions.yaml.
248+
TODO: finish description
249+
'''
250+
251+
print('Calculating duration and cost for action:', self.name)
252+
# print(self.type)
253+
254+
# --- Towing & Transport ---
255+
if self.type == 'tow':
256+
pass
257+
elif self.type == 'transport_components':
258+
pass
259+
260+
# --- Mooring & Anchors ---
261+
elif self.type == 'install_anchor':
262+
pass
263+
elif self.type == 'retrieve_anchor':
264+
pass
265+
elif self.type == 'load_mooring':
266+
pass
267+
elif self.type == 'lay_mooring':
268+
pass
269+
elif self.type == 'mooring_hookup':
270+
pass
271+
272+
# --- Heavy Lift & Installation ---
273+
elif self.type == 'install_wec':
274+
pass
275+
elif self.type == 'install_semisub':
276+
pass
277+
elif self.type == 'install_spar':
278+
pass
279+
elif self.type == 'install_tlp':
280+
pass
281+
elif self.type == 'install_wtg':
282+
pass
283+
284+
# --- Cable Operations ---
285+
elif self.type == 'lay_cable':
286+
pass
287+
elif self.type == 'retrieve_cable':
288+
pass
289+
elif self.type == 'lay_and_bury_cable':
290+
pass
291+
elif self.type == 'backfill_rockdump':
292+
pass
293+
294+
# --- Survey & Monitoring ---
295+
elif self.type == 'site_survey':
296+
pass
297+
elif self.type == 'monitor_installation':
298+
pass
299+
else:
300+
raise ValueError(f"Action type '{self.type}' not recognized.")
201301

202302

203303
def evaluateAssets(self, assets):
@@ -209,6 +309,8 @@ def evaluateAssets(self, assets):
209309
Each asset is a vessel or port object.
210310
211311
'''
312+
313+
# error check that assets is a dict of {role_name, asset dict}, and not just an asset dict?
212314

213315
# Assign each specified asset to its respective role
214316
for akey, aval in assets.items():

famodel/irma/actions.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,23 @@
55
# Old format: requirements and capabilities
66
# New format: roles, which lists asset roles, each with associated required capabilities
77

8+
# The code that models and checks these structures is action.calcDurationAndCost(). Structural changes here will not be reflected in the code unless changes are made there as well
9+
10+
### Example action ###
11+
12+
# example_action:
13+
# objects: [] or {} "The FAModel object types that are supported in this action"
14+
# requirements: [] "Asset types" **Unused**
15+
# roles: "the roles that assets need to fill. A way a grouping capabilities so multiple assets can be assigned to an action"
16+
# role1:
17+
# - capability 1
18+
# - capability 2
19+
# role2:
20+
# - capability 3
21+
# duration_h: 0.0 "Duration in hours"
22+
# Hs_m: 0.0 "Wave height constraints in meters"
23+
# description: <text> "A description"
24+
825
# --- Towing & Transport ---
926

1027
tow:

famodel/irma/irma.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,8 @@ def findCompatibleVessels(self):
300300
print('Creating project without RAFT\n')
301301
print(os.getcwd())
302302
# create project object
303-
project = Project(file='C:/Code/FAModel/examples/OntologySample200m_1turb.yaml', raft=False)
303+
# project = Project(file='C:/Code/FAModel/examples/OntologySample200m_1turb.yaml', raft=False) # for Windows
304+
project = Project(file='../../examples/OntologySample200m_1turb.yaml', raft=False) # for Mac
304305
# create moorpy system of the array, include cables in the system
305306
project.getMoorPyArray(cables=1)
306307
# plot in 3d, using moorpy system for the mooring and cable plots
@@ -329,6 +330,7 @@ def findCompatibleVessels(self):
329330

330331
# add and register anchor install action(s)
331332
a1 = sc.addAction('install_anchor', 'install_anchor-1', objects=[anchor])
333+
a1.evaluateAssets({'carrier' : sc.vessels["MPSV_01"]})
332334

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

0 commit comments

Comments
 (0)