# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

# Recommendation: keep the logging config like this, otherwise might be flooded with unnecessary information
import logging
logging.basicConfig(level=logging.WARNING)

import crestdsl.model as crest
from crestdsl.simulation import Simulator
from crestdsl.ui import plot


# + code_folding=[]
class Resources(object):
    electricity = crest.Resource("Watt", crest.REAL)
    switch = crest.Resource("switch", ["on", "off"])
    light = crest.Resource("Lumen", crest.INTEGER)
    counter = crest.Resource("Count", crest.INTEGER)
    time = crest.Resource("minutes", crest.REAL)
    celsius = crest.Resource("Celsius", crest.REAL)
    fahrenheit = crest.Resource("Fahrenheit", crest.REAL)

    real = crest.Resource("real", crest.REAL)


# + code_folding=[0]
class LightElement(crest.Entity):
    """This is a definition of a new Entity type. It derives from CREST's Entity base class."""
    
    """we define ports - each has a resource and an initial value"""
    electricity = crest.Input(resource=Resources.electricity, value=0)
    light = crest.Output(resource=Resources.light, value=0)
    
    """automaton states - don't forget to specify one as the current state"""
    on = crest.State()
    off = current = crest.State()
    
    """transitions and guards (as lambdas)"""
    off_to_on = crest.Transition(source=off, target=on, guard=(lambda self: self.electricity.value >= 100))
    on_to_off = crest.Transition(source=on, target=off, guard=(lambda self: self.electricity.value < 100))
    
    """
    update functions. They are related to a state, define the port to be updated and return the port's new value
    Remember that updates need two parameters: self and dt.
    """
    @crest.update(state=on, target=light)
    def set_light_on(self, dt=0):
        return 800

    @crest.update(state=off, target=light)
    def set_light_off(self, dt=0):
        return 0


# + code_folding=[0]
class HeatElement(crest.Entity):
    """ Ports """
    electricity = crest.Input(resource=Resources.electricity, value=0)
    switch = crest.Input(resource=Resources.switch, value="off")  # the heatelement has its own switch
    heat = crest.Output(resource=Resources.celsius, value=0)      # and produces a celsius value (i.e. the temperature increase underneath the lamp)
    
    internal_temp = crest.Local(resource=Resources.celsius, value=0)
    
    on = crest.State()
    off = current = crest.State()
    
    # transitions and guards (as lambdas)
    turn_on = crest.Transition(source=off, target=on, guard=(lambda self: self.switch.value == "on" and self.internal_temp.value < 130))
    switch_off = crest.Transition(source=on, target=off, guard=(lambda self: self.switch.value == "off"))
    time_out = crest.Transition(source=on, target=off, guard=(lambda self: self.internal_temp.value >= 150))

    """Update"""
    @crest.update(state=on, target=heat)
    def heat_output(self, dt):
        return self.electricity.value / 100

    @crest.update(state=off, target=heat)
    def heat_output_off(self, dt):
        return 0
    
    @crest.update(state=off, target=internal_temp)
    def reduce_timeout(self, dt):
        calculated_temp = self.internal_temp.value - 5 * dt
        if calculated_temp <= 0:
            return 0
        else:
            return calculated_temp
    
    @crest.update(state=on, target=internal_temp)
    def increase_timeout(self, dt):
        calculated_temp = self.internal_temp.value + self.electricity.value / 10 * dt
        if calculated_temp >= 150:
            return 150
        else:
            return calculated_temp
    
he = HeatElement()
sim = Simulator(he)
he.switch.value = "on"
he.electricity.value = 100
sim.stabilise()
sim.advance(16)
# he.switch.value = "off"
# sim.advance(5)
sim.plot()


# + code_folding=[0]
class Adder(crest.LogicalEntity):
    heat_in = crest.Input(resource=Resources.celsius, value=0)
    room_temp_in = crest.Input(resource=Resources.celsius, value=22)
    temperature = crest.Output(resource=Resources.celsius, value=22)
    
    state = current = crest.State()
    @crest.update(state=state, target=temperature)
    def add(self, dt):
        return self.heat_in.value + self.room_temp_in.value
    
plot(Adder())


# + code_folding=[]
class GrowLamp(crest.Entity):
    
    """ - - - - - - - PORTS - - - - - - - - - - """
    electricity = crest.Input(resource=Resources.electricity, value=0)
    switch = crest.Input(resource=Resources.switch, value="off")
    heat_switch = crest.Input(resource=Resources.switch, value="on")
    room_temperature = crest.Input(resource=Resources.fahrenheit, value=71.6)
    
    light = crest.Output(resource=Resources.light, value=3.1415*1000) # note that these are bogus values for now
    temperature = crest.Output(resource=Resources.celsius, value=42) # yes, nonsense..., they are updated when simulated
    
    on_time = crest.Local(resource=Resources.time, value=0)
    on_count = crest.Local(resource=Resources.counter, value=0)
    
    """ - - - - - - - SUBENTITIES - - - - - - - - - - """
    lightelement = LightElement()
    heatelement = HeatElement()
    adder = Adder()
    
    
    """ - - - - - - - INFLUENCES - - - - - - - - - - """
    """
    Influences specify a source port and a target port. 
    They are always executed, independent of the automaton's state.
    Since they are called directly with the source-port's value, a self-parameter is not necessary.
    """
    @crest.influence(source=room_temperature, target=adder.room_temp_in)
    def fahrenheit_to_celsius(value):
        return (value - 32) * 5 / 9
    
    # we can also define updates and influences with lambda functions... 
    heat_to_add = crest.Influence(source=heatelement.heat, target=adder.heat_in, function=(lambda val: val))
    
    # if the lambda function doesn't do anything (like the one above) we can omit it entirely...
    add_to_temp           = crest.Influence(source=adder.temperature, target=temperature)
    light_to_light        = crest.Influence(source=lightelement.light, target=light)
    heat_switch_influence = crest.Influence(source=heat_switch, target=heatelement.switch)
    
    
    """ - - - - - - - STATES & TRANSITIONS - - - - - - - - - - """
    on = crest.State()
    off = current = crest.State()
    error = crest.State()
    
    off_to_on = crest.Transition(source=off, target=on, guard=(lambda self: self.switch.value == "on" and self.electricity.value >= 100))
    on_to_off = crest.Transition(source=on, target=off, guard=(lambda self: self.switch.value == "off" or self.electricity.value < 100))
    
    # transition to error state if the lamp ran for more than 1000.5 time units
    @crest.transition(source=on, target=error)
    def to_error(self):
        """More complex transitions can be defined as a function. We can use variables and calculations"""
        timeout = self.on_time.value >= 1000.5
        heat_is_on = self.heatelement.switch.value == "on"
        return timeout and heat_is_on
    
    """ - - - - - - - UPDATES - - - - - - - - - - """
    #crest. LAMP is OFF or ERROR
    @crest.update(state=[off, error], target=lightelement.electricity)
    def update_light_elec_off(self, dt):
        # no electricity
        return 0

    @crest.update(state=[off, error], target=heatelement.electricity)
    def update_heat_elec_off(self, dt):
        # no electricity
        return 0
    
    @crest.update(state=on, target=lightelement.electricity)
    def update_light_elec_on(self, dt):
        # the lightelement gets the first 100Watt
        return 100
    
    @crest.update(state=on, target=heatelement.electricity)
    def update_heat_elec_on(self, dt):
        # the heatelement gets the rest
        return self.electricity.value - 100
        
    @crest.update(state=on, target=on_time)
    def update_time(self, dt):
        # also update the on_time so we know whether we overheat
        return self.on_time.value + dt
        
    """ - - - - - - - ACTIONS - - - - - - - - - - """
    # let's add an action that counts the number of times we switch to state "on"
    @crest.action(transition=off_to_on, target=on_count)
    def count_switching_on(self):
        """
        Actions are functions that are executed when the related transition is fired.
        Note that actions do not have a dt.
        """
        return self.on_count.value + 1

# create an instance!
gl = GrowLamp()
plot(gl)

StarcoderdataJupyterScriptsDedupFiltered