# Copyright 2021-2023 AIPlan4EU project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from typing import Optional, Union
from unified_planning.model.expression import NumericExpression
from unified_planning.model.fnode import FNode
from unified_planning.environment import get_environment, Environment
from unified_planning.exceptions import UPProblemDefinitionError
from unified_planning.model import (
Timepoint,
Timing,
TimepointKind,
Fluent,
NumericConstant,
)
import unified_planning as up
from unified_planning.model.scheduling.chronicle import Chronicle
[docs]
class Activity(Chronicle):
"""Activity is essentially an interval with start and end timepoint that facilitates defining constraints in the
associated :class:`SchedulingProblem`"""
def __init__(
self, name: str, duration: int = 1, _env: Optional[Environment] = None
):
Chronicle.__init__(self, name, _env=_env)
self._start = Timepoint(TimepointKind.START, container=name)
self._end = Timepoint(TimepointKind.END, container=name)
self.set_fixed_duration(duration)
@property
def start(self) -> Timepoint:
"""Returns a reference to the start time of this activity."""
return self._start
@property
def end(self) -> Timepoint:
"""Returns a reference to the start time of this activity."""
return self._end
@property
def duration(self) -> "up.model.timing.DurationInterval":
"""Returns the `activity` `duration interval`."""
return self._duration
[docs]
def set_fixed_duration(self, value: "up.model.expression.NumericExpression"):
"""
Sets the `duration interval` for this `activity` as the interval `[value, value]`.
:param value: The `value` set as both edges of this `action's duration`.
"""
(value_exp,) = self._environment.expression_manager.auto_promote(value)
self._set_duration_constraint(up.model.timing.FixedDuration(value_exp))
[docs]
def set_duration_bounds(
self,
lower: "up.model.expression.NumericExpression",
upper: "up.model.expression.NumericExpression",
):
"""
Sets the `duration interval` for this `activity` as the interval `[lower, upper]`.
:param lower: The value set as the lower edge of this `action's duration`.
:param upper: The value set as the upper edge of this `action's duration`.
"""
lower_exp, upper_exp = self._environment.expression_manager.auto_promote(
lower, upper
)
self._set_duration_constraint(
up.model.timing.ClosedDurationInterval(lower_exp, upper_exp)
)
def _set_duration_constraint(self, duration: "up.model.timing.DurationInterval"):
"""
Sets the `duration interval` for this `action`.
:param duration: The new `duration interval` of this `action`.
"""
lower, upper = duration.lower, duration.upper
tlower = self._environment.type_checker.get_type(lower)
tupper = self._environment.type_checker.get_type(upper)
assert tlower.is_int_type() or tlower.is_real_type()
assert tupper.is_int_type() or tupper.is_real_type()
if (
lower.is_constant()
and upper.is_constant()
and (
upper.constant_value() < lower.constant_value()
or (
upper.constant_value() == lower.constant_value()
and (duration.is_left_open() or duration.is_right_open())
)
)
):
raise UPProblemDefinitionError(
f"{duration} is an empty interval duration of action: {self.name}."
)
self._duration = duration
[docs]
def uses(self, resource: Union[Fluent, FNode], amount: NumericExpression = 1):
"""Asserts that the activity borrows a given amount (1 by default) of the resource.
The borrowed resources will be reusable by another activity at the time epoch immediately
succeeding the activity end.
:param resource: Fluent expression that denotes the resource taht is used.
:param amount: Quantity of the resource that is borrowed over the course of the activity.
"""
self.add_decrease_effect(self.start, resource, amount)
self.add_increase_effect(self.end, resource, amount)
[docs]
def add_release_date(self, date: NumericExpression):
"""Sets the earliest date at which the activity can be started."""
self.add_constraint(get_environment().expression_manager.LE(date, self.start))
[docs]
def add_deadline(self, date: NumericExpression):
"""Sets the latest date at which the activity might end."""
self.add_constraint(get_environment().expression_manager.LE(self.end, date))
[docs]
def clone(self):
"""Generates a copy of this activity."""
new = Activity(self.name, _env=self._environment)
self._clone_to(new)
new._duration = self._duration
return new
def __hash__(self):
return Chronicle.__hash__(self) + hash(self._duration)
def __eq__(self, other):
return (
isinstance(other, Activity)
and Chronicle.__eq__(self, other)
and self._duration == other._duration
)