def calculate_system(self, entity=None, include_subentities=True): logger.debug("FAST: Calculating for all entities") if not hasattr(self, "cache"): self.init_z3_constraints_and_vars() all_dts = [] # """ do the other things """ new_cache = translate_to_context(self.cache, z3.Context()) for influence in model.get_all_influences(entity): if self.contains_if_condition(influence): self.get_condition_change_enablers(influence, all_dts, new_cache) # updates = [up for up in get_updates(self.entity) if up.state == up._parent.current] for update in model.get_all_updates(entity): if update.state is update._parent.current: # only the currently active updates if self.contains_if_condition(update): self.get_condition_change_enablers(update, all_dts, new_cache) # TODO: check for transitions whether they can be done by time only for trans in model.get_all_transitions(entity): if trans._parent.current is trans.source: self.get_transition_time(trans, all_dts, new_cache) """ Return all behaviour change times """ return all_dts
def check_port_connections(self): """ Verify that a port has maximum one influence OR one update per state writing to it. when an influence is defined, no action can write to that port. """ all_ports = crest.get_all_ports(self.model) influences_to_target = {p: [] for p in all_ports} updates_to_target = {p: [] for p in all_ports} actions_to_target = {p: [] for p in all_ports} # fill data stores for inf in crest.get_all_influences(self.model): influences_to_target[inf.target].append(inf) for up in crest.get_all_updates(self.model): updates_to_target[up.target].append(up) for action in crest.get_all_actions(self.model): actions_to_target[action.target].append(action) for port in all_ports: assert not (len(influences_to_target[port]) > 0 and ( len(updates_to_target[port]) > 0 or len(actions_to_target[port]) > 0) ), f"There are [influences and (updates or actions)] writing to port {port._name} (entity: {port._parent._name})" assert len(influences_to_target[port]) < 2, f"There are two influences writing to {port._name}" states = [update.state for update in updates_to_target[port]] assert len(states) == len(set(states)), f"Port {port._name} (entity: {port._parent._name}) is written by multiple updates linked to the same state" transitions = [action.transition for action in actions_to_target[port]] assert len(transitions) == len(set(transitions)), f"Port {port._name} (entity: {port._parent._name}) is written by multiple actions linked to the same transition"
def check_influence_sanity(self): """Check that each influence is properly named, has a source from the "sources" of an entity and a target port that is in the "targets" of the same entity. Also verifies the signature of the influence function. """ for influence in crest.get_all_influences(self.model): assert influence._name is not None, f"There is an Influence in {influence._parent._name} ({influence._parent.__class__.__name__}) whose name is 'None'" assert influence._name != "", f"There is an Update in {influence._parent._name} ({influence._parent.__class__.__name__}) whose name is empty string" assert isinstance(influence.source, crest.Port), f"Influence {influence._name}'s source is not a crest.Port" assert influence.source in api.get_sources(influence._parent), f"Influence's source {influence.source._name} ({influence.source}) is not in the sources of entity {influence._parent._name} ({influence._parent})" assert isinstance(influence.target, crest.Port), f"Influence {influence._name}'s target is not a crest.Port" assert influence.target in api.get_targets(influence._parent), f"Influence's target {influence.target._name} ({influence.target}) is not in the targets of entity {influence._parent._name} ({influence._parent})" assert isinstance(influence.function, (crestml.LearnedFunction, types.FunctionType)), f"Influence {influence._name}'s function needs to be of type types.FunctionType or crestdsl.ml.LearnedFunction" assert len(inspect.signature(influence.function).parameters) == 1, f"An influence should not have arguments (except the input value)"
def check_objects_have_parents_and_are_not_referenced_twice(self): """ - check that ports, states, updates, influences and transitions have a parent specificaiton each. - Test that they also are only used once (i.e. they only appear once in the list) """ # logger.debug("ports:") all_objs = crest.get_all_ports(self.model) # for o in all_objs: # print(o._name, o._parent) for obj in all_objs: assert all_objs.count(obj) == 1, f"Port {obj._name} has been used multiple times" assert obj._parent is not None, f"Port {obj._name} has no parent definition" # logger.debug("states:") all_objs = crest.get_all_states(self.model) # for o in all_objs: # print(o._name, o._parent) for obj in all_objs: assert all_objs.count(obj) == 1, f"State {obj._name} has been used multiple times" assert obj._parent is not None, f"State {obj._name} has no parent definition" # logger.debug("updates:") all_objs = crest.get_all_updates(self.model) # for o in all_objs: # print(o._name, o._parent) for obj in all_objs: assert all_objs.count(obj) == 1, f"Update {obj._name} has been used multiple times" assert obj._parent is not None, f"Update {obj._name} has no parent definition" # logger.debug("influences") all_objs = crest.get_all_influences(self.model) # for o in all_objs: # print(o._name, o._parent) for obj in all_objs: assert all_objs.count(obj) == 1, f"Influence {obj._name} has been used multiple times" assert obj._parent is not None, f"Influence {obj._name} has no parent definition" # logger.debug("transitions:") all_objs = crest.get_all_transitions(self.model) # for o in all_objs: # print(o._name, o._parent) for obj in all_objs: assert all_objs.count(obj) == 1, f"Transition '{obj._name}' has been used multiple times" assert obj._parent is not None, f"Transition '{obj._name}' has no parent definition"
def init_z3_constraints_and_vars(entity, timeunit, use_integer_and_real): cache = SimpleNamespace() # create port variables for all ports cache.z3_vars = {} cache.z3_port_constraints = {} dt_var = get_z3_var(timeunit, 'dt') cache.z3_vars['dt'] = dt_var cache.z3_vars['dt'].type = timeunit for port in model.get_all_ports(entity): portname = port._name portname_with_parent = port._parent._name + "." + port._name variable = get_z3_variable(port, port._name) pre_var = get_z3_variable(port, port._name + "_0") cache.z3_vars[port] = { portname: variable, portname_with_parent: variable, portname + "_0": pre_var, portname_with_parent + "_0": pre_var, portname + ".pre": pre_var, portname_with_parent + ".pre": pre_var, } # pre_value = get_z3_value(port, port._name + "_0") # cache.z3_port_constraints[port] = pre_var == pre_value # init condition needs to be set # create entity constraints for all modifiers cache.z3_modifier_constraints = {} cache.z3_conditionchanged_constraintsets = {} for influence in model.get_all_influences(entity): constraints = get_constraints_from_modifier( influence, cache.z3_vars, use_integer_and_real=use_integer_and_real, cache=False) cache.z3_modifier_constraints[influence] = constraints # TODO: this should be nicer somehow ... # add port and constraint for the influence param z3_src = cache.z3_vars[influence.source][influence.source._name] params = SH.get_param_names(influence.function) param_key = params[0] + "_" + str(id(influence)) z3_param = get_z3_variable(influence.source, params[0], str(id(influence))) cache.z3_vars[param_key] = {params[0] + "_0": z3_param} conv = Z3ConditionChangeCalculator( cache.z3_vars, entity=influence._parent, container=influence, use_integer_and_real=use_integer_and_real) cache.z3_conditionchanged_constraintsets[influence] = ( conv.calculate_constraints(influence.function), z3_src == z3_param) for update in model.get_all_updates(entity): constraints = get_constraints_from_modifier(update, cache.z3_vars, use_integer_and_real, cache=False) cache.z3_modifier_constraints[update] = constraints conv = Z3ConditionChangeCalculator( cache.z3_vars, entity=update._parent, container=update, use_integer_and_real=use_integer_and_real) cache.z3_conditionchanged_constraintsets[update] = ( conv.calculate_constraints(update.function), []) for transition in model.get_all_transitions(entity): conv = Z3Converter(cache.z3_vars, entity=transition._parent, container=transition, use_integer_and_real=use_integer_and_real) guard_constraint = conv.to_z3(transition.guard) cache.z3_modifier_constraints[transition] = guard_constraint return cache
def get_modifier_map(root_entity, port_list, cache=True): """Creates a dict that has ports as keys and a list of influences/updates that influence those ports as values.""" logger.debug( f"creating modifier map for ports {[p._name +' (in: '+ p._parent._name+')' for p in port_list]}" ) modifier_map = {port: list() for port in port_list} map_change = True all_updates = get_all_updates(root_entity) all_influences = get_all_influences(root_entity) while map_change: map_change = False # initially we think there are no changes for port, modifiers in modifier_map.copy().items( ): # iterate over a copy, so we can modify the original list # we only look at ports that have no influences (it might be because there exist none, but that small overhead is okay for now) if len(modifiers) == 0: logger.debug( f"trying to find modifiers for port '{port._name}' of entity '{port._parent._name} ({port._parent.__class__.__name__})'" ) influences = [ inf for inf in all_influences if port == inf.target ] modifier_map[port].extend(influences) for inf in influences: logger.debug( f"'{port._name}' is modified by influence '{inf._name}'" ) # this means influences is not empty, hence we change the map (probably) map_change = True if inf.source not in modifier_map: modifier_map[inf.source] = list( ) # add an empty list, the next iteration will try to fill it updates = [ up for up in all_updates if port == up.target and up.state == up._parent.current ] modifier_map[port].extend(updates) for up in updates: # logger.debug(f"'{port._name}' is modified by update '{up._name}'") # read_ports = SH.get_read_ports_from_update(up.function, up) # +[up.target] accessed_ports = SH.get_accessed_ports(up.function, up, exclude_pre=False, cache=cache) # logger.debug(f"'{up._name}' in '{up._parent._name}' reads the following ports: {[(p._name, p._parent._name) for p in accessed_ports]}") for read_port in accessed_ports: # this means there are updates and we change the map map_change = True if read_port not in modifier_map: # logger.debug(f"adding {read_port._name} to modifier_map") modifier_map[read_port] = list( ) # add an empty list, the next iteration will try to fill it logger.debug( f"the modifier map looks like this: \n{pformat(prettify_modifier_map(modifier_map))}" ) return modifier_map
def calculate_system(self, entity=None, include_subentities=True): logger.debug("FAST: Calculating for all entities") if not hasattr(self, "cache"): self.init_z3_constraints_and_vars() all_dts = [] """ setup workers with own context for each """ job_queue = queue.Queue() num_threads = 4 thread_workers = [] for i in range(num_threads): new_cache = translate_to_context(self.cache, z3.Context()) thread_worker = threading.Thread(target=self.thread_crawler, args=(job_queue, new_cache, all_dts)) thread_worker.setDaemon(True) thread_worker.start() """ Fill queue with stuff to do """ # TODO: check for transitions whether they can be done by time only for trans in model.get_all_transitions(entity): if trans._parent.current is trans.source: job_queue.put((self.get_transition_time, trans)) for influence in model.get_all_influences(entity): if self.contains_if_condition(influence): job_queue.put((self.get_condition_change_enablers, influence)) # updates = [up for up in get_updates(self.entity) if up.state == up._parent.current] for update in model.get_all_updates(entity): if update.state is update._parent.current: # only the currently active updates if self.contains_if_condition(update): job_queue.put((self.get_condition_change_enablers, update)) """ wait for queue to finish """ job_queue.join() for tw in thread_workers: assert not tw.isAlive() # """ do the other things """ # workers = [] # for influence in model.get_all_influences(entity): # if self.contains_if_condition(influence): # ctx_i = z3.Context() # new_cache = translate_to_context(self.cache, ctx_i) # worker = threading.Thread(target=self.get_condition_change_enablers, args=(influence, all_dts, new_cache)) # worker.start() # workers.append(worker) # worker.join() # # # # updates = [up for up in get_updates(self.entity) if up.state == up._parent.current] # for update in model.get_all_updates(entity): # if update.state is update._parent.current: # only the currently active updates # if self.contains_if_condition(update): # ctx_i = z3.Context() # new_cache = translate_to_context(self.cache, ctx_i) # worker = threading.Thread(target=self.get_condition_change_enablers, args=(update, all_dts, new_cache)) # worker.start() # workers.append(worker) # worker.join() # # # TODO: check for transitions whether they can be done by time only # for trans in model.get_all_transitions(entity): # if trans._parent.current is trans.source: # ctx_i = z3.Context() # new_cache = translate_to_context(self.cache, ctx_i) # worker = threading.Thread(target=self.get_transition_time, args=(trans, all_dts, new_cache)) # worker.start() # workers.append(worker) # worker.join() # print(f"Working on {len(workers)} threads") # for worker in workers: # worker.join() """ Return all behaviour change times """ return all_dts