Generate functions for all API usecases for better static code features. See #2693.

This commit is contained in:
Dion Moult
2024-05-06 14:35:39 +10:00
parent 10f894e2ea
commit d11ec67129
330 changed files with 13283 additions and 13751 deletions
@@ -23,35 +23,38 @@ import ifcopenshell.util.date
import ifcopenshell.util.sequence
def recalculate_schedule(file, work_schedule=None) -> None:
"""Calculate the critical path and floats for a work schedule
This implements critical path analysis, using the forward pass and
backward pass method. When run, any tasks that have no float will be
marked as critical, and both the total and free floats will be
populated for all task times.
Cyclical relationships are detected and will result in a recursion
error.
:param work_schedule: The IfcWorkSchedule to perform the calculation on.
:type work_schedule: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
.. code:: python
# See the example for ifcopenshell.api.sequence.cascade_schedule for
# details of how to set up a basic set of tasks and calculate the
# critical path. Typically cascade_schedule is run prior to ensure
# that dates are correct.
"""
usecase = Usecase()
usecase.file = file
usecase.settings = {"work_schedule": work_schedule}
return usecase.execute()
class Usecase:
def __init__(self, file, work_schedule=None):
"""Calculate the critical path and floats for a work schedule
This implements critical path analysis, using the forward pass and
backward pass method. When run, any tasks that have no float will be
marked as critical, and both the total and free floats will be
populated for all task times.
Cyclical relationships are detected and will result in a recursion
error.
:param work_schedule: The IfcWorkSchedule to perform the calculation on.
:type work_schedule: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
.. code:: python
# See the example for ifcopenshell.api.sequence.cascade_schedule for
# details of how to set up a basic set of tasks and calculate the
# critical path. Typically cascade_schedule is run prior to ensure
# that dates are correct.
"""
self.file = file
self.settings = {"work_schedule": work_schedule}
def execute(self):
# The method implemented is the same as shown here:
# https://www.youtube.com/watch?v=qTErIV6OqLg
@@ -84,9 +87,7 @@ class Usecase:
break # We have an infinite loop due to a cyclic graph
if is_cyclic:
raise RecursionError(
"Task graph is cyclic and so critical path method cannot be performed."
)
raise RecursionError("Task graph is cyclic and so critical path method cannot be performed.")
return
self.pending_nodes = set(self.g.nodes)
@@ -112,9 +113,7 @@ class Usecase:
self.g = nx.DiGraph()
self.edges = []
self.g.add_node("start", duration=0, duration_type="ELAPSEDTIME", calendar=None)
self.g.add_node(
"finish", duration=0, duration_type="ELAPSEDTIME", calendar=None
)
self.g.add_node("finish", duration=0, duration_type="ELAPSEDTIME", calendar=None)
for rel in self.settings["work_schedule"].Controls:
for related_object in rel.RelatedObjects:
if not related_object.is_a("IfcTask"):
@@ -129,9 +128,7 @@ class Usecase:
return
if task.TaskTime and task.TaskTime.ScheduleDuration:
duration = ifcopenshell.util.date.ifc2datetime(
task.TaskTime.ScheduleDuration
).days
duration = ifcopenshell.util.date.ifc2datetime(task.TaskTime.ScheduleDuration).days
duration_type = task.TaskTime.DurationType
else:
duration = 0
@@ -150,11 +147,11 @@ class Usecase:
rel.RelatingProcess.id(),
task.id(),
{
"lag_time": 0
if not rel.TimeLag
else ifcopenshell.util.date.ifc2datetime(
rel.TimeLag.LagValue.wrappedValue
).days,
"lag_time": (
0
if not rel.TimeLag
else ifcopenshell.util.date.ifc2datetime(rel.TimeLag.LagValue.wrappedValue).days
),
"type": self.sequence_type_map[rel.SequenceType],
},
)
@@ -162,16 +159,20 @@ class Usecase:
]
)
predecessor_types = [rel.SequenceType for rel in ifcopenshell.util.sequence.get_sequence_assignment(task, "predecessor")]
successor_types = [rel.SequenceType for rel in ifcopenshell.util.sequence.get_sequence_assignment(task, "successor")]
predecessor_types = [
rel.SequenceType for rel in ifcopenshell.util.sequence.get_sequence_assignment(task, "predecessor")
]
successor_types = [
rel.SequenceType for rel in ifcopenshell.util.sequence.get_sequence_assignment(task, "successor")
]
if not predecessor_types:
self.edges.append(("start", task.id(), {"lag_time": 0, "type": "FS"}))
if task.TaskTime and task.TaskTime.ScheduleStart:
self.start_dates.append(
ifcopenshell.util.date.ifc2datetime(task.TaskTime.ScheduleStart)
)
self.g.nodes[task.id()]["early_start"] = ifcopenshell.util.date.ifc2datetime(task.TaskTime.ScheduleStart) # we assume this task is constrained to start on this date
self.start_dates.append(ifcopenshell.util.date.ifc2datetime(task.TaskTime.ScheduleStart))
self.g.nodes[task.id()]["early_start"] = ifcopenshell.util.date.ifc2datetime(
task.TaskTime.ScheduleStart
) # we assume this task is constrained to start on this date
if not successor_types:
self.edges.append((task.id(), "finish", {"lag_time": 0, "type": "FF"}))
@@ -188,25 +189,13 @@ class Usecase:
self.file,
task_time=task.TaskTime,
attributes={
"FreeFloat": ifcopenshell.util.date.datetime2ifc(
data["free_float"], "IfcDuration"
),
"TotalFloat": ifcopenshell.util.date.datetime2ifc(
data["total_float"], "IfcDuration"
),
"FreeFloat": ifcopenshell.util.date.datetime2ifc(data["free_float"], "IfcDuration"),
"TotalFloat": ifcopenshell.util.date.datetime2ifc(data["total_float"], "IfcDuration"),
"IsCritical": data["total_float"].days == 0,
"EarlyStart": ifcopenshell.util.date.datetime2ifc(
data["early_start"], "IfcDateTime"
),
"EarlyFinish": ifcopenshell.util.date.datetime2ifc(
data["early_finish"], "IfcDateTime"
),
"LateStart": ifcopenshell.util.date.datetime2ifc(
data["late_start"], "IfcDateTime"
),
"LateFinish": ifcopenshell.util.date.datetime2ifc(
data["late_finish"], "IfcDateTime"
),
"EarlyStart": ifcopenshell.util.date.datetime2ifc(data["early_start"], "IfcDateTime"),
"EarlyFinish": ifcopenshell.util.date.datetime2ifc(data["early_finish"], "IfcDateTime"),
"LateStart": ifcopenshell.util.date.datetime2ifc(data["late_start"], "IfcDateTime"),
"LateFinish": ifcopenshell.util.date.datetime2ifc(data["late_finish"], "IfcDateTime"),
},
)
@@ -246,11 +235,7 @@ class Usecase:
if edge["lag_time"]:
days += edge["lag_time"]
if days:
starts.append(
datetime.datetime.combine(
self.offset_date(finish, days, data), datetime.time(9)
)
)
starts.append(datetime.datetime.combine(self.offset_date(finish, days, data), datetime.time(9)))
starts.append(
datetime.datetime.combine(
self.offset_date(finish, days, predecessor_data),
@@ -265,9 +250,7 @@ class Usecase:
return
if edge["lag_time"]:
starts.append(self.offset_date(start, edge["lag_time"], data))
starts.append(
self.offset_date(start, edge["lag_time"], predecessor_data)
)
starts.append(self.offset_date(start, edge["lag_time"], predecessor_data))
else:
starts.append(start)
elif edge["type"] == "FF":
@@ -275,12 +258,8 @@ class Usecase:
if finish is None:
return
if edge["lag_time"]:
finishes.append(
self.offset_date(finish, edge["lag_time"], data)
)
finishes.append(
self.offset_date(finish, edge["lag_time"], predecessor_data)
)
finishes.append(self.offset_date(finish, edge["lag_time"], data))
finishes.append(self.offset_date(finish, edge["lag_time"], predecessor_data))
else:
finishes.append(finish)
elif edge["type"] == "SF":
@@ -292,9 +271,7 @@ class Usecase:
days += edge["lag_time"]
if days or edge["lag_time"]:
finishes.append(
datetime.datetime.combine(
self.offset_date(start, days, data), datetime.time(17)
)
datetime.datetime.combine(self.offset_date(start, days, data), datetime.time(17))
)
finishes.append(
datetime.datetime.combine(
@@ -317,9 +294,7 @@ class Usecase:
if potential_finish > data["early_finish"]:
data["early_finish"] = potential_finish
else:
data[
"early_start"
] = ifcopenshell.util.sequence.get_start_or_finish_date(
data["early_start"] = ifcopenshell.util.sequence.get_start_or_finish_date(
data["early_finish"],
datetime.timedelta(days=data["duration"]),
data["duration_type"],
@@ -375,9 +350,7 @@ class Usecase:
days += edge["lag_time"]
if days or edge["lag_time"]:
finishes.append(
datetime.datetime.combine(
self.offset_date(start, -days, data), datetime.time(17)
)
datetime.datetime.combine(self.offset_date(start, -days, data), datetime.time(17))
)
finishes.append(
datetime.datetime.combine(
@@ -402,9 +375,7 @@ class Usecase:
return
if edge["lag_time"]:
starts.append(self.offset_date(start, -edge["lag_time"], data))
starts.append(
self.offset_date(start, -edge["lag_time"], successor_data)
)
starts.append(self.offset_date(start, -edge["lag_time"], successor_data))
else:
starts.append(start)
free_floats.append(
@@ -421,12 +392,8 @@ class Usecase:
if finish is None:
return
if edge["lag_time"]:
finishes.append(
self.offset_date(finish, -edge["lag_time"], data)
)
finishes.append(
self.offset_date(finish, -edge["lag_time"], successor_data)
)
finishes.append(self.offset_date(finish, -edge["lag_time"], data))
finishes.append(self.offset_date(finish, -edge["lag_time"], successor_data))
else:
finishes.append(finish)
free_floats.append(
@@ -447,9 +414,7 @@ class Usecase:
days += edge["lag_time"]
if days:
starts.append(
datetime.datetime.combine(
self.offset_date(finish, -days, data), datetime.time(9)
)
datetime.datetime.combine(self.offset_date(finish, -days, data), datetime.time(9))
)
starts.append(
datetime.datetime.combine(
@@ -471,13 +436,8 @@ class Usecase:
if starts and finishes:
data["late_start"] = min(starts)
data["late_finish"] = min(finishes)
if (
self.offset_date(data["late_start"], data["duration"], data)
< data["late_finish"]
):
data[
"late_finish"
] = ifcopenshell.util.sequence.get_start_or_finish_date(
if self.offset_date(data["late_start"], data["duration"], data) < data["late_finish"]:
data["late_finish"] = ifcopenshell.util.sequence.get_start_or_finish_date(
data["late_start"],
datetime.timedelta(days=data["duration"]),
data["duration_type"],
@@ -485,9 +445,7 @@ class Usecase:
date_type="FINISH",
)
else:
data[
"late_start"
] = ifcopenshell.util.sequence.get_start_or_finish_date(
data["late_start"] = ifcopenshell.util.sequence.get_start_or_finish_date(
data["late_finish"],
datetime.timedelta(days=data["duration"]),
data["duration_type"],
@@ -528,9 +486,7 @@ class Usecase:
data["total_float"] = data["late_finish"] - data["early_finish"]
# If the float is within the span of a single day, it may show as a 8 hours
if data["total_float"].seconds == 60 * 60 * 8:
data["total_float"] = datetime.timedelta(
days=data["total_float"].days + 1
)
data["total_float"] = datetime.timedelta(days=data["total_float"].days + 1)
data["free_float"] = min(free_floats) if free_floats else None
# If the float is within the span of a single day, it may show as a 8 hours