diff --git a/.gitmodules b/.gitmodules index fe58fbce..0f1d640e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,4 @@ [submodule "Ecdar-ProtoBuf"] path = Ecdar-ProtoBuf - url = ../Ecdar-ProtoBuf.git + url = https://github.com/Ecdar/Ecdar-ProtoBuf.git + branch = futureproof2 diff --git a/Ecdar-ProtoBuf b/Ecdar-ProtoBuf index e42e35db..ae8364e6 160000 --- a/Ecdar-ProtoBuf +++ b/Ecdar-ProtoBuf @@ -1 +1 @@ -Subproject commit e42e35db63efacd7ab42aecd17c9a52b291c7be9 +Subproject commit ae8364e6c8942a5cfde24adec779ca87d4431e4a diff --git a/benches/simulation_bench.rs b/benches/simulation_bench.rs index 0c3bae49..a7ad26a0 100644 --- a/benches/simulation_bench.rs +++ b/benches/simulation_bench.rs @@ -20,13 +20,15 @@ fn create_step_request( last_response: &SimulationStartRequest, ) -> SimulationStepRequest { let cache = ModelCache::default(); - helper::create_step_request( + helper::create_step_requests( component_names, components_path, composition, ConcreteEcdarBackend::handle_start_simulation(last_response.clone(), cache) .map(Response::new), ) + .next() + .unwrap() } fn start_simulation(c: &mut Criterion, id: &str, request: SimulationStartRequest) { diff --git a/benches/threadpool_bench.rs b/benches/threadpool_bench.rs index 277bf559..298fb002 100644 --- a/benches/threadpool_bench.rs +++ b/benches/threadpool_bench.rs @@ -55,7 +55,6 @@ fn create_query_request(json: &[String], query: &str, hash: u32) -> Request &Component { if let Some(component) = self.loaded_components.get(component_name) { + assert_eq!(component_name, component.get_name()); component } else { panic!("The component '{}' could not be retrieved", component_name); @@ -212,6 +213,7 @@ impl ComponentLoader for JsonProjectLoader { } if let Some(component) = self.loaded_components.get(component_name) { + assert_eq!(component_name, component.get_name()); component } else { panic!("The component '{}' could not be retrieved", component_name); @@ -292,6 +294,7 @@ pub struct XmlProjectLoader { impl ComponentLoader for XmlProjectLoader { fn get_component(&mut self, component_name: &str) -> &Component { if let Some(component) = self.loaded_components.get(component_name) { + assert_eq!(component_name, component.get_name()); component } else { panic!("The component '{}' could not be retrieved", component_name); diff --git a/src/DataReader/mod.rs b/src/DataReader/mod.rs index ab2eff35..1fddfa0e 100644 --- a/src/DataReader/mod.rs +++ b/src/DataReader/mod.rs @@ -5,6 +5,5 @@ pub mod parse_edge; pub mod parse_invariant; pub mod parse_queries; pub mod proto_reader; -pub mod proto_writer; pub mod serialization; pub mod xml_parser; diff --git a/src/DataReader/parse_edge.rs b/src/DataReader/parse_edge.rs index 1a1ee12e..14c2c157 100644 --- a/src/DataReader/parse_edge.rs +++ b/src/DataReader/parse_edge.rs @@ -64,7 +64,7 @@ impl Update { } } -pub fn parse(edge_attribute_str: &str) -> Result> { +pub fn parse(edge_attribute_str: &str) -> Result>> { let mut pairs = EdgeParser::parse(Rule::edgeAttribute, edge_attribute_str) .unwrap_or_else(|e| panic!("Could not parse as rule with error: {}", e)); let pair = pairs.next().unwrap(); diff --git a/src/DataReader/parse_invariant.rs b/src/DataReader/parse_invariant.rs index cfb6880b..04d27a3b 100644 --- a/src/DataReader/parse_invariant.rs +++ b/src/DataReader/parse_invariant.rs @@ -9,7 +9,7 @@ use pest::Parser; #[grammar = "DataReader/grammars/invariant_grammar.pest"] pub struct InvariantParser; -pub fn parse(edge_attribute_str: &str) -> Result> { +pub fn parse(edge_attribute_str: &str) -> Result>> { let mut pairs = InvariantParser::parse(Rule::invariant, edge_attribute_str) .unwrap_or_else(|e| panic!("Could not parse as rule with error: {}", e)); let pair = pairs.next().unwrap(); diff --git a/src/DataReader/proto_reader.rs b/src/DataReader/proto_reader.rs index 05bf908f..481813f7 100644 --- a/src/DataReader/proto_reader.rs +++ b/src/DataReader/proto_reader.rs @@ -1,19 +1,32 @@ +use std::collections::HashMap; +use std::convert::TryInto; + use edbm::util::constraints::{Conjunction, Constraint, Disjunction, Inequality, RawInequality}; use edbm::zones::OwnedFederation; -use crate::component::{Component, Edge, State}; +use crate::component::{Component, Declarations, State}; use crate::ProtobufServer::services::{ - ComponentClock as ProtoComponentClock, ComponentsInfo, Conjunction as ProtoConjunction, - Constraint as ProtoConstraint, Decision as ProtoDecision, Disjunction as ProtoDisjunction, - Edge as ProtoEdge, Federation as ProtoFederation, LocationTuple as ProtoLocationTuple, + clock::Clock as ClockEnum, Clock as ProtoClock, ComponentsInfo, Constraint as ProtoConstraint, + Decision as ProtoDecision, Disjunction as ProtoDisjunction, LocationTree as ProtoLocationTree, SimulationInfo, State as ProtoState, }; use crate::Simulation::decision::Decision; +use crate::System::specifics::SpecificLocation; use crate::TransitionSystems::transition_system::component_loader_to_transition_system; -use crate::TransitionSystems::{LocationID, LocationTuple, TransitionSystemPtr}; +use crate::TransitionSystems::{LocationTree, TransitionSystemPtr}; use super::component_loader::{parse_components_if_some, ComponentContainer}; +/// Borrows a [`ComponentsInfo`] and returns the corresponding [`Vec`] of [`Component`]s. +pub fn components_info_to_components(components_info: &ComponentsInfo) -> Vec { + components_info + .components + .iter() + .flat_map(parse_components_if_some) + .flatten() + .collect() +} + /// Borrows a [`SimulationInfo`] and returns the corresponding [`TransitionsSystemPtr`]. /// /// # Panics @@ -31,16 +44,6 @@ pub fn simulation_info_to_transition_system( component_loader_to_transition_system(&mut component_container, &composition) } -/// Borrows a [`ComponentsInfo`] and returns the corresponding [`Vec`] of [`Component`]s. -pub fn components_info_to_components(components_info: &ComponentsInfo) -> Vec { - components_info - .components - .iter() - .flat_map(parse_components_if_some) - .flatten() - .collect() -} - /// Consumes a [`ProtoDecision`] and the borrows the [`TransitionsSystemPtr`] it belongs to and returns the corresponding [`Decision`]. /// /// # Panics @@ -50,15 +53,21 @@ pub fn components_info_to_components(components_info: &ComponentsInfo) -> Vec, ) -> Decision { let proto_state: ProtoState = proto_decision.source.unwrap(); let state = proto_state_to_state(proto_state, system); - let proto_edge: ProtoEdge = proto_decision.edge.unwrap(); - let decided = proto_edge_to_edge(proto_edge, components); + let next_proto_state = proto_decision.destination.unwrap(); + let next_state = proto_state_to_state(next_proto_state, system); - Decision::new(state, decided) + let action = proto_decision.action; + + Decision { + state, + action, + transition: None, + next_state, + } } /// Consumes a [`ProtoState`] and the borrows the [`TransitionsSystemPtr`] it belongs to and returns the corresponding [`State`]. @@ -66,208 +75,180 @@ pub fn proto_decision_to_decision( /// # Panics /// If: /// - `state.federation` is `None`. -/// - `state.location_tuple` is `None`. +/// - `state.location_tree` is `None`. pub fn proto_state_to_state(state: ProtoState, system: &TransitionSystemPtr) -> State { - let proto_federation: ProtoFederation = state.federation.unwrap(); - let federation: OwnedFederation = - proto_federation_to_owned_federation(proto_federation, system); + let proto_zone: ProtoDisjunction = state.zone.unwrap(); + let federation: OwnedFederation = proto_zone_to_owned_federation(proto_zone, system); - let proto_location_tuple: ProtoLocationTuple = state.location_tuple.unwrap(); - let location_tuple = - proto_location_tuple_to_location_tuple(&proto_location_tuple, system).unwrap(); + let proto_location_tree: ProtoLocationTree = state.location_tree.unwrap(); + let location_tree = proto_location_tree_to_location_tree(proto_location_tree, system); - State::create(location_tuple, federation) + // Ensure that the invariants are applied to the state + let federation = location_tree.apply_invariants(federation); + + State::create(location_tree, federation) } -fn proto_location_tuple_to_location_tuple( - location_tuple: &ProtoLocationTuple, +fn proto_location_tree_to_location_tree( + location_tree: ProtoLocationTree, system: &TransitionSystemPtr, -) -> Option { - let id_looking_for: Vec = location_tuple - .locations - .iter() - .map(|l| LocationID::Simple { - location_id: l.id.to_string(), - component_id: l - .specific_component - .as_ref() - .map(|c| c.component_name.to_string()), - }) - .collect(); - - system - .get_all_locations() - .into_iter() - .map(|tuple| (tuple.id.clone(), tuple)) - .map(|(id, tuple)| (id.inorder_vec_tranform(), tuple)) - .find(|(id, _)| id.iter().eq(id_looking_for.iter())) - .map(|(_, tuple)| tuple) -} +) -> LocationTree { + let target: SpecificLocation = location_tree.into(); -fn proto_edge_to_edge(proto_edge: ProtoEdge, components: Vec) -> Edge { - components - .into_iter() - .map(|c| c.get_edges().to_owned()) - .reduce(|acc, es| acc.into_iter().chain(es.into_iter()).collect()) - .unwrap() - .into_iter() - .find(|e| e.id == proto_edge.id) - .unwrap() + system.construct_location_tree(target).unwrap() } fn proto_constraint_to_constraint( proto_constraint: ProtoConstraint, - system: &TransitionSystemPtr, + map: &HashMap, ) -> Constraint { - fn determine_index(clock: ProtoComponentClock, system: &TransitionSystemPtr) -> usize { - if clock.clock_name == "0" && clock.specific_component.is_none() { - 0 - } else { - system - .clock_name_and_component_to_index( - &clock.clock_name, - &clock.specific_component.unwrap().component_name, - ) - .unwrap() + fn determine_index(clock: ProtoClock, map: &HashMap) -> usize { + match clock.clock.unwrap() { + ClockEnum::ComponentClock(clock) => { + let comp = clock.component_instance.as_ref().unwrap(); + let (name, decl) = map.get(&comp.component_index).unwrap(); + assert_eq!(name, &comp.component_name); + *decl.get_clock_index_by_name(&clock.clock_name).unwrap() + } + ClockEnum::SystemClock(sc) => sc.clock_index.try_into().unwrap(), + ClockEnum::ZeroClock(_) => 0, } } let x_clock = proto_constraint.x.unwrap(); - let i = determine_index(x_clock, system); + let i = determine_index(x_clock, map); let y_clock = proto_constraint.y.unwrap(); - let j = determine_index(y_clock, system); + let j = determine_index(y_clock, map); let inequality = match proto_constraint.strict { true => Inequality::LS(proto_constraint.c), false => Inequality::LE(proto_constraint.c), }; - let ineq: RawInequality = RawInequality::from_inequality(&inequality); - Constraint::new(i, j, ineq) + Constraint::new(i, j, RawInequality::from_inequality(&inequality)) } -fn proto_federation_to_owned_federation( - proto_federation: ProtoFederation, +fn proto_zone_to_owned_federation( + proto_zone: ProtoDisjunction, system: &TransitionSystemPtr, ) -> OwnedFederation { - let proto_disjunction: ProtoDisjunction = proto_federation.disjunction.unwrap(); + // Get the vector of conjunctions from the proto + let proto_conjunctions = proto_zone.conjunctions; - let proto_conjunctions: Vec = proto_disjunction.conjunctions; - let proto_constraints: Vec> = proto_conjunctions + // Generate map from component index to declarations (include component name for sanity check) + let infos = system.comp_infos(); + let map = infos .iter() - .map(|conjunction| conjunction.constraints.clone()) - .collect(); - - let mut constraints: Vec> = Vec::new(); + .map(|c| (c.id, (c.name.clone(), &c.declarations))) + .collect::>(); - for vec_proto_constraint in proto_constraints { - let mut constraint_vec: Vec = Vec::new(); - for proto_constraint in vec_proto_constraint { - let constraint = proto_constraint_to_constraint(proto_constraint, system); - constraint_vec.push(constraint); - } - constraints.push(constraint_vec); - } - - let mut conjunctions: Vec = Vec::new(); + // Convert the proto conjunctions to real conjunctions + let conjunctions = proto_conjunctions + .into_iter() + .map(|c| { + Conjunction::new( + c.constraints + .into_iter() + .map(|c| proto_constraint_to_constraint(c, &map)) + .collect(), + ) + }) + .collect(); - for constraint_vec in constraints { - let conjunction = Conjunction::new(constraint_vec); - conjunctions.push(conjunction); - } + // Create the disjunction + let disj = Disjunction::new(conjunctions); - let disjunction: Disjunction = Disjunction::new(conjunctions); - OwnedFederation::from_disjunction(&disjunction, system.get_dim()) + // Create the federation + OwnedFederation::from_disjunction(&disj, system.get_dim()) } #[cfg(test)] mod tests { - use crate::{ - tests::Simulation::test_data::{ - create_EcdarUniversity_Machine3and1_with_nonempty_Federation_Decision, - create_EcdarUniversity_Machine_Decision, create_EcdarUniversity_Machine_component, - create_EcdarUniversity_Machine_system, - create_EcdarUniversity_Machine_with_nonempty_Federation_Decision, - }, - DataReader::{json_reader::read_json_component, proto_reader::proto_decision_to_decision}, - Simulation::decision::Decision, - TransitionSystems::transition_system::components_to_transition_system, - }; + use crate::{tests::refinement::Helper::json_get_system, System::specifics::SpecificState}; - #[test] - fn proto_decision_to_decision__ProtoDecision_with_universal_ProtoFederation__returns_correct_Decision( - ) { - // Arrange - let proto_decision = create_EcdarUniversity_Machine_Decision(); - let system = create_EcdarUniversity_Machine_system(); + use super::*; - let component = create_EcdarUniversity_Machine_component(); - let expected_edge = component.find_edge_from_id("E29").unwrap(); - let expected_source = system.get_initial_state().unwrap(); - let expected_decision = Decision::new(expected_source, expected_edge.to_owned()); + use test_case::test_case; - // Act - let actual_decision = proto_decision_to_decision(proto_decision, &system, vec![component]); + const PATH: &str = "samples/json/EcdarUniversity"; - // Assert + fn assert_state_equals(state1: &State, state2: &State) { + assert!( + state1.zone_ref().equals(state2.zone_ref()), + "Zones are not equal" + ); assert_eq!( - format!("{:?}", actual_decision), - format!("{:?}", expected_decision) + *state1.get_location(), + *state2.get_location(), + "Location trees are not equal" ); } - #[test] - fn proto_decision_to_decision__ProtoDecision_with_nonuniversal_ProtoFederation__returns_correct_Decision( - ) { - // Arrange - let proto_decision = create_EcdarUniversity_Machine_with_nonempty_Federation_Decision(); - let system = create_EcdarUniversity_Machine_system(); - - let component = create_EcdarUniversity_Machine_component(); - let expected_edge = component.find_edge_from_id("E29").unwrap(); - let action = "tea"; - let mut expected_source = system.get_initial_state().unwrap(); - let transition = - system.next_transitions_if_available(expected_source.get_location(), action); - transition - .first() - .unwrap() - .use_transition(&mut expected_source); - let expected_decision = Decision::new(expected_source, expected_edge.to_owned()); - - // Act - let actual_decision = proto_decision_to_decision(proto_decision, &system, vec![component]); - - // Assert - assert_eq!( - format!("{:?}", actual_decision), - format!("{:?}", expected_decision) - ); + fn convert_to_proto_and_back(state: &State, system: &TransitionSystemPtr) -> State { + let specific_state = SpecificState::from_state(state, &**system); + let proto_state: ProtoState = specific_state.into(); + proto_state_to_state(proto_state, system) + } + + #[test_case(PATH, "Researcher"; "Researcher state")] + #[test_case(PATH, "Machine"; "Machine state")] + #[test_case(PATH, "Machine || Researcher || Administration"; "Comp state")] + #[test_case(PATH, "Spec"; "Spec state")] + #[test_case(PATH, "Spec // Machine"; "Machine Spec state")] + #[test_case(PATH, "Spec // Administration"; "Administration Spec state")] + #[test_case(PATH, "Spec // Researcher"; "Researcher Spec state")] + #[test_case(PATH, "Spec // Researcher // Administration"; "Researcher Administration Spec state")] + #[test_case(PATH, "Spec // Researcher // Machine"; "Researcher Machine Spec state")] + #[test_case(PATH, "Spec // Machine // Administration"; "Machine Administration Spec state")] + fn initial_state_conversion_test(path: &str, query: &str) { + let system = json_get_system(path, query); + let initial_state = system.get_initial_state().unwrap(); + let initial_state2 = convert_to_proto_and_back(&initial_state, &system); + + assert_state_equals(&initial_state, &initial_state2) + } + + #[test_case(PATH, "Researcher"; "Researcher state")] + #[test_case(PATH, "Machine"; "Machine state")] + #[test_case(PATH, "Machine || Researcher || Administration"; "Comp state")] + #[test_case(PATH, "Spec"; "Spec state")] + #[test_case(PATH, "Spec // Machine"; "Machine Spec state")] + #[test_case(PATH, "Spec // Administration"; "Administration Spec state")] + #[test_case(PATH, "Spec // Researcher"; "Researcher Spec state")] + #[test_case(PATH, "Spec // Researcher // Administration"; "Researcher Administration Spec state")] + #[test_case(PATH, "Spec // Researcher // Machine"; "Researcher Machine Spec state")] + #[test_case(PATH, "Spec // Machine // Administration"; "Machine Administration Spec state")] + fn next_state_conversion_test(path: &str, query: &str) { + let system = json_get_system(path, query); + let initial_state = system.get_initial_state().unwrap(); + + fn rec_test_next(state: &State, system: &TransitionSystemPtr, depth: usize) { + if depth == 0 { + return; + } + for action in system.get_actions() { + for t in system.next_transitions(&state.decorated_locations, &action) { + let state = t.use_transition_alt(state); + if let Some(state) = state { + let next_state = convert_to_proto_and_back(&state, system); + assert_state_equals(&state, &next_state); + rec_test_next(&state, system, depth - 1); + }; + } + } + } + + // Explore the 3-step neighbourhood of the initial state and ensure that the conversion is correct + rec_test_next(&initial_state, &system, 3); } #[test] - fn proto_decision_to_decision__ProtoDecision_with_conjunction_of_components__returns_correct_Decision( - ) { - // Arrange - let machine3 = read_json_component("samples/json/EcdarUniversity", "Machine3"); - let machine = read_json_component("samples/json/EcdarUniversity", "Machine"); - let components = vec![machine3, machine.clone()]; - let system = components_to_transition_system(components.clone(), "( Machine3 && Machine )"); - let proto_decision = - create_EcdarUniversity_Machine3and1_with_nonempty_Federation_Decision(); - - let expected_edge = machine.find_edge_from_id("E29").unwrap(); - let expected_source = system.get_initial_state().unwrap(); - let expected_decision = Decision::new(expected_source, expected_edge.to_owned()); - - // Act - let actual_decision = proto_decision_to_decision(proto_decision, &system, components); - - // Assert - assert_eq!( - format!("{:?}", actual_decision), - format!("{:?}", expected_decision) - ); + fn empty_state_test() { + let system = json_get_system(PATH, "Spec // Machine // Administration"); + let mut initial_state = system.get_initial_state().unwrap(); + initial_state.update_zone(|zone| zone.set_empty()); + let initial_state2 = convert_to_proto_and_back(&initial_state, &system); + assert_state_equals(&initial_state, &initial_state2) } } diff --git a/src/DataReader/proto_writer.rs b/src/DataReader/proto_writer.rs deleted file mode 100644 index da43c175..00000000 --- a/src/DataReader/proto_writer.rs +++ /dev/null @@ -1,334 +0,0 @@ -use edbm::{ - util::constraints::{Conjunction, Constraint, Disjunction}, - zones::OwnedFederation, -}; - -use crate::{ - component::State, - ProtobufServer::services::{ - ComponentClock, Conjunction as ProtoConjunction, Constraint as ProtoConstraint, - DecisionPoint as ProtoDecisionPoint, Disjunction as ProtoDisjunction, Edge as ProtoEdge, - Federation as ProtoFederation, Location as ProtoLocation, - LocationTuple as ProtoLocationTuple, SpecificComponent, State as ProtoState, - }, - Simulation::decision_point::DecisionPoint, - TransitionSystems::{LocationID, LocationTuple, TransitionSystemPtr}, -}; - -/// Returns the [`ProtoDecisionPoint`] equivalent to the given [`DecisionPoint`] in the context of the given [`TransitionsSystemPtr`]. -pub fn decision_point_to_proto_decision_point( - decision_point: &DecisionPoint, - system: &TransitionSystemPtr, -) -> ProtoDecisionPoint { - let source = state_to_proto_state(decision_point.source(), system); - - let edges = decision_point - .possible_decisions() - .iter() - .map(edge_id_to_proto_edge) - .collect(); - - ProtoDecisionPoint { - source: Some(source), - edges, - } -} - -fn state_to_proto_state(s: &State, system: &TransitionSystemPtr) -> ProtoState { - let location_tuple = location_tuple_to_proto_location_tuple(s.get_location()); - let federation = federation_to_proto_federation(s.zone_ref(), system); - - ProtoState { - location_tuple: Some(location_tuple), - federation: Some(federation), - } -} - -fn location_tuple_to_proto_location_tuple(l: &LocationTuple) -> ProtoLocationTuple { - ProtoLocationTuple { - locations: location_id_to_proto_location_vec(&l.id), - } -} - -fn location_id_to_proto_location_vec(id: &LocationID) -> Vec { - match id { - LocationID::Simple { - location_id, - component_id, - } => vec![ProtoLocation { - id: location_id.to_string(), - specific_component: Some(SpecificComponent { - component_name: component_id.clone().unwrap_or_default(), - component_index: 0, - }), - }], - LocationID::Conjunction(l, r) - | LocationID::Composition(l, r) - | LocationID::Quotient(l, r) => location_id_to_proto_location_vec(l) - .into_iter() - .chain(location_id_to_proto_location_vec(r).into_iter()) - .collect(), - LocationID::AnyLocation => vec![], - } -} - -fn federation_to_proto_federation( - federation: &OwnedFederation, - system: &TransitionSystemPtr, -) -> ProtoFederation { - ProtoFederation { - disjunction: Some(disjunction_to_proto_disjunction( - &federation.minimal_constraints(), - system, - )), - } -} - -fn disjunction_to_proto_disjunction( - disjunction: &Disjunction, - system: &TransitionSystemPtr, -) -> ProtoDisjunction { - ProtoDisjunction { - conjunctions: disjunction - .conjunctions - .iter() - .map(|conjunction| conjunction_to_proto_conjunction(conjunction, system)) - .collect(), - } -} - -fn conjunction_to_proto_conjunction( - conjunction: &Conjunction, - system: &TransitionSystemPtr, -) -> ProtoConjunction { - ProtoConjunction { - constraints: conjunction - .constraints - .iter() - .map(|constraint| constraint_to_proto_constraint(constraint, system)) - .collect(), - } -} - -fn constraint_to_proto_constraint( - constraint: &Constraint, - system: &TransitionSystemPtr, -) -> ProtoConstraint { - fn clock_name(clock_name_and_component: Option<&(String, String)>) -> String { - const ZERO_CLOCK_NAME: &str = "0"; - match clock_name_and_component { - Some((clock_name, _)) => clock_name.to_string(), - // If an index does not correspond to an index we assume it's the zero clock - None => ZERO_CLOCK_NAME.to_string(), - } - } - - fn clock_component( - clock_name_and_component: Option<&(String, String)>, - ) -> Option { - clock_name_and_component.map(|x| SpecificComponent { - component_name: x.1.to_string(), - component_index: 0, - }) - } - - let x = system.index_to_clock_name_and_component(&constraint.i); - let y = system.index_to_clock_name_and_component(&constraint.j); - - ProtoConstraint { - x: Some(ComponentClock { - specific_component: clock_component(x.as_ref()), - clock_name: clock_name(x.as_ref()), - }), - y: Some(ComponentClock { - specific_component: clock_component(y.as_ref()), - clock_name: clock_name(y.as_ref()), - }), - strict: constraint.ineq().is_strict(), - c: constraint.ineq().bound(), - } -} - -fn edge_id_to_proto_edge(edge: &String) -> ProtoEdge { - ProtoEdge { - id: edge.to_string(), - specific_component: None, // Edge id's are unique thus this is not needed - } -} - -#[cfg(test)] -mod tests { - use super::{decision_point_to_proto_decision_point, state_to_proto_state}; - use crate::component::Component; - use crate::tests::Simulation::test_data::{ - create_EcdarUniversity_Machine_system, create_decision_point_after_taking_E5, - create_initial_decision_point, get_composition_response_Administration_Machine_Researcher, - initial_transition_decision_point_EcdarUniversity_Machine, - }; - use crate::DataReader::proto_reader::proto_state_to_state; - use crate::TransitionSystems::transition_system::components_to_transition_system; - use crate::{ - DataReader::json_reader::read_json_component, Simulation::decision_point::DecisionPoint, - }; - use test_case::test_case; - - #[test_case( - vec![ - read_json_component("samples/json/EcdarUniversity", "Machine"), - ], - "(Machine)"; - "(Machine)" - )] - #[test_case( - vec![ - read_json_component("samples/json/EcdarUniversity", "Administration"), - read_json_component("samples/json/EcdarUniversity", "Machine"), - ], - "(Administration || Machine)"; - "(Administration || Machine)" - )] - #[test_case( - vec![ - read_json_component("samples/json/EcdarUniversity", "HalfAdm1"), - read_json_component("samples/json/EcdarUniversity", "HalfAdm2"), - ], - "(HalfAdm1 && HalfAdm2)"; - "(HalfAdm1 && HalfAdm2)" - )] - #[test_case( - vec![ - read_json_component("samples/json/Simulation", "NonConvexFederation"), - ], - "(NonConvexFederation)"; - "(NonConvexFederation)" - )] - fn state_to_proto_state_to_state_is_same_state(components: Vec, composition: &str) { - let system = components_to_transition_system(components, composition); - let initial = system.get_initial_state().unwrap(); - - // exploit the fact that: - // x == convert (convert x) - assert_eq!( - format!("{:?}", initial), - format!( - "{:?}", - proto_state_to_state(state_to_proto_state(&initial, &system), &system) - ) - ); - } - - // TODO: this specific case fails because: - // TransitionSystem::clock_name_and_component_to_index_map can only map component and clock to one clock... - #[ignore = "won't fix, see comment"] - #[test_case( - vec![ - read_json_component("samples/json/EcdarUniversity", "Machine"), - ], - "(Machine && Machine)"; - "(Machine && Machine)" - )] - fn state_to_proto_state_to_state_is_same_state_____dup_for_ignore( - components: Vec, - composition: &str, - ) { - let system = components_to_transition_system(components, composition); - let initial = system.get_initial_state().unwrap(); - - // exploit the fact that: - // x == convert (convert x) - assert_eq!( - format!("{:?}", initial), - format!( - "{:?}", - proto_state_to_state(state_to_proto_state(&initial, &system), &system) - ) - ); - } - - #[test] - fn decision_point_to_proto_decision_point__initial_DecisionPoint_EcdarUniversity_Administration_par_Machine_par_Researcher__returns_correct_ProtoDecisionPoint( - ) { - // Arrange - let project_path = "samples/json/EcdarUniversity"; - - let administration = read_json_component(project_path, "Administration"); - let machine = read_json_component(project_path, "Machine"); - let researcher = read_json_component(project_path, "Researcher"); - - let combined = vec![administration, machine, researcher]; - let composition = "(Administration || Machine || Researcher)"; - - let system = components_to_transition_system(combined, composition); - - let decision_point = DecisionPoint::new( - system.get_initial_state().unwrap(), - vec![ - "E11".to_string(), - "E16".to_string(), - "E29".to_string(), - "E44".to_string(), - ], - ); - - let binding = get_composition_response_Administration_Machine_Researcher() - .unwrap() - .into_inner(); - let expected = binding.new_decision_points.first().unwrap(); - - // Act - let actual = decision_point_to_proto_decision_point(&decision_point, &system); - - // Assert - assert_eq!(format!("{:?}", actual), format!("{:?}", expected)) - } - - #[test] - fn decision_point_to_proto_decision_point__initial_DecisionPoint_EcdarUniversity_Machine__returns_correct_ProtoDecisionPoint( - ) { - // Arrange - let transitionDecisionPoint = initial_transition_decision_point_EcdarUniversity_Machine(); - let system = create_EcdarUniversity_Machine_system(); - - let decisionPoint = DecisionPoint::new( - transitionDecisionPoint.source().to_owned(), - vec!["E27".to_string(), "E29".to_string()], - ); - - let expected = create_initial_decision_point(); - - // Act - let actual = decision_point_to_proto_decision_point(&decisionPoint, &system); - - // Assert - assert_eq!(actual.source, expected.source); - assert_eq!(actual.edges.len(), 2); - assert!(actual.edges.contains(&expected.edges[0])); - assert!(actual.edges.contains(&expected.edges[1])); - } - - #[test] - fn decision_point_to_proto_decision_point__initial_DecisionPoint_EcdarUniversity_Machine_after_tea__returns_correct_ProtoDecisionPoint( - ) { - // Arrange - let system = create_EcdarUniversity_Machine_system(); - let mut after_tea = system.get_initial_state().unwrap(); - let action = "tea"; - let binding = system.next_transitions_if_available(after_tea.get_location(), action); - let tea_transition = binding.first().unwrap(); - tea_transition.use_transition(&mut after_tea); - - let decisionPoint = - DecisionPoint::new(after_tea, vec!["E27".to_string(), "E29".to_string()]); - - let expected = create_decision_point_after_taking_E5(); - - // Act - let actual = decision_point_to_proto_decision_point(&decisionPoint, &system); - - // Assert - assert_eq!(actual.source, expected.source); - assert_eq!(actual.edges.len(), 2); - assert!(actual.edges.contains(&expected.edges[0])); - assert!(actual.edges.contains(&expected.edges[1])); - } -} diff --git a/src/DataReader/serialization.rs b/src/DataReader/serialization.rs index 1229d0de..ccd75126 100644 --- a/src/DataReader/serialization.rs +++ b/src/DataReader/serialization.rs @@ -33,6 +33,7 @@ impl DummyNail { #[derive(Serialize)] pub struct DummyEdge { + pub id: String, #[serde(rename = "sourceLocation")] pub source_location: String, #[serde(rename = "targetLocation")] @@ -77,6 +78,7 @@ impl From for DummyEdge { } DummyEdge { + id: item.id, source_location: item.source_location, target_location: item.target_location, sync_type: item.sync_type, diff --git a/src/ModelObjects/component.rs b/src/ModelObjects/component.rs index 01d55f22..27bc26db 100644 --- a/src/ModelObjects/component.rs +++ b/src/ModelObjects/component.rs @@ -12,7 +12,7 @@ use edbm::util::constraints::ClockIndex; use crate::ModelObjects::representations::BoolExpression; use crate::TransitionSystems::{CompositionType, TransitionSystem}; -use crate::TransitionSystems::{LocationTuple, TransitionID}; +use crate::TransitionSystems::{LocationTree, TransitionID}; use edbm::zones::OwnedFederation; use log::info; use serde::{Deserialize, Serialize}; @@ -90,7 +90,7 @@ impl Component { let vec: Vec<&Location> = self .get_locations() .iter() - .filter(|location| location.get_location_type() == &LocationType::Initial) + .filter(|location| location.get_location_type() == LocationType::Initial) .collect(); vec.first().copied() @@ -187,13 +187,8 @@ impl Component { .expect("Couldn't find clock with index") .to_owned(); self.declarations.clocks.remove(&name); - self.declarations - .clocks - .values_mut() - .filter(|val| **val > index) - .for_each(|val| *val -= 1); - // Yeets from updates + // Removes from from updates self.edges .iter_mut() .filter(|e| e.update.is_some()) @@ -250,12 +245,12 @@ fn contain(channels: &[String], channel: &str) -> bool { /// this should probably be refactored as it causes unnecessary confusion #[derive(Clone, Debug)] pub struct State { - pub decorated_locations: LocationTuple, + pub decorated_locations: LocationTree, zone_sentinel: Option, } impl State { - pub fn create(decorated_locations: LocationTuple, zone: OwnedFederation) -> Self { + pub fn create(decorated_locations: LocationTree, zone: OwnedFederation) -> Self { State { decorated_locations, zone_sentinel: Some(zone), @@ -267,7 +262,7 @@ impl State { } pub fn from_location( - decorated_locations: LocationTuple, + decorated_locations: LocationTree, dimensions: ClockIndex, ) -> Option { let mut fed = OwnedFederation::init(dimensions); @@ -283,18 +278,30 @@ impl State { }) } + pub fn apply_invariants(&mut self) { + let fed = self.take_zone(); + let new_fed = self.decorated_locations.apply_invariants(fed); + self.set_zone(new_fed); + } + pub fn zone_ref(&self) -> &OwnedFederation { self.zone_sentinel.as_ref().unwrap() } - pub fn take_zone(&mut self) -> OwnedFederation { + fn take_zone(&mut self) -> OwnedFederation { self.zone_sentinel.take().unwrap() } - pub fn set_zone(&mut self, zone: OwnedFederation) { + fn set_zone(&mut self, zone: OwnedFederation) { self.zone_sentinel = Some(zone); } + pub fn update_zone(&mut self, update: impl FnOnce(OwnedFederation) -> OwnedFederation) { + let fed = self.take_zone(); + let new_fed = update(fed); + self.set_zone(new_fed); + } + pub fn is_subset_of(&self, other: &Self) -> bool { if self.decorated_locations != other.decorated_locations { return false; @@ -303,7 +310,7 @@ impl State { self.zone_ref().subset_eq(other.zone_ref()) } - pub fn get_location(&self) -> &LocationTuple { + pub fn get_location(&self) -> &LocationTree { &self.decorated_locations } @@ -314,7 +321,7 @@ impl State { } } -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, Copy)] pub enum LocationType { Normal, Initial, @@ -323,6 +330,15 @@ pub enum LocationType { Any, } +impl LocationType { + pub fn combine(self, other: Self) -> Self { + match (self, other) { + (LocationType::Initial, LocationType::Initial) => LocationType::Initial, + _ => LocationType::Normal, + } + } +} + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] #[serde(into = "DummyLocation")] pub struct Location { @@ -349,8 +365,8 @@ impl Location { pub fn get_invariant(&self) -> &Option { &self.invariant } - pub fn get_location_type(&self) -> &LocationType { - &self.location_type + pub fn get_location_type(&self) -> LocationType { + self.location_type } pub fn get_urgency(&self) -> &String { &self.urgency @@ -369,13 +385,13 @@ pub struct Transition { /// The ID of the transition, based on the edges it is created from. pub id: TransitionID, pub guard_zone: OwnedFederation, - pub target_locations: LocationTuple, + pub target_locations: LocationTree, pub updates: Vec, } impl Transition { /// Create a new transition not based on an edge with no identifier - pub fn new(target_locations: &LocationTuple, dim: ClockIndex) -> Transition { + pub fn new(target_locations: &LocationTree, dim: ClockIndex) -> Transition { Transition { id: TransitionID::None, guard_zone: OwnedFederation::universe(dim), @@ -389,12 +405,7 @@ impl Transition { let target_loc_name = &edge.target_location; let target_loc = comp.get_location_by_name(target_loc_name); - let target_locations = LocationTuple::simple( - target_loc, - Some(comp.get_name().to_owned()), - comp.get_declarations(), - dim, - ); + let target_locations = LocationTree::simple(target_loc, comp.get_declarations(), dim); let mut compiled_updates = vec![]; if let Some(updates) = edge.get_update() { @@ -447,7 +458,7 @@ impl Transition { for l in left { for r in right { let target_locations = - LocationTuple::compose(&l.target_locations, &r.target_locations, comp); + LocationTree::compose(&l.target_locations, &r.target_locations, comp); let guard_zone = l.guard_zone.clone().intersection(&r.guard_zone); @@ -498,8 +509,8 @@ impl Transition { // TODO: will we ever need this method? #[allow(dead_code)] fn get_guard_from_allowed( - from_loc: &LocationTuple, - to_loc: &LocationTuple, + from_loc: &LocationTree, + to_loc: &LocationTree, updates: Vec, guard: Option, dim: ClockIndex, @@ -533,7 +544,7 @@ impl Transition { zone.intersection(&self.guard_zone) } - pub fn move_locations(&self, locations: &mut LocationTuple) { + pub fn move_locations(&self, locations: &mut LocationTree) { *locations = self.target_locations.clone(); } diff --git a/src/ModelObjects/state.rs b/src/ModelObjects/state.rs deleted file mode 100644 index 43bd85d8..00000000 --- a/src/ModelObjects/state.rs +++ /dev/null @@ -1,46 +0,0 @@ -/// State is a struct used for initial verification of consistency, and determinism as a state that also hols a dbm -/// This is done as the type used in refinement state pair assumes to sides of an operation -/// this should probably be refactored as it causes unnecessary confusion -#[derive(Clone, std::cmp::PartialEq)] -pub struct State<'a> { - pub decorated_locations: LocationTuple<'a>, - pub zone: Federation, -} - -impl<'a> State<'a> { - pub fn create(decorated_locations: LocationTuple<'a>, zone: Federation) -> Self { - State { - decorated_locations, - zone, - } - } - - pub fn from_location(decorated_locations: LocationTuple<'a>, dimensions: u32) -> Option { - let mut zone = Federation::init(dimensions); - - if !decorated_locations.apply_invariants(&mut zone) { - return None; - } - - Some(State { - decorated_locations, - zone, - }) - } - - pub fn is_subset_of(&self, other: &Self) -> bool { - if self.decorated_locations != other.decorated_locations { - return false; - } - - self.zone.subset_eq(&other.zone) - } - - pub fn get_location(&self, index: usize) -> &Location { - self.decorated_locations.get_location(index) - } - - pub fn get_declarations(&self, index: usize) -> &Declarations { - self.decorated_locations.get_decl(index) - } -} diff --git a/src/ModelObjects/statepair.rs b/src/ModelObjects/statepair.rs index 4e5e52d0..ad553fd1 100644 --- a/src/ModelObjects/statepair.rs +++ b/src/ModelObjects/statepair.rs @@ -1,12 +1,12 @@ use edbm::zones::OwnedFederation; -use crate::TransitionSystems::{LocationTuple, TransitionSystemPtr}; +use crate::TransitionSystems::{LocationTree, TransitionSystemPtr}; use std::fmt::{Display, Formatter}; #[derive(Clone, Debug)] pub struct StatePair { - pub locations1: LocationTuple, - pub locations2: LocationTuple, + pub locations1: LocationTree, + pub locations2: LocationTree, /// The sentinel (Option) allows us to take ownership of the internal fed from a mutable reference zone_sentinel: Option, } @@ -14,8 +14,8 @@ pub struct StatePair { impl StatePair { pub fn create( dimensions: usize, - locations1: LocationTuple, - locations2: LocationTuple, + locations1: LocationTree, + locations2: LocationTree, ) -> StatePair { let zone = OwnedFederation::init(dimensions); @@ -26,16 +26,16 @@ impl StatePair { } } - pub fn get_locations1(&self) -> &LocationTuple { + pub fn get_locations1(&self) -> &LocationTree { &self.locations1 } - pub fn get_locations2(&self) -> &LocationTuple { + pub fn get_locations2(&self) -> &LocationTree { &self.locations2 } //Used to allow borrowing both states as mutable - pub fn get_mut_states(&mut self, is_states1: bool) -> (&mut LocationTuple, &mut LocationTuple) { + pub fn get_mut_states(&mut self, is_states1: bool) -> (&mut LocationTree, &mut LocationTree) { if is_states1 { (&mut self.locations1, &mut self.locations2) } else { @@ -44,7 +44,7 @@ impl StatePair { } #[allow(dead_code)] - pub fn get_locations(&self, is_states1: bool) -> (&LocationTuple, &LocationTuple) { + pub fn get_locations(&self, is_states1: bool) -> (&LocationTree, &LocationTree) { if is_states1 { (&self.locations1, &self.locations2) } else { diff --git a/src/ProtobufServer/ecdar_requests/send_query.rs b/src/ProtobufServer/ecdar_requests/send_query.rs index 9c9b761a..d07f1b6c 100644 --- a/src/ProtobufServer/ecdar_requests/send_query.rs +++ b/src/ProtobufServer/ecdar_requests/send_query.rs @@ -2,37 +2,37 @@ use std::collections::HashMap; use std::sync::Arc; use crate::component::Component; -use crate::extract_system_rep::SystemRecipeFailure; +use crate::extract_system_rep::ExecutableQueryError; use crate::xml_parser::parse_xml_from_str; use crate::DataReader::component_loader::ModelCache; use crate::DataReader::json_reader::json_to_component; use crate::DataReader::json_writer::component_to_json; use crate::DataReader::parse_queries; use crate::ModelObjects::queries::Query; -use crate::ModelObjects::statepair::StatePair; use crate::ProtobufServer::services::component::Rep; use crate::ProtobufServer::services::query_response::{ - ComponentResult, ConsistencyResult as ProtobufConsistencyResult, - DeterminismResult as ProtobufDeterminismResult, ReachabilityResult, RefinementResult, - Result as ProtobufResult, + Error as InnerError, Result as ProtobufResult, Success, }; use crate::ProtobufServer::services::{ - self, Component as ProtobufComponent, ComponentClock as ProtobufComponentClock, - Conjunction as ProtobufConjunction, Constraint as ProtobufConstraint, - Disjunction as ProtobufDisjunction, Federation, Location, LocationTuple, QueryRequest, - QueryResponse, SpecificComponent, State, + Component as ProtobufComponent, QueryRequest, QueryResponse, }; use crate::ProtobufServer::ConcreteEcdarBackend; -use crate::System::executable_query::QueryResult; -use crate::System::local_consistency::{ - ConsistencyFailure, ConsistencyResult, DeterminismFailure, DeterminismResult, +use crate::System::query_failures::{ + ConsistencyFailure, DeterminismFailure, PathFailure, QueryResult, RefinementFailure, + SystemRecipeFailure, }; -use crate::System::refine::{self, RefinementFailure}; + use crate::System::{extract_system_rep, input_enabler}; -use crate::TransitionSystems::{self, LocationID, TransitionID}; + use log::trace; use tonic::Status; +fn string_error(error: impl Into) -> ProtobufResult { + ProtobufResult::Error(InnerError { + error: error.into(), + }) +} + impl ConcreteEcdarBackend { pub fn handle_send_query( query_request: QueryRequest, @@ -63,31 +63,29 @@ impl ConcreteEcdarBackend { }; component_container.set_settings(query_request.settings.unwrap_or(crate::DEFAULT_SETTINGS)); - if query_request.ignored_input_outputs.is_some() { - return Err(Status::unimplemented( - "ignored input outputs are currently not supported", - )); - } - - let executable_query = + let out = match extract_system_rep::create_executable_query(&query, &mut component_container) { - Ok(query) => query, - Err(e) => { - return Err(Status::invalid_argument(format!( - "Creation of query failed: {}", - e - ))) + Ok(query) => { + let result = query.execute(); + Ok(QueryResponse { + query_id: query_request.query_id, + info: vec![], // TODO: Should be logs + result: Some(result.into()), + }) + } + Err(ExecutableQueryError::Custom(e)) => Err(Status::invalid_argument(format!( + "Creation of query failed: {}", + e + ))), + Err(ExecutableQueryError::SystemRecipeFailure(failure)) => { + Ok(QueryResponse { + query_id: query_request.query_id, + info: vec![], // TODO: Should be logs + result: Some(failure.into()), + }) } }; - let result = executable_query.execute(); - - let reply = QueryResponse { - query_id: query_request.query_id, - info: vec![], // TODO: Should be logs - result: convert_ecdar_result(&result), - }; - - Ok(reply) + out } } @@ -142,327 +140,54 @@ fn create_components(components: Vec) -> HashMap { comp_hashmap } -fn convert_ecdar_result(query_result: &QueryResult) -> Option { - match query_result { - QueryResult::Refinement(refines) => match refines { - refine::RefinementResult::Success => { - Some(ProtobufResult::Refinement(RefinementResult { - success: true, - reason: "".to_string(), - relation: vec![], - state: None, - action: vec![], // Empty vec![] is used, when no failing action is available. - })) - } - refine::RefinementResult::Failure(failure) => convert_refinement_failure(failure), - }, - - QueryResult::Reachability(res) => { - let proto_path = TransitionID::split_into_component_lists( - &res.path - .as_ref() - .unwrap() - .iter() - .map(|t| t.id.clone()) - .collect(), - ); - - match proto_path { - Ok(p) => { - // Format into result expected by protobuf - let component_paths = p - .iter() - .map(|component_path| services::Path { - edge_ids: component_path - .concat() // Concat to break the edges of the transitions into one vec instead a vec of vecs. - .iter() - .map(|id| id.to_string()) - .collect(), - }) - .collect(); - - Some(ProtobufResult::Reachability(ReachabilityResult { - success: res.was_reachable, - reason: if res.was_reachable { - "".to_string() - } else { - "No path exists".to_string() - }, - state: None, - component_paths, - })) - } - Err(e) => Some(ProtobufResult::Error(format!( - "Internal error occurred during reachability check: {}", - e - ))), - } - } - - QueryResult::GetComponent(comp) => Some(ProtobufResult::Component(ComponentResult { - component: Some(ProtobufComponent { - rep: Some(Rep::Json(component_to_json(comp))), +impl From for ProtobufResult { + fn from(result: QueryResult) -> ProtobufResult { + match result { + QueryResult::Reachability(Ok(path)) => ProtobufResult::ReachabilityPath(path.into()), + QueryResult::Refinement(Ok(_)) + | QueryResult::Consistency(Ok(_)) + | QueryResult::Determinism(Ok(_)) => ProtobufResult::Success(Success {}), + QueryResult::Refinement(Err(fail)) => fail.into(), + QueryResult::Consistency(Err(fail)) => fail.into(), + QueryResult::Determinism(Err(fail)) => fail.into(), + QueryResult::Reachability(Err(fail)) => fail.into(), + + QueryResult::GetComponent(comp) => ProtobufResult::Component(ProtobufComponent { + rep: Some(Rep::Json(component_to_json(&comp))), }), - })), - QueryResult::Consistency(is_consistent) => match is_consistent { - ConsistencyResult::Success => { - Some(ProtobufResult::Consistency(ProtobufConsistencyResult { - success: true, - reason: "".to_string(), - state: None, - action: vec![], - })) - } - ConsistencyResult::Failure(failure) => match failure { - ConsistencyFailure::NoInitialLocation | ConsistencyFailure::EmptyInitialState => { - Some(ProtobufResult::Consistency(ProtobufConsistencyResult { - success: false, - reason: failure.to_string(), - state: None, - action: vec![], - })) - } - ConsistencyFailure::NotConsistentFrom(location_id, action) - | ConsistencyFailure::NotDeterministicFrom(location_id, action) => { - Some(ProtobufResult::Consistency(ProtobufConsistencyResult { - success: false, - reason: failure.to_string(), - state: Some(State { - location_tuple: Some(LocationTuple { - locations: vec![Location { - id: location_id.to_string(), - specific_component: Some(SpecificComponent { - component_name: location_id.get_component_id()?, - component_index: 0, - }), - }], - }), - federation: None, - }), - action: vec![action.to_string()], - })) - } - ConsistencyFailure::NotDisjoint(srf) => { - Some(ProtobufResult::Consistency(ProtobufConsistencyResult { - success: false, - reason: srf.reason.to_string(), - state: Some(State { - location_tuple: Some(make_location_vec_from_srf(srf))?, - federation: None, - }), - action: srf.actions.clone(), - })) - } - }, - }, - QueryResult::Determinism(is_deterministic) => match is_deterministic { - DeterminismResult::Success => { - Some(ProtobufResult::Determinism(ProtobufDeterminismResult { - success: true, - reason: "".to_string(), - state: None, - action: vec![], - })) - } - DeterminismResult::Failure(DeterminismFailure::NotDeterministicFrom( - location_id, - action, - )) => Some(ProtobufResult::Determinism(ProtobufDeterminismResult { - success: false, - reason: "Not deterministic From Location".to_string(), - state: Some(State { - location_tuple: Some(LocationTuple { - locations: vec![Location { - id: location_id.to_string(), - specific_component: Some(SpecificComponent { - component_name: location_id.get_component_id()?, - component_index: 0, - }), - }], - }), - federation: None, - }), - action: vec![action.to_string()], - })), - DeterminismResult::Failure(DeterminismFailure::NotDisjoint(srf)) => { - Some(ProtobufResult::Determinism(ProtobufDeterminismResult { - success: false, - reason: srf.reason.to_string(), - state: Some(State { - location_tuple: Some(make_location_vec_from_srf(srf))?, - federation: None, - }), - action: srf.actions.clone(), - })) - } - }, - - QueryResult::Error(message) => Some(ProtobufResult::Error(message.clone())), - } -} -fn convert_refinement_failure(failure: &RefinementFailure) -> Option { - match failure { - RefinementFailure::NotDisjointAndNotSubset(srf) => { - Some(ProtobufResult::Refinement(RefinementResult { - success: false, - reason: "Not Disjoint and Not Subset".to_string(), - relation: vec![], - state: Some(State { - location_tuple: Some(make_location_vec_from_srf(srf))?, - federation: None, - }), - action: srf.actions.clone(), - })) - } - RefinementFailure::NotSubset - | RefinementFailure::EmptySpecification - | RefinementFailure::EmptyImplementation => { - Some(ProtobufResult::Refinement(RefinementResult { - success: false, - relation: vec![], - state: None, - reason: failure.to_string(), - action: vec![], - })) - } - RefinementFailure::NotDisjoint(srf) => Some(ProtobufResult::Refinement(RefinementResult { - success: false, - relation: vec![], - state: Some(State { - location_tuple: Some(make_location_vec_from_srf(srf))?, - federation: None, - }), - reason: srf.reason.clone(), - action: srf.actions.clone(), - })), - RefinementFailure::CutsDelaySolutions(state_pair) - | RefinementFailure::InitialState(state_pair) - | RefinementFailure::EmptyTransition2s(state_pair) - | RefinementFailure::NotEmptyResult(state_pair) => { - Some(ProtobufResult::Refinement(RefinementResult { - success: false, - relation: vec![], - state: Some(State { - federation: make_proto_zone(state_pair), - location_tuple: Some(LocationTuple { - locations: make_location_vec( - state_pair.get_locations1(), - state_pair.get_locations2(), - ), - }), - }), - reason: failure.to_string(), - action: vec![], - })) - } - RefinementFailure::ConsistencyFailure(location_id, action) - | RefinementFailure::DeterminismFailure(location_id, action) => { - Some(ProtobufResult::Refinement(RefinementResult { - success: false, - reason: failure.to_string(), - state: Some(State { - location_tuple: Some(LocationTuple { - locations: vec![Location { - id: value_in_location(location_id), - specific_component: value_in_component(location_id.as_ref()), - }], - }), - federation: None, - }), - action: vec![value_in_action(action)], - relation: vec![], - })) + QueryResult::RecipeFailure(recipe) => recipe.into(), + QueryResult::CustomError(custom) => string_error(custom), } } } -fn make_location_vec_from_srf(srf: &SystemRecipeFailure) -> Option { - let a = vec![ - Location { - id: "".to_string(), - specific_component: Some(SpecificComponent { - component_name: srf.left_name.clone()?, - component_index: 0, - }), - }, - Location { - id: "".to_string(), - specific_component: Some(SpecificComponent { - component_name: srf.right_name.clone()?, - component_index: 1, - }), - }, - ]; - Some(LocationTuple { locations: a }) -} - -fn make_location_vec( - locations1: &TransitionSystems::LocationTuple, - locations2: &TransitionSystems::LocationTuple, -) -> Vec { - let loc_vec: Vec = vec![ - Location { - id: locations1.id.to_string(), - specific_component: Some(SpecificComponent { - component_name: locations1.id.get_component_id().unwrap(), - component_index: 0, - }), - }, - Location { - id: locations2.id.to_string(), - specific_component: Some(SpecificComponent { - component_name: locations2.id.get_component_id().unwrap(), - component_index: 0, - }), - }, - ]; - loc_vec +impl From for ProtobufResult { + fn from(fail: SystemRecipeFailure) -> ProtobufResult { + ProtobufResult::Model(fail.into()) + } } -fn make_proto_zone(state_pair: &StatePair) -> Option { - let disjunction = state_pair.ref_zone().minimal_constraints(); - let mut conjunctions: Vec = vec![]; - for conjunction in disjunction.conjunctions.iter() { - let mut constraints: Vec = vec![]; - for constraint in conjunction.constraints.iter() { - constraints.push(ProtobufConstraint { - x: Some(ProtobufComponentClock { - specific_component: value_in_component(Some(&state_pair.locations1.id)), - clock_name: constraint.i.to_string(), - }), - y: Some(ProtobufComponentClock { - specific_component: value_in_component(Some(&state_pair.locations1.id)), - clock_name: constraint.j.to_string(), - }), - strict: constraint.ineq().is_strict(), - c: constraint.ineq().bound(), - }); - } - conjunctions.push(ProtobufConjunction { constraints }) +impl From for ProtobufResult { + fn from(fail: DeterminismFailure) -> ProtobufResult { + ProtobufResult::Determinism(fail.into()) } - Some(Federation { - disjunction: Some(ProtobufDisjunction { conjunctions }), - }) } -fn value_in_location(maybe_location: &Option) -> String { - match maybe_location { - Some(location_id) => location_id.to_string(), - None => "".to_string(), +impl From for ProtobufResult { + fn from(fail: ConsistencyFailure) -> ProtobufResult { + ProtobufResult::Consistency(fail.into()) } } -fn value_in_component(maybe_location: Option<&LocationID>) -> Option { - maybe_location.map(|location_id| SpecificComponent { - component_name: location_id.get_component_id().unwrap(), - component_index: 0, - }) +impl From for ProtobufResult { + fn from(fail: RefinementFailure) -> ProtobufResult { + ProtobufResult::Refinement(fail.into()) + } } -fn value_in_action(maybe_action: &Option) -> String { - match maybe_action { - Some(action) => action.to_string(), - None => "".to_string(), +impl From for ProtobufResult { + fn from(fail: PathFailure) -> ProtobufResult { + ProtobufResult::Reachability(fail.into()) } } diff --git a/src/ProtobufServer/ecdar_requests/start_simulation.rs b/src/ProtobufServer/ecdar_requests/start_simulation.rs index c13c8cb0..be09b195 100644 --- a/src/ProtobufServer/ecdar_requests/start_simulation.rs +++ b/src/ProtobufServer/ecdar_requests/start_simulation.rs @@ -1,9 +1,9 @@ use crate::DataReader::component_loader::ModelCache; use crate::DataReader::proto_reader::simulation_info_to_transition_system; -use crate::DataReader::proto_writer::decision_point_to_proto_decision_point; use crate::ProtobufServer::services::{SimulationStartRequest, SimulationStepResponse}; use crate::ProtobufServer::ConcreteEcdarBackend; -use crate::Simulation::decision_point::DecisionPoint; +use crate::Simulation::decision::Decision; +use crate::System::specifics::SpecificDecision; use tonic::Status; @@ -13,22 +13,18 @@ impl ConcreteEcdarBackend { request: SimulationStartRequest, _cache: ModelCache, // TODO should be used... ) -> Result { - fn option_to_vec(option: Option) -> Vec { - match option { - Some(item) => vec![item], - None => vec![], - } - } - let simulation_info = request.simulation_info.unwrap(); let transition_system = simulation_info_to_transition_system(&simulation_info); - let initial = DecisionPoint::initial(&transition_system) - .map(|i| decision_point_to_proto_decision_point(&i, &transition_system)); + // Get the decisions from the initial state and convert them to proto + let initial = Decision::get_initial_decisions(&transition_system) + .into_iter() + .map(|i| SpecificDecision::from_decision(&i, &*transition_system).into()) + .collect(); Ok(SimulationStepResponse { - new_decision_points: option_to_vec(initial), + new_decision_points: initial, }) } } diff --git a/src/ProtobufServer/ecdar_requests/take_simulation_step.rs b/src/ProtobufServer/ecdar_requests/take_simulation_step.rs index bb3d8f62..d932611f 100644 --- a/src/ProtobufServer/ecdar_requests/take_simulation_step.rs +++ b/src/ProtobufServer/ecdar_requests/take_simulation_step.rs @@ -3,16 +3,13 @@ use tonic::Status; use crate::{ DataReader::{ component_loader::ModelCache, - proto_reader::{ - components_info_to_components, proto_decision_to_decision, - simulation_info_to_transition_system, - }, - proto_writer::decision_point_to_proto_decision_point, + proto_reader::{proto_decision_to_decision, simulation_info_to_transition_system}, }, ProtobufServer::{ services::{SimulationStepRequest, SimulationStepResponse}, ConcreteEcdarBackend, }, + System::specifics::SpecificDecision, }; impl ConcreteEcdarBackend { @@ -25,19 +22,16 @@ impl ConcreteEcdarBackend { let request_message = request; let simulation_info = request_message.simulation_info.unwrap(); - let components = - components_info_to_components(simulation_info.components_info.as_ref().unwrap()); - let system = simulation_info_to_transition_system(&simulation_info); let chosen_decision = request_message.chosen_decision.unwrap(); - let chosen_decision = proto_decision_to_decision(chosen_decision, &system, components); + let chosen_decision = proto_decision_to_decision(chosen_decision, &system); let decision_points = chosen_decision.resolve(&system); let decision_points = decision_points .into_iter() - .map(|dp| decision_point_to_proto_decision_point(&dp, &system)) + .map(|i| SpecificDecision::from_decision(&i, &*system).into()) .collect(); let simulation_step_response = SimulationStepResponse { diff --git a/src/ProtobufServer/mod.rs b/src/ProtobufServer/mod.rs index 9994e67d..1fab5e8b 100644 --- a/src/ProtobufServer/mod.rs +++ b/src/ProtobufServer/mod.rs @@ -1,5 +1,6 @@ mod ecdar_backend; mod ecdar_requests; +mod proto_conversions; mod server; pub mod services { diff --git a/src/ProtobufServer/proto_conversions.rs b/src/ProtobufServer/proto_conversions.rs new file mode 100644 index 00000000..7aafb720 --- /dev/null +++ b/src/ProtobufServer/proto_conversions.rs @@ -0,0 +1,354 @@ +use crate::ProtobufServer::services::query_response::{ + ConsistencyFailure as ProtobufConsistencyFailure, + DeterminismFailure as ProtobufDeterminismFailure, ModelFailure, ReachabilityFailure, + ReachabilityPath, RefinementFailure as ProtobufRefinementFailure, +}; +use crate::ProtobufServer::services::{ + self, clock::Clock as ProtoClockEnum, clock::ComponentClock as ProtoComponentClock, + clock::SystemClock as ProtoSystemClock, ActionFailure as ProtobufActionFailure, + BinaryLocationOperator, Clock as ProtoClock, ComponentInstance as ProtoSpecificComponent, + Conjunction as ProtoConjunction, Constraint as ProtoConstraint, + Disjunction as ProtoDisjunction, LeafLocation, LocationTree, State as ProtoState, +}; +use crate::System::query_failures::*; +use crate::System::specifics::{ + SpecialLocation, SpecificClock, SpecificClockVar, SpecificComp, SpecificConjunction, + SpecificConstraint, SpecificDecision, SpecificDisjunction, SpecificEdge, SpecificLocation, + SpecificPath, SpecificState, +}; + +impl From for ProtoState { + fn from(state: SpecificState) -> Self { + ProtoState { + location_tree: Some(state.locations.into()), + zone: Some(state.constraints.into()), + } + } +} + +impl From for LocationTree { + fn from(loc: SpecificLocation) -> Self { + use services::location_tree::NodeType; + match loc { + SpecificLocation::BranchLocation(left, right, op) => LocationTree { + node_type: Some(NodeType::BinaryLocationOp(Box::new( + BinaryLocationOperator { + left: Some(Box::new((*left).into())), + right: Some(Box::new((*right).into())), + operator: match op { + SystemType::Conjunction => 0, + SystemType::Composition => 1, + SystemType::Quotient => 2, + SystemType::Refinement => 3, + _ => unreachable!(), + }, + }, + ))), + }, + SpecificLocation::ComponentLocation { comp, location_id } => LocationTree { + node_type: Some(NodeType::LeafLocation(LeafLocation { + id: location_id, + component_instance: Some(comp.into()), + })), + }, + SpecificLocation::SpecialLocation(kind) => LocationTree { + node_type: Some(NodeType::SpecialLocation(match kind { + SpecialLocation::Universal => 0, + SpecialLocation::Error => 1, + })), + }, + } + } +} + +impl From for SpecificLocation { + fn from(loc: LocationTree) -> Self { + use services::location_tree::NodeType; + match loc.node_type.unwrap() { + NodeType::BinaryLocationOp(branch) => { + use services::binary_location_operator::Operator; + let sys_type = match branch.operator() { + Operator::Conjunction => SystemType::Conjunction, + Operator::Composition => SystemType::Composition, + Operator::Quotient => SystemType::Quotient, + Operator::Refinement => SystemType::Refinement, + }; + + let left = Box::new((*branch.left.unwrap()).into()); + let right = Box::new((*branch.right.unwrap()).into()); + + SpecificLocation::BranchLocation(left, right, sys_type) + } + NodeType::LeafLocation(leaf) => SpecificLocation::ComponentLocation { + comp: leaf.component_instance.unwrap().into(), + location_id: leaf.id, + }, + NodeType::SpecialLocation(special) => match special { + 0 => SpecificLocation::SpecialLocation(SpecialLocation::Universal), + 1 => SpecificLocation::SpecialLocation(SpecialLocation::Error), + _ => panic!("Invalid special location id"), + }, + } + } +} + +impl From for ProtoSpecificComponent { + fn from(comp: SpecificComp) -> Self { + ProtoSpecificComponent { + component_name: comp.name, + component_index: comp.id, + } + } +} + +impl From for SpecificComp { + fn from(comp: ProtoSpecificComponent) -> Self { + SpecificComp { + name: comp.component_name, + id: comp.component_index, + } + } +} + +impl From for ProtoDisjunction { + fn from(disj: SpecificDisjunction) -> Self { + ProtoDisjunction { + conjunctions: disj + .conjunctions + .into_iter() + .map(|conj| conj.into()) + .collect(), + } + } +} + +impl From for ProtoConjunction { + fn from(conj: SpecificConjunction) -> Self { + ProtoConjunction { + constraints: conj.constraints.into_iter().map(|c| c.into()).collect(), + } + } +} + +impl From for ProtoConstraint { + fn from(constraint: SpecificConstraint) -> Self { + Self { + x: Some(constraint.i.into()), + y: Some(constraint.j.into()), + strict: constraint.strict, + c: constraint.c, + } + } +} + +impl From for ProtoClock { + fn from(clock: SpecificClockVar) -> Self { + use std::convert::TryFrom; + match clock { + SpecificClockVar::Zero => Self { + clock: Some(ProtoClockEnum::ZeroClock(Default::default())), + }, + SpecificClockVar::ComponentClock(clock) => Self { + clock: Some(ProtoClockEnum::ComponentClock(clock.into())), + }, + SpecificClockVar::SystemClock(clock_index) => Self { + clock: Some(ProtoClockEnum::SystemClock(ProtoSystemClock { + clock_index: u32::try_from(clock_index) + .expect("Could not fit clock index in u32"), + })), + }, + } + } +} + +impl From for ProtoComponentClock { + fn from(clock: SpecificClock) -> Self { + Self { + component_instance: Some(clock.comp.into()), + clock_name: clock.name, + } + } +} + +fn state_action_to_proto(state: SpecificState, action: Action) -> services::StateAction { + services::StateAction { + state: Some(state.into()), + action: action.name, + } +} + +impl From for services::action_failure::ActionSet { + fn from(set: ActionSet) -> Self { + Self { + actions: set.actions.into_iter().collect(), + system: set.system, + is_input: set.is_input, + } + } +} + +impl From for ProtobufActionFailure { + fn from(af: ActionFailure) -> Self { + let enum_id = match af { + ActionFailure::NotSubset(_, _) => 0, // As defined in the proto file + ActionFailure::NotDisjoint(_, _) => 1, // As defined in the proto file + }; + + match af { + ActionFailure::NotSubset(a, b) | ActionFailure::NotDisjoint(a, b) => { + ProtobufActionFailure { + failure: enum_id, + action_sets: vec![a.into(), b.into()], + } + } + } + } +} + +impl From for ProtobufDeterminismFailure { + fn from(df: DeterminismFailure) -> Self { + Self { + system: df.system, + failure_state: Some(state_action_to_proto(df.state, df.action)), + } + } +} + +impl From for ProtobufConsistencyFailure { + fn from(cf: ConsistencyFailure) -> Self { + use services::query_response::consistency_failure::Failure; + match cf { + ConsistencyFailure::NoInitialState { system } => ProtobufConsistencyFailure { + system, + failure: Some(Failure::NoInitialState(0)), + }, + ConsistencyFailure::NotDeterministic(det) => { + let df: ProtobufDeterminismFailure = det.into(); + ProtobufConsistencyFailure { + system: df.system.clone(), + failure: Some(Failure::Determinism(df)), + } + } + ConsistencyFailure::InconsistentLoc { system, state } + | ConsistencyFailure::InconsistentFrom { system, state } => { + ProtobufConsistencyFailure { + system, + failure: Some(Failure::FailureState(state.into())), + } + } + } + } +} + +impl From for ProtobufRefinementFailure { + fn from(rf: RefinementFailure) -> Self { + use services::query_response::refinement_failure::Failure; + use services::query_response::refinement_failure::RefinementStateFailure; + + match rf { + RefinementFailure::CutsDelaySolutions { + action, + state, + system, + } => ProtobufRefinementFailure { + system: system.name, + failure: Some(Failure::RefinementState(RefinementStateFailure { + unmatched: 1, + state: Some(state_action_to_proto(state, action)), + })), + }, + + RefinementFailure::CannotMatch { + action, + state, + system, + } => ProtobufRefinementFailure { + system: system.name, + failure: Some(Failure::RefinementState(RefinementStateFailure { + unmatched: 0, + state: Some(state_action_to_proto(state, action)), + })), + }, + + RefinementFailure::Precondition(precond) => { + use crate::System::query_failures::RefinementPrecondition::*; + match precond { + EmptyChild { child, system } => ProtobufRefinementFailure { + system: system.name, + failure: Some(Failure::EmptySystem(child)), + }, + EmptyInitialState { system } => ProtobufRefinementFailure { + system: system.name, + failure: Some(Failure::NoInitialState(0)), + }, + InconsistentChild(cons, system) => ProtobufRefinementFailure { + system: system.name, + failure: Some(Failure::InconsistentChild(cons.into())), + }, + ActionMismatch(action, system) => ProtobufRefinementFailure { + system: system.name, + failure: Some(Failure::ActionMismatch(action.into())), + }, + } + } + } + } +} + +impl From for ModelFailure { + fn from(srf: SystemRecipeFailure) -> Self { + use services::query_response::model_failure::Failure; + match srf { + SystemRecipeFailure::Action(action, sys) => ModelFailure { + system: sys.name, + failure: Some(Failure::ActionMismatch(action.into())), + }, + SystemRecipeFailure::Inconsistent(cf, sys) => ModelFailure { + system: sys.name, + failure: Some(Failure::InconsistentConjunction(cf.into())), + }, + } + } +} + +impl From for ReachabilityPath { + fn from(path: SpecificPath) -> Self { + use services::Path as ProtoPath; + + Self { + path: Some(ProtoPath { + decisions: path.path.into_iter().map(|d| d.into()).collect(), + }), + } + } +} + +impl From for services::Decision { + fn from(decision: SpecificDecision) -> Self { + Self { + source: Some(decision.source_state.into()), + action: decision.action, + edges: decision.edges.into_iter().map(|e| e.into()).collect(), + destination: Some(decision.destination_state.into()), + } + } +} + +impl From for ReachabilityFailure { + fn from(pf: PathFailure) -> Self { + match pf { + PathFailure::Unreachable => Self { + failure: 0, // As defined in the proto file + }, + } + } +} + +impl From for services::Edge { + fn from(edge: SpecificEdge) -> Self { + Self { + id: edge.edge_id, + component_instance: Some(edge.comp.into()), + } + } +} diff --git a/src/Simulation/decision.rs b/src/Simulation/decision.rs index 4085ab48..b1fa6218 100644 --- a/src/Simulation/decision.rs +++ b/src/Simulation/decision.rs @@ -1,38 +1,90 @@ use crate::{ - component::{Edge, State}, + component::{State, Transition}, TransitionSystems::TransitionSystemPtr, }; -use super::{decision_point::DecisionPoint, transition_decision::TransitionDecision}; - -/// Represent a decision in a any composition of components, that has been taken: In the current `source` state I have `decided` to use this [`Edge`]. -#[derive(Debug)] +/// Represent a decision in a any composition of components: In the current `state` [`State`] we have decided to take this `action` [`String`]. +#[derive(Debug, Clone)] pub struct Decision { - source: State, - decided: Edge, + pub state: State, + pub action: String, + pub transition: Option, + pub next_state: State, } impl Decision { - pub fn new(source: State, decided: Edge) -> Self { - Self { source, decided } + /// Resolves a [`Decision`]: use the `action` in the `state` and return a [`Vec`] of the possible [`Decision`]s from the destination [`State`]. + /// + /// # Panics + /// Panics if the [`Decision`] leads to no new states or is ambiguous (leads to multiple new states) + pub fn resolve(&self, system: &TransitionSystemPtr) -> Vec { + let transitions = system.next_transitions(&self.state.decorated_locations, &self.action); + let mut next_states: Vec<_> = transitions + .into_iter() + .filter_map(|transition| transition.use_transition_alt(&self.state)) + .collect(); + + assert_ne!(next_states.len(), 0, "Decision leads to no new states"); + assert_eq!( + next_states.len(), + 1, + "Ambiguous decision leads to multiple new states" + ); + + let next_state = next_states.pop().unwrap(); + + Decision::get_decisions_from_state(next_state, system) } - pub fn source(&self) -> &State { - &self.source + pub fn from_state_transition( + mut state: State, + transition: &Transition, + action: impl Into, + ) -> Option { + // Get the zone that is allowed by the transition + let allowed = transition.get_allowed_federation(); + // Intersect the state zone with the allowed zone + state.update_zone(|zone| zone.intersection(&allowed)); + // Check if the new state is empty + if !state.zone_ref().is_empty() { + let next_state = transition.use_transition_alt(&state).expect( + "If the allowed zone is non-empty, the transition should lead to a non-empty state", + ); + Some(Decision { + state, + action: action.into(), + transition: Some(transition.to_owned()), + next_state, + }) + } else { + None + } } - pub fn decided(&self) -> &Edge { - &self.decided + /// Get all possible [`Decision`]s from a [`State`] + pub fn get_decisions_from_state(state: State, system: &TransitionSystemPtr) -> Vec { + let mut next_decisions = vec![]; + + for action in system.get_actions() { + let possible_transitions = system.next_transitions(&state.decorated_locations, &action); + for t in possible_transitions { + if let Some(decision) = Decision::from_state_transition(state.clone(), &t, &action) + { + next_decisions.push(decision); + } + } + } + + next_decisions } - /// Resolves a [`Decision`]: use the `decided` [`Edge`] and returns a [`Vec`] of the [`DecisionPoint`]s of the destination [`State`]s. - /// - /// Some `decided` [`Edge`]s lead to ambiguity ie. they correspond to multiple [`Transition`]s. Thus one [`Edge`] can lead to multiple [`State`]s. - pub fn resolve(&self, system: &TransitionSystemPtr) -> Vec { - TransitionDecision::from(self, system) - .into_iter() - .filter_map(|transition_decision| transition_decision.resolve(system)) - .map(|transition_decision_point| DecisionPoint::from(&transition_decision_point)) - .collect() + /// Get all possible [`Decision`]s from the initial state of a [`TransitionSystemPtr`] + pub fn get_initial_decisions(system: &TransitionSystemPtr) -> Vec { + Decision::get_decisions_from_state( + system + .get_initial_state() + .expect("Expected system to have initial state"), + system, + ) } } diff --git a/src/Simulation/decision_point.rs b/src/Simulation/decision_point.rs deleted file mode 100644 index 8801cb5b..00000000 --- a/src/Simulation/decision_point.rs +++ /dev/null @@ -1,86 +0,0 @@ -use itertools::Itertools; -use regex::Regex; - -use crate::{ - component::State, - TransitionSystems::{TransitionID, TransitionSystemPtr}, -}; - -use super::transition_decision_point::TransitionDecisionPoint; - -/// Represents a decision in any composition of components: In the current `source` state there is a decision of using one of the `possible_decisions`. -#[derive(Clone, Debug)] -pub struct DecisionPoint { - source: State, - possible_decisions: Vec, -} - -impl DecisionPoint { - pub fn new(source: State, possible_decisions: Vec) -> Self { - Self { - source, - possible_decisions, - } - } - - pub fn source(&self) -> &State { - &self.source - } - - pub fn possible_decisions(&self) -> &[String] { - self.possible_decisions.as_ref() - } - - /// Returns the initial [`DecisionPoint`] in the given [`TransitionSystemPrt`]. - pub fn initial(system: &TransitionSystemPtr) -> Option { - TransitionDecisionPoint::initial(system).map(|initial| DecisionPoint::from(&initial)) - } -} - -impl From<&TransitionDecisionPoint> for DecisionPoint { - fn from(transition_decision_point: &TransitionDecisionPoint) -> Self { - fn is_edge(x: &str) -> bool { - let is_not_edge_regex = Regex::new("(input_).*").unwrap(); // `.unwrap()` always return `Some(...)` here - !is_not_edge_regex.is_match(x) - } - let possible_decisions = transition_decision_point - .possible_decisions() - .iter() - .flat_map(|transition| transition.id.get_leaves().concat()) - .filter_map(|transition_id| match transition_id { - TransitionID::Simple(v) => Some(v), - TransitionID::None => None, - _ => panic!("transition_id should not be other than Simple(_) and None"), - }) - .unique() - .filter(|x| is_edge(x)) - .sorted() - .collect(); - - DecisionPoint { - source: transition_decision_point.source().clone(), - possible_decisions, - } - } -} - -#[cfg(test)] -mod test { - use crate::tests::Simulation::test_data::initial_transition_decision_point_EcdarUniversity_Machine; - - use super::DecisionPoint; - - #[test] - fn from__initial_EcdarUniversity_Machine__returns_correct_DecisionPoint() { - // Arrange - let transition_decision_point = initial_transition_decision_point_EcdarUniversity_Machine(); - - // Act - let actual = DecisionPoint::from(&transition_decision_point); - - // Assert - assert_eq!(actual.possible_decisions.len(), 2); - assert!(actual.possible_decisions().contains(&"E27".to_string())); - assert!(actual.possible_decisions().contains(&"E29".to_string())); - } -} diff --git a/src/Simulation/mod.rs b/src/Simulation/mod.rs index 5058751e..939ef543 100644 --- a/src/Simulation/mod.rs +++ b/src/Simulation/mod.rs @@ -1,5 +1,2 @@ pub mod decision; -pub mod decision_point; pub mod graph_layout; -pub mod transition_decision; -pub mod transition_decision_point; diff --git a/src/Simulation/transition_decision.rs b/src/Simulation/transition_decision.rs deleted file mode 100644 index e3843bdb..00000000 --- a/src/Simulation/transition_decision.rs +++ /dev/null @@ -1,202 +0,0 @@ -use crate::{ - component::{State, Transition}, - TransitionSystems::{TransitionID, TransitionSystemPtr}, -}; - -use super::{decision::Decision, transition_decision_point::TransitionDecisionPoint}; - -/// Represent a decision in a transition system, that has been taken: In the current `source` [`State`] I have `decided` to use this [`Transition`]. -#[derive(Debug)] -pub struct TransitionDecision { - source: State, - decided: Transition, -} - -impl TransitionDecision { - /// Returns all [`TransitionDecision`]s equivalent to the given [`Decision`] in relation to the given [`TransitionSystemPtr`]. - pub fn from(decision: &Decision, system: &TransitionSystemPtr) -> Vec { - fn contains(transition: &Transition, edge_id: &String) -> bool { - transition - .id - .get_leaves() - .concat() - .iter() - .filter_map(|x| match x { - TransitionID::Simple(x) => Some(x), - _ => None, - }) - .any(|x| x == edge_id) - } - let source = decision.source().to_owned(); - let action = decision.decided().get_sync(); - let edge_id = &decision.decided().id; - - // Choose transitions that correspond to a given edge. - system - .next_transitions_if_available(source.get_location(), action) - .into_iter() - .filter(|t| contains(t, edge_id)) - .map(|t| TransitionDecision { - source: source.to_owned(), - decided: t, - }) - .collect::>() - } - - /// Resolves a [`TransitionDecision`]: use the `decided` [`Transition`] and return the [`TransitionDecisionPoint`] of the destination [`State`]. - pub fn resolve(&self, system: &TransitionSystemPtr) -> Option { - let mut source = self.source.to_owned(); - match self.decided.use_transition(&mut source) { - true => Some(TransitionDecisionPoint::from(system, &source)), - false => None, - } - } -} - -#[cfg(test)] -mod tests { - use crate::{ - tests::Simulation::{ - helper::create_system_from_path, - test_data::{create_EcdarUniversity_Machine_system, create_Simulation_Machine_system}, - }, - DataReader::json_reader::read_json_component, - Simulation::{ - decision::Decision, transition_decision::TransitionDecision, - transition_decision_point::TransitionDecisionPoint, - }, - TransitionSystems::TransitionSystemPtr, - }; - - #[test] - fn from__Determinism_NonDeterminismCom__returns_non_deterministic_answer() { - // Arrange - let path = "samples/json/Determinism"; - let component = "NonDeterminismCom"; - let system = create_system_from_path(path, component); - let component = read_json_component(path, component); - - let decision = Decision::new( - system.get_initial_state().unwrap(), - component.get_edges().first().unwrap().to_owned(), - ); - - let expected_len = 1; - - // Act - let actual = TransitionDecision::from(&decision, &system); - - // Assert - assert_eq!(actual.len(), expected_len); - } - - #[test] - fn from__edge_with_action_that_maps_to_single_transition__returns_correct_TransitionDecision() { - // Arrange - let system = create_EcdarUniversity_Machine_system(); - let component = read_json_component("samples/json/EcdarUniversity", "Machine"); - let initial = system.get_initial_state().unwrap(); - let edge = component.get_edges()[4].clone(); - - let decision = Decision::new(initial.clone(), edge); - - let expected = TransitionDecision { - source: initial.clone(), - decided: system - .next_transitions(initial.get_location(), "tea") - .first() - .unwrap() - .to_owned(), - }; - - // Act n Assert - act_and_assert__from__good_Decision__returns_correct_TransitionDecision( - system, decision, expected, - ); - } - - #[test] - fn from__edge_with_action_that_maps_to_multiple_transitions__returns_correct_TransitionDecision( - ) { - // Arrange - let system = create_Simulation_Machine_system(); - let component = read_json_component("samples/json/Simulation", "SimMachine"); - let initial = system.get_initial_state().unwrap(); - let edges = component.get_edges().clone(); - - let decision = Decision::new(initial.clone(), edges[0].clone()); - - let edge_action = edges[0].get_sync(); - - let expected = TransitionDecision { - source: initial.clone(), - decided: system.next_transitions(initial.get_location(), edge_action)[0].clone(), - }; - - // Act n Assert - act_and_assert__from__good_Decision__returns_correct_TransitionDecision( - system, decision, expected, - ); - } - - fn act_and_assert__from__good_Decision__returns_correct_TransitionDecision( - system: TransitionSystemPtr, - decision: Decision, - expected: TransitionDecision, - ) { - // Act - let binding = TransitionDecision::from(&decision, &system); - let actual = binding.first().unwrap(); - - // Assert - assert_eq!(format!("{:?}", actual), format!("{:?}", expected)) - } - - // Yes this test is stupid and bad, no you will not remove it >:( - #[test] - fn resolve__EcdarUniversity_Machine__correct_TransitionDecisionPoint() { - // Arrange - let system = create_EcdarUniversity_Machine_system(); - - let initial = system.get_initial_state().unwrap(); - - let transition = system - .next_transitions_if_available(initial.get_location(), "coin") - .first() - .unwrap() - .to_owned(); - - let decision = TransitionDecision { - source: initial.clone(), - decided: transition.clone(), - }; - - // Act - let actual = decision.resolve(&system).unwrap(); - - // Assert - let actual_source = format!("{:?}", actual.source()); - let actual_possible_decisions: Vec = actual - .possible_decisions() - .iter() - .map(|x| format!("{:?}", x)) - .collect(); - - let mut source = initial; - transition.use_transition(&mut source); - let expected = TransitionDecisionPoint::from(&system, &source); - let expected_source = format!("{:?}", expected.source()); - let expected_possible_decisions = expected - .possible_decisions() - .iter() - .map(|x| format!("{:?}", x)); - - assert_eq!(actual_source, expected_source); - assert_eq!( - actual_possible_decisions.len(), - expected_possible_decisions.len() - ); - - expected_possible_decisions.for_each(|x| assert!(actual_possible_decisions.contains(&x))); - } -} diff --git a/src/Simulation/transition_decision_point.rs b/src/Simulation/transition_decision_point.rs deleted file mode 100644 index ac1204b6..00000000 --- a/src/Simulation/transition_decision_point.rs +++ /dev/null @@ -1,124 +0,0 @@ -use crate::{ - component::{State, Transition}, - TransitionSystems::TransitionSystemPtr, -}; - -/// Represents a decision in a transition system: In the current `source` state there is a decision of using one of the `possible_decisions`. -#[derive(Debug, Clone)] -pub struct TransitionDecisionPoint { - source: State, - possible_decisions: Vec, -} - -impl TransitionDecisionPoint { - /// Constructs the initial [`TransitionDecisionPoint`] for a given [`TransitionSystemPtr`]. - pub fn initial(system: &TransitionSystemPtr) -> Option { - system - .get_initial_state() - .map(|source| Self::from(system, &source)) - } - - /// Constructs the [`TransitionDecisionPoint`] from a `source` [`State`] and a given [`TransitionSystemPtr`]. - pub fn from(system: &TransitionSystemPtr, source: &State) -> TransitionDecisionPoint { - let actions = system.get_actions(); - let transitions: Vec = actions - .into_iter() - // Map actions to transitions. An action can map to multiple actions thus flatten - .flat_map(|action| system.next_transitions_if_available(source.get_location(), &action)) - // Filter transitions that can be used - .filter(|transition| transition.use_transition_alt(source).is_some()) - .collect(); - - TransitionDecisionPoint { - source: source.to_owned(), - possible_decisions: transitions, - } - } - - pub fn source(&self) -> &State { - &self.source - } - - pub fn possible_decisions(&self) -> &[Transition] { - self.possible_decisions.as_ref() - } -} - -#[cfg(test)] -mod tests { - use super::TransitionDecisionPoint; - use crate::tests::Simulation::test_data::{ - create_EcdarUniversity_Machine4_system, create_EcdarUniversity_Machine_system, - }; - - #[test] - fn initial__EcdarUniversity_Machine__return_correct_state() { - // Arrange - let system = create_EcdarUniversity_Machine_system(); - let expected = system.get_initial_state().unwrap(); - - // Act - let actual = TransitionDecisionPoint::initial(&system).unwrap().source; - - // Assert - assert_eq!(format!("{:?}", actual), format!("{:?}", expected)) - } - - // TODO this test is confusing - #[test] - fn initial__EcdarUniversity_Machine__correct_transitions() { - // Arrange - let system = create_EcdarUniversity_Machine_system(); - - // Act - let actual: Vec = TransitionDecisionPoint::initial(&system) - .unwrap() - .possible_decisions - .into_iter() - .map(|x| format!("{:?}", x)) // shhhhhh, close your eyes, this is not logic - .collect(); - - // Assert - let expected_len = 2; - assert_eq!(actual.len(), expected_len); - - let expected_tea_transition = &format!( - "{:?}", - system.next_transitions_if_available(&system.get_initial_location().unwrap(), "tea")[0] - ); - assert!(actual.contains(expected_tea_transition)); - - let expected_coin_transition = &format!( - "{:?}", - system.next_transitions_if_available(&system.get_initial_location().unwrap(), "coin") - [0] - ); - assert!(actual.contains(expected_coin_transition)); - } - - // TODO this test is confusing - #[test] - fn initial__EcdarUniversity_Machine4__correct_transitions() { - // Arrange - let system = create_EcdarUniversity_Machine4_system(); - - // Act - let actual: Vec = TransitionDecisionPoint::initial(&system) - .unwrap() - .possible_decisions - .into_iter() - .map(|x| format!("{:?}", x)) // still no logic to be found here - .collect(); - - // Assert - let expected_len = 1; - assert_eq!(actual.len(), expected_len); - - let expected_coin_transition = &format!( - "{:?}", - system.next_transitions_if_available(&system.get_initial_location().unwrap(), "coin") - [0] - ); - assert!(actual.contains(expected_coin_transition)); - } -} diff --git a/src/System/executable_query.rs b/src/System/executable_query.rs index 6f252ef9..95bfa2fe 100644 --- a/src/System/executable_query.rs +++ b/src/System/executable_query.rs @@ -1,58 +1,45 @@ -use edbm::util::constraints::ClockIndex; - -use crate::component::Transition; use crate::DataReader::component_loader::ComponentLoader; -use crate::ModelObjects::component::Component; use crate::ModelObjects::component::State; use crate::System::reachability; -use crate::System::reachability::Path; use crate::System::refine; use crate::System::save_component::combine_components; -use crate::TransitionSystems::transition_system::PrecheckResult; use crate::TransitionSystems::TransitionSystemPtr; -use super::extract_system_rep::SystemRecipe; -use super::local_consistency::{ConsistencyFailure, ConsistencyResult, DeterminismResult}; -use super::refine::RefinementResult; +use super::query_failures::PathFailure; +use super::query_failures::QueryResult; use super::save_component::PruningStrategy; - -pub enum QueryResult { - Reachability(Path), // This represents a path from start state to end state - Refinement(RefinementResult), - GetComponent(Component), - Consistency(ConsistencyResult), - Determinism(DeterminismResult), - Error(String), -} +use super::specifics::SpecificDecision; impl QueryResult { pub fn print_result(&self, query_str: &str) { match self { - QueryResult::Refinement(RefinementResult::Success) => satisfied(query_str), - QueryResult::Refinement(RefinementResult::Failure(failure)) => { + QueryResult::Refinement(Ok(_)) => satisfied(query_str), + QueryResult::Refinement(Err(failure)) => { not_satisfied(query_str); println!("\nGot failure: {}", failure); } - QueryResult::Reachability(path) => { - if path.was_reachable { + QueryResult::Reachability(path) => match path { + Ok(path) => { satisfied(query_str); - print_path(path.path.as_ref().unwrap()); - } else { - not_satisfied(query_str) + print_path(&path.path); } - } + Err(PathFailure::Unreachable) => { + not_satisfied(query_str); + } + }, - QueryResult::Consistency(ConsistencyResult::Success) => satisfied(query_str), - QueryResult::Consistency(ConsistencyResult::Failure(_)) => not_satisfied(query_str), + QueryResult::Consistency(Ok(_)) => satisfied(query_str), + QueryResult::Consistency(Err(_)) => not_satisfied(query_str), - QueryResult::Determinism(DeterminismResult::Success) => satisfied(query_str), - QueryResult::Determinism(DeterminismResult::Failure(_)) => not_satisfied(query_str), + QueryResult::Determinism(Ok(_)) => satisfied(query_str), + QueryResult::Determinism(Err(_)) => not_satisfied(query_str), QueryResult::GetComponent(_) => { println!("{} -- Component succesfully created", query_str) } - QueryResult::Error(_) => println!("{} -- Failed", query_str), + QueryResult::CustomError(_) => println!("{} -- Failed", query_str), + QueryResult::RecipeFailure(_) => not_satisfied(query_str), }; } } @@ -65,10 +52,15 @@ fn not_satisfied(query_str: &str) { println!("{} -- Property is NOT satisfied", query_str); } -fn print_path(path: &Vec) { +fn print_path(path: &Vec) { println!("Edges that have been taken:"); - for transition in path { - println!("{}", transition.id); + for SpecificDecision { + source_state, + action, + .. + } in path + { + println!("{} from {}", action, source_state); } } @@ -85,12 +77,7 @@ impl ExecutableQuery for RefinementExecutor { fn execute(self: Box) -> QueryResult { let (sys1, sys2) = (self.sys1, self.sys2); - match refine::check_refinement(sys1, sys2) { - RefinementResult::Success => QueryResult::Refinement(RefinementResult::Success), - RefinementResult::Failure(the_failure) => { - QueryResult::Refinement(RefinementResult::Failure(the_failure)) - } - } + refine::check_refinement(sys1, sys2).into() } } @@ -107,14 +94,8 @@ pub struct ReachabilityExecutor { } impl ExecutableQuery for ReachabilityExecutor { fn execute(self: Box) -> QueryResult { - match reachability::find_path( - self.start_state, - self.end_state, - self.transition_system.as_ref(), - ) { - Ok(res) => QueryResult::Reachability(res), - Err(err_msg) => QueryResult::Error(err_msg), - } + reachability::find_specific_path(self.start_state, self.end_state, &self.transition_system) + .into() } } @@ -138,29 +119,12 @@ impl<'a> ExecutableQuery for GetComponentExecutor<'a> { } pub struct ConsistencyExecutor { - pub recipe: Box, - pub dim: ClockIndex, + pub system: TransitionSystemPtr, } impl ExecutableQuery for ConsistencyExecutor { fn execute(self: Box) -> QueryResult { - let res = match self.recipe.compile(self.dim) { - Ok(system) => match system.precheck_sys_rep() { - PrecheckResult::Success => QueryResult::Consistency(ConsistencyResult::Success), - PrecheckResult::NotDeterministic(location, action) => { - QueryResult::Consistency(ConsistencyResult::Failure( - ConsistencyFailure::NotDeterministicFrom(location, action), - )) - } - PrecheckResult::NotConsistent(failure) => { - QueryResult::Consistency(ConsistencyResult::Failure(failure)) - } - }, - Err(error) => QueryResult::Consistency(ConsistencyResult::Failure( - ConsistencyFailure::NotDisjoint(error), - )), - }; - res + self.system.precheck_sys_rep().into() } } @@ -170,7 +134,6 @@ pub struct DeterminismExecutor { impl ExecutableQuery for DeterminismExecutor { fn execute(self: Box) -> QueryResult { - let is_deterministic = self.system.is_deterministic(); - QueryResult::Determinism(is_deterministic) + self.system.check_determinism().into() } } diff --git a/src/System/extract_state.rs b/src/System/extract_state.rs index d8278e56..5740fac3 100644 --- a/src/System/extract_state.rs +++ b/src/System/extract_state.rs @@ -4,7 +4,7 @@ use crate::extract_system_rep::SystemRecipe; use crate::EdgeEval::constraint_applyer::apply_constraints_to_state; use crate::ModelObjects::component::State; use crate::ModelObjects::representations::{BoolExpression, QueryExpression}; -use crate::TransitionSystems::{CompositionType, LocationID, LocationTuple, TransitionSystemPtr}; +use crate::TransitionSystems::{CompositionType, LocationID, LocationTree, TransitionSystemPtr}; use std::slice::Iter; /// This function takes a [`QueryExpression`], the system recipe, and the transitionsystem - @@ -27,11 +27,12 @@ pub fn get_state( _ => unreachable!(), }; } - - Ok(State::create( - build_location_tuple(&mut locations.iter(), machine, system)?, + let mut state = State::create( + build_location_tree(&mut locations.iter(), machine, system)?, create_zone_given_constraints(clock.as_deref(), system)?, - )) + ); + state.apply_invariants(); + Ok(state) } else { Err(format!( "The following information \"{}\" could not be used to create a State", @@ -58,43 +59,40 @@ fn create_zone_given_constraints( .map_err(|clock| format!("Clock {} does not exist in the transition system", clock)) } -fn build_location_tuple( +fn build_location_tree( locations: &mut Iter<&str>, machine: &SystemRecipe, system: &TransitionSystemPtr, -) -> Result { +) -> Result { match machine { SystemRecipe::Composition(left, right) => { let (left_system, right_system) = system.get_children(); - Ok(LocationTuple::compose( - &build_location_tuple(locations, left, left_system)?, - &build_location_tuple(locations, right, right_system)?, + Ok(LocationTree::compose( + &build_location_tree(locations, left, left_system)?, + &build_location_tree(locations, right, right_system)?, CompositionType::Composition, )) } SystemRecipe::Conjunction(left, right) => { let (left_system, right_system) = system.get_children(); - Ok(LocationTuple::compose( - &build_location_tuple(locations, left, left_system)?, - &build_location_tuple(locations, right, right_system)?, + Ok(LocationTree::compose( + &build_location_tree(locations, left, left_system)?, + &build_location_tree(locations, right, right_system)?, CompositionType::Conjunction, )) } SystemRecipe::Quotient(left, right, ..) => { let (left_system, right_system) = system.get_children(); - Ok(LocationTuple::merge_as_quotient( - &build_location_tuple(locations, left, left_system)?, - &build_location_tuple(locations, right, right_system)?, + Ok(LocationTree::merge_as_quotient( + &build_location_tree(locations, left, left_system)?, + &build_location_tree(locations, right, right_system)?, )) } SystemRecipe::Component(component) => match locations.next().unwrap().trim() { // It is ensured .next() will not give a None, since the number of location is same as number of component. This is also being checked in validate_reachability_input function, that is called before get_state - "_" => Ok(LocationTuple::build_any_location_tuple()), + "_" => Ok(LocationTree::build_any_location_tree()), str => system - .get_location(&LocationID::Simple { - location_id: str.to_string(), - component_id: Some(component.get_name().clone()), - }) + .get_location(&LocationID::Simple(str.to_string())) .ok_or(format!( "Location {} does not exist in the component {}", str, diff --git a/src/System/extract_system_rep.rs b/src/System/extract_system_rep.rs index 13d7b109..42d70fec 100644 --- a/src/System/extract_system_rep.rs +++ b/src/System/extract_system_rep.rs @@ -13,75 +13,29 @@ use crate::TransitionSystems::{ CompiledComponent, Composition, Conjunction, Quotient, TransitionSystemPtr, }; +use super::query_failures::SystemRecipeFailure; use crate::component::State; use crate::System::pruning; use crate::TransitionSystems::transition_system::ClockReductionInstruction; use edbm::util::constraints::ClockIndex; use log::debug; use simple_error::bail; -use std::error::Error; -pub struct SystemRecipeFailure { - pub reason: String, - pub left_name: Option, - pub right_name: Option, - pub actions: Vec, +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ExecutableQueryError { + SystemRecipeFailure(SystemRecipeFailure), + Custom(String), } -impl From for String { +impl From for ExecutableQueryError { fn from(failure: SystemRecipeFailure) -> Self { - failure.reason + ExecutableQueryError::SystemRecipeFailure(failure) } } -impl std::fmt::Display for SystemRecipeFailure { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "SystemRecipeFailure: {}", self.reason) - } -} - -impl std::fmt::Debug for SystemRecipeFailure { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "SystemRecipeFailure: {}", self.reason) - } -} -impl Error for SystemRecipeFailure { - fn description(&self) -> &str { - &self.reason - } -} - -impl SystemRecipeFailure { - pub fn new_from_component(reason: String, component: Component, actions: Vec) -> Self { - SystemRecipeFailure { - reason, - left_name: Some(component.get_name().to_string()), - right_name: None, - actions, - } - } - pub fn new( - reason: String, - left: TransitionSystemPtr, - right: TransitionSystemPtr, - actions: Vec, - ) -> Self { - let mut left_name = None; - let mut right_name = None; - - if let Some(location) = left.get_initial_location() { - left_name = location.id.get_component_id() - } - if let Some(location) = right.get_initial_location() { - right_name = location.id.get_component_id() - } - - SystemRecipeFailure { - reason, - left_name, - right_name, - actions, - } +impl> From for ExecutableQueryError { + fn from(failure: T) -> Self { + ExecutableQueryError::Custom(failure.into()) } } @@ -90,7 +44,7 @@ impl SystemRecipeFailure { pub fn create_executable_query<'a>( full_query: &Query, component_loader: &'a mut (dyn ComponentLoader + 'static), -) -> Result, Box> { +) -> Result, ExecutableQueryError> { let mut dim: ClockIndex = 0; if let Some(query) = full_query.get_query() { @@ -102,12 +56,14 @@ pub fn create_executable_query<'a>( let mut right = get_system_recipe(right_side, component_loader, &mut dim, &mut quotient_index); if !component_loader.get_settings().disable_clock_reduction { - clock_reduction::clock_reduce(&mut left, Some(&mut right), &mut dim, quotient_index.is_some())?; + clock_reduction::clock_reduce(&mut left, Some(&mut right), &mut dim, quotient_index)?; } + let mut component_index = 0; + Ok(Box::new(RefinementExecutor { - sys1: left.compile(dim)?, - sys2: right.compile(dim)?, + sys1: left.compile_with_index(dim, &mut component_index)?, + sys2: right.compile_with_index(dim, &mut component_index)?, }))}, QueryExpression::Reachability(automata, start, end) => { let machine = get_system_recipe(automata, component_loader, &mut dim, &mut None); @@ -144,16 +100,15 @@ pub fn create_executable_query<'a>( query_expression, component_loader, &mut dim, - &mut quotient_index + &mut quotient_index, ); if !component_loader.get_settings().disable_clock_reduction { - clock_reduction::clock_reduce(&mut recipe, None, &mut dim, quotient_index.is_some())?; + clock_reduction::clock_reduce(&mut recipe, None, &mut dim, quotient_index)?; } Ok(Box::new(ConsistencyExecutor { - recipe, - dim + system: recipe.compile(dim)? })) }, QueryExpression::Determinism(query_expression) => { @@ -162,11 +117,11 @@ pub fn create_executable_query<'a>( query_expression, component_loader, &mut dim, - &mut quotient_index + &mut quotient_index, ); if !component_loader.get_settings().disable_clock_reduction { - clock_reduction::clock_reduce(&mut recipe, None, &mut dim, quotient_index.is_some())?; + clock_reduction::clock_reduce(&mut recipe, None, &mut dim, quotient_index)?; } Ok(Box::new(DeterminismExecutor { @@ -180,11 +135,11 @@ pub fn create_executable_query<'a>( query_expression, component_loader, &mut dim, - &mut quotient_index + &mut quotient_index, ); if !component_loader.get_settings().disable_clock_reduction { - clock_reduction::clock_reduce(&mut recipe, None, &mut dim, quotient_index.is_some())?; + clock_reduction::clock_reduce(&mut recipe, None, &mut dim, quotient_index)?; } Ok(Box::new( @@ -206,11 +161,10 @@ pub fn create_executable_query<'a>( query_expression, component_loader, &mut dim, - &mut quotient_index - ); + &mut quotient_index, ); if !component_loader.get_settings().disable_clock_reduction { - clock_reduction::clock_reduce(&mut recipe, None, &mut dim, quotient_index.is_some())?; + clock_reduction::clock_reduce(&mut recipe, None, &mut dim, quotient_index)?; } Ok(Box::new( @@ -226,7 +180,7 @@ pub fn create_executable_query<'a>( } , // Should handle consistency, Implementation, determinism and specification here, but we cant deal with it atm anyway - _ => bail!("Not yet setup to handle {:?}", query), + _ => bail!("Not yet setup to handle query"), } } else { bail!("No query was supplied for extraction") @@ -243,23 +197,46 @@ pub enum SystemRecipe { impl SystemRecipe { pub fn compile(self, dim: ClockIndex) -> Result { + let mut component_index = 0; + self._compile(dim + 1, &mut component_index) + } + + pub fn compile_with_index( + self, + dim: ClockIndex, + component_index: &mut u32, + ) -> Result { + self._compile(dim + 1, component_index) + } + + fn _compile( + self, + dim: ClockIndex, + component_index: &mut u32, + ) -> Result { match self { - SystemRecipe::Composition(left, right) => { - Composition::new(left.compile(dim)?, right.compile(dim)?, dim + 1) - } - SystemRecipe::Conjunction(left, right) => { - Conjunction::new(left.compile(dim)?, right.compile(dim)?, dim + 1) - } + SystemRecipe::Composition(left, right) => Composition::new( + left._compile(dim, component_index)?, + right._compile(dim, component_index)?, + dim, + ), + SystemRecipe::Conjunction(left, right) => Conjunction::new( + left._compile(dim, component_index)?, + right._compile(dim, component_index)?, + dim, + ), SystemRecipe::Quotient(left, right, clock_index) => Quotient::new( - left.compile(dim)?, - right.compile(dim)?, + left._compile(dim, component_index)?, + right._compile(dim, component_index)?, clock_index, - dim + 1, + dim, ), - SystemRecipe::Component(comp) => match CompiledComponent::compile(*comp, dim + 1) { - Ok(comp) => Ok(comp), - Err(err) => Err(err), - }, + SystemRecipe::Component(comp) => { + match CompiledComponent::compile(*comp, dim, component_index) { + Ok(comp) => Ok(comp), + Err(err) => Err(err), + } + } } } @@ -404,64 +381,118 @@ pub(crate) mod clock_reduction { /// Function for a "safer" clock reduction that handles both the dimension of the DBM and the quotient index if needed be /// # Arguments - /// `lhs`: The (main) `SystemRecipe` to clock reduce\n - /// `rhs`: An optional `SystemRecipe` used for multiple operands (Refinement)\n + /// `lhs`: The (main) [`SystemRecipe`] to clock reduce\n + /// `rhs`: An optional [`SystemRecipe`] used for multiple operands (Refinement)\n /// `dim`: A mutable reference to the DBMs dimension for updating\n - /// `has_quotient`: A boolean to indicate if there is a quotient clock to update + /// `quotient_clock`: The clock for the quotient (This is not reduced) /// # Returns - /// A `Result` used if the `SystemRecipe`(s) fail during compilation + /// A `Result` used if the [`SystemRecipe`](s) fail during compilation pub fn clock_reduce( lhs: &mut Box, - mut rhs: Option<&mut Box>, + rhs: Option<&mut Box>, dim: &mut usize, - has_quotient: bool, - ) -> Result<(), String> { - let clocks = if let Some(ref mut r) = rhs { - intersect( - lhs.clone().compile(*dim)?.find_redundant_clocks(), - r.clone().compile(*dim)?.find_redundant_clocks(), - ) - } else { - lhs.clone().compile(*dim)?.find_redundant_clocks() - }; + quotient_clock: Option, + ) -> Result<(), SystemRecipeFailure> { + if *dim == 0 { + return Ok(()); + } else if rhs.is_none() { + return clock_reduce_single(lhs, dim, quotient_clock); + } + let rhs = rhs.unwrap(); + + let (l_clocks, r_clocks) = filter_redundant_clocks( + lhs.clone().compile(*dim)?.find_redundant_clocks(), + rhs.clone().compile(*dim)?.find_redundant_clocks(), + quotient_clock, + lhs.get_components() + .iter() + .flat_map(|c| c.declarations.clocks.values().cloned()) + .max() + .unwrap_or_default(), + ); + + debug!("Clocks to be reduced: {l_clocks:?} + {l_clocks:?}"); + *dim -= l_clocks + .iter() + .chain(r_clocks.iter()) + .fold(0, |acc, c| acc + c.clocks_removed_count()); + debug!("New dimension: {dim}"); + rhs.reduce_clocks(r_clocks); + lhs.reduce_clocks(l_clocks); + compress_component_decls(lhs.get_components(), Some(rhs.get_components())); + if quotient_clock.is_some() { + lhs.change_quotient(*dim); + rhs.change_quotient(*dim); + } + + Ok(()) + } + + /// Clock reduces a "single_expression", such as consistency + /// # Arguments + /// + /// * `sys`: The [`SystemRecipe`] to clock reduce + /// * `dim`: the dimension of the system + /// * `quotient_clock`: The clock for the quotient (This is not reduced) + /// + /// returns: Result<(), SystemRecipeFailure> + fn clock_reduce_single( + sys: &mut Box, + dim: &mut usize, + quotient_clock: Option, + ) -> Result<(), SystemRecipeFailure> { + let mut clocks = sys.clone().compile(*dim)?.find_redundant_clocks(); + clocks.retain(|ins| ins.get_clock_index() != quotient_clock.unwrap_or_default()); debug!("Clocks to be reduced: {clocks:?}"); *dim -= clocks .iter() .fold(0, |acc, c| acc + c.clocks_removed_count()); debug!("New dimension: {dim}"); - if let Some(r) = rhs { - r.reduce_clocks(clocks.clone()); - lhs.reduce_clocks(clocks); - compress_component_decls(lhs.get_components(), Some(r.get_components())); - if has_quotient { - lhs.change_quotient(*dim); - r.change_quotient(*dim); - } - } else { - lhs.reduce_clocks(clocks); - compress_component_decls(lhs.get_components(), None); - if has_quotient { - lhs.change_quotient(*dim); - } + sys.reduce_clocks(clocks); + compress_component_decls(sys.get_components(), None); + if quotient_clock.is_some() { + sys.change_quotient(*dim); } Ok(()) } - fn intersect( + fn filter_redundant_clocks( lhs: Vec, rhs: Vec, - ) -> Vec { - lhs.iter() - .filter(|r| r.is_replace()) - .chain(rhs.iter().filter(|r| r.is_replace())) - .chain( - lhs.iter() - .filter(|r| !r.is_replace()) - .filter_map(|c| rhs.iter().filter(|r| !r.is_replace()).find(|rc| *rc == c)), - ) - .cloned() - .collect() + quotient_clock: Option, + split_index: ClockIndex, + ) -> ( + Vec, + Vec, + ) { + fn get_unique_redundant_clocks bool>( + l: Vec, + r: Vec, + quotient: ClockIndex, + bound_predicate: P, + ) -> Vec { + l.into_iter() + // Takes clock instructions that also occur in the rhs system + // This is done because the lhs also finds the redundant clocks from the rhs, + // so to ensure that it should be removed, we check if it occurs on both sides + // which would mean it can be removed + // e.g "A <= B", we can find clocks from B that are not used in A, so they are marked as remove + .filter(|ins| r.contains(ins)) + // Takes all the clocks within the bounds of the given system + // This is done to ensure that we don't try to remove a clock from the rhs system + .filter(|ins| bound_predicate(ins.get_clock_index())) + // Removes the quotient clock + .filter(|ins| ins.get_clock_index() != quotient) + .collect() + } + let quotient_clock = quotient_clock.unwrap_or_default(); + ( + get_unique_redundant_clocks(lhs.clone(), rhs.clone(), quotient_clock, |c| { + c <= split_index + }), + get_unique_redundant_clocks(rhs, lhs, quotient_clock, |c| c > split_index), + ) } fn compress_component_decls( diff --git a/src/System/local_consistency.rs b/src/System/local_consistency.rs index 30c2bb88..4bd1f75f 100644 --- a/src/System/local_consistency.rs +++ b/src/System/local_consistency.rs @@ -1,128 +1,38 @@ -use std::fmt; - use edbm::zones::OwnedFederation; use log::warn; -use crate::extract_system_rep::SystemRecipeFailure; use crate::ModelObjects::component::State; -use crate::TransitionSystems::{LocationID, TransitionSystem}; - -/// The result of a consistency check. -/// If there was a failure, [ConsistencyFailure] will specify the failure. -pub enum ConsistencyResult { - Success, - Failure(ConsistencyFailure), -} - -impl fmt::Display for ConsistencyResult { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - ConsistencyResult::Success => write!(f, "Succes"), - ConsistencyResult::Failure(_) => write!(f, "failure"), - } - } -} -/// The failure of a consistency check. -/// Variants with [LocationID] are specific locations that cause the failure. -#[derive(Debug)] -pub enum ConsistencyFailure { - NoInitialLocation, - EmptyInitialState, - NotConsistentFrom(LocationID, String), - NotDeterministicFrom(LocationID, String), - NotDisjoint(SystemRecipeFailure), -} - -impl fmt::Display for ConsistencyFailure { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - ConsistencyFailure::NoInitialLocation => write!(f, "No Initial State"), - ConsistencyFailure::EmptyInitialState => write!(f, "Empty Initial State"), - ConsistencyFailure::NotConsistentFrom(location, action) => { - write!( - f, - "Not Consistent From {} Failing action {}", - location, action - ) - } - ConsistencyFailure::NotDeterministicFrom(location, action) => { - write!( - f, - "Not Deterministic From {} Failing action {}", - location, action - ) - } - ConsistencyFailure::NotDisjoint(srf) => { - write!(f, "Not Disjoint: {} {:?}", srf.reason, srf.actions) - } - } - } -} +use crate::System::query_failures::{ConsistencyFailure, DeterminismFailure}; +use crate::TransitionSystems::TransitionSystem; -/// The result of a determinism check. -/// Failure includes the [LocationID]. -pub enum DeterminismResult { - Success, - //Failure should contain a DeterminsmFailure which should contain either a location + a string, or a SystemRecipeFailure - Failure(DeterminismFailure), -} - -pub enum DeterminismFailure { - NotDeterministicFrom(LocationID, String), - NotDisjoint(SystemRecipeFailure), -} - -impl fmt::Display for DeterminismResult { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - DeterminismResult::Success => write!(f, "Success"), - DeterminismResult::Failure(DeterminismFailure::NotDeterministicFrom( - location, - action, - )) => { - write!( - f, - "Not Deterministic From {} failing action {}", - location, action - ) - } - DeterminismResult::Failure(DeterminismFailure::NotDisjoint(srf)) => { - write!(f, "Not Disjoint: {} {:?}", srf.reason, srf.actions) - } - } - } -} +use super::query_failures::{ConsistencyResult, DeterminismResult}; ///Local consistency check WITH pruning. pub fn is_least_consistent(system: &dyn TransitionSystem) -> ConsistencyResult { if system.get_initial_location().is_none() { - return ConsistencyResult::Failure(ConsistencyFailure::NoInitialLocation); - //TODO: figure out whether we want empty TS to be consistent - } - - let mut passed = vec![]; - let state = system.get_initial_state(); - if state.is_none() { + ConsistencyFailure::no_initial_state(system) + } else if let Some(mut state) = system.get_initial_state() { + let mut passed = vec![]; + state.extrapolate_max_bounds(system); + consistency_least_helper(state, &mut passed, system) + } else { warn!("Empty initial state"); - return ConsistencyResult::Failure(ConsistencyFailure::EmptyInitialState); + ConsistencyFailure::no_initial_state(system) } - let mut state = state.unwrap(); - state.extrapolate_max_bounds(system); - consistency_least_helper(state, &mut passed, system) } ///Checks if a [TransitionSystem] is deterministic. -pub fn is_deterministic(system: &dyn TransitionSystem) -> DeterminismResult { +pub fn check_determinism(system: &dyn TransitionSystem) -> DeterminismResult { if system.get_initial_location().is_none() { - return DeterminismResult::Success; + return Ok(()); } let mut passed = vec![]; let state = system.get_initial_state(); if state.is_none() { - return DeterminismResult::Success; + return Ok(()); } let mut state = state.unwrap(); - state.set_zone(OwnedFederation::universe(system.get_dim())); + state.update_zone(|_| OwnedFederation::universe(system.get_dim())); is_deterministic_helper(state, &mut passed, system) } @@ -132,7 +42,7 @@ fn is_deterministic_helper( system: &dyn TransitionSystem, ) -> DeterminismResult { if state.is_contained_in_list(passed_list) { - return DeterminismResult::Success; + return Ok(()); } passed_list.push(state.clone()); @@ -150,41 +60,30 @@ fn is_deterministic_helper( state.get_location().id, action ); - return DeterminismResult::Failure(DeterminismFailure::NotDeterministicFrom( - state.get_location().id.clone(), - action, - )); + return DeterminismFailure::from(system, action, &state); } location_fed += allowed_fed; new_state.extrapolate_max_bounds(system); - if let DeterminismResult::Failure(DeterminismFailure::NotDeterministicFrom( - location, - action, - )) = is_deterministic_helper(new_state, passed_list, system) - { - return DeterminismResult::Failure(DeterminismFailure::NotDeterministicFrom( - location, action, - )); - } + is_deterministic_helper(new_state, passed_list, system)?; } } } - DeterminismResult::Success + Ok(()) } /// Local consistency check WITHOUT pruning #[allow(dead_code)] pub fn is_fully_consistent(system: &dyn TransitionSystem) -> ConsistencyResult { if system.get_initial_location().is_none() { - return ConsistencyResult::Failure(ConsistencyFailure::NoInitialLocation); + return ConsistencyFailure::no_initial_state(system); } let mut passed = vec![]; let state = system.get_initial_state(); if state.is_none() { warn!("Empty initial state"); - return ConsistencyResult::Failure(ConsistencyFailure::EmptyInitialState); + return ConsistencyFailure::no_initial_state(system); } consistency_fully_helper(state.unwrap(), &mut passed, system) } @@ -194,19 +93,14 @@ pub fn consistency_least_helper( passed_list: &mut Vec, system: &dyn TransitionSystem, ) -> ConsistencyResult { - let mut failing_action = String::new(); - if state.is_contained_in_list(passed_list) { - return ConsistencyResult::Success; + return Ok(()); } if state.decorated_locations.is_universal() { - return ConsistencyResult::Success; + return Ok(()); } if state.decorated_locations.is_inconsistent() { - return ConsistencyResult::Failure(ConsistencyFailure::NotConsistentFrom( - state.get_location().id.clone(), - failing_action, - )); + return ConsistencyFailure::inconsistent(system, &state); } passed_list.push(state.clone()); @@ -216,23 +110,14 @@ pub fn consistency_least_helper( let mut new_state = state.clone(); if transition.use_transition(&mut new_state) { new_state.extrapolate_max_bounds(system); - if let ConsistencyResult::Failure(failure) = - consistency_least_helper(new_state, passed_list, system) - { - warn!( - "Input \"{input}\" not consistent from {}", - state.get_location().id - ); - return ConsistencyResult::Failure(failure); - } - } else { - failing_action = input.clone(); + + consistency_least_helper(new_state, passed_list, system)?; } } } if state.zone_ref().can_delay_indefinitely() { - return ConsistencyResult::Success; + return Ok(()); } for output in system.get_output_actions() { @@ -240,21 +125,14 @@ pub fn consistency_least_helper( let mut new_state = state.clone(); if transition.use_transition(&mut new_state) { new_state.extrapolate_max_bounds(system); - if let ConsistencyResult::Success = - consistency_least_helper(new_state, passed_list, system) - { - return ConsistencyResult::Success; + if let Ok(()) = consistency_least_helper(new_state, passed_list, system) { + return Ok(()); } - } else { - failing_action = output.clone(); } } } warn!("No saving outputs from {}", state.get_location().id); - ConsistencyResult::Failure(ConsistencyFailure::NotConsistentFrom( - state.get_location().id.clone(), - failing_action, - )) + ConsistencyFailure::inconsistent_from(system, &state) } #[allow(dead_code)] @@ -263,10 +141,8 @@ fn consistency_fully_helper( passed_list: &mut Vec, system: &dyn TransitionSystem, ) -> ConsistencyResult { - let mut failing_action = String::new(); - if state.is_contained_in_list(passed_list) { - return ConsistencyResult::Success; + return Ok(()); } passed_list.push(state.clone()); @@ -278,13 +154,7 @@ fn consistency_fully_helper( if new_state.is_subset_of(&state) { continue; } - if let ConsistencyResult::Failure(failure) = - consistency_fully_helper(new_state, passed_list, system) - { - return ConsistencyResult::Failure(failure); - } - } else { - failing_action = input.clone(); + consistency_fully_helper(new_state, passed_list, system)?; } } } @@ -301,26 +171,17 @@ fn consistency_fully_helper( output_existed = true; - if let ConsistencyResult::Failure(failure) = - consistency_fully_helper(new_state, passed_list, system) - { - return ConsistencyResult::Failure(failure); - } - } else { - failing_action = output.clone(); + consistency_fully_helper(new_state, passed_list, system)?; } } } if output_existed { - ConsistencyResult::Success + Ok(()) } else { let last_state = passed_list.last().unwrap(); match last_state.zone_ref().can_delay_indefinitely() { - false => ConsistencyResult::Failure(ConsistencyFailure::NotConsistentFrom( - last_state.get_location().id.clone(), - failing_action, - )), - true => ConsistencyResult::Success, + false => ConsistencyFailure::inconsistent_from(system, &state), + true => Ok(()), } } } diff --git a/src/System/mod.rs b/src/System/mod.rs index 08fba6bc..086885cc 100644 --- a/src/System/mod.rs +++ b/src/System/mod.rs @@ -4,6 +4,8 @@ pub mod extract_system_rep; pub mod input_enabler; pub mod local_consistency; pub mod pruning; +pub mod query_failures; pub mod reachability; pub mod refine; pub mod save_component; +pub mod specifics; diff --git a/src/System/pruning.rs b/src/System/pruning.rs index 59f69475..88f88fd1 100644 --- a/src/System/pruning.rs +++ b/src/System/pruning.rs @@ -8,9 +8,8 @@ use crate::ModelObjects::component::{ }; use crate::ModelObjects::representations::BoolExpression; use crate::System::save_component::combine_components; -use crate::TransitionSystems::transition_system::PrecheckResult; use crate::TransitionSystems::TransitionSystemPtr; -use crate::TransitionSystems::{CompiledComponent, LocationTuple}; +use crate::TransitionSystems::{CompiledComponent, LocationTree}; use std::collections::{HashMap, HashSet}; @@ -21,9 +20,7 @@ pub fn prune_system(ts: TransitionSystemPtr, dim: ClockIndex) -> TransitionSyste let outputs = ts.get_output_actions(); let comp = combine_components(&ts, PruningStrategy::NoPruning); - if let PrecheckResult::NotDeterministic(_, _) | PrecheckResult::NotConsistent(_) = - ts.precheck_sys_rep() - { + if ts.precheck_sys_rep().is_err() { panic!("Trying to prune transitions system which is not least consistent") } @@ -150,7 +147,7 @@ pub fn prune( new_comp.get_edges().len() ); - match CompiledComponent::compile_with_actions(new_comp, inputs, outputs, dim) { + match CompiledComponent::compile_with_actions(new_comp, inputs, outputs, dim, 0) { Ok(comp) => Ok(comp), Err(e) => Err(format!("Pruning failed: {}", e)), } @@ -499,12 +496,7 @@ fn is_immediately_inconsistent( comp: &Component, dimensions: ClockIndex, ) -> bool { - let loc = LocationTuple::simple( - location, - Some(comp.get_name().to_owned()), - &comp.declarations, - dimensions, - ); + let loc = LocationTree::simple(location, &comp.declarations, dimensions); loc.is_inconsistent() diff --git a/src/System/query_failures.rs b/src/System/query_failures.rs new file mode 100644 index 00000000..ec533030 --- /dev/null +++ b/src/System/query_failures.rs @@ -0,0 +1,723 @@ +use std::{collections::HashSet, fmt}; + +use crate::{ + component::{Component, State}, + ModelObjects::statepair::StatePair, + TransitionSystems::{CompositionType, TransitionSystem, TransitionSystemPtr}, +}; + +use super::specifics::{SpecificPath, SpecificState}; + +/// Represents how a system is composed at the highest level +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum SystemType { + /// A refinement between two systems + Refinement, + /// A quotient of two systems + Quotient, + /// A composition of two systems + Composition, + /// A conjunction of two systems + Conjunction, + /// A simple component, not composed with anything else + Simple, +} + +impl SystemType { + /// Returns the string representation of the operator for this composition type + pub fn op(&self) -> String { + match self { + Self::Quotient => r"\\", + Self::Refinement => "<=", + Self::Composition => "||", + Self::Conjunction => "&&", + Self::Simple => panic!("Simple composition type should not be displayed"), + } + .to_string() + } +} + +impl fmt::Display for SystemType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Quotient => write!(f, "Quotient"), + Self::Refinement => write!(f, "Refinement"), + Self::Composition => write!(f, "Composition"), + Self::Conjunction => write!(f, "Conjunction"), + Self::Simple => write!(f, "Component"), + } + } +} + +/// Represents a system of components as a [String] `name` and the type of the highest level composition `sys_type` +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct System { + pub name: String, + pub sys_type: SystemType, +} +impl System { + /// Creates a new refinement system from two systems, `sys1` and `sys2` + pub fn refinement(sys1: &dyn TransitionSystem, sys2: &dyn TransitionSystem) -> Self { + Self { + name: format!("{} <= {}", sys1.to_string(), sys2.to_string()), + sys_type: SystemType::Refinement, + } + } + /// Creates a new system from a single [TransitionSystem] + pub fn from(sys: &dyn TransitionSystem) -> Self { + Self { + name: sys.to_string(), + sys_type: sys.get_composition_type().into(), + } + } + /// Creates a new system from two [TransitionSystem]s, `sys1` and `sys2`, and the type of the composition `sys_type` + pub fn from2( + sys1: &dyn TransitionSystem, + sys2: &dyn TransitionSystem, + sys_type: SystemType, + ) -> Self { + Self { + name: format!( + "{} {} {}", + sys1.to_string(), + sys_type.op(), + sys2.to_string() + ), + sys_type, + } + } +} + +/// Represents a set of actions in a system of components. The system is represented by a [String] `system`, +/// along with the `actions` and whether the actions are all inputs (`is_input`) or all outputs (`!is_input`). +/// +/// For representing a single action, see [Action]. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ActionSet { + pub system: String, + pub actions: HashSet, + pub is_input: bool, +} + +impl fmt::Display for ActionSet { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let singular = false; //self.actions.len() == 1; + let action_type = if self.is_input { "Input" } else { "Output" }; + + if singular { + write!( + f, + "{} {:?} in system '{}'", + action_type, self.actions, self.system + ) + } else { + write!( + f, + "{}s {:?} in system '{}'", + action_type, self.actions, self.system + ) + } + } +} + +/// Represents a single action in a system of components. The system is represented by a [String] `system`, +/// along with the `action` and whether the action is an input (`is_input`). +/// +/// For representing a set of actions, see [ActionSet]. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Action { + pub name: String, + pub is_input: bool, +} + +impl fmt::Display for Action { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.is_input { + write!(f, "Input \"{}\"", self.name) + } else { + write!(f, "Output \"{}\"", self.name) + } + } +} + +impl Action { + /// Creates a new [Action] from a [String] `name` and whether the action is an input (`is_input`). + pub fn new(name: String, is_input: bool) -> Self { + Self { name, is_input } + } +} + +/// Represents the different types of results that can be returned from a query +#[derive(Clone, Debug)] +pub enum QueryResult { + /// A query failed because the recipe was invalid. e.g. a conjunction was empty or actions mismatched in a composition. + RecipeFailure(SystemRecipeFailure), + /// A reachability query returned a path or failure, see [PathResult]. + Reachability(PathResult), + /// A refinement query returned a success or failure, see [RefinementResult]. + Refinement(RefinementResult), + /// A consistency query returned a success or failure, see [ConsistencyResult]. + Consistency(ConsistencyResult), + /// A determinism query returned a success or failure, see [DeterminismResult]. + Determinism(DeterminismResult), + /// A get components query returned a new component. + GetComponent(Component), + /// The query resulted in an unclassified error. + CustomError(String), +} + +pub type PathResult = Result; + +//TODO: add refinement Ok result +#[allow(clippy::result_large_err)] +pub type RefinementResult = Result<(), RefinementFailure>; + +pub type ConsistencyResult = Result<(), ConsistencyFailure>; + +pub type DeterminismResult = Result<(), DeterminismFailure>; + +/// Represents the different ways that a reachability query can fail +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum PathFailure { + /// The target state was unreachable from the initial state + Unreachable, +} + +/// Represents the different ways that a refinement query can fail +#[allow(clippy::result_large_err)] +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum RefinementFailure { + /// The refinement failed for `system` because the right side cannot match left sides delay after taking `action` from `state`. + CutsDelaySolutions { + system: System, + action: Action, + state: SpecificState, + }, + /// The refinement failed for `system` because one side could not match the `action` from `state`. + CannotMatch { + system: System, + action: Action, + state: SpecificState, + }, + /// The refinement failed on a precondition, see [RefinementPrecondition]. + Precondition(RefinementPrecondition), +} + +/// Represents the different preconditions that a refinement check can fail on +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum RefinementPrecondition { + /// The refinement `system` failed because the `child` had no initial location. + EmptyChild { child: String, system: System }, + /// The refinement `system` failed because the initial state was empty. + EmptyInitialState { system: System }, + /// The refinement `System` failed because of a `ConsistencyFailure`, see [ConsistencyFailure]. + InconsistentChild(ConsistencyFailure, System), + /// The refinement `System` failed because of an `ActionFailure`, see [ActionFailure]. + ActionMismatch(ActionFailure, System), +} + +impl RefinementFailure { + /// Creates a new [RefinementFailure] that failed because the `left` or `!left` system was empty. + pub fn empty_child( + sys1: &dyn TransitionSystem, + sys2: &dyn TransitionSystem, + left: bool, + ) -> RefinementResult { + Err(RefinementFailure::Precondition( + RefinementPrecondition::EmptyChild { + child: if left { + sys1.to_string() + } else { + sys2.to_string() + }, + system: System::refinement(sys1, sys2), + }, + )) + } + /// Creates a new [RefinementFailure] that failed because the initial state was empty. + pub fn empty_initial( + sys1: &dyn TransitionSystem, + sys2: &dyn TransitionSystem, + ) -> RefinementResult { + Err(RefinementFailure::Precondition( + RefinementPrecondition::EmptyInitialState { + system: System::refinement(sys1, sys2), + }, + )) + } + + /// Creates a new [RefinementFailure] that failed because a system could not match an `action` from a [state pair](StatePair). + pub fn cannot_match( + sys1: &dyn TransitionSystem, + sys2: &dyn TransitionSystem, + action: impl Into, + state: &StatePair, + ) -> RefinementResult { + let action: String = action.into(); + let is_input = sys1.inputs_contain(&action) || sys2.inputs_contain(&action); + Err(RefinementFailure::CannotMatch { + system: System::refinement(sys1, sys2), + action: Action::new(action, is_input), + state: SpecificState::from_state_pair(state, sys1, sys2), + }) + } + + /// Creates a new [RefinementFailure] that failed because of a cut delay solution. + pub fn cuts_delays( + sys1: &dyn TransitionSystem, + sys2: &dyn TransitionSystem, + action: impl Into, + state: &StatePair, + ) -> RefinementResult { + let action: String = action.into(); + let is_input = sys1.inputs_contain(&action) || sys2.inputs_contain(&action); + Err(RefinementFailure::CutsDelaySolutions { + system: System::refinement(sys1, sys2), + action: Action::new(action, is_input), + state: SpecificState::from_state_pair(state, sys1, sys2), + }) + } +} + +/// Represents the different ways that actions can mismatch. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ActionFailure { + /// The actions in the first [ActionSet] are not a subset of the actions in the second [ActionSet]. + NotSubset(ActionSet, ActionSet), + /// The actions in the first [ActionSet] are not disjoint from the actions in the second [ActionSet]. + NotDisjoint(ActionSet, ActionSet), +} +#[allow(clippy::result_large_err)] +impl ActionFailure { + /// Creates a new [Result] that failed because the actions in `actions1` from `sys1` are not a disjoint from the actions in `actions2` from `sys2`. + pub fn not_disjoint( + (sys1, actions1): (&dyn TransitionSystem, HashSet), + (sys2, actions2): (&dyn TransitionSystem, HashSet), + ) -> Result { + let is_input1 = sys1.get_input_actions() == actions1; + let is_input2 = sys2.get_input_actions() == actions2; + + debug_assert!(is_input1 || sys1.get_output_actions() == actions1); + debug_assert!(is_input2 || sys2.get_output_actions() == actions2); + + Err(ActionFailure::NotDisjoint( + ActionSet { + system: sys1.to_string(), + actions: actions1, + is_input: is_input1, + }, + ActionSet { + system: sys2.to_string(), + actions: actions2, + is_input: is_input2, + }, + )) + } + + /// Creates a new [Result] that failed because the actions in `inputs` are not a disjoint from the actions in `outputs`. + pub fn not_disjoint_IO( + name: impl Into, + inputs: HashSet, + outputs: HashSet, + ) -> Result<(), ActionFailure> { + let system = name.into(); + Err(ActionFailure::NotDisjoint( + ActionSet { + system: system.clone(), + actions: inputs, + is_input: true, + }, + ActionSet { + system, + actions: outputs, + is_input: false, + }, + )) + } + + /// Creates a new [Result] that failed because the actions in `actions1` from `sys1` are not a subset of the actions in `actions2` from `sys2`. + pub fn not_subset( + (sys1, actions1): (&dyn TransitionSystem, HashSet), + (sys2, actions2): (&dyn TransitionSystem, HashSet), + ) -> Result<(), ActionFailure> { + let is_input1 = sys1.get_input_actions() == actions1; + let is_input2 = sys2.get_input_actions() == actions2; + + debug_assert!(is_input1 || sys1.get_output_actions() == actions1); + debug_assert!(is_input2 || sys2.get_output_actions() == actions2); + + Err(ActionFailure::NotSubset( + ActionSet { + system: sys1.to_string(), + actions: actions1, + is_input: is_input1, + }, + ActionSet { + system: sys2.to_string(), + actions: actions2, + is_input: is_input2, + }, + )) + } + + /// Converts this [ActionFailure] into a [RefinementPrecondition] given the two [TransitionSystem]s that failed. + pub fn to_precondition( + self, + sys1: &dyn TransitionSystem, + sys2: &dyn TransitionSystem, + ) -> RefinementPrecondition { + RefinementPrecondition::ActionMismatch(self, System::refinement(sys1, sys2)) + } + + /// Converts this [ActionFailure] into a [SystemRecipeFailure] given the [TransitionSystem] that failed. + pub fn to_recipe_failure(self, sys: &dyn TransitionSystem) -> SystemRecipeFailure { + SystemRecipeFailure::Action(self, System::from(sys)) + } + + /// Converts this [ActionFailure] into a [SystemRecipeFailure] given the name of the system `sys` that failed. + pub fn to_simple_failure(self, sys: impl Into) -> SystemRecipeFailure { + SystemRecipeFailure::Action( + self, + System { + name: sys.into(), + sys_type: SystemType::Simple, + }, + ) + } + + /// Converts this [ActionFailure] that occured during the construction of a [Quotient](crate::TransitionSystems::Quotient) into a [SystemRecipeFailure] given the two [TransitionSystem]s that failed. + pub fn to_rfq(self, T: &TransitionSystemPtr, S: &TransitionSystemPtr) -> SystemRecipeFailure { + SystemRecipeFailure::Action( + self, + System::from2(T.as_ref(), S.as_ref(), SystemType::Quotient), + ) + } + + /// Converts this [ActionFailure] that occured during the construction of a [Composition](crate::TransitionSystems::Composition) into a [SystemRecipeFailure] given the two [TransitionSystem]s that failed. + pub fn to_rfcomp( + self, + left: TransitionSystemPtr, + right: TransitionSystemPtr, + ) -> SystemRecipeFailure { + SystemRecipeFailure::Action( + self, + System::from2(left.as_ref(), right.as_ref(), SystemType::Composition), + ) + } + + /// Converts this [ActionFailure] that occured during the construction of a [Conjunction](crate::TransitionSystems::Conjunction) into a [SystemRecipeFailure] given the two [TransitionSystem]s that failed. + pub fn to_rfconj( + self, + left: TransitionSystemPtr, + right: TransitionSystemPtr, + ) -> SystemRecipeFailure { + SystemRecipeFailure::Action( + self, + System::from2(left.as_ref(), right.as_ref(), SystemType::Conjunction), + ) + } +} + +/// A query failed because the recipe was invalid. e.g. a conjunction was empty or actions mismatched in a composition +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum SystemRecipeFailure { + /// The recipe failed because of an action mismatch, see [ActionFailure]. + Action(ActionFailure, System), + /// The recipe failed because a conjunction in the system was empty (and therefore inconsistent). + Inconsistent(ConsistencyFailure, System), +} + +/// Represents the different ways that clock reduction can fail. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ClockReductionFailure {} + +/// Represents the different ways that a [TransitionSystem] can fail to be consistent. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ConsistencyFailure { + /// The `system` has no initial state. + NoInitialState { system: String }, + /// The system is not deterministic. + NotDeterministic(DeterminismFailure), + /// The `system` cannot prune an inconsistent locaction `state`. + InconsistentLoc { + system: String, + state: SpecificState, + }, + /// The `system` cannot prune an inconsistent `state`. + InconsistentFrom { + system: String, + state: SpecificState, + }, +} + +impl ConsistencyFailure { + /// Creates a new [ConsistencyFailure] that failed because the system has no initial state. + pub fn no_initial_state(system: &dyn TransitionSystem) -> ConsistencyResult { + Err(ConsistencyFailure::NoInitialState { + system: system.to_string(), + }) + } + + /// Creates a new [ConsistencyFailure] that failed because the system cannot prune an inconsistent location `state`. + pub fn inconsistent(system: &dyn TransitionSystem, state: &State) -> ConsistencyResult { + Err(ConsistencyFailure::InconsistentLoc { + system: system.to_string(), + state: SpecificState::from_state(state, system), + }) + } + + /// Creates a new [ConsistencyFailure] that failed because the system cannot prune an inconsistent `state`. + pub fn inconsistent_from( + system: &dyn TransitionSystem, + //action: impl Into, + state: &State, + ) -> ConsistencyResult { + //let action: String = action.into(); + //let is_input = system.inputs_contain(&action); + Err(ConsistencyFailure::InconsistentFrom { + system: system.to_string(), + //action: Action::new(action, is_input), + state: SpecificState::from_state(state, system), + }) + } + + /// Converts this [ConsistencyFailure] into a [RefinementPrecondition] given the two [TransitionSystem]s that failed. + pub fn to_precondition( + self, + sys1: &dyn TransitionSystem, + sys2: &dyn TransitionSystem, + ) -> RefinementPrecondition { + RefinementPrecondition::InconsistentChild(self, System::refinement(sys1, sys2)) + } + + /// Converts this [ConsistencyFailure] into a [SystemRecipeFailure] given the [TransitionSystem] that failed. + pub fn to_recipe_failure(self, sys: &dyn TransitionSystem) -> SystemRecipeFailure { + SystemRecipeFailure::Inconsistent(self, System::from(sys)) + } + + /// Converts this [ConsistencyFailure] that occured during the construction of a [Quotient](crate::TransitionSystems::Quotient) into a [SystemRecipeFailure] given the two [TransitionSystem]s that failed. + pub fn to_rfq(self, T: &TransitionSystemPtr, S: &TransitionSystemPtr) -> SystemRecipeFailure { + SystemRecipeFailure::Inconsistent( + self, + System::from2(T.as_ref(), S.as_ref(), SystemType::Quotient), + ) + } +} + +/// Represents how a [TransitionSystem] named `system` failed to be deterministic for `action` in `state`. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct DeterminismFailure { + pub system: String, + pub action: Action, + pub state: SpecificState, +} + +impl DeterminismFailure { + /// Creates a new [DeterminismFailure] from a `system`, `action`, and `state`. + pub fn from( + system: &dyn TransitionSystem, + action: impl Into, + state: &State, + ) -> DeterminismResult { + let action: String = action.into(); + let is_input = system.inputs_contain(&action); + Err(DeterminismFailure { + system: system.to_string(), + action: Action::new(action, is_input), + state: SpecificState::from_state(state, system), + }) + } +} + +// ---------------------------- // +// --- Format Display Impl --- // +// ---------------------------- // + +impl std::fmt::Display for DeterminismFailure { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "The system '{}' is not deterministic in state {} for {}", + self.system, self.state, self.action + ) + } +} + +impl std::fmt::Display for RefinementFailure { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + RefinementFailure::CutsDelaySolutions { + system, + action, + state, + } => write!( + f, + "The refinement '{}' fails because delay solutions are cut in state {} for {}", + system.name, state, action + ), + RefinementFailure::CannotMatch { + system, + action, + state, + } => write!( + f, + "The refinement '{}' fails in state {} because {} cannot be matched", + system.name, state, action + ), + RefinementFailure::Precondition(precond) => precond.fmt(f), + } + } +} + +impl std::fmt::Display for ConsistencyFailure { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ConsistencyFailure::NoInitialState { system } => write!( + f, + "The system '{}' is inconsistent because it has no initial state", + system + ), + ConsistencyFailure::NotDeterministic(determ) => determ.fmt(f), + ConsistencyFailure::InconsistentLoc { system, state } + | ConsistencyFailure::InconsistentFrom { system, state } => write!( + f, + "The system '{}' is inconsistent because there are no saving outputs from state {}", + system, state + ), + } + } +} + +impl std::fmt::Display for SystemRecipeFailure { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SystemRecipeFailure::Action(action, system) => write!( + f, + "{} in {} is invalid: {}", + system.sys_type, system.name, action + ), + SystemRecipeFailure::Inconsistent(cf, system) => { + write!( + f, + "{} in {} is invalid: {}", + system.sys_type, system.name, cf + ) + } + } + } +} + +impl std::fmt::Display for ActionFailure { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ActionFailure::NotSubset(a, b) => { + write!(f, "{} are not a subset of {}", a, b) + } + ActionFailure::NotDisjoint(a, b) => { + write!(f, "{} are not disjoint from {}", a, b) + } + } + } +} + +impl std::fmt::Display for ClockReductionFailure { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Unknown error occured during clock reduction") + } +} + +impl std::fmt::Display for RefinementPrecondition { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + RefinementPrecondition::EmptyChild { child, system } => write!( + f, + "The refinement '{}' fails because '{}' is empty", + system.name, child + ), + RefinementPrecondition::EmptyInitialState { system } => write!( + f, + "The refinement '{}' fails because it has no initial state", + system.name + ), + RefinementPrecondition::InconsistentChild(cf, system) => write!( + f, + "The refinement '{}' fails because of inconsistent system: {}", + system.name, cf + ), + RefinementPrecondition::ActionMismatch(action, system) => { + write!( + f, + "The refinement '{}' fails based on actions: {}", + system.name, action + ) + } + } + } +} + +// ------------------------------- // +// - Ugly conversions begin here - // +// ----- You have been warned ---- // +// ------------------------------- // +mod conversions { + use super::*; + use std::error::Error; + impl Error for SystemRecipeFailure {} + impl Error for ClockReductionFailure {} + impl Error for RefinementFailure {} + impl Error for ConsistencyFailure {} + impl Error for DeterminismFailure {} + + impl From for RefinementFailure { + fn from(failure: RefinementPrecondition) -> Self { + RefinementFailure::Precondition(failure) + } + } + + impl From for SystemType { + fn from(comp: CompositionType) -> Self { + match comp { + CompositionType::Quotient => SystemType::Quotient, + CompositionType::Composition => SystemType::Composition, + CompositionType::Conjunction => SystemType::Conjunction, + CompositionType::Simple => SystemType::Simple, + } + } + } + + impl From for ConsistencyFailure { + fn from(failure: DeterminismFailure) -> Self { + ConsistencyFailure::NotDeterministic(failure) + } + } + + impl From for QueryResult { + fn from(res: SystemRecipeFailure) -> Self { + QueryResult::RecipeFailure(res) + } + } + + impl From for QueryResult { + fn from(res: PathResult) -> Self { + QueryResult::Reachability(res) + } + } + + impl From for QueryResult { + fn from(res: ConsistencyResult) -> Self { + QueryResult::Consistency(res) + } + } + + impl From for QueryResult { + fn from(res: DeterminismResult) -> Self { + QueryResult::Determinism(res) + } + } + + impl From for QueryResult { + fn from(res: RefinementResult) -> Self { + QueryResult::Refinement(res) + } + } +} diff --git a/src/System/reachability.rs b/src/System/reachability.rs index 59c0797c..8062081d 100644 --- a/src/System/reachability.rs +++ b/src/System/reachability.rs @@ -1,41 +1,40 @@ use edbm::zones::OwnedFederation; -use crate::component::LocationType; +use super::query_failures::PathFailure; +use super::specifics::SpecificPath; use crate::ModelObjects::component::{State, Transition}; -use crate::TransitionSystems::{LocationID, TransitionSystem}; +use crate::Simulation::decision::Decision; +use crate::TransitionSystems::{LocationID, TransitionSystemPtr}; use std::collections::{HashMap, VecDeque}; use std::rc::Rc; +use super::query_failures::PathResult; + /// This holds the result of a reachability query +#[derive(Debug, Clone)] pub struct Path { - pub path: Option>, - pub was_reachable: bool, + pub path: Vec, } + // This holds which transition from which state (the destination_state of the previous_sub_path) we took to reach this state struct SubPath { previous_sub_path: Option>, destination_state: State, - transition: Option, + transition: Option<(Transition, String)>, } fn is_trivially_unreachable(start_state: &State, end_state: &State) -> bool { + // If any of the zones are empty + if start_state.zone_ref().is_empty() || end_state.zone_ref().is_empty() { + return true; + } + // If the end location has invariants and these do not have an intersection (overlap) with the zone of the end state of the query if let Some(invariants) = end_state.get_location().get_invariants() { if !end_state.zone_ref().has_intersection(invariants) { return true; } } - // If the start state is a universal or inconsistent location and the end state isn't - // Since universal and inconsistent locations cannot reach other locations - if matches!( - start_state.decorated_locations.loc_type, - LocationType::Universal | LocationType::Inconsistent - ) && !start_state - .decorated_locations - .compare_partial_locations(&end_state.decorated_locations) - { - return true; - } false } @@ -74,16 +73,21 @@ fn is_trivially_unreachable(start_state: &State, end_state: &State) -> bool { pub fn find_path( start_state: State, end_state: State, - system: &dyn TransitionSystem, -) -> Result { + system: &TransitionSystemPtr, +) -> Result { if is_trivially_unreachable(&start_state, &end_state) { - return Ok(Path { - path: None, - was_reachable: false, - }); + return Err(PathFailure::Unreachable); } - Ok(reachability_search(&start_state, &end_state, system)) + reachability_search(&start_state, &end_state, system) +} + +pub fn find_specific_path( + start_state: State, + end_state: State, + system: &TransitionSystemPtr, +) -> PathResult { + find_path(start_state, end_state, system).map(|p| SpecificPath::from_path(&p, system.as_ref())) } /// Currently runs a BFS search on the transition system. @@ -93,13 +97,11 @@ pub fn find_path( fn reachability_search( start_state: &State, end_state: &State, - system: &dyn TransitionSystem, -) -> Path { + system: &TransitionSystemPtr, +) -> Result { // Apply the invariant of the start state to the start state - let mut start_clone = start_state.clone(); - let start_zone = start_clone.take_zone(); - let zone = start_clone.decorated_locations.apply_invariants(start_zone); - start_clone.set_zone(zone); + let mut start_state = start_state.clone(); + start_state.apply_invariants(); // hashmap linking every location to all its current zones let mut visited_states: HashMap> = HashMap::new(); @@ -112,21 +114,21 @@ fn reachability_search( // Push start state to visited state visited_states.insert( - start_clone.get_location().id.clone(), - vec![start_clone.zone_ref().clone()], + start_state.get_location().id.clone(), + vec![start_state.zone_ref().clone()], ); - // Push state state to frontier + // Push initial state to frontier frontier_states.push_back(Rc::new(SubPath { previous_sub_path: None, - destination_state: start_clone, + destination_state: start_state.clone(), transition: None, })); // Take the first state from the frontier and explore it while let Some(sub_path) = frontier_states.pop_front() { if reached_end_state(&sub_path.destination_state, end_state) { - return make_path(sub_path); + return Ok(make_path(sub_path, start_state)); } for action in &actions { @@ -139,15 +141,13 @@ fn reachability_search( &mut frontier_states, &mut visited_states, system, + action, ); } } } // If nothing has been found, it is not reachable - Path { - path: None, - was_reachable: false, - } + Err(PathFailure::Unreachable) } fn reached_end_state(cur_state: &State, end_state: &State) -> bool { @@ -162,11 +162,13 @@ fn take_transition( transition: &Transition, frontier_states: &mut VecDeque>, visited_states: &mut HashMap>, - system: &dyn TransitionSystem, + system: &TransitionSystemPtr, + action: &str, ) { let mut new_state = sub_path.destination_state.clone(); if transition.use_transition(&mut new_state) { - new_state.extrapolate_max_bounds(system); // Ensures the bounds cant grow infinitely, avoiding infinite loops in an edge case TODO: does not take end state zone into account, leading to a very rare edge case + // TODO: bounds here are not always correct, they should take the added bounds from the target state into account + new_state.extrapolate_max_bounds(system.as_ref()); // Ensures the bounds cant grow infinitely, avoiding infinite loops let new_location_id = &new_state.get_location().id; let existing_zones = visited_states.entry(new_location_id.clone()).or_default(); // If this location has not already been reached (explored) with a larger zone @@ -182,7 +184,7 @@ fn take_transition( frontier_states.push_back(Rc::new(SubPath { previous_sub_path: Some(Rc::clone(sub_path)), destination_state: new_state, - transition: Some(transition.clone()), + transition: Some((transition.clone(), action.to_string())), })); } } @@ -209,8 +211,8 @@ fn remove_existing_subsets_of_zone( existing_zones.retain(|existing_zone| !existing_zone.subset_eq(new_zone)); } /// Makes the path from the last subpath -fn make_path(mut sub_path: Rc) -> Path { - let mut path: Vec = Vec::new(); +fn make_path(mut sub_path: Rc, start_state: State) -> Path { + let mut path: Vec<(Transition, String)> = Vec::new(); // Traverse the subpaths to make the path (from end location to start location) while sub_path.previous_sub_path.is_some() { path.push(sub_path.transition.clone().unwrap()); @@ -218,9 +220,18 @@ fn make_path(mut sub_path: Rc) -> Path { } // Reverse the path since the transitions are in reverse order (now from start location to end location) path.reverse(); + let mut state = start_state; - Path { - path: Some(path), - was_reachable: true, + let mut decisions = Vec::new(); + + for (transition, action) in path { + let decision = Decision::from_state_transition(state.clone(), &transition, action) + .expect("If the transition is in a path, it should lead to a non-empty state"); + + decisions.push(decision); + + transition.use_transition(&mut state); } + + Path { path: decisions } } diff --git a/src/System/refine.rs b/src/System/refine.rs index 5eb1fc56..80156adc 100644 --- a/src/System/refine.rs +++ b/src/System/refine.rs @@ -1,42 +1,18 @@ use edbm::zones::OwnedFederation; -use log::{debug, info, log_enabled, trace, warn, Level}; +use log::{debug, info, log_enabled, trace, Level}; use crate::DataTypes::{PassedStateList, PassedStateListExt, WaitingStateList}; use crate::ModelObjects::component::Transition; -use crate::extract_system_rep::SystemRecipeFailure; use crate::ModelObjects::statepair::StatePair; -use crate::System::local_consistency::ConsistencyFailure; -use crate::TransitionSystems::common::CollectionOperation; -use crate::TransitionSystems::transition_system::PrecheckResult; -use crate::TransitionSystems::{LocationID, LocationTuple, TransitionSystemPtr}; +use crate::System::query_failures::RefinementFailure; +use crate::TransitionSystems::{LocationTree, TransitionSystemPtr}; use std::collections::HashSet; -use std::fmt; -/// The result of a refinement check. [RefinementFailure] specifies the failure. -#[allow(clippy::large_enum_variant)] //TODO: consider boxing the large fields to reduce the total size of the enum -pub enum RefinementResult { - Success, - Failure(RefinementFailure), -} +use super::query_failures::{ActionFailure, RefinementPrecondition, RefinementResult}; + +const SUCCESS: RefinementResult = Ok(()); -/// The failure of a refinement check. Variants with [StatePair] include the -/// state that caused the failure. Variants with [LocationID] include the -/// locations that caused failure. -#[derive(Debug)] -pub enum RefinementFailure { - NotDisjointAndNotSubset(SystemRecipeFailure), - NotDisjoint(SystemRecipeFailure), - NotSubset, - CutsDelaySolutions(StatePair), - InitialState(StatePair), - EmptySpecification, - EmptyImplementation, - EmptyTransition2s(StatePair), - NotEmptyResult(StatePair), - ConsistencyFailure(Option, Option), - DeterminismFailure(Option, Option), -} enum StatePairResult { Valid, EmptyTransition2s, @@ -44,35 +20,21 @@ enum StatePairResult { CutsDelaySolutions, } -impl fmt::Display for RefinementFailure { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +impl StatePairResult { + fn check( + &self, + sys1: &TransitionSystemPtr, + sys2: &TransitionSystemPtr, + action: &str, + curr_pair: &StatePair, + ) -> RefinementResult { match self { - RefinementFailure::NotDisjointAndNotSubset(_) => { - write!(f, "Not Disjoint and Not Subset") - } - RefinementFailure::NotDisjoint(_) => write!(f, "Not Disjoint"), - RefinementFailure::NotSubset => write!(f, "Not Subset"), - RefinementFailure::CutsDelaySolutions(_) => write!(f, "Cuts Delay Solutions"), - RefinementFailure::InitialState(_) => write!(f, "Error in Initial State"), - RefinementFailure::EmptySpecification => write!(f, "Empty Specification"), - RefinementFailure::EmptyImplementation => write!(f, "Empty Implementation"), - RefinementFailure::EmptyTransition2s(_) => write!(f, "Empty Transition2s"), - RefinementFailure::NotEmptyResult(_) => write!(f, "Not Empty Result on State Pair"), - RefinementFailure::ConsistencyFailure(location, action) => { - write!( - f, - "Not Consistent From {} failing action {}", - location.as_ref().unwrap(), - action.as_ref().unwrap() - ) + StatePairResult::Valid => Ok(()), + StatePairResult::EmptyTransition2s | StatePairResult::NotEmptyResult => { + RefinementFailure::cannot_match(sys1.as_ref(), sys2.as_ref(), action, curr_pair) } - RefinementFailure::DeterminismFailure(location, action) => { - write!( - f, - "Not Deterministic From {} failing action {}", - location.as_ref().unwrap(), - action.as_ref().unwrap() - ) + StatePairResult::CutsDelaySolutions => { + RefinementFailure::cuts_delays(sys1.as_ref(), sys2.as_ref(), action, curr_pair) } } } @@ -133,10 +95,7 @@ pub fn check_refinement(sys1: TransitionSystemPtr, sys2: TransitionSystemPtr) -> debug!("Dimensions: {}", dimensions); //Firstly we check the preconditions - if let RefinementResult::Failure(failure) = check_preconditions(&sys1, &sys2) { - warn!("Refinement failed with failure: {}", failure); - return RefinementResult::Failure(failure); - } + check_preconditions(&sys1, &sys2)?; // Common inputs and outputs let inputs = common_actions(&sys1, &sys2, true); @@ -167,14 +126,14 @@ pub fn check_refinement(sys1: TransitionSystemPtr, sys2: TransitionSystemPtr) -> if initial_locations_1.is_none() { if initial_locations_2.is_none() { // Both are empty, so trivially true - return RefinementResult::Success; + return SUCCESS; } - return RefinementResult::Failure(RefinementFailure::EmptyImplementation); + return RefinementFailure::empty_child(sys1.as_ref(), sys2.as_ref(), true); } if initial_locations_2.is_none() { //The empty automata cannot implement - return RefinementResult::Failure(RefinementFailure::EmptySpecification); + return RefinementFailure::empty_child(sys1.as_ref(), sys2.as_ref(), false); } let initial_locations_1 = initial_locations_1.unwrap(); @@ -187,7 +146,7 @@ pub fn check_refinement(sys1: TransitionSystemPtr, sys2: TransitionSystemPtr) -> ); if !prepare_init_state(&mut initial_pair, initial_locations_1, initial_locations_2) { - return RefinementResult::Failure(RefinementFailure::InitialState(initial_pair)); + return RefinementFailure::empty_initial(sys1.as_ref(), sys2.as_ref()); } initial_pair.extrapolate_max_bounds(context.sys1, context.sys2); @@ -209,52 +168,14 @@ pub fn check_refinement(sys1: TransitionSystemPtr, sys2: TransitionSystemPtr) -> sys2.next_outputs(curr_pair.get_locations2(), output) }; - match has_valid_state_pairs( + has_valid_state_pairs( &output_transition1, &output_transition2, &curr_pair, &mut context, true, - ) { - StatePairResult::Valid => trace!("Created state pairs for input {}", output), - StatePairResult::EmptyTransition2s => { - log_refinement_check_failure( - &output_transition1, - &output_transition2, - &curr_pair, - &context, - output, - false, - ); - return RefinementResult::Failure(RefinementFailure::EmptyTransition2s( - curr_pair, - )); - } - StatePairResult::NotEmptyResult => { - log_refinement_check_failure( - &output_transition1, - &output_transition2, - &curr_pair, - &context, - output, - false, - ); - return RefinementResult::Failure(RefinementFailure::NotEmptyResult(curr_pair)); - } - StatePairResult::CutsDelaySolutions => { - log_refinement_check_failure( - &output_transition1, - &output_transition2, - &curr_pair, - &context, - output, - false, - ); - return RefinementResult::Failure(RefinementFailure::CutsDelaySolutions( - curr_pair, - )); - } - } + ) + .check(&sys1, &sys2, output, &curr_pair)?; } for input in &inputs { @@ -268,52 +189,14 @@ pub fn check_refinement(sys1: TransitionSystemPtr, sys2: TransitionSystemPtr) -> let input_transitions2 = sys2.next_inputs(curr_pair.get_locations2(), input); - match has_valid_state_pairs( + has_valid_state_pairs( &input_transitions2, &input_transitions1, &curr_pair, &mut context, false, - ) { - StatePairResult::Valid => trace!("Created state pairs for input {}", input), - StatePairResult::EmptyTransition2s => { - log_refinement_check_failure( - &input_transitions1, - &input_transitions2, - &curr_pair, - &context, - input, - true, - ); - return RefinementResult::Failure(RefinementFailure::EmptyTransition2s( - curr_pair, - )); - } - StatePairResult::NotEmptyResult => { - log_refinement_check_failure( - &input_transitions1, - &input_transitions2, - &curr_pair, - &context, - input, - true, - ); - return RefinementResult::Failure(RefinementFailure::NotEmptyResult(curr_pair)); - } - StatePairResult::CutsDelaySolutions => { - log_refinement_check_failure( - &input_transitions1, - &input_transitions2, - &curr_pair, - &context, - input, - true, - ); - return RefinementResult::Failure(RefinementFailure::CutsDelaySolutions( - curr_pair, - )); - } - } + ) + .check(&sys1, &sys2, input, &curr_pair)?; } } info!("Refinement check passed"); @@ -322,36 +205,7 @@ pub fn check_refinement(sys1: TransitionSystemPtr, sys2: TransitionSystemPtr) -> print_relation(&context.passed_list); } - RefinementResult::Success -} - -fn log_refinement_check_failure( - transitions1: &Vec, - transitions2: &Vec, - curr_pair: &StatePair, - context: &RefinementContext, - action: &String, - is_input: bool, -) { - let action_type = if is_input { - String::from("Input") - } else { - String::from("Output") - }; - info!("Refinement check failed for {} {:?}", action_type, action); - if log_enabled!(Level::Debug) { - debug!("Transitions1:"); - for t in transitions1 { - debug!("{}", t); - } - debug!("Transitions2:"); - for t in transitions2 { - debug!("{}", t); - } - debug!("Current pair: {}", curr_pair); - debug!("Relation:"); - print_relation(&context.passed_list); - } + SUCCESS } fn print_relation(passed_list: &PassedStateList) { @@ -551,8 +405,8 @@ fn build_state_pair( fn prepare_init_state( initial_pair: &mut StatePair, - initial_locations_1: LocationTuple, - initial_locations_2: LocationTuple, + initial_locations_1: LocationTree, + initial_locations_2: LocationTree, ) -> bool { let mut sp_zone = initial_pair.take_zone(); sp_zone = initial_locations_1.apply_invariants(sp_zone); @@ -563,67 +417,14 @@ fn prepare_init_state( !initial_pair.ref_zone().is_empty() } -fn check_preconditions(sys1: &TransitionSystemPtr, sys2: &TransitionSystemPtr) -> RefinementResult { - match sys1.precheck_sys_rep() { - PrecheckResult::Success => {} - PrecheckResult::NotDeterministic(location, action) => { - warn!("Refinement failed - sys1 is not deterministic"); - return RefinementResult::Failure(RefinementFailure::DeterminismFailure( - Some(location), - Some(action), - )); - } - PrecheckResult::NotConsistent(failure) => { - warn!("Refinement failed - sys1 is inconsistent"); - match failure { - ConsistencyFailure::NoInitialLocation | ConsistencyFailure::EmptyInitialState => { - return RefinementResult::Failure(RefinementFailure::ConsistencyFailure( - None, None, - )) - } - ConsistencyFailure::NotConsistentFrom(location, action) - | ConsistencyFailure::NotDeterministicFrom(location, action) => { - return RefinementResult::Failure(RefinementFailure::ConsistencyFailure( - Some(location), - Some(action), - )) - } - ConsistencyFailure::NotDisjoint(srf) => { - return RefinementResult::Failure(RefinementFailure::NotDisjoint(srf)) - } - } - } - } - match sys2.precheck_sys_rep() { - PrecheckResult::Success => {} - PrecheckResult::NotDeterministic(location, action) => { - warn!("Refinement failed - sys2 is not deterministic"); - return RefinementResult::Failure(RefinementFailure::DeterminismFailure( - Some(location), - Some(action), - )); - } - PrecheckResult::NotConsistent(failure) => { - warn!("Refinement failed - sys2 is inconsistent"); - match failure { - ConsistencyFailure::NoInitialLocation | ConsistencyFailure::EmptyInitialState => { - return RefinementResult::Failure(RefinementFailure::ConsistencyFailure( - None, None, - )) - } - ConsistencyFailure::NotConsistentFrom(location, action) - | ConsistencyFailure::NotDeterministicFrom(location, action) => { - return RefinementResult::Failure(RefinementFailure::ConsistencyFailure( - Some(location), - Some(action), - )) - } - ConsistencyFailure::NotDisjoint(srf) => { - return RefinementResult::Failure(RefinementFailure::NotDisjoint(srf)) - } - } - } - } +fn check_preconditions( + sys1: &TransitionSystemPtr, + sys2: &TransitionSystemPtr, +) -> Result<(), RefinementPrecondition> { + sys1.precheck_sys_rep() + .map_err(|e| e.to_precondition(sys1.as_ref(), sys2.as_ref()))?; + sys2.precheck_sys_rep() + .map_err(|e| e.to_precondition(sys1.as_ref(), sys2.as_ref()))?; let s_outputs = sys1.get_output_actions(); let t_outputs = sys2.get_output_actions(); @@ -631,46 +432,19 @@ fn check_preconditions(sys1: &TransitionSystemPtr, sys2: &TransitionSystemPtr) - let s_inputs = sys1.get_input_actions(); let t_inputs = sys2.get_input_actions(); - let mut disjoint = true; - - let mut actions = vec![]; - if let Err(action) = s_inputs.is_disjoint_action(&t_outputs) { - disjoint = false; - actions.extend(action); - } - if let Err(action) = t_inputs.is_disjoint_action(&s_outputs) { - disjoint = false; - actions.extend(action); - } - - let subset = s_inputs.is_subset(&t_inputs) && t_outputs.is_subset(&s_outputs); - - debug!("Disjoint {disjoint}, subset {subset}"); - debug!("S i:{s_inputs:?} o:{s_outputs:?}"); - debug!("T i:{t_inputs:?} o:{t_outputs:?}"); - - if !disjoint && !subset { - warn!("Refinement failed - Systems are not disjoint and not subset"); - RefinementResult::Failure(RefinementFailure::NotDisjointAndNotSubset( - SystemRecipeFailure::new( - "Not Disjoint and not subset".to_string(), - sys1.to_owned(), - sys2.to_owned(), - actions, - ), - )) - } else if !subset { - warn!("Refinement failed - Systems are not subset"); - RefinementResult::Failure(RefinementFailure::NotSubset) - } else if !disjoint { - warn!("Refinement failed - Systems not disjoint"); - RefinementResult::Failure(RefinementFailure::NotDisjoint(SystemRecipeFailure::new( - "Not Disjoint".to_string(), - sys1.to_owned(), - sys2.to_owned(), - actions, - ))) + if !s_inputs.is_disjoint(&t_outputs) { + ActionFailure::not_disjoint((sys1.as_ref(), s_inputs), (sys2.as_ref(), t_outputs)) + .map_err(|e| e.to_precondition(sys1.as_ref(), sys2.as_ref())) + } else if !t_inputs.is_disjoint(&s_outputs) { + ActionFailure::not_disjoint((sys2.as_ref(), t_inputs), (sys1.as_ref(), s_outputs)) + .map_err(|e| e.to_precondition(sys1.as_ref(), sys2.as_ref())) + } else if !s_inputs.is_subset(&t_inputs) { + ActionFailure::not_subset((sys1.as_ref(), s_inputs), (sys2.as_ref(), t_inputs)) + .map_err(|e| e.to_precondition(sys1.as_ref(), sys2.as_ref())) + } else if !t_outputs.is_subset(&s_outputs) { + ActionFailure::not_subset((sys2.as_ref(), t_outputs), (sys1.as_ref(), s_outputs)) + .map_err(|e| e.to_precondition(sys1.as_ref(), sys2.as_ref())) } else { - RefinementResult::Success + Ok(()) } } diff --git a/src/System/save_component.rs b/src/System/save_component.rs index 08f3337c..ca060507 100644 --- a/src/System/save_component.rs +++ b/src/System/save_component.rs @@ -1,6 +1,7 @@ +use crate::component::LocationType; use crate::ModelObjects::component::{Component, Declarations, Edge, Location, SyncType}; use crate::ModelObjects::representations::BoolExpression; -use crate::TransitionSystems::{LocationTuple, TransitionSystemPtr}; +use crate::TransitionSystems::{LocationTree, TransitionSystemPtr}; use std::collections::HashMap; pub enum PruningStrategy { @@ -15,19 +16,19 @@ pub fn combine_components( system: &TransitionSystemPtr, reachability: PruningStrategy, ) -> Component { - let mut location_tuples = vec![]; + let mut location_trees = vec![]; let mut edges = vec![]; let clocks = get_clock_map(system); match reachability { Reachable => { - collect_reachable_edges_and_locations(system, &mut location_tuples, &mut edges, &clocks) + collect_reachable_edges_and_locations(system, &mut location_trees, &mut edges, &clocks) } NoPruning => { - collect_all_edges_and_locations(system, &mut location_tuples, &mut edges, &clocks) + collect_all_edges_and_locations(system, &mut location_trees, &mut edges, &clocks) } }; - let locations = get_locations_from_tuples(&location_tuples, &clocks); + let locations = get_locations_from_trees(&location_trees, &clocks); Component { name: "".to_string(), @@ -40,11 +41,11 @@ pub fn combine_components( } } -pub fn get_locations_from_tuples( - location_tuples: &[LocationTuple], +pub fn get_locations_from_trees( + location_trees: &[LocationTree], clock_map: &HashMap, ) -> Vec { - location_tuples + location_trees .iter() .cloned() .map(|loc_vec| { @@ -52,10 +53,16 @@ pub fn get_locations_from_tuples( BoolExpression::from_disjunction(&fed.minimal_constraints(), clock_map) }); + let location_type = if loc_vec.is_initial() { + LocationType::Initial + } else { + LocationType::Normal + }; + Location { id: loc_vec.id.to_string(), invariant, - location_type: loc_vec.loc_type, + location_type, urgency: "NORMAL".to_string(), //TODO: Handle different urgencies eventually } }) @@ -84,7 +91,7 @@ pub fn get_clock_map(sysrep: &TransitionSystemPtr) -> HashMap( representation: &'a TransitionSystemPtr, - locations: &mut Vec, + locations: &mut Vec, edges: &mut Vec, clock_map: &HashMap, ) { @@ -97,7 +104,7 @@ fn collect_all_edges_and_locations<'a>( fn collect_reachable_edges_and_locations<'a>( representation: &'a TransitionSystemPtr, - locations: &mut Vec, + locations: &mut Vec, edges: &mut Vec, clock_map: &HashMap, ) { @@ -118,9 +125,9 @@ fn collect_reachable_edges_and_locations<'a>( } fn collect_reachable_locations<'a>( - location: &LocationTuple, + location: &LocationTree, representation: &'a TransitionSystemPtr, - locations: &mut Vec, + locations: &mut Vec, ) { for input in [true, false].iter() { for sync in if *input { @@ -144,7 +151,7 @@ fn collect_reachable_locations<'a>( } fn collect_edges_from_location( - location: &LocationTuple, + location: &LocationTree, representation: &TransitionSystemPtr, edges: &mut Vec, clock_map: &HashMap, @@ -154,7 +161,7 @@ fn collect_edges_from_location( } fn collect_specific_edges_from_location( - location: &LocationTuple, + location: &LocationTree, representation: &TransitionSystemPtr, edges: &mut Vec, input: bool, diff --git a/src/System/specifics.rs b/src/System/specifics.rs new file mode 100644 index 00000000..d9db1b65 --- /dev/null +++ b/src/System/specifics.rs @@ -0,0 +1,423 @@ +use std::{collections::HashMap, fmt}; + +use edbm::util::constraints::{ClockIndex, Conjunction, Constraint, Disjunction}; + +use crate::{ + component::State, + ModelObjects::statepair::StatePair, + Simulation::decision::Decision, + TransitionSystems::{ + transition_system::ComponentInfoTree, CompositionType, LocationID, TransitionID, + TransitionSystem, + }, +}; + +use super::{query_failures::SystemType, reachability::Path}; + +/// Intermediate representation of a [decision](Decision) from a `source` specific state to a `destination` specific state with an `action`. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct SpecificDecision { + pub source_state: SpecificState, + pub action: String, + pub edges: Vec, + pub destination_state: SpecificState, +} + +fn transition_id_to_specific_edges( + id: TransitionID, + system: &dyn TransitionSystem, + edges: &mut Vec, +) { + match id { + TransitionID::Conjunction(left, right) => { + assert_eq!(system.get_composition_type(), CompositionType::Conjunction); + let (l, r) = system.get_children(); + transition_id_to_specific_edges(*left, &**l, edges); + transition_id_to_specific_edges(*right, &**r, edges); + } + TransitionID::Composition(left, right) => { + assert_eq!(system.get_composition_type(), CompositionType::Composition); + let (l, r) = system.get_children(); + transition_id_to_specific_edges(*left, &**l, edges); + transition_id_to_specific_edges(*right, &**r, edges); + } + TransitionID::Quotient(lefts, rights) => { + assert_eq!(system.get_composition_type(), CompositionType::Quotient); + let (l, r) = system.get_children(); + for left in lefts { + transition_id_to_specific_edges(left, &**l, edges); + } + for right in rights { + transition_id_to_specific_edges(right, &**r, edges); + } + } + TransitionID::Simple(edge_id) => { + assert_eq!(system.get_composition_type(), CompositionType::Simple); + if let ComponentInfoTree::Info(info) = system.comp_infos() { + let edge = SpecificEdge::new(info.name.clone(), edge_id, info.id); + edges.push(edge); + } else { + unreachable!("Simple transition system should have ComponentInfoTree::Info") + } + } + TransitionID::None => {} + } +} + +impl SpecificDecision { + pub fn from_decision(decision: &Decision, system: &dyn TransitionSystem) -> Self { + let mut edges = vec![]; + if let Some(t) = &decision.transition { + transition_id_to_specific_edges(t.id.clone(), system, &mut edges); + } + + Self { + source_state: SpecificState::from_state(&decision.state, system), + action: decision.action.clone(), + edges, + destination_state: SpecificState::from_state(&decision.next_state, system), + } + } +} + +/// Intermediate representation of a [path](Path) of [decisions](SpecificDecision). +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct SpecificPath { + pub path: Vec, +} + +impl SpecificPath { + pub fn from_path(path: &Path, system: &dyn TransitionSystem) -> Self { + Self { + path: path + .path + .iter() + .map(|d| SpecificDecision::from_decision(d, system)) + .collect(), + } + } +} + +/// Intermediate representation of a component instance. `id` is used to distinguish different instances of the same components in a system. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct SpecificComp { + pub name: String, + pub id: u32, +} + +impl SpecificComp { + pub fn new(name: String, id: u32) -> Self { + Self { name, id } + } +} + +/// Intermediate representation of an [edge](crate::ModelObjects::component::Edge) in a component instance. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct SpecificEdge { + pub comp: SpecificComp, + pub edge_id: String, +} + +impl SpecificEdge { + pub fn new( + component_name: impl Into, + edge_id: impl Into, + component_id: u32, + ) -> Self { + Self { + comp: SpecificComp::new(component_name.into(), component_id), + edge_id: edge_id.into(), + } + } +} + +/// Intermediate representaton of a [disjunction](Disjunction) of conjunctions of clock constraints. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct SpecificDisjunction { + pub conjunctions: Vec, +} + +impl SpecificDisjunction { + pub fn from(disj: Disjunction, sys: &HashMap) -> Self { + Self { + conjunctions: disj + .conjunctions + .into_iter() + .map(|c| SpecificConjunction::from(c, sys)) + .collect(), + } + } +} + +/// Intermediate representaton of a [conjunction](Conjunction) of clock constraints. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct SpecificConjunction { + pub constraints: Vec, +} + +impl SpecificConjunction { + pub fn from(conj: Conjunction, sys: &HashMap) -> Self { + Self { + constraints: conj + .constraints + .into_iter() + .map(|c| SpecificConstraint::from(c, sys)) + .collect(), + } + } +} + +/// Intermediate representation of a [clock](ClockIndex) used in a constraint. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum SpecificClockVar { + /// The zero clock. + Zero, + /// A clock in a component instance. + ComponentClock(SpecificClock), + /// A clock without a component instance. E.g. a quotient clock. + SystemClock(ClockIndex), +} + +/// Intermediate representation of a clock [constraint](Constraint) of the form `i-j ) -> Self { + fn map_clock( + clock: ClockIndex, + sys: &HashMap, + ) -> SpecificClockVar { + match clock { + 0 => SpecificClockVar::Zero, + _ => match sys.get(&clock) { + Some(c) => SpecificClockVar::ComponentClock(c.clone()), + None => SpecificClockVar::SystemClock(clock), + }, + } + } + + Self { + i: map_clock(constraint.i, sys), + j: map_clock(constraint.j, sys), + strict: constraint.ineq().is_strict(), + c: constraint.ineq().bound(), + } + } +} + +/// Intermediate representation of a [State] in a system with its `locations` and zone `constraints`. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct SpecificState { + pub locations: SpecificLocation, + pub constraints: SpecificDisjunction, +} + +/// Intermediate representation of a [LocationID](crate::TransitionSystems::location_id::LocationID) in a system. +/// It is a binary tree with either [component](SpecificComp) locations or [special](SpecialLocation) locations at the leaves. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum SpecificLocation { + /// A location in a component instance. + ComponentLocation { + comp: SpecificComp, + location_id: String, + }, + /// A branch with two child locations. + BranchLocation(Box, Box, SystemType), + /// A special location. E.g. `Error` or `Universal` from a quotient. + SpecialLocation(SpecialLocation), +} + +impl SpecificLocation { + pub fn new( + component_name: impl Into, + location_id: impl Into, + component_id: u32, + ) -> Self { + Self::ComponentLocation { + comp: SpecificComp::new(component_name.into(), component_id), + location_id: location_id.into(), + } + } + + /// Assume that the location is a branch location and return the left and right child. + /// # Panics + /// Panics if the location is not a branch location. + pub fn split(self) -> (Self, Self) { + match self { + SpecificLocation::BranchLocation(left, right, _) => (*left, *right), + _ => unreachable!("Cannot split non-branch location"), + } + } +} + +impl fmt::Display for SpecificLocation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + SpecificLocation::ComponentLocation { comp, location_id } => { + write!(f, "{}.{}", comp.name, location_id) + } + SpecificLocation::BranchLocation(left, right, op) => { + write!(f, "({}{}{})", left, op.op(), right) + } + SpecificLocation::SpecialLocation(spec) => write!(f, "{}", spec), + } + } +} + +/// Intermediate representation of a [special](crate::TransitionSystems::location_id::LocationID::Special) location. E.g. `Error` or `Universal` from a quotient. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum SpecialLocation { + Universal, + Error, +} + +impl fmt::Display for SpecialLocation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + SpecialLocation::Universal => write!(f, "[Universal]"), + SpecialLocation::Error => write!(f, "[Error]"), + } + } +} + +impl SpecificState { + /// Create a new [SpecificState] from a [State] and its transition system. + pub fn from_state(state: &State, sys: &dyn TransitionSystem) -> Self { + let locations = state_specific_location(state, sys); + let clock_map = specific_clock_comp_map(sys); + + let constraints = state.zone_ref().minimal_constraints(); + let constraints = SpecificDisjunction::from(constraints, &clock_map); + Self { + locations, + constraints, + } + } + + /// Create a new [SpecificState] from a [StatePair] and its pair of transition systems. + pub fn from_state_pair( + state: &StatePair, + sys1: &dyn TransitionSystem, + sys2: &dyn TransitionSystem, + ) -> Self { + let locations = state_pair_specific_location(state, sys1, sys2); + + let clock_map = specific_clock_comp_map_composite(sys1, sys2); + + let constraints = state.ref_zone().minimal_constraints(); + let constraints = SpecificDisjunction::from(constraints, &clock_map); + Self { + locations, + constraints, + } + } +} + +impl fmt::Display for SpecificState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let locs = &self.locations; + + write!(f, "({})", locs) + // TODO: maybe show constraints + // write!(f, "({} | {})", locs, self.constraints) + } +} + +/// Intermediate representation of a clock name in a specific [component instance](SpecificComp). +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct SpecificClock { + pub name: String, + pub comp: SpecificComp, +} + +impl SpecificClock { + pub fn new(name: String, comp: SpecificComp) -> Self { + Self { name, comp } + } +} + +/// Construct a map from clock indices to [SpecificClock]s for the transition system. +pub fn specific_clock_comp_map(sys: &dyn TransitionSystem) -> HashMap { + sys.comp_infos() + .iter() + .flat_map(|comp| { + comp.declarations + .clocks + .iter() + .map(move |(clock, &clock_id)| { + ( + clock_id, + SpecificClock::new( + clock.clone(), + SpecificComp::new(comp.name.clone(), comp.id), + ), + ) + }) + }) + .collect() +} + +/// Construct a map from clock indices to [SpecificClock]s for the transition system pair. +pub fn specific_clock_comp_map_composite( + sys1: &dyn TransitionSystem, + sys2: &dyn TransitionSystem, +) -> HashMap { + let mut map = specific_clock_comp_map(sys1); + map.extend(specific_clock_comp_map(sys2)); + map +} + +/// Get the [SpecificLocation] of a [StatePair] given the transition systems. +pub fn state_pair_specific_location( + state: &StatePair, + sys1: &dyn TransitionSystem, + sys2: &dyn TransitionSystem, +) -> SpecificLocation { + let left = specific_location(&state.locations1.id, sys1); + let right = specific_location(&state.locations2.id, sys2); + SpecificLocation::BranchLocation(Box::new(left), Box::new(right), SystemType::Refinement) +} + +/// Get the [SpecificLocation] of a [State] given the transition system. +pub fn state_specific_location(state: &State, sys: &dyn TransitionSystem) -> SpecificLocation { + specific_location(&state.decorated_locations.id, sys) +} + +/// Get the [SpecificLocation] of a [LocationID] given the transition system. +pub fn specific_location(location_id: &LocationID, sys: &dyn TransitionSystem) -> SpecificLocation { + fn inner(location_id: &LocationID, infos: ComponentInfoTree) -> SpecificLocation { + match location_id { + LocationID::Conjunction(left, right) + | LocationID::Composition(left, right) + | LocationID::Quotient(left, right) => { + let (i_left, i_right) = infos.split(); + SpecificLocation::BranchLocation( + Box::new(inner(left, i_left)), + Box::new(inner(right, i_right)), + match location_id { + LocationID::Conjunction(_, _) => SystemType::Conjunction, + LocationID::Composition(_, _) => SystemType::Composition, + LocationID::Quotient(_, _) => SystemType::Quotient, + _ => unreachable!(), + }, + ) + } + LocationID::Simple(loc_id) => { + let info = infos.info(); + SpecificLocation::ComponentLocation { + comp: SpecificComp::new(info.name.clone(), info.id), + location_id: loc_id.clone(), + } + } + LocationID::Special(kind) => SpecificLocation::SpecialLocation(kind.clone()), + LocationID::AnyLocation => unreachable!("AnyLocation should not be used in a state"), + } + } + inner(location_id, sys.comp_infos()) +} diff --git a/src/TransitionSystems/common.rs b/src/TransitionSystems/common.rs index 291a78c1..aaaa805e 100644 --- a/src/TransitionSystems/common.rs +++ b/src/TransitionSystems/common.rs @@ -7,18 +7,18 @@ use edbm::{ }; use log::warn; -use crate::TransitionSystems::CompositionType; use crate::{ ModelObjects::component::{Declarations, State, Transition}, - System::local_consistency::{ConsistencyResult, DeterminismResult}, + System::{query_failures::DeterminismResult, specifics::SpecificLocation}, }; +use crate::{System::query_failures::ConsistencyResult, TransitionSystems::CompositionType}; -use super::{LocationTuple, TransitionSystem, TransitionSystemPtr}; +use super::{LocationTree, TransitionSystem, TransitionSystemPtr}; -pub trait ComposedTransitionSystem: DynClone { - fn next_transitions(&self, location: &LocationTuple, action: &str) -> Vec; +pub(super) trait ComposedTransitionSystem: DynClone { + fn next_transitions(&self, location: &LocationTree, action: &str) -> Vec; - fn is_locally_consistent(&self) -> ConsistencyResult; + fn check_local_consistency(&self) -> ConsistencyResult; fn get_children(&self) -> (&TransitionSystemPtr, &TransitionSystemPtr); @@ -36,24 +36,20 @@ pub trait ComposedTransitionSystem: DynClone { clone_trait_object!(ComposedTransitionSystem); impl TransitionSystem for T { - fn get_local_max_bounds(&self, loc: &LocationTuple) -> Bounds { - if loc.is_universal() || loc.is_inconsistent() { - Bounds::new(self.get_dim()) - } else { - let (left, right) = self.get_children(); - let loc_l = loc.get_left(); - let loc_r = loc.get_right(); - let mut bounds_l = left.get_local_max_bounds(loc_l); - let bounds_r = right.get_local_max_bounds(loc_r); - bounds_l.add_bounds(&bounds_r); - bounds_l - } + fn get_local_max_bounds(&self, loc: &LocationTree) -> Bounds { + let (left, right) = self.get_children(); + let loc_l = loc.get_left(); + let loc_r = loc.get_right(); + let mut bounds_l = left.get_local_max_bounds(loc_l); + let bounds_r = right.get_local_max_bounds(loc_r); + bounds_l.add_bounds(&bounds_r); + bounds_l } fn get_dim(&self) -> ClockIndex { self.get_dim() } - fn next_transitions(&self, location: &LocationTuple, action: &str) -> Vec { + fn next_transitions(&self, location: &LocationTree, action: &str) -> Vec { self.next_transitions(location, action) } fn get_input_actions(&self) -> HashSet { @@ -71,29 +67,29 @@ impl TransitionSystem for T { .collect() } - fn get_initial_location(&self) -> Option { + fn get_initial_location(&self) -> Option { let (left, right) = self.get_children(); let l = left.get_initial_location()?; let r = right.get_initial_location()?; - Some(LocationTuple::compose(&l, &r, self.get_composition_type())) + Some(LocationTree::compose(&l, &r, self.get_composition_type())) } - fn get_all_locations(&self) -> Vec { + fn get_all_locations(&self) -> Vec { let (left, right) = self.get_children(); - let mut location_tuples = vec![]; + let mut location_trees = vec![]; let left = left.get_all_locations(); let right = right.get_all_locations(); for loc1 in &left { for loc2 in &right { - location_tuples.push(LocationTuple::compose( + location_trees.push(LocationTree::compose( loc1, loc2, self.get_composition_type(), )); } } - location_tuples + location_trees } /// Returns the declarations of both children. @@ -105,21 +101,16 @@ impl TransitionSystem for T { comps } - fn is_deterministic(&self) -> DeterminismResult { + fn check_determinism(&self) -> DeterminismResult { let (left, right) = self.get_children(); - if let DeterminismResult::Success = left.is_deterministic() { - if let DeterminismResult::Success = right.is_deterministic() { - DeterminismResult::Success - } else { - right.is_deterministic() - } - } else { - left.is_deterministic() - } + left.check_determinism()?; + right.check_determinism() } - fn is_locally_consistent(&self) -> ConsistencyResult { - self.is_locally_consistent() + fn check_local_consistency(&self) -> ConsistencyResult { + let (left, right) = self.get_children(); + left.check_local_consistency()?; + right.check_local_consistency() } fn get_initial_state(&self) -> Option { @@ -141,6 +132,18 @@ impl TransitionSystem for T { fn get_composition_type(&self) -> CompositionType { self.get_composition_type() } + + fn construct_location_tree(&self, target: SpecificLocation) -> Result { + let (left, right) = self.get_children(); + let (t_left, t_right) = target.split(); + let loc_l = left.construct_location_tree(t_left)?; + let loc_r = right.construct_location_tree(t_right)?; + Ok(LocationTree::compose( + &loc_l, + &loc_r, + self.get_composition_type(), + )) + } } pub trait CollectionOperation { diff --git a/src/TransitionSystems/compiled_component.rs b/src/TransitionSystems/compiled_component.rs index 3f0f4d2d..7c201d9f 100644 --- a/src/TransitionSystems/compiled_component.rs +++ b/src/TransitionSystems/compiled_component.rs @@ -1,40 +1,38 @@ -use crate::extract_system_rep::SystemRecipeFailure; use crate::ModelObjects::component::{ Component, DeclarationProvider, Declarations, State, Transition, }; -use crate::System::local_consistency::{self, ConsistencyResult, DeterminismResult}; -use crate::TransitionSystems::{LocationTuple, TransitionSystem, TransitionSystemPtr}; +use crate::System::local_consistency::{self}; +use crate::System::query_failures::{ + ActionFailure, ConsistencyResult, DeterminismResult, SystemRecipeFailure, +}; +use crate::System::specifics::SpecificLocation; +use crate::TransitionSystems::{LocationTree, TransitionSystem, TransitionSystemPtr}; use edbm::util::bounds::Bounds; use edbm::util::constraints::ClockIndex; use std::collections::hash_set::HashSet; use std::collections::HashMap; use std::iter::FromIterator; -use super::common::CollectionOperation; +use super::transition_system::ComponentInfoTree; use super::{CompositionType, LocationID}; type Action = String; #[derive(Clone)] -struct ComponentInfo { - name: String, - declarations: Declarations, +pub struct ComponentInfo { + pub name: String, + pub id: u32, + pub declarations: Declarations, max_bounds: Bounds, } -impl ComponentInfo { - pub fn _name(&self) -> &str { - self.name.as_ref() - } -} - #[derive(Clone)] pub struct CompiledComponent { inputs: HashSet, outputs: HashSet, - locations: HashMap, + locations: HashMap, location_edges: HashMap>, - initial_location: Option, + initial_location: Option, comp_info: ComponentInfo, dim: ClockIndex, } @@ -45,26 +43,19 @@ impl CompiledComponent { inputs: HashSet, outputs: HashSet, dim: ClockIndex, + id: u32, ) -> Result, SystemRecipeFailure> { - if let Err(actions) = inputs.is_disjoint_action(&outputs) { - return Err(SystemRecipeFailure::new_from_component( - "Input is not disjoint from output".to_string(), - component, - actions, - )); + if !inputs.is_disjoint(&outputs) { + ActionFailure::not_disjoint_IO(&component.name, inputs.clone(), outputs.clone()) + .map_err(|e| e.to_simple_failure(&component.name))?; } - let locations: HashMap = component + let locations: HashMap = component .get_locations() .iter() .map(|loc| { - let tuple = LocationTuple::simple( - loc, - Some(component.get_name().to_owned()), - component.get_declarations(), - dim, - ); - (tuple.id.clone(), tuple) + let loc = LocationTree::simple(loc, component.get_declarations(), dim); + (loc.id.clone(), loc) }) .collect(); @@ -72,10 +63,7 @@ impl CompiledComponent { locations.keys().map(|k| (k.clone(), vec![])).collect(); for edge in component.get_edges() { - let id = LocationID::Simple { - location_id: edge.source_location.clone(), - component_id: Some(component.get_name().to_owned()), - }; + let id = LocationID::Simple(edge.source_location.clone()); let transition = Transition::from(&component, edge, dim); location_edges .get_mut(&id) @@ -97,6 +85,7 @@ impl CompiledComponent { name: component.name, declarations: component.declarations, max_bounds, + id, }, })) } @@ -104,11 +93,13 @@ impl CompiledComponent { pub fn compile( component: Component, dim: ClockIndex, + component_index: &mut u32, ) -> Result, SystemRecipeFailure> { let inputs = HashSet::from_iter(component.get_input_actions()); let outputs = HashSet::from_iter(component.get_output_actions()); - - Self::compile_with_actions(component, inputs, outputs, dim) + let index = *component_index; + *component_index += 1; + Self::compile_with_actions(component, inputs, outputs, dim, index) } fn _comp_info(&self) -> &ComponentInfo { @@ -117,7 +108,7 @@ impl CompiledComponent { } impl TransitionSystem for CompiledComponent { - fn get_local_max_bounds(&self, loc: &LocationTuple) -> Bounds { + fn get_local_max_bounds(&self, loc: &LocationTree) -> Bounds { if loc.is_universal() || loc.is_inconsistent() { Bounds::new(self.get_dim()) } else { @@ -129,7 +120,7 @@ impl TransitionSystem for CompiledComponent { self.dim } - fn next_transitions(&self, locations: &LocationTuple, action: &str) -> Vec { + fn next_transitions(&self, locations: &LocationTree, action: &str) -> Vec { assert!(self.actions_contain(action)); let is_input = self.inputs_contain(action); @@ -165,11 +156,11 @@ impl TransitionSystem for CompiledComponent { self.inputs.union(&self.outputs).cloned().collect() } - fn get_initial_location(&self) -> Option { + fn get_initial_location(&self) -> Option { self.initial_location.clone() } - fn get_all_locations(&self) -> Vec { + fn get_all_locations(&self) -> Vec { self.locations.values().cloned().collect() } @@ -177,11 +168,11 @@ impl TransitionSystem for CompiledComponent { vec![&self.comp_info.declarations] } - fn is_deterministic(&self) -> DeterminismResult { - local_consistency::is_deterministic(self) + fn check_determinism(&self) -> DeterminismResult { + local_consistency::check_determinism(self) } - fn is_locally_consistent(&self) -> ConsistencyResult { + fn check_local_consistency(&self) -> ConsistencyResult { local_consistency::is_least_consistent(self) } @@ -192,7 +183,7 @@ impl TransitionSystem for CompiledComponent { } fn get_children(&self) -> (&TransitionSystemPtr, &TransitionSystemPtr) { - unimplemented!() + unreachable!() } fn get_composition_type(&self) -> CompositionType { @@ -203,11 +194,39 @@ impl TransitionSystem for CompiledComponent { self.comp_info.declarations.clone() } - fn get_location(&self, id: &LocationID) -> Option { + fn get_location(&self, id: &LocationID) -> Option { self.locations.get(id).cloned() } fn component_names(&self) -> Vec<&str> { vec![&self.comp_info.name] } + + fn comp_infos(&'_ self) -> ComponentInfoTree<'_> { + ComponentInfoTree::Info(&self.comp_info) + } + + fn to_string(&self) -> String { + self.comp_info.name.clone() + } + + fn construct_location_tree(&self, target: SpecificLocation) -> Result { + match target { + SpecificLocation::ComponentLocation { comp, location_id } => { + assert_eq!(comp.name, self.comp_info.name); + self.get_all_locations() + .into_iter() + .find(|loc| loc.id == LocationID::Simple(location_id.clone())) + .ok_or_else(|| { + format!( + "Could not find location {} in component {}", + location_id, self.comp_info.name + ) + }) + } + SpecificLocation::BranchLocation(_, _, _) | SpecificLocation::SpecialLocation(_) => { + unreachable!("Should not happen at the level of a component.") + } + } + } } diff --git a/src/TransitionSystems/composition.rs b/src/TransitionSystems/composition.rs index ee5347ec..088255a6 100644 --- a/src/TransitionSystems/composition.rs +++ b/src/TransitionSystems/composition.rs @@ -1,12 +1,11 @@ use edbm::util::constraints::ClockIndex; -use crate::extract_system_rep::SystemRecipeFailure; use crate::ModelObjects::component::Transition; -use crate::System::local_consistency::ConsistencyResult; -use crate::TransitionSystems::{LocationTuple, TransitionSystem, TransitionSystemPtr}; +use crate::System::query_failures::{ActionFailure, SystemRecipeFailure}; +use crate::TransitionSystems::{LocationTree, TransitionSystem, TransitionSystemPtr}; use std::collections::hash_set::HashSet; -use super::common::{CollectionOperation, ComposedTransitionSystem}; +use super::common::ComposedTransitionSystem; use super::CompositionType; #[derive(Clone)] @@ -37,13 +36,12 @@ impl Composition { let right_out = right.get_output_actions(); let right_actions = right_in.union(&right_out).cloned().collect::>(); - if let Err(actions) = left_out.is_disjoint_action(&right_out) { - return Err(SystemRecipeFailure::new( - "Invalid parallel composition, outputs are not disjoint".to_string(), - left, - right, - actions, - )); + if !left_out.is_disjoint(&right_out) { + return ActionFailure::not_disjoint( + (left.as_ref(), left_out), + (right.as_ref(), right_out), + ) + .map_err(|e| e.to_rfcomp(left, right)); } // Act_i = Act1_i \ Act2_o ∪ Act2_i \ Act1_o @@ -77,7 +75,7 @@ impl Composition { } impl ComposedTransitionSystem for Composition { - fn next_transitions(&self, location: &LocationTuple, action: &str) -> Vec { + fn next_transitions(&self, location: &LocationTree, action: &str) -> Vec { assert!(self.actions_contain(action)); let loc_left = location.get_left(); @@ -110,18 +108,6 @@ impl ComposedTransitionSystem for Composition { unreachable!() } - fn is_locally_consistent(&self) -> ConsistencyResult { - if let ConsistencyResult::Success = self.left.is_locally_consistent() { - if let ConsistencyResult::Success = self.right.is_locally_consistent() { - ConsistencyResult::Success - } else { - self.right.is_locally_consistent() - } - } else { - self.left.is_locally_consistent() - } - } - fn get_children(&self) -> (&TransitionSystemPtr, &TransitionSystemPtr) { (&self.left, &self.right) } @@ -145,4 +131,9 @@ impl ComposedTransitionSystem for Composition { fn get_output_actions(&self) -> HashSet { self.outputs.clone() } + + fn check_local_consistency(&self) -> crate::System::query_failures::ConsistencyResult { + self.left.check_local_consistency()?; + self.right.check_local_consistency() + } } diff --git a/src/TransitionSystems/conjunction.rs b/src/TransitionSystems/conjunction.rs index 133f6c88..c89031f8 100644 --- a/src/TransitionSystems/conjunction.rs +++ b/src/TransitionSystems/conjunction.rs @@ -1,14 +1,14 @@ use edbm::util::constraints::ClockIndex; -use crate::extract_system_rep::SystemRecipeFailure; use crate::ModelObjects::component::Transition; -use crate::System::local_consistency::{self, ConsistencyResult}; +use crate::System::local_consistency; +use crate::System::query_failures::{ActionFailure, ConsistencyResult, SystemRecipeFailure}; use crate::TransitionSystems::{ - CompositionType, LocationTuple, TransitionSystem, TransitionSystemPtr, + CompositionType, LocationTree, TransitionSystem, TransitionSystemPtr, }; use std::collections::hash_set::HashSet; -use super::common::{CollectionOperation, ComposedTransitionSystem}; +use super::common::ComposedTransitionSystem; #[derive(Clone)] pub struct Conjunction { @@ -32,25 +32,19 @@ impl Conjunction { let right_in = right.get_input_actions(); let right_out = right.get_output_actions(); - let mut is_disjoint = true; - let mut actions = vec![]; - - if let Err(actions1) = left_in.is_disjoint_action(&right_out) { - is_disjoint = false; - actions.extend(actions1); - } - if let Err(actions2) = left_out.is_disjoint_action(&right_in) { - is_disjoint = false; - actions.extend(actions2); + if !left_in.is_disjoint(&right_out) { + return ActionFailure::not_disjoint( + (left.as_ref(), left_in), + (right.as_ref(), right_out), + ) + .map_err(|e| e.to_rfconj(left, right)); } - - if !(is_disjoint) { - return Err(SystemRecipeFailure::new( - "Invalid conjunction, outputs and inputs are not disjoint".to_string(), - left, - right, - actions, - )); + if !left_out.is_disjoint(&right_in) { + return ActionFailure::not_disjoint( + (left.as_ref(), left_out), + (right.as_ref(), right_in), + ) + .map_err(|e| e.to_rfconj(left, right)); } let outputs = left @@ -72,20 +66,14 @@ impl Conjunction { outputs, dim, }); - if let ConsistencyResult::Failure(_) = local_consistency::is_least_consistent(ts.as_ref()) { - return Err(SystemRecipeFailure::new( - "Invalid conjunction, not least consistent".to_string(), - ts.left, - ts.right, - vec![], - )); - } + local_consistency::is_least_consistent(ts.as_ref()) + .map_err(|e| e.to_recipe_failure(ts.as_ref()))?; Ok(ts) } } impl ComposedTransitionSystem for Conjunction { - fn next_transitions(&self, location: &LocationTuple, action: &str) -> Vec { + fn next_transitions(&self, location: &LocationTree, action: &str) -> Vec { assert!(self.actions_contain(action)); let loc_left = location.get_left(); @@ -97,8 +85,8 @@ impl ComposedTransitionSystem for Conjunction { Transition::combinations(&left, &right, CompositionType::Conjunction) } - fn is_locally_consistent(&self) -> ConsistencyResult { - ConsistencyResult::Success // By definition from the Conjunction::new() + fn check_local_consistency(&self) -> ConsistencyResult { + Ok(()) // By definition from the Conjunction::new() } fn get_children(&self) -> (&TransitionSystemPtr, &TransitionSystemPtr) { diff --git a/src/TransitionSystems/location_id.rs b/src/TransitionSystems/location_id.rs index d396e63f..509fa204 100644 --- a/src/TransitionSystems/location_id.rs +++ b/src/TransitionSystems/location_id.rs @@ -1,6 +1,6 @@ use std::fmt::{Display, Formatter}; -use crate::ModelObjects::representations::QueryExpression; +use crate::{ModelObjects::representations::QueryExpression, System::specifics::SpecialLocation}; #[derive(Debug, Clone, Eq, Hash, PartialEq)] pub enum LocationID { @@ -8,10 +8,8 @@ pub enum LocationID { Composition(Box, Box), Quotient(Box, Box), /// Represents the potentially complete identifier of a location - Simple { - location_id: String, - component_id: Option, - }, + Simple(String), + Special(SpecialLocation), /// Used for representing a partial state and it is generated when a location's name is set as `_` AnyLocation, } @@ -36,54 +34,20 @@ impl LocationID { } } - /// Does an inorder walk of the [`LocationID`] tree mapping it to a list of [`LocationID::Simple`]. - pub fn inorder_vec_tranform(&self) -> Vec { - match self { - LocationID::Composition(left, right) - | LocationID::Quotient(left, right) - | LocationID::Conjunction(left, right) => { - let mut left = left.inorder_vec_tranform(); - let mut right = right.inorder_vec_tranform(); - left.append(&mut right); - left - } - LocationID::Simple { - location_id, - component_id, - } => vec![LocationID::Simple { - location_id: location_id.to_string(), - component_id: component_id.as_ref().map(|x| x.to_string()), - }], - LocationID::AnyLocation => vec![LocationID::AnyLocation], - } - } - /// It check whether the [`LocationID`] is a partial location by search through [`LocationID`] structure and see if there is any [`LocationID::AnyLocation`] pub fn is_partial_location(&self) -> bool { + // TODO: Remove this function and implement it on a new PartialLocationID type match self { LocationID::Composition(left, right) | LocationID::Conjunction(left, right) | LocationID::Quotient(left, right) => { left.is_partial_location() || right.is_partial_location() } - LocationID::Simple { .. } => false, + LocationID::Simple { .. } | LocationID::Special(_) => false, LocationID::AnyLocation => true, } } - ///Gets the component_id of from a [`LocationID::Simple`] returns a clone. - pub fn get_component_id(&self) -> Option { - if let LocationID::Simple { - location_id: _, - component_id, - } = self - { - component_id.clone() - } else { - None - } - } - pub(super) fn get_unique_string(&self) -> String { match self { LocationID::Composition(a, b) => { @@ -96,14 +60,8 @@ impl LocationID { format!("({}\\{})", a.get_unique_string(), b.get_unique_string()) } LocationID::AnyLocation => "_".to_string(), - LocationID::Simple { - location_id, - component_id, - } => format!( - "{}.{}", - component_id.clone().unwrap_or_else(|| "(None)".to_string()), - location_id - ), + LocationID::Simple(location_id) => location_id.clone(), + LocationID::Special(location_id) => location_id.to_string(), } } } @@ -121,10 +79,7 @@ impl From for LocationID { LocationID::Quotient(Box::new((*left).into()), Box::new((*right).into())) } QueryExpression::Parentheses(inner) => (*inner).into(), - QueryExpression::VarName(name) => LocationID::Simple { - location_id: name, - component_id: None, - }, + QueryExpression::VarName(name) => LocationID::Simple(name), _ => panic!( "Cannot convert queryexpression with {:?} to LocationID", item @@ -139,65 +94,45 @@ impl Display for LocationID { LocationID::Conjunction(left, right) => { match **left { LocationID::Conjunction(_, _) => write!(f, "{}", (*left))?, - LocationID::Simple { - location_id: _, - component_id: _, - } => write!(f, "{}", (*left))?, + LocationID::Simple(_) => write!(f, "{}", (*left))?, _ => write!(f, "({})", (*left))?, }; write!(f, "&&")?; match **right { LocationID::Conjunction(_, _) => write!(f, "{}", (*right))?, - LocationID::Simple { - location_id: _, - component_id: _, - } => write!(f, "{}", (*right))?, + LocationID::Simple(_) => write!(f, "{}", (*right))?, _ => write!(f, "({})", (*right))?, }; } LocationID::Composition(left, right) => { match **left { LocationID::Composition(_, _) => write!(f, "{}", (*left))?, - LocationID::Simple { - location_id: _, - component_id: _, - } => write!(f, "{}", (*left))?, + LocationID::Simple(_) => write!(f, "{}", (*left))?, _ => write!(f, "({})", (*left))?, }; write!(f, "||")?; match **right { LocationID::Composition(_, _) => write!(f, "{}", (*right))?, - LocationID::Simple { - location_id: _, - component_id: _, - } => write!(f, "{}", (*right))?, + LocationID::Simple(_) => write!(f, "{}", (*right))?, _ => write!(f, "({})", (*right))?, }; } LocationID::Quotient(left, right) => { match **left { - LocationID::Simple { - location_id: _, - component_id: _, - } => write!(f, "{}", (*left))?, + LocationID::Simple(_) => write!(f, "{}", (*left))?, _ => write!(f, "({})", (*left))?, }; write!(f, "\\\\")?; match **right { - LocationID::Simple { - location_id: _, - component_id: _, - } => write!(f, "{}", (*right))?, + LocationID::Simple(_) => write!(f, "{}", (*right))?, _ => write!(f, "({})", (*right))?, }; } - LocationID::Simple { - location_id, - component_id: _, - } => { + LocationID::Simple(location_id) => { write!(f, "{}", location_id)?; } LocationID::AnyLocation => write!(f, "_")?, + LocationID::Special(location_id) => write!(f, "{}", location_id)?, } Ok(()) } diff --git a/src/TransitionSystems/location_tuple.rs b/src/TransitionSystems/location_tree.rs similarity index 51% rename from src/TransitionSystems/location_tuple.rs rename to src/TransitionSystems/location_tree.rs index 0dc21ae2..bfdfb678 100644 --- a/src/TransitionSystems/location_tuple.rs +++ b/src/TransitionSystems/location_tree.rs @@ -16,28 +16,45 @@ pub enum CompositionType { } #[derive(Clone, Debug)] -pub struct LocationTuple { +pub struct LocationTree { pub id: LocationID, /// The invariant for the `Location` pub invariant: Option, - pub loc_type: LocationType, - left: Option>, - right: Option>, + loc_type: LocationType, + left: Option>, + right: Option>, } -impl PartialEq for LocationTuple { +impl PartialEq for LocationTree { fn eq(&self, other: &Self) -> bool { self.id == other.id && self.loc_type == other.loc_type } } -impl LocationTuple { - pub fn simple( - location: &Location, - component_id: Option, - decls: &Declarations, - dim: ClockIndex, - ) -> Self { +impl LocationTree { + pub fn universal() -> Self { + LocationTree { + id: LocationID::Special(crate::System::specifics::SpecialLocation::Universal), + invariant: None, + loc_type: LocationType::Universal, + left: None, + right: None, + } + } + + pub fn error(dim: ClockIndex, quotient_clock_index: ClockIndex) -> Self { + let inv = OwnedFederation::universe(dim).constrain_eq(quotient_clock_index, 0); + + LocationTree { + id: LocationID::Special(crate::System::specifics::SpecialLocation::Error), + invariant: Some(inv), + loc_type: LocationType::Inconsistent, + left: None, + right: None, + } + } + + pub fn simple(location: &Location, decls: &Declarations, dim: ClockIndex) -> Self { let invariant = if let Some(inv) = location.get_invariant() { let mut fed = OwnedFederation::universe(dim); fed = apply_constraints_to_state(inv, decls, fed).unwrap(); @@ -45,23 +62,20 @@ impl LocationTuple { } else { None }; - LocationTuple { - id: LocationID::Simple { - location_id: location.get_id().clone(), - component_id, - }, + LocationTree { + id: LocationID::Simple(location.get_id().clone()), invariant, - loc_type: location.get_location_type().clone(), + loc_type: location.get_location_type(), left: None, right: None, } } - /// This method is used to a build partial [`LocationTuple`]. - /// A partial [`LocationTuple`] means it has a [`LocationID`] that is [`LocationID::AnyLocation`]. - /// A partial [`LocationTuple`] has `None` in the field `invariant` since a partial [`LocationTuple`] + /// This method is used to a build partial [`LocationTree`]. + /// A partial [`LocationTree`] means it has a [`LocationID`] that is [`LocationID::AnyLocation`]. + /// A partial [`LocationTree`] has `None` in the field `invariant` since a partial [`LocationTree`] /// covers more than one location, and therefore there is no specific `invariant` - pub fn build_any_location_tuple() -> Self { - LocationTuple { + pub fn build_any_location_tree() -> Self { + LocationTree { id: LocationID::AnyLocation, invariant: None, loc_type: LocationType::Any, @@ -74,21 +88,9 @@ impl LocationTuple { pub fn merge_as_quotient(left: &Self, right: &Self) -> Self { let id = LocationID::Quotient(Box::new(left.id.clone()), Box::new(right.id.clone())); - if left.loc_type == right.loc_type - && (left.loc_type == LocationType::Universal - || left.loc_type == LocationType::Inconsistent) - { - return left.clone(); - } - - let loc_type = - if left.loc_type == LocationType::Initial && right.loc_type == LocationType::Initial { - LocationType::Initial - } else { - LocationType::Normal - }; + let loc_type = left.loc_type.combine(right.loc_type); - LocationTuple { + LocationTree { id, invariant: None, loc_type, @@ -109,10 +111,6 @@ impl LocationTuple { _ => panic!("Invalid composition type {:?}", comp), }; - if left.loc_type == right.loc_type && (left.is_universal() || left.is_inconsistent()) { - return left.clone(); - } - let invariant = if let Some(inv1) = &left.invariant { if let Some(inv2) = &right.invariant { Some(inv1.clone().intersection(inv2)) @@ -123,14 +121,9 @@ impl LocationTuple { right.invariant.clone() }; - let loc_type = - if left.loc_type == LocationType::Initial && right.loc_type == LocationType::Initial { - LocationType::Initial - } else { - LocationType::Normal - }; + let loc_type = left.loc_type.combine(right.loc_type); - LocationTuple { + LocationTree { id, invariant, loc_type, @@ -150,17 +143,11 @@ impl LocationTuple { fed } - pub fn get_left(&self) -> &LocationTuple { - if self.is_universal() || self.is_inconsistent() { - return self; - } + pub fn get_left(&self) -> &LocationTree { self.left.as_ref().unwrap() } - pub fn get_right(&self) -> &LocationTuple { - if self.is_universal() || self.is_inconsistent() { - return self; - } + pub fn get_right(&self) -> &LocationTree { self.right.as_ref().unwrap() } @@ -176,8 +163,8 @@ impl LocationTuple { self.loc_type == LocationType::Inconsistent } - /// This function is used when you want to compare [`LocationTuple`]s that can contain partial locations. - pub fn compare_partial_locations(&self, other: &LocationTuple) -> bool { + /// This function is used when you want to compare [`LocationTree`]s that can contain partial locations. + pub fn compare_partial_locations(&self, other: &LocationTree) -> bool { match (&self.id, &other.id) { (LocationID::Composition(..), LocationID::Composition(..)) | (LocationID::Conjunction(..), LocationID::Conjunction(..)) @@ -190,46 +177,8 @@ impl LocationTuple { (LocationID::AnyLocation, LocationID::Simple { .. }) | (LocationID::Simple { .. }, LocationID::AnyLocation) | (LocationID::AnyLocation, LocationID::AnyLocation) => true, - ( - LocationID::Simple { - location_id: loc_id_1, - component_id: comp_id_1, - }, - LocationID::Simple { - location_id: loc_id_2, - component_id: comp_id_2, - }, - ) => loc_id_1 == loc_id_2 && comp_id_1 == comp_id_2, - // These six arms below are for comparing universal or inconsistent location with partial location. - (LocationID::Simple { .. }, LocationID::Composition(..)) - | (LocationID::Simple { .. }, LocationID::Conjunction(..)) - | (LocationID::Simple { .. }, LocationID::Quotient(..)) => { - self.handle_universal_inconsistent_compare(other) - } - (LocationID::Composition(..), LocationID::Simple { .. }) - | (LocationID::Conjunction(..), LocationID::Simple { .. }) - | (LocationID::Quotient(..), LocationID::Simple { .. }) => { - other.handle_universal_inconsistent_compare(self) - } + (LocationID::Simple(loc_id_1), LocationID::Simple(loc_id_2)) => loc_id_1 == loc_id_2, (_, _) => false, } } - - fn handle_universal_inconsistent_compare(&self, other: &LocationTuple) -> bool { - (self.is_universal() || self.is_inconsistent()) - && other.is_universal_or_inconsistent(&self.loc_type) - } - - fn is_universal_or_inconsistent(&self, loc_type: &LocationType) -> bool { - match self.id { - LocationID::Conjunction(..) - | LocationID::Composition(..) - | LocationID::Quotient(..) => { - self.get_left().is_universal_or_inconsistent(loc_type) - && self.get_right().is_universal_or_inconsistent(loc_type) - } - LocationID::Simple { .. } => self.loc_type == *loc_type, - LocationID::AnyLocation => true, - } - } } diff --git a/src/TransitionSystems/mod.rs b/src/TransitionSystems/mod.rs index df8d0cc4..f2d2552f 100644 --- a/src/TransitionSystems/mod.rs +++ b/src/TransitionSystems/mod.rs @@ -4,16 +4,16 @@ mod compiled_component; mod composition; mod conjunction; pub mod location_id; -mod location_tuple; +mod location_tree; mod quotient; mod transition_id; pub mod transition_system; -pub use compiled_component::CompiledComponent; +pub use compiled_component::{CompiledComponent, ComponentInfo}; pub use composition::Composition; pub use conjunction::Conjunction; pub use location_id::LocationID; -pub use location_tuple::{CompositionType, LocationTuple}; +pub use location_tree::{CompositionType, LocationTree}; pub use quotient::Quotient; pub use transition_id::TransitionID; pub use transition_system::{TransitionSystem, TransitionSystemPtr}; diff --git a/src/TransitionSystems/quotient.rs b/src/TransitionSystems/quotient.rs index b7032424..6870a8e1 100644 --- a/src/TransitionSystems/quotient.rs +++ b/src/TransitionSystems/quotient.rs @@ -2,20 +2,16 @@ use edbm::util::constraints::ClockIndex; use edbm::zones::OwnedFederation; use log::debug; -use crate::extract_system_rep::SystemRecipeFailure; use crate::EdgeEval::updater::CompiledUpdate; use crate::ModelObjects::component::Declarations; -use crate::ModelObjects::component::{Location, LocationType, State, Transition}; -use crate::System::local_consistency::{ConsistencyResult, DeterminismResult}; -use crate::TransitionSystems::common::CollectionOperation; -use crate::TransitionSystems::transition_system::PrecheckResult; +use crate::ModelObjects::component::{State, Transition}; +use crate::System::query_failures::{ + ActionFailure, ConsistencyResult, DeterminismResult, SystemRecipeFailure, +}; +use crate::System::specifics::{SpecialLocation, SpecificLocation}; use edbm::util::bounds::Bounds; -use crate::ModelObjects::representations::{ArithExpression, BoolExpression}; - -use crate::TransitionSystems::{ - LocationTuple, TransitionID, TransitionSystem, TransitionSystemPtr, -}; +use crate::TransitionSystems::{LocationTree, TransitionID, TransitionSystem, TransitionSystemPtr}; use std::collections::hash_set::HashSet; use std::vec; @@ -27,8 +23,8 @@ pub struct Quotient { S: TransitionSystemPtr, inputs: HashSet, outputs: HashSet, - universal_location: Location, - inconsistent_location: Location, + universal_location: LocationTree, + inconsistent_location: LocationTree, decls: Declarations, quotient_clock_index: ClockIndex, new_input_name: String, @@ -36,8 +32,6 @@ pub struct Quotient { dim: ClockIndex, } -static INCONSISTENT_LOC_NAME: &str = "Inconsistent"; -static UNIVERSAL_LOC_NAME: &str = "Universal"; impl Quotient { #[allow(clippy::new_ret_no_self)] pub fn new( @@ -46,58 +40,16 @@ impl Quotient { new_clock_index: ClockIndex, dim: ClockIndex, ) -> Result { - if let Err(actions) = S - .get_output_actions() - .is_disjoint_action(&T.get_input_actions()) - { - return Err(SystemRecipeFailure::new( - "s_out and t_in not disjoint in quotient!".to_string(), - T, - S, - actions, - )); - } - - match T.precheck_sys_rep() { - PrecheckResult::Success => {} - _ => { - return Err(SystemRecipeFailure::new( - "T (left) must be least consistent for quotient".to_string(), - T, - S, - vec![], - )); - } - } - match S.precheck_sys_rep() { - PrecheckResult::Success => {} - _ => { - return Err(SystemRecipeFailure::new( - "S (right) must be least consistent for quotient".to_string(), - T, - S, - vec![], - )); - } + if !S.get_output_actions().is_disjoint(&T.get_input_actions()) { + ActionFailure::not_disjoint( + (S.as_ref(), S.get_output_actions()), + (T.as_ref(), T.get_input_actions()), + ) + .map_err(|e| e.to_rfq(&T, &S))?; } - let universal_location = Location { - id: UNIVERSAL_LOC_NAME.to_string(), - invariant: None, - location_type: LocationType::Universal, - urgency: "".to_string(), - }; - - let inconsistent_location = Location { - id: INCONSISTENT_LOC_NAME.to_string(), - // xnew <= 0 - invariant: Some(BoolExpression::LessEQ( - Box::new(ArithExpression::VarName("quotient_xnew".to_string())), - Box::new(ArithExpression::Int(0)), - )), - location_type: LocationType::Inconsistent, - urgency: "".to_string(), - }; + T.precheck_sys_rep().map_err(|e| e.to_rfq(&T, &S))?; + S.precheck_sys_rep().map_err(|e| e.to_rfq(&T, &S))?; let mut inputs: HashSet = T .get_input_actions() @@ -150,8 +102,8 @@ impl Quotient { S, inputs, outputs, - universal_location, - inconsistent_location, + universal_location: LocationTree::universal(), + inconsistent_location: LocationTree::error(dim, new_clock_index), decls, quotient_clock_index: new_clock_index, new_input_name, @@ -162,7 +114,7 @@ impl Quotient { } impl TransitionSystem for Quotient { - fn get_local_max_bounds(&self, loc: &LocationTuple) -> Bounds { + fn get_local_max_bounds(&self, loc: &LocationTree) -> Bounds { if loc.is_universal() || loc.is_inconsistent() { let mut b = Bounds::new(self.get_dim()); b.add_upper(self.quotient_clock_index, 0); @@ -183,14 +135,13 @@ impl TransitionSystem for Quotient { self.dim } - fn next_transitions(&self, location: &LocationTuple, action: &str) -> Vec { + fn next_transitions(&self, location: &LocationTree, action: &str) -> Vec { assert!(self.actions_contain(action)); let is_input = self.inputs_contain(action); let mut transitions = vec![]; //Rules [universal] and [inconsistent] - if location.is_inconsistent() { //Rule 10 if is_input { @@ -214,11 +165,6 @@ impl TransitionSystem for Quotient { let t = self.T.next_transitions_if_available(loc_t, action); let s = self.S.next_transitions_if_available(loc_s, action); - let inconsistent_location = - LocationTuple::simple(&self.inconsistent_location, None, &self.decls, self.dim); - let universal_location = - LocationTuple::simple(&self.universal_location, None, &self.decls, self.dim); - //Rule 1 if self.S.actions_contain(action) && self.T.actions_contain(action) { for t_transition in &t { @@ -282,7 +228,7 @@ impl TransitionSystem for Quotient { transitions.push(Transition { id: TransitionID::Quotient(Vec::new(), s.iter().map(|t| t.id.clone()).collect()), guard_zone: (!inv_l_s) + (!g_s), - target_locations: universal_location, + target_locations: self.universal_location.clone(), updates: vec![], }); } else { @@ -292,7 +238,7 @@ impl TransitionSystem for Quotient { transitions.push(Transition { id: TransitionID::None, guard_zone: !inv_l_s, - target_locations: universal_location, + target_locations: self.universal_location.clone(), updates: vec![], }); } @@ -324,7 +270,7 @@ impl TransitionSystem for Quotient { vec![s_transition.id.clone()], ), guard_zone, - target_locations: inconsistent_location.clone(), + target_locations: self.inconsistent_location.clone(), updates, }) } @@ -344,7 +290,7 @@ impl TransitionSystem for Quotient { transitions.push(Transition { id: TransitionID::None, guard_zone, - target_locations: inconsistent_location, + target_locations: self.inconsistent_location.clone(), updates, }) } @@ -381,7 +327,7 @@ impl TransitionSystem for Quotient { fn get_actions(&self) -> HashSet { self.inputs.union(&self.outputs).cloned().collect() } - fn get_initial_location(&self) -> Option { + fn get_initial_location(&self) -> Option { let (t, s) = self.get_children(); Some(merge( &t.get_initial_location()?, @@ -389,27 +335,22 @@ impl TransitionSystem for Quotient { )) } - fn get_all_locations(&self) -> Vec { - let mut location_tuples = vec![]; + fn get_all_locations(&self) -> Vec { + let mut location_trees = vec![]; let left = self.T.get_all_locations(); let right = self.S.get_all_locations(); for loc_t in &left { for loc_s in &right { let location = merge(loc_t, loc_s); - location_tuples.push(location); + location_trees.push(location); } } - let inconsistent = - LocationTuple::simple(&self.inconsistent_location, None, &self.decls, self.dim); - let universal = - LocationTuple::simple(&self.universal_location, None, &self.decls, self.dim); - - location_tuples.push(inconsistent); - location_tuples.push(universal); + location_trees.push(self.inconsistent_location.clone()); + location_trees.push(self.universal_location.clone()); - location_tuples + location_trees } fn get_decls(&self) -> Vec<&Declarations> { @@ -419,28 +360,14 @@ impl TransitionSystem for Quotient { comps } - fn is_deterministic(&self) -> DeterminismResult { - if let DeterminismResult::Success = self.T.is_deterministic() { - if let DeterminismResult::Success = self.S.is_deterministic() { - DeterminismResult::Success - } else { - self.S.is_deterministic() - } - } else { - self.T.is_deterministic() - } + fn check_determinism(&self) -> DeterminismResult { + self.T.check_determinism()?; + self.S.check_determinism() } - fn is_locally_consistent(&self) -> ConsistencyResult { - if let ConsistencyResult::Success = self.T.is_locally_consistent() { - if let ConsistencyResult::Success = self.S.is_locally_consistent() { - ConsistencyResult::Success - } else { - self.S.is_locally_consistent() - } - } else { - self.T.is_locally_consistent() - } + fn check_local_consistency(&self) -> ConsistencyResult { + self.S.check_local_consistency()?; + self.T.check_local_consistency() } fn get_initial_state(&self) -> Option { @@ -456,18 +383,35 @@ impl TransitionSystem for Quotient { fn get_composition_type(&self) -> CompositionType { CompositionType::Quotient } + + fn construct_location_tree(&self, target: SpecificLocation) -> Result { + match target { + SpecificLocation::BranchLocation(left, right, _) => { + let left = self.T.construct_location_tree(*left)?; + let right = self.S.construct_location_tree(*right)?; + Ok(merge(&left, &right)) + } + SpecificLocation::SpecialLocation(SpecialLocation::Universal) => { + Ok(self.universal_location.clone()) + } + SpecificLocation::SpecialLocation(SpecialLocation::Error) => { + Ok(self.inconsistent_location.clone()) + } + SpecificLocation::ComponentLocation { .. } => unreachable!("Should not occur"), + } + } } -fn merge(t: &LocationTuple, s: &LocationTuple) -> LocationTuple { - LocationTuple::merge_as_quotient(t, s) +fn merge(t: &LocationTree, s: &LocationTree) -> LocationTree { + LocationTree::merge_as_quotient(t, s) } -fn get_allowed_fed(from: &LocationTuple, transition: &Transition) -> OwnedFederation { +fn get_allowed_fed(from: &LocationTree, transition: &Transition) -> OwnedFederation { let fed = transition.get_allowed_federation(); from.apply_invariants(fed) } -fn get_invariant(loc: &LocationTuple, dim: ClockIndex) -> OwnedFederation { +fn get_invariant(loc: &LocationTree, dim: ClockIndex) -> OwnedFederation { match loc.get_invariants() { Some(inv) => inv.clone(), None => OwnedFederation::universe(dim), diff --git a/src/TransitionSystems/transition_system.rs b/src/TransitionSystems/transition_system.rs index 78601c5a..f0abebfa 100644 --- a/src/TransitionSystems/transition_system.rs +++ b/src/TransitionSystems/transition_system.rs @@ -1,7 +1,9 @@ -use super::{CompositionType, LocationID, LocationTuple}; +use super::ComponentInfo; +use super::{CompositionType, LocationID, LocationTree}; use crate::DataReader::parse_queries::Rule; use crate::EdgeEval::updater::CompiledUpdate; -use crate::System::local_consistency::DeterminismFailure; +use crate::System::query_failures::{ConsistencyResult, DeterminismResult}; +use crate::System::specifics::SpecificLocation; use crate::{ component::Component, extract_system_rep::get_system_recipe, @@ -9,39 +11,62 @@ use crate::{ ComponentLoader, DataReader::component_loader::ComponentContainer, ModelObjects::component::{Declarations, State, Transition}, - System::local_consistency::DeterminismResult, - System::local_consistency::{ConsistencyFailure, ConsistencyResult}, }; use dyn_clone::{clone_trait_object, DynClone}; use edbm::util::{bounds::Bounds, constraints::ClockIndex}; -use log::warn; use pest::Parser; use std::collections::hash_map::Entry; +use std::collections::{hash_set::HashSet, HashMap}; use std::hash::Hash; -use std::{ - collections::{hash_set::HashSet, HashMap}, - iter::zip, -}; pub type TransitionSystemPtr = Box; pub type Action = String; pub type EdgeTuple = (Action, Transition); pub type EdgeIndex = (LocationID, usize); -/// Precheck can fail because of either consistency or determinism. -pub enum PrecheckResult { - Success, - NotDeterministic(LocationID, String), - NotConsistent(ConsistencyFailure), +pub enum ComponentInfoTree<'a> { + Info(&'a ComponentInfo), + Composition(Box>, Box>), +} + +impl<'a> ComponentInfoTree<'a> { + pub fn iter(&'a self) -> Box + '_> { + match self { + ComponentInfoTree::Info(info) => Box::new(std::iter::once(*info)), + ComponentInfoTree::Composition(left, right) => { + Box::new(left.iter().chain(right.iter())) + } + } + } + + pub fn split(self) -> (ComponentInfoTree<'a>, ComponentInfoTree<'a>) { + match self { + ComponentInfoTree::Composition(left, right) => (*left, *right), + ComponentInfoTree::Info(_) => { + unreachable!("Cannot split a ComponentInfoTree with only one ComponentInfo") + } + } + } + + pub fn info(&self) -> &ComponentInfo { + match self { + ComponentInfoTree::Info(info) => info, + ComponentInfoTree::Composition(_, _) => { + unreachable!( + "Cannot get info from a ComponentInfoTree with more than one ComponentInfo" + ) + } + } + } } pub trait TransitionSystem: DynClone { - fn get_local_max_bounds(&self, loc: &LocationTuple) -> Bounds; + fn get_local_max_bounds(&self, loc: &LocationTree) -> Bounds; fn get_dim(&self) -> ClockIndex; fn next_transitions_if_available( &self, - location: &LocationTuple, + location: &LocationTree, action: &str, ) -> Vec { if self.actions_contain(action) { @@ -51,14 +76,14 @@ pub trait TransitionSystem: DynClone { } } - fn next_transitions(&self, location: &LocationTuple, action: &str) -> Vec; + fn next_transitions(&self, location: &LocationTree, action: &str) -> Vec; - fn next_outputs(&self, location: &LocationTuple, action: &str) -> Vec { + fn next_outputs(&self, location: &LocationTree, action: &str) -> Vec { debug_assert!(self.get_output_actions().contains(action)); self.next_transitions(location, action) } - fn next_inputs(&self, location: &LocationTuple, action: &str) -> Vec { + fn next_inputs(&self, location: &LocationTree, action: &str) -> Vec { debug_assert!(self.get_input_actions().contains(action)); self.next_transitions(location, action) } @@ -81,11 +106,11 @@ pub trait TransitionSystem: DynClone { self.get_actions().contains(action) } - fn get_initial_location(&self) -> Option; + fn get_initial_location(&self) -> Option; - fn get_all_locations(&self) -> Vec; + fn get_all_locations(&self) -> Vec; - fn get_location(&self, id: &LocationID) -> Option { + fn get_location(&self, id: &LocationID) -> Option { self.get_all_locations() .iter() .find(|loc| loc.id == *id) @@ -94,21 +119,9 @@ pub trait TransitionSystem: DynClone { fn get_decls(&self) -> Vec<&Declarations>; - fn precheck_sys_rep(&self) -> PrecheckResult { - if let DeterminismResult::Failure(DeterminismFailure::NotDeterministicFrom( - location, - action, - )) = self.is_deterministic() - { - warn!("Not deterministic"); - return PrecheckResult::NotDeterministic(location, action); - } - - if let ConsistencyResult::Failure(failure) = self.is_locally_consistent() { - warn!("Not consistent"); - return PrecheckResult::NotConsistent(failure); - } - PrecheckResult::Success + fn precheck_sys_rep(&self) -> ConsistencyResult { + self.check_determinism()?; + self.check_local_consistency() } fn get_combined_decls(&self) -> Declarations { let mut clocks = HashMap::new(); @@ -122,9 +135,9 @@ pub trait TransitionSystem: DynClone { Declarations { ints, clocks } } - fn is_deterministic(&self) -> DeterminismResult; + fn check_determinism(&self) -> DeterminismResult; - fn is_locally_consistent(&self) -> ConsistencyResult; + fn check_local_consistency(&self) -> ConsistencyResult; fn get_initial_state(&self) -> Option; @@ -132,6 +145,27 @@ pub trait TransitionSystem: DynClone { fn get_composition_type(&self) -> CompositionType; + fn comp_infos(&'_ self) -> ComponentInfoTree<'_> { + let (left, right) = self.get_children(); + let left_info = left.comp_infos(); + let right_info = right.comp_infos(); + ComponentInfoTree::Composition(Box::new(left_info), Box::new(right_info)) + } + + fn to_string(&self) -> String { + if self.get_composition_type() == CompositionType::Simple { + panic!("Simple Transition Systems should implement to_string() themselves.") + } + let (left, right) = self.get_children(); + let comp = match self.get_composition_type() { + CompositionType::Conjunction => "&&", + CompositionType::Composition => "||", + CompositionType::Quotient => r"\\", + CompositionType::Simple => unreachable!(), + }; + format!("({} {} {})", left.to_string(), comp, right.to_string()) + } + /// Returns a [`Vec`] of all component names in a given [`TransitionSystem`]. fn component_names(&self) -> Vec<&str> { let children = self.get_children(); @@ -144,57 +178,15 @@ pub trait TransitionSystem: DynClone { .collect() } - /// Maps a clock- and component name to a clock index for a given [`TransitionSystem`]. - fn clock_name_and_component_to_index(&self, name: &str, component: &str) -> Option { - let index_to_clock_name_and_component = self.clock_name_and_component_to_index_map(); - index_to_clock_name_and_component - .get(&(name.to_string(), component.to_string())) - .copied() - } - - /// Maps a clock index to a clock- and component name for a given [`TransitionSystem`]. - fn index_to_clock_name_and_component(&self, index: &usize) -> Option<(String, String)> { - fn invert(hash_map: HashMap) -> HashMap - where - T2: Hash + Eq, - { - hash_map.into_iter().map(|x| (x.1, x.0)).collect() - } - - let index_to_clock_name_and_component = self.clock_name_and_component_to_index_map(); - let index_to_clock_name_and_component = invert(index_to_clock_name_and_component); - index_to_clock_name_and_component - .get(index) - .map(|x| x.to_owned()) - } - - /// Returns a [`HashMap`] from clock- and component names to clock indices. - fn clock_name_and_component_to_index_map(&self) -> HashMap<(String, String), usize> { - let binding = self.component_names(); - let component_names = binding.into_iter(); - let binding = self.get_decls(); - let clock_to_index = binding.into_iter().map(|decl| decl.clocks.to_owned()); - - zip(component_names, clock_to_index) - .map(|x| { - x.1.iter() - .map(|y| ((y.0.to_owned(), x.0.to_string()), y.1.to_owned())) - .collect::>() - }) - .fold(HashMap::new(), |accumulator, head| { - accumulator.into_iter().chain(head).collect() - }) - } - ///Constructs a [CLockAnalysisGraph], ///where nodes represents locations and Edges represent transitions fn get_analysis_graph(&self) -> ClockAnalysisGraph { let mut graph: ClockAnalysisGraph = ClockAnalysisGraph::empty(); graph.dim = self.get_dim(); - let location = self.get_initial_location().unwrap(); let actions = self.get_actions(); - - self.find_edges_and_nodes(&location, &actions, &mut graph); + for location in self.get_all_locations() { + self.find_edges_and_nodes(&location, &actions, &mut graph); + } graph } @@ -204,7 +196,7 @@ pub trait TransitionSystem: DynClone { ///saves these as [ClockAnalysisEdge]s and [ClockAnalysisNode]s in the [ClockAnalysisGraph] fn find_edges_and_nodes( &self, - location: &LocationTuple, + location: &LocationTree, actions: &HashSet, graph: &mut ClockAnalysisGraph, ) { @@ -248,15 +240,6 @@ pub trait TransitionSystem: DynClone { } graph.edges.push(edge); - - //Calls itself on the transitions target location if the location is not already in - //represented as a node in the graph. - if !graph - .nodes - .contains_key(&transition.target_locations.id.get_unique_string()) - { - self.find_edges_and_nodes(&transition.target_locations, actions, graph); - } } } } @@ -264,6 +247,8 @@ pub trait TransitionSystem: DynClone { fn find_redundant_clocks(&self) -> Vec { self.get_analysis_graph().find_clock_redundancies() } + + fn construct_location_tree(&self, target: SpecificLocation) -> Result; } /// Returns a [`TransitionSystemPtr`] equivalent to a `composition` of some `components`. @@ -310,10 +295,10 @@ impl ClockReductionInstruction { } } - pub(crate) fn is_replace(&self) -> bool { + pub(crate) fn get_clock_index(&self) -> ClockIndex { match self { - ClockReductionInstruction::RemoveClock { .. } => false, - ClockReductionInstruction::ReplaceClocks { .. } => true, + ClockReductionInstruction::RemoveClock { clock_index } + | ClockReductionInstruction::ReplaceClocks { clock_index, .. } => *clock_index, } } } diff --git a/src/lib.rs b/src/lib.rs index a9d3f376..e8a6c57c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,7 +20,6 @@ pub use crate::System::extract_system_rep; pub use ModelObjects::component; pub use ModelObjects::queries; pub use ProtobufServer::start_grpc_server_with_tokio; -pub use System::executable_query::QueryResult; /// The default settings pub const DEFAULT_SETTINGS: Settings = Settings { diff --git a/src/main.rs b/src/main.rs index 24e27358..db2a2ab0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,12 @@ #![allow(non_snake_case)] use clap::{load_yaml, App}; use reveaal::logging::setup_logger; +use reveaal::System::query_failures::QueryResult; use reveaal::ProtobufServer::services::query_request::Settings; use reveaal::{ extract_system_rep, parse_queries, start_grpc_server_with_tokio, xml_parser, ComponentLoader, - JsonProjectLoader, ProjectLoader, Query, QueryResult, XmlProjectLoader, + JsonProjectLoader, ProjectLoader, Query, XmlProjectLoader, }; use std::env; @@ -46,7 +47,7 @@ fn start_using_cli(matches: &clap::ArgMatches) { let result = executable_query.execute(); - if let QueryResult::Error(err) = result { + if let QueryResult::CustomError(err) = result { panic!("{}", err); } diff --git a/src/tests/ClockReduction/advanced_clock_removal_test.rs b/src/tests/ClockReduction/advanced_clock_removal_test.rs index 2e3c5643..3203e096 100644 --- a/src/tests/ClockReduction/advanced_clock_removal_test.rs +++ b/src/tests/ClockReduction/advanced_clock_removal_test.rs @@ -18,7 +18,7 @@ pub mod test { let mut system_recipe_copy = Box::new(system_recipe); - clock_reduction::clock_reduce(&mut system_recipe_copy, None, &mut dimensions, false) + clock_reduction::clock_reduce(&mut system_recipe_copy, None, &mut dimensions, None) .unwrap(); //We let it use the unreduced amount of dimensions so we can catch the error @@ -32,22 +32,45 @@ pub mod test { let graph = compiled.get_analysis_graph(); for edge in &graph.edges { match format!("{}->{}", edge.from, edge.to).as_str() { - "(Component1.L0&&Component2.L4)->(Component1.L1&&Component2.L5)" => { - assert_eq!(edge.guard_dependencies.len(), 2, "edge (Component1.L0&&Component2.L4)->(Component1.L1&&Component2.L5) should only have 1 guard dependency"); + "(L0&&L4)->(L1&&L5)" => { + assert_eq!( + edge.guard_dependencies.len(), + 2, + "edge (L0&&L4)->(L1&&L5) should only have 1 guard dependency" + ); assert!(edge.guard_dependencies.is_subset(&HashSet::from([0, 1]))); - assert_eq!(edge.updates.len(), 0, "(Component1.L0&&Component2.L4)->(Component1.L1&&Component2.L5) should have no updates"); + assert_eq!( + edge.updates.len(), + 0, + "(L0&&L4)->(L1&&L5) should have no updates" + ); } - "(Component1.L1&&Component2.L5)->(Component1.L2&&Component2.L6)" => { - assert_eq!(edge.guard_dependencies.len(), 0, "edge (Component1.L0&&Component2.L4)->(Component1.L1&&Component2.L5) should only have 2 guard dependency"); + "(L1&&L5)->(L2&&L6)" => { + assert_eq!( + edge.guard_dependencies.len(), + 0, + "edge (L0&&L4)->(L1&&L5) should only have 2 guard dependency" + ); for update in &edge.updates { - assert_eq!(update.clock_index, 1, "edge (Component1.L0&&Component2.L4)->(Component1.L1&&Component2.L5) should only update clock 1"); + assert_eq!( + update.clock_index, 1, + "edge (L0&&L4)->(L1&&L5) should only update clock 1" + ); } } - "(Component1.L2&&Component2.L6)->(Component1.L3&&Component2.L7)" => { - assert_eq!(edge.guard_dependencies.len(), 0, "edge (Component1.L0&&Component2.L4)->(Component1.L1&&Component2.L5) should only have 1 guard dependency"); - assert_eq!(edge.updates.len(), 0, "(Component1.L2&&Component2.L6)->(Component1.L3&&Component2.L7) should have no updates"); + "(L2&&L6)->(L3&&L7)" => { + assert_eq!( + edge.guard_dependencies.len(), + 0, + "edge (L0&&L4)->(L1&&L5) should only have 1 guard dependency" + ); + assert_eq!( + edge.updates.len(), + 0, + "(L2&&L6)->(L3&&L7) should have no updates" + ); } - _ => panic!("unknown edge"), + e => panic!("unknown edge {}", e), } } } diff --git a/src/tests/ClockReduction/clock_removal_test.rs b/src/tests/ClockReduction/clock_removal_test.rs index 2da51f94..a1872d7a 100644 --- a/src/tests/ClockReduction/clock_removal_test.rs +++ b/src/tests/ClockReduction/clock_removal_test.rs @@ -1,5 +1,8 @@ #[cfg(test)] pub mod clock_removal_tests { + use crate::component::Component; + use crate::extract_system_rep::{clock_reduction, SystemRecipe}; + use crate::tests::refinement::Helper::json_run_query; use crate::DataReader::json_reader::read_json_component; use crate::TransitionSystems::{CompiledComponent, TransitionSystem}; use std::collections::HashSet; @@ -11,22 +14,36 @@ pub mod clock_removal_tests { check_declarations_unused_clocks_are_removed("Component3", "c"); } + impl Component { + fn fit_decls(&mut self, index: edbm::util::constraints::ClockIndex) { + self.declarations + .clocks + .values_mut() + .filter(|val| **val > index) + .for_each(|val| *val -= 1); + } + } + fn check_declarations_unused_clocks_are_removed(component_name: &str, clock: &str) { let mut component = read_json_component( "samples/json/ClockReductionTest/UnusedClock", component_name, ); - let clock_index = component + let clock_index = *component .declarations .get_clock_index_by_name(clock) .unwrap(); - component.remove_clock(*clock_index); + component.remove_clock(clock_index); + component.fit_decls(clock_index); - let clock_reduced_compiled_component = - CompiledComponent::compile(component.clone(), component.declarations.clocks.len() + 1) - .unwrap(); + let clock_reduced_compiled_component = CompiledComponent::compile( + component.clone(), + component.declarations.clocks.len() + 1, + &mut 0, + ) + .unwrap(); let decls = clock_reduced_compiled_component.get_decls(); @@ -49,9 +66,12 @@ pub mod clock_removal_tests { component.replace_clock(*clock_1_index, &duplicate_clocks_index); - let clock_reduced_compiled_component = - CompiledComponent::compile(component.clone(), component.declarations.clocks.len() + 1) - .unwrap(); + let clock_reduced_compiled_component = CompiledComponent::compile( + component.clone(), + component.declarations.clocks.len() + 1, + &mut 0, + ) + .unwrap(); let decls = clock_reduced_compiled_component.get_decls(); @@ -59,4 +79,56 @@ pub mod clock_removal_tests { assert_eq!(*decls[0].clocks.get_key_value("y").unwrap().1, 1); assert_eq!(*decls[0].clocks.get_key_value("z").unwrap().1, 1); } + + #[test] + fn test_no_used_clock() { + const PATH: &str = "samples/json/AG"; + + let comp = read_json_component(PATH, "A"); + + let mut dim = comp.declarations.clocks.len(); + assert_eq!( + dim, 4, + "As of writing these tests, this component has 4 unused clocks" + ); + + let recipe = SystemRecipe::Component(Box::from(comp)); + clock_reduction::clock_reduce(&mut Box::from(recipe), None, &mut dim, None).unwrap(); + assert_eq!(dim, 0, "After removing the clocks, the dim should be 0"); + + assert!( + json_run_query(PATH, "consistency: A").is_ok(), + "A should be consistent" + ); + } + + #[test] + fn test_no_used_clock_multi() { + const PATH: &str = "samples/json/AG"; + let mut dim = 0; + let mut lhs = read_json_component(PATH, "A"); + lhs.set_clock_indices(&mut dim); + let mut rhs = read_json_component(PATH, "A"); + rhs.set_clock_indices(&mut dim); + + assert_eq!( + dim, 8, + "As of writing these tests, these component has 8 unused clocks" + ); + assert_eq!( + lhs.declarations.clocks.len() + rhs.declarations.clocks.len(), + 8 + ); + + let l = SystemRecipe::Component(Box::from(lhs)); + let r = SystemRecipe::Component(Box::from(rhs)); + clock_reduction::clock_reduce(&mut Box::from(l), Some(&mut Box::from(r)), &mut dim, None) + .unwrap(); + assert_eq!(dim, 0, "After removing the clocks, the dim should be 0"); + + assert!( + json_run_query(PATH, "refinement: A <= A").is_ok(), + "A should refine itself" + ); + } } diff --git a/src/tests/ClockReduction/redundant_clock_detection_test.rs b/src/tests/ClockReduction/redundant_clock_detection_test.rs index 3d3e27bd..61a7c9c5 100644 --- a/src/tests/ClockReduction/redundant_clock_detection_test.rs +++ b/src/tests/ClockReduction/redundant_clock_detection_test.rs @@ -15,7 +15,8 @@ pub mod test { let expected_clocks = ["x".to_string(), "y".to_string(), "z".to_string()]; let component = read_json_component_and_process(REDUNDANT_CLOCKS_TEST_PROJECT, "Component1"); - let compiled_component = CompiledComponent::compile(component.clone(), DIM).unwrap(); + let compiled_component = + CompiledComponent::compile(component.clone(), DIM, &mut 0).unwrap(); let clock_index_x = component .declarations .get_clock_index_by_name(&expected_clocks[0]) diff --git a/src/tests/ClockReduction/unused_clock_detection_test.rs b/src/tests/ClockReduction/unused_clock_detection_test.rs index b78e9114..405a966c 100644 --- a/src/tests/ClockReduction/unused_clock_detection_test.rs +++ b/src/tests/ClockReduction/unused_clock_detection_test.rs @@ -12,9 +12,12 @@ mod unused_clocks_tests { component_name, ); - let compiled_component = - CompiledComponent::compile(component.clone(), component.declarations.clocks.len() + 1) - .unwrap(); + let compiled_component = CompiledComponent::compile( + component.clone(), + component.declarations.clocks.len() + 1, + &mut 0, + ) + .unwrap(); let clock_index = component .declarations @@ -34,9 +37,12 @@ mod unused_clocks_tests { component_name, ); - let compiled_component = - CompiledComponent::compile(component.clone(), component.declarations.clocks.len() + 1) - .unwrap(); + let compiled_component = CompiledComponent::compile( + component.clone(), + component.declarations.clocks.len() + 1, + &mut 0, + ) + .unwrap(); let clock_index = component .declarations diff --git a/src/tests/Simulation/helper.rs b/src/tests/Simulation/helper.rs index af27bef1..06ba36c8 100644 --- a/src/tests/Simulation/helper.rs +++ b/src/tests/Simulation/helper.rs @@ -1,183 +1,13 @@ -use std::{fs, vec}; +use std::fs; -use tonic::{Request, Response, Status}; +use tonic::{Response, Status}; +use crate::ProtobufServer::services::component::Rep; use crate::ProtobufServer::services::{ - self, Component as ProtoComponent, ComponentsInfo as ProtoComponentsInfo, - Decision as ProtoDecision, Edge as ProtoEdge, SimulationInfo as ProtoSimulationInfo, - SimulationStartRequest, SimulationStepRequest, SimulationStepResponse, State as ProtoState, + Component as ProtoComponent, ComponentsInfo as ProtoComponentsInfo, + SimulationInfo as ProtoSimulationInfo, SimulationStartRequest, SimulationStepRequest, + SimulationStepResponse, }; -use crate::{ - DataReader::json_reader::read_json_component, - ProtobufServer::services::component::Rep, - TransitionSystems::{ - transition_system::components_to_transition_system, CompositionType, TransitionSystemPtr, - }, -}; - -pub fn create_system_from_path(path: &str, name: &str) -> TransitionSystemPtr { - let component = read_json_component(path, name); - components_to_transition_system(vec![component], name) -} - -pub fn create_simulation_info( - composition: String, - components: Vec, -) -> ProtoSimulationInfo { - ProtoSimulationInfo { - component_composition: composition, - components_info: Some(ProtoComponentsInfo { - components, - components_hash: 0, - }), - user_id: 0, - } -} - -pub fn create_composition_string(comp_names: &Vec<&str>, comp_type: CompositionType) -> String { - let mut composition = String::new(); - for (i, name) in comp_names.iter().enumerate() { - composition.push_str(name); - if i < comp_names.len() - 1 { - match comp_type { - CompositionType::Conjunction => composition.push_str(" && "), - CompositionType::Composition => composition.push_str(" || "), - CompositionType::Quotient => { - unimplemented!("Quotient composition not implemented") - } - CompositionType::Simple => unimplemented!("Simple composition not implemented"), - } - } - } - composition -} - -pub fn create_components(comp_names: &[&str], sample_name: String) -> Vec { - let components: Vec = comp_names - .iter() - .map(|name| { - create_json_component_as_string(format!( - "samples/json/{}/Components/{}.json", - sample_name, name - )) - }) - .collect(); - - let components: Vec = components - .iter() - .map(|string| ProtoComponent { - rep: Some(Rep::Json(string.clone())), - }) - .collect(); - - components -} - -pub fn create_1tuple_state_with_single_constraint( - id: &str, - component_name: &str, - component_index: u32, - clock_x_name: &str, - clock_y_name: &str, - clock_constraint: i32, - is_constrain_strict: bool, -) -> services::State { - services::State { - location_tuple: Some(services::LocationTuple { - locations: vec![services::Location { - id: String::from(id), - specific_component: Some(services::SpecificComponent { - component_name: String::from(component_name), - component_index, - }), - }], - }), - federation: Some(services::Federation { - disjunction: Some(services::Disjunction { - conjunctions: vec![services::Conjunction { - constraints: vec![ - // constraint (x - y <= c) - services::Constraint { - x: Some(services::ComponentClock { - specific_component: Some(services::SpecificComponent { - component_name: String::from(component_name), - component_index, - }), - clock_name: String::from(clock_x_name), - }), - y: Some(services::ComponentClock { - specific_component: Some(services::SpecificComponent { - component_name: String::from(component_name), - component_index, - }), - clock_name: String::from(clock_y_name), - }), - strict: is_constrain_strict, - c: clock_constraint, - }, - ], - }], - }), - }), - } -} - -pub fn create_json_component_as_string(path: String) -> String { - fs::read_to_string(path).unwrap() -} - -pub fn create_simulation_step_request( - simulation_info: ProtoSimulationInfo, - source: services::State, - edge: services::Edge, -) -> SimulationStepRequest { - SimulationStepRequest { - simulation_info: Some(simulation_info), - chosen_decision: Some(services::Decision { - source: Some(source), - edge: Some(edge), - }), - } -} - -pub fn create_simulation_start_request( - composition: String, - component_json: String, -) -> Request { - Request::new(SimulationStartRequest { - simulation_info: Some(create_simulation_info_from(composition, component_json)), - }) -} - -pub fn create_empty_state() -> ProtoState { - ProtoState { - location_tuple: None, - federation: None, - } -} - -pub fn create_empty_edge() -> ProtoEdge { - ProtoEdge { - id: String::from(""), - specific_component: None, - } -} - -pub fn create_simulation_info_from( - composition: String, - component_json: String, -) -> ProtoSimulationInfo { - ProtoSimulationInfo { - user_id: 0, - component_composition: composition, - components_info: Some(ProtoComponentsInfo { - components: vec![ProtoComponent { - rep: Some(services::component::Rep::Json(component_json)), - }], - components_hash: 0, - }), - } -} pub fn create_start_request( component_names: &[&str], @@ -190,36 +20,21 @@ pub fn create_start_request( } } -pub fn create_step_request( +pub fn create_step_requests( component_names: &[&str], components_path: &str, composition: &str, last_response: Result, Status>, -) -> SimulationStepRequest { +) -> impl Iterator { let simulation_info = create_simulation_info_1(component_names, components_path, composition); let last_response = last_response.unwrap().into_inner(); - let source = last_response - .new_decision_points - .first() - .unwrap() - .source - .to_owned(); - let decision = last_response + last_response .new_decision_points - .first() - .unwrap() - .edges - .first() - .unwrap() - .to_owned(); - - SimulationStepRequest { - simulation_info: Some(simulation_info), - chosen_decision: Some(ProtoDecision { - source, - edge: Some(decision), - }), - } + .into_iter() + .map(move |d| SimulationStepRequest { + simulation_info: Some(simulation_info.clone()), + chosen_decision: Some(d), + }) } fn create_simulation_info_1( diff --git a/src/tests/Simulation/mod.rs b/src/tests/Simulation/mod.rs index b6dc7e9c..485bb81a 100644 --- a/src/tests/Simulation/mod.rs +++ b/src/tests/Simulation/mod.rs @@ -1,2 +1 @@ pub mod helper; -pub mod test_data; diff --git a/src/tests/Simulation/test_data.rs b/src/tests/Simulation/test_data.rs deleted file mode 100644 index ebe740d2..00000000 --- a/src/tests/Simulation/test_data.rs +++ /dev/null @@ -1,1310 +0,0 @@ -use std::fs; - -use tonic::{Request, Response, Status}; - -use crate::ProtobufServer::services::{ - Component as ProtoComponent, ComponentClock as ProtoComponentClock, - Conjunction as ProtoConjunction, Constraint as ProtoConstraint, Decision as ProtoDecision, - DecisionPoint as ProtoDecisionPoint, Disjunction as ProtoDisjunction, Edge as ProtoEdge, - Federation as ProtoFederation, Location as ProtoLocation, LocationTuple as ProtoLocationTuple, - SimulationStartRequest, SimulationStepRequest, SimulationStepResponse, - SpecificComponent as ProtoSpecificComponent, State as ProtoState, -}; -use crate::Simulation::transition_decision_point::TransitionDecisionPoint; -use crate::TransitionSystems::CompositionType; -use crate::{ - component::Component, DataReader::json_reader::read_json_component, - TransitionSystems::TransitionSystemPtr, -}; - -use super::helper::{ - create_1tuple_state_with_single_constraint, create_components, create_composition_string, - create_empty_edge, create_empty_state, create_simulation_info, create_simulation_info_from, - create_simulation_start_request, create_simulation_step_request, create_system_from_path, -}; - -static ECDAR_UNI: &str = "samples/json/EcdarUniversity"; - -pub fn create_EcdarUniversity_Machine_component() -> Component { - let project_path = "samples/json/EcdarUniversity"; - read_json_component(project_path, "Machine") -} - -pub fn create_EcdarUniversity_Machine_system() -> TransitionSystemPtr { - create_system_from_path("samples/json/EcdarUniversity", "Machine") -} - -pub fn create_EcdarUniversity_HalfAdm1_system() -> TransitionSystemPtr { - create_system_from_path("samples/json/EcdarUniversity", "HalfAdm1") -} - -pub fn create_EcdarUniversity_HalfAdm2_system() -> TransitionSystemPtr { - create_system_from_path("samples/json/EcdarUniversity", "HalfAdm2") -} - -pub fn create_EcdarUniversity_Administration_system() -> TransitionSystemPtr { - create_system_from_path("samples/json/EcdarUniversity", "Administration") -} - -pub fn create_EcdarUniversity_Researcher_system() -> TransitionSystemPtr { - create_system_from_path("samples/json/EcdarUniversity", "Researcher") -} - -pub fn create_Simulation_Machine_system() -> TransitionSystemPtr { - create_system_from_path("samples/json/Simulation", "SimMachine") -} - -pub fn create_EcdarUniversity_Machine4_system() -> TransitionSystemPtr { - create_system_from_path("samples/json/EcdarUniversity", "Machine4") -} - -pub fn create_EcdarUniversity_Machine_Decision() -> ProtoDecision { - // kopieret fra create_EcdarUnversity_Machine_Initial_Decision_Point men ved ikke hvordan det kunne gøres til en funktion smart - let specific_comp_dp = ProtoSpecificComponent { - component_name: "Machine".to_string(), - component_index: 1, - }; - - let conjunction_dp = ProtoConjunction { - constraints: vec![], - }; - - let disjunction_dp = ProtoDisjunction { - conjunctions: vec![conjunction_dp], - }; - - let federation_dp = ProtoFederation { - disjunction: Some(disjunction_dp), - }; - - let location_dp1 = ProtoLocation { - id: "L5".to_string(), - specific_component: Some(specific_comp_dp.clone()), - }; - - let loc_tuple_dp = ProtoLocationTuple { - locations: vec![location_dp1], - }; - - let source_dp = ProtoState { - location_tuple: Some(loc_tuple_dp), - federation: Some(federation_dp), - }; - - let edge29 = ProtoEdge { - id: "E29".to_string(), - specific_component: Some(specific_comp_dp), - }; - - ProtoDecision { - source: Some(source_dp), - edge: Some(edge29), - } -} - -pub fn create_EcdarUniversity_Machine_with_nonempty_Federation_Decision() -> ProtoDecision { - // kopieret fra create_EcdarUnversity_Machine_Initial_Decision_Point men ved ikke hvordan det kunne gøres til en funktion smart - let specific_comp_dp = ProtoSpecificComponent { - component_name: "Machine".to_string(), - component_index: 1, - }; - - let componentclock_dp1 = ProtoComponentClock { - specific_component: None, - clock_name: "0".to_string(), - }; - let componentclock_dp2 = ProtoComponentClock { - specific_component: Some(specific_comp_dp.clone()), - clock_name: "y".to_string(), - }; - - let constraint29_dp = ProtoConstraint { - x: Some(componentclock_dp1), - y: Some(componentclock_dp2), - strict: false, - c: -2, - }; - - let conjunction_dp = ProtoConjunction { - constraints: vec![constraint29_dp], - }; - - let disjunction_dp = ProtoDisjunction { - conjunctions: vec![conjunction_dp], - }; - - let federation_dp = ProtoFederation { - disjunction: Some(disjunction_dp), - }; - - let location_dp1 = ProtoLocation { - id: "L5".to_string(), - specific_component: Some(specific_comp_dp.clone()), - }; - - let loc_tuple_dp = ProtoLocationTuple { - locations: vec![location_dp1], - }; - - let source_dp = ProtoState { - location_tuple: Some(loc_tuple_dp), - federation: Some(federation_dp), - }; - - let edge29 = ProtoEdge { - id: "E29".to_string(), - specific_component: Some(specific_comp_dp), - }; - - ProtoDecision { - source: Some(source_dp), - edge: Some(edge29), - } -} - -pub fn create_EcdarUniversity_Machine3and1_with_nonempty_Federation_Decision() -> ProtoDecision { - // kopieret fra create_EcdarUnversity_Machine_Initial_Decision_Point men ved ikke hvordan det kunne gøres til en funktion smart - let specific_comp_dp1 = ProtoSpecificComponent { - component_name: "Machine".to_string(), - component_index: 1, - }; - - let source_dp = ProtoState { - location_tuple: Some(ProtoLocationTuple { - locations: vec![ - ProtoLocation { - id: "L8".to_string(), - specific_component: Some(ProtoSpecificComponent { - component_name: "Machine3".to_string(), - component_index: 0, - }), - }, - ProtoLocation { - id: "L5".to_string(), - specific_component: Some(ProtoSpecificComponent { - component_name: "Machine".to_string(), - component_index: 0, - }), - }, - ], - }), - federation: Some(ProtoFederation { - disjunction: Some(ProtoDisjunction { - conjunctions: vec![ProtoConjunction { - constraints: vec![ - ProtoConstraint { - x: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "Machine3".to_string(), - component_index: 0, - }), - clock_name: "y".to_string(), - }), - y: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "Machine".to_string(), - component_index: 0, - }), - clock_name: "y".to_string(), - }), - strict: false, - c: 0, - }, - ProtoConstraint { - x: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "Machine".to_string(), - component_index: 0, - }), - clock_name: "y".to_string(), - }), - y: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "Machine3".to_string(), - component_index: 0, - }), - clock_name: "y".to_string(), - }), - strict: false, - c: 0, - }, - ], - }], - }), - }), - }; - - let edge29 = ProtoEdge { - id: "E29".to_string(), - specific_component: Some(specific_comp_dp1), - }; - - ProtoDecision { - source: Some(source_dp), - edge: Some(edge29), - } -} - -pub fn initial_transition_decision_point_EcdarUniversity_Machine() -> TransitionDecisionPoint { - let system = create_EcdarUniversity_Machine_system(); - TransitionDecisionPoint::initial(&system).unwrap() -} - -pub fn get_state_after_Administration_Machine_Researcher_composition() -> ProtoState { - ProtoState { - location_tuple: Some(ProtoLocationTuple { - locations: vec![ - ProtoLocation { - id: "L0".to_string(), - specific_component: Some(ProtoSpecificComponent { - component_name: "Administration".to_string(), - component_index: 0, - }), - }, - ProtoLocation { - id: "L5".to_string(), - specific_component: Some(ProtoSpecificComponent { - component_name: "Machine".to_string(), - component_index: 0, - }), - }, - ProtoLocation { - id: "L6".to_string(), - specific_component: Some(ProtoSpecificComponent { - component_name: "Researcher".to_string(), - component_index: 0, - }), - }, - ], - }), - federation: Some(ProtoFederation { - disjunction: Some(ProtoDisjunction { - conjunctions: vec![ProtoConjunction { - constraints: vec![ - ProtoConstraint { - x: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "Administration".to_string(), - component_index: 0, - }), - clock_name: "z".to_string(), - }), - y: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "Machine".to_string(), - component_index: 0, - }), - clock_name: "y".to_string(), - }), - strict: false, - c: 0, - }, - ProtoConstraint { - x: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "Machine".to_string(), - component_index: 0, - }), - clock_name: "y".to_string(), - }), - y: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "Researcher".to_string(), - component_index: 0, - }), - clock_name: "x".to_string(), - }), - strict: false, - c: 0, - }, - ProtoConstraint { - x: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "Researcher".to_string(), - component_index: 0, - }), - clock_name: "x".to_string(), - }), - y: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "Administration".to_string(), - component_index: 0, - }), - clock_name: "z".to_string(), - }), - strict: false, - c: 0, - }, - ], - }], - }), - }), - } -} - -pub fn get_composition_response_Administration_Machine_Researcher( -) -> Result, Status> { - let proto_decision_point = ProtoDecisionPoint { - source: Some(ProtoState { - location_tuple: Some(ProtoLocationTuple { - locations: vec![ - ProtoLocation { - id: "L0".to_string(), - specific_component: Some(ProtoSpecificComponent { - component_name: "Administration".to_string(), - component_index: 0, - }), - }, - ProtoLocation { - id: "L5".to_string(), - specific_component: Some(ProtoSpecificComponent { - component_name: "Machine".to_string(), - component_index: 0, - }), - }, - ProtoLocation { - id: "L6".to_string(), - specific_component: Some(ProtoSpecificComponent { - component_name: "Researcher".to_string(), - component_index: 0, - }), - }, - ], - }), - federation: Some(ProtoFederation { - disjunction: Some(ProtoDisjunction { - conjunctions: vec![ProtoConjunction { - constraints: vec![ - ProtoConstraint { - x: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "Administration".to_string(), - component_index: 0, - }), - clock_name: "z".to_string(), - }), - y: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "Machine".to_string(), - component_index: 0, - }), - clock_name: "y".to_string(), - }), - strict: false, - c: 0, - }, - ProtoConstraint { - x: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "Machine".to_string(), - component_index: 0, - }), - clock_name: "y".to_string(), - }), - y: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "Researcher".to_string(), - component_index: 0, - }), - clock_name: "x".to_string(), - }), - strict: false, - c: 0, - }, - ProtoConstraint { - x: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "Researcher".to_string(), - component_index: 0, - }), - clock_name: "x".to_string(), - }), - y: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "Administration".to_string(), - component_index: 0, - }), - clock_name: "z".to_string(), - }), - strict: false, - c: 0, - }, - ], - }], - }), - }), - }), - edges: vec![ - ProtoEdge { - id: "E11".to_string(), - specific_component: None, - }, - ProtoEdge { - id: "E16".to_string(), - specific_component: None, - }, - ProtoEdge { - id: "E29".to_string(), - specific_component: None, - }, - ProtoEdge { - id: "E44".to_string(), - specific_component: None, - }, - ], - }; - - let response = SimulationStepResponse { - new_decision_points: vec![proto_decision_point], - }; - - Ok(Response::new(response)) -} - -pub fn get_composition_response_Administration_Machine_Researcher_after_E29( -) -> Result, Status> { - let decisionpoint1 = ProtoDecisionPoint { - source: Some(ProtoState { - location_tuple: Some(ProtoLocationTuple { - locations: vec![ - ProtoLocation { - id: "L0".to_string(), - specific_component: Some(ProtoSpecificComponent { - component_name: "Administration".to_string(), - component_index: 0, - }), - }, - ProtoLocation { - id: "L5".to_string(), - specific_component: Some(ProtoSpecificComponent { - component_name: "Machine".to_string(), - component_index: 0, - }), - }, - ProtoLocation { - id: "L7".to_string(), - specific_component: Some(ProtoSpecificComponent { - component_name: "Researcher".to_string(), - component_index: 0, - }), - }, - ], - }), - federation: Some(ProtoFederation { - disjunction: Some(ProtoDisjunction { - conjunctions: vec![ProtoConjunction { - constraints: vec![ - ProtoConstraint { - x: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "Administration".to_string(), - component_index: 0, - }), - clock_name: "z".to_string(), - }), - y: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "Machine".to_string(), - component_index: 0, - }), - clock_name: "y".to_string(), - }), - strict: false, - c: 0, - }, - ProtoConstraint { - x: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "Administration".to_string(), - component_index: 0, - }), - clock_name: "z".to_string(), - }), - y: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "Researcher".to_string(), - component_index: 0, - }), - clock_name: "x".to_string(), - }), - strict: false, - c: 15, - }, - ProtoConstraint { - x: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "Machine".to_string(), - component_index: 0, - }), - clock_name: "y".to_string(), - }), - y: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "Administration".to_string(), - component_index: 0, - }), - clock_name: "z".to_string(), - }), - strict: false, - c: 0, - }, - ProtoConstraint { - x: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "Researcher".to_string(), - component_index: 0, - }), - clock_name: "x".to_string(), - }), - y: Some(ProtoComponentClock { - specific_component: None, - clock_name: "0".to_string(), - }), - strict: false, - c: 8, - }, - ProtoConstraint { - x: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "Researcher".to_string(), - component_index: 0, - }), - clock_name: "x".to_string(), - }), - y: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "Administration".to_string(), - component_index: 0, - }), - clock_name: "z".to_string(), - }), - strict: false, - c: -2, - }, - ], - }], - }), - }), - }), - edges: vec![ - ProtoEdge { - id: "E13".to_string(), - specific_component: None, - }, - ProtoEdge { - id: "E29".to_string(), - specific_component: None, - }, - ProtoEdge { - id: "E44".to_string(), - specific_component: None, - }, - ProtoEdge { - id: "E9".to_string(), - specific_component: None, - }, - ], - }; - let decisionpoint2 = ProtoDecisionPoint { - source: Some(ProtoState { - location_tuple: Some(ProtoLocationTuple { - locations: vec![ - ProtoLocation { - id: "L0".to_string(), - specific_component: Some(ProtoSpecificComponent { - component_name: "Administration".to_string(), - component_index: 0, - }), - }, - ProtoLocation { - id: "L5".to_string(), - specific_component: Some(ProtoSpecificComponent { - component_name: "Machine".to_string(), - component_index: 0, - }), - }, - ProtoLocation { - id: "U0".to_string(), - specific_component: Some(ProtoSpecificComponent { - component_name: "Researcher".to_string(), - component_index: 0, - }), - }, - ], - }), - federation: Some(ProtoFederation { - disjunction: Some(ProtoDisjunction { - conjunctions: vec![ProtoConjunction { - constraints: vec![ - ProtoConstraint { - x: Some(ProtoComponentClock { - specific_component: None, - clock_name: "0".to_string(), - }), - y: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "Administration".to_string(), - component_index: 0, - }), - clock_name: "z".to_string(), - }), - strict: true, - c: -15, - }, - ProtoConstraint { - x: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "Administration".to_string(), - component_index: 0, - }), - clock_name: "z".to_string(), - }), - y: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "Machine".to_string(), - component_index: 0, - }), - clock_name: "y".to_string(), - }), - strict: false, - c: 0, - }, - ProtoConstraint { - x: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "Machine".to_string(), - component_index: 0, - }), - clock_name: "y".to_string(), - }), - y: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "Researcher".to_string(), - component_index: 0, - }), - clock_name: "x".to_string(), - }), - strict: false, - c: 0, - }, - ProtoConstraint { - x: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "Researcher".to_string(), - component_index: 0, - }), - clock_name: "x".to_string(), - }), - y: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "Administration".to_string(), - component_index: 0, - }), - clock_name: "z".to_string(), - }), - strict: false, - c: 0, - }, - ], - }], - }), - }), - }), - edges: vec![ - ProtoEdge { - id: "E29".to_string(), - specific_component: None, - }, - ProtoEdge { - id: "E44".to_string(), - specific_component: None, - }, - ], - }; - let response = SimulationStepResponse { - new_decision_points: vec![decisionpoint1, decisionpoint2], - }; - - Ok(Response::new(response)) -} - -pub fn get_state_after_HalfAdm1_HalfAdm2_conjunction() -> ProtoState { - ProtoState { - location_tuple: Some(ProtoLocationTuple { - locations: vec![ - ProtoLocation { - id: "L12".to_string(), - specific_component: Some(ProtoSpecificComponent { - component_name: "HalfAdm1".to_string(), - component_index: 0, - }), - }, - ProtoLocation { - id: "L14".to_string(), - specific_component: Some(ProtoSpecificComponent { - component_name: "HalfAdm2".to_string(), - component_index: 0, - }), - }, - ], - }), - federation: Some(ProtoFederation { - disjunction: Some(ProtoDisjunction { - conjunctions: vec![ProtoConjunction { - constraints: vec![ - ProtoConstraint { - x: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "HalfAdm1".to_string(), - component_index: 0, - }), - clock_name: "x".to_string(), - }), - y: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "HalfAdm2".to_string(), - component_index: 0, - }), - clock_name: "y".to_string(), - }), - strict: false, - c: 0, - }, - ProtoConstraint { - x: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "HalfAdm2".to_string(), - component_index: 0, - }), - clock_name: "y".to_string(), - }), - y: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "HalfAdm1".to_string(), - component_index: 0, - }), - clock_name: "x".to_string(), - }), - strict: false, - c: 0, - }, - ], - }], - }), - }), - } -} - -pub fn get_conjunction_response_HalfAdm1_HalfAdm2( -) -> Result, Status> { - let proto_decision_point = ProtoDecisionPoint { - source: Some(ProtoState { - location_tuple: Some(ProtoLocationTuple { - locations: vec![ - ProtoLocation { - id: "L12".to_string(), - specific_component: Some(ProtoSpecificComponent { - component_name: "HalfAdm1".to_string(), - component_index: 0, - }), - }, - ProtoLocation { - id: "L14".to_string(), - specific_component: Some(ProtoSpecificComponent { - component_name: "HalfAdm2".to_string(), - component_index: 0, - }), - }, - ], - }), - federation: Some(ProtoFederation { - disjunction: Some(ProtoDisjunction { - conjunctions: vec![ProtoConjunction { - constraints: vec![ - ProtoConstraint { - x: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "HalfAdm1".to_string(), - component_index: 0, - }), - clock_name: "x".to_string(), - }), - y: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "HalfAdm2".to_string(), - component_index: 0, - }), - clock_name: "y".to_string(), - }), - strict: false, - c: 0, - }, - ProtoConstraint { - x: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "HalfAdm2".to_string(), - component_index: 0, - }), - clock_name: "y".to_string(), - }), - y: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "HalfAdm1".to_string(), - component_index: 0, - }), - clock_name: "x".to_string(), - }), - strict: false, - c: 0, - }, - ], - }], - }), - }), - }), - edges: vec![ - ProtoEdge { - id: "E30".to_string(), - specific_component: None, - }, - ProtoEdge { - id: "E35".to_string(), - specific_component: None, - }, - ProtoEdge { - id: "E37".to_string(), - specific_component: None, - }, - ProtoEdge { - id: "E42".to_string(), - specific_component: None, - }, - ], - }; - - let response = SimulationStepResponse { - new_decision_points: vec![proto_decision_point], - }; - - Ok(Response::new(response)) -} - -pub fn get_conjunction_response_HalfAdm1_HalfAdm2_after_E37( -) -> Result, Status> { - let new_decision_points = ProtoDecisionPoint { - source: Some(ProtoState { - location_tuple: Some(ProtoLocationTuple { - locations: vec![ - ProtoLocation { - id: "L13".to_string(), - specific_component: Some(ProtoSpecificComponent { - component_name: "HalfAdm1".to_string(), - component_index: 0, - }), - }, - ProtoLocation { - id: "L14".to_string(), - specific_component: Some(ProtoSpecificComponent { - component_name: "HalfAdm2".to_string(), - component_index: 0, - }), - }, - ], - }), - federation: Some(ProtoFederation { - disjunction: Some(ProtoDisjunction { - conjunctions: vec![ProtoConjunction { - constraints: vec![ - ProtoConstraint { - x: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "HalfAdm1".to_string(), - component_index: 0, - }), - clock_name: "x".to_string(), - }), - y: Some(ProtoComponentClock { - specific_component: None, - clock_name: "0".to_string(), - }), - strict: false, - c: 2, - }, - ProtoConstraint { - x: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "HalfAdm1".to_string(), - component_index: 0, - }), - clock_name: "x".to_string(), - }), - y: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "HalfAdm2".to_string(), - component_index: 0, - }), - clock_name: "y".to_string(), - }), - strict: false, - c: 0, - }, - ], - }], - }), - }), - }), - edges: vec![ - ProtoEdge { - id: "E30".to_string(), - specific_component: None, - }, - ProtoEdge { - id: "E35".to_string(), - specific_component: None, - }, - ProtoEdge { - id: "E36".to_string(), - specific_component: None, - }, - ProtoEdge { - id: "E38".to_string(), - specific_component: None, - }, - ProtoEdge { - id: "E40".to_string(), - specific_component: None, - }, - ProtoEdge { - id: "E41".to_string(), - specific_component: None, - }, - ], - }; - - let response = SimulationStepResponse { - new_decision_points: vec![new_decision_points], - }; - Ok(Response::new(response)) -} - -// A request that Chooses the FAT EDGE: -// -// ----coin? E3----> -// / -// (L5,y>=0)=====tea! E5=====> -// -pub fn create_good_request() -> tonic::Request { - let simulation_info = - create_simulation_info_from(String::from("Machine"), create_sample_json_component()); - let initial_decision_point = create_initial_decision_point(); - let chosen_source = initial_decision_point.source.clone().unwrap(); - let chosen_edge = initial_decision_point.edges[1].clone(); - - tonic::Request::new(create_simulation_step_request( - simulation_info, - chosen_source, - chosen_edge, - )) -} - -pub fn create_expected_response_to_good_request() -> Result, Status> -{ - Ok(Response::new(SimulationStepResponse { - new_decision_points: vec![create_decision_point_after_taking_E5()], - })) -} - -pub fn create_mismatched_request_1() -> Request { - let simulation_info = - create_simulation_info_from(String::from("Machine"), create_sample_json_component()); - let chosen_source = create_state_not_in_machine(); - let chosen_edge = create_edges_from_L5()[0].clone(); - - tonic::Request::new(create_simulation_step_request( - simulation_info, - chosen_source, - chosen_edge, - )) -} - -pub fn create_expected_response_to_mismatched_request_1( -) -> Result, Status> { - Err(tonic::Status::invalid_argument( - "Mismatch between decision and system, state not in system", - )) -} - -pub fn create_mismatched_request_2() -> Request { - let simulation_info = - create_simulation_info_from(String::from("Machine"), create_sample_json_component()); - - let chosen_source = create_state_setup_for_mismatch(); - let chosen_edge = create_edges_from_L5()[1].clone(); // Should not be able to choose this edge - Request::new(create_simulation_step_request( - simulation_info, - chosen_source, - chosen_edge, - )) -} - -pub fn create_expected_response_to_mismatched_request_2( -) -> Result, Status> { - Err(tonic::Status::invalid_argument( - "Mismatch between decision and system, could not make transition", - )) -} - -pub fn create_malformed_component_request() -> Request { - let simulation_info = create_simulation_info_from(String::from(""), String::from("")); - let chosen_source = create_empty_state(); - let chosen_edge = create_empty_edge(); - - Request::new(create_simulation_step_request( - simulation_info, - chosen_source, - chosen_edge, - )) -} - -pub fn create_response_to_malformed_component_request( -) -> Result, Status> { - Err(Status::invalid_argument("Malformed component, bad json")) -} - -pub fn create_malformed_composition_request() -> Request { - let simulation_info = - create_simulation_info_from(String::from(""), create_sample_json_component()); - let chosen_source = create_empty_state(); - let chosen_edge = create_empty_edge(); - - Request::new(create_simulation_step_request( - simulation_info, - chosen_source, - chosen_edge, - )) -} - -pub fn create_response_to_malformed_compostion_request( -) -> Result, Status> { - Err(Status::invalid_argument( - "Malformed composition, bad expression", - )) -} - -// A || B || C -pub fn create_composition_request() -> Request { - let comp_names = vec!["Administration", "Machine", "Researcher"]; - let sample_name = "EcdarUniversity".to_string(); - let composition_string = "Administration || Machine || Researcher".to_string(); - - let components: Vec = create_components(&comp_names, sample_name); - let simulation_info = create_simulation_info(composition_string, components); - - let edge = ProtoEdge { - id: "E29".to_string(), - specific_component: None, - }; - - let source = get_state_after_Administration_Machine_Researcher_composition(); - - let simulation_step_request = create_simulation_step_request(simulation_info, source, edge); - - Request::new(simulation_step_request) -} - -pub fn create_expected_response_to_composition_request( -) -> Result, Status> { - get_composition_response_Administration_Machine_Researcher_after_E29() -} - -// A && B -pub fn create_conjunction_request() -> Request { - let comp_names = vec!["HalfAdm1", "HalfAdm2"]; - let sample_name = "EcdarUniversity".to_string(); - let composition_string = "HalfAdm1 && HalfAdm2".to_string(); - create_composition_string(&comp_names, CompositionType::Conjunction); - - let components: Vec = create_components(&comp_names, sample_name); - let simulation_info = create_simulation_info(composition_string, components); - - let edge = ProtoEdge { - id: "E37".to_string(), - specific_component: None, - }; - - let source = get_state_after_HalfAdm1_HalfAdm2_conjunction(); - - let simulation_step_request = create_simulation_step_request(simulation_info, source, edge); - - Request::new(simulation_step_request) -} - -pub fn create_expected_response_to_conjunction_request( -) -> Result, Status> { - get_conjunction_response_HalfAdm1_HalfAdm2_after_E37() -} - -pub fn create_good_start_request() -> Request { - create_simulation_start_request(String::from("Machine"), create_sample_json_component()) -} - -pub fn create_expected_response_to_good_start_request( -) -> Result, Status> { - Ok(Response::new(SimulationStepResponse { - new_decision_points: vec![create_initial_decision_point()], - })) -} - -pub fn create_malformed_component_start_request() -> Request { - create_simulation_start_request(String::from(""), String::from("")) -} - -pub fn create_malformed_composition_start_request() -> Request { - create_simulation_start_request(String::from(""), create_sample_json_component()) -} - -// A || B || C -pub fn create_composition_start_request() -> Request { - let comp_names = vec!["Administration", "Machine", "Researcher"]; - let sample_name = "EcdarUniversity".to_string(); - - let composition = create_composition_string(&comp_names, CompositionType::Composition); - let components: Vec = create_components(&comp_names, sample_name); - - let simulation_info = create_simulation_info(composition, components); - - Request::new(SimulationStartRequest { - simulation_info: Some(simulation_info), - }) -} - -pub fn create_expected_response_to_composition_start_request( -) -> Result, Status> { - get_composition_response_Administration_Machine_Researcher() -} - -// A && B -pub fn create_conjunction_start_request() -> Request { - let comp_names = vec!["HalfAdm1", "HalfAdm2"]; - let sample_name = "EcdarUniversity".to_string(); - let composition_string = create_composition_string(&comp_names, CompositionType::Conjunction); - - let components: Vec = create_components(&comp_names, sample_name); - let simulation_info = create_simulation_info(composition_string, components); - - Request::new(SimulationStartRequest { - simulation_info: Some(simulation_info), - }) -} - -pub fn create_expected_response_to_conjunction_start_request( -) -> Result, Status> { - get_conjunction_response_HalfAdm1_HalfAdm2() -} - -pub fn create_edges_from_L5() -> Vec { - vec![ - ProtoEdge { - id: "E27".to_string(), - specific_component: None, - }, - ProtoEdge { - id: "E29".to_string(), - specific_component: None, - }, - ] -} - -// Create the decision point drawn below: -// -// -----coin? E3-----> -// / -// (L5, universe)-------tea! E5-----> -// -pub fn create_initial_decision_point() -> ProtoDecisionPoint { - ProtoDecisionPoint { - source: Some(ProtoState { - location_tuple: Some(ProtoLocationTuple { - locations: vec![ProtoLocation { - id: "L5".to_string(), - specific_component: Some(ProtoSpecificComponent { - component_name: "Machine".to_string(), - component_index: 0, - }), - }], - }), - federation: Some(ProtoFederation { - disjunction: Some(ProtoDisjunction { - conjunctions: vec![ProtoConjunction { - constraints: vec![], - }], - }), - }), - }), - edges: create_edges_from_L5(), - } -} - -// Returns the Machine component as a String, in the .json format -pub fn create_sample_json_component() -> String { - fs::read_to_string(format!("{}/Components/Machine.json", ECDAR_UNI)).unwrap() -} - -// Create the decision point drawn below: -// -// -----coin? E3-----> -// / -// (L5,y>=2)-------tea! E5-----> -// -pub fn create_decision_point_after_taking_E5() -> ProtoDecisionPoint { - ProtoDecisionPoint { - source: Some(ProtoState { - location_tuple: Some(ProtoLocationTuple { - locations: vec![ProtoLocation { - id: "L5".to_string(), - specific_component: Some(ProtoSpecificComponent { - component_name: "Machine".to_string(), - component_index: 0, - }), - }], - }), - federation: Some(ProtoFederation { - disjunction: Some(ProtoDisjunction { - conjunctions: vec![ProtoConjunction { - constraints: vec![ProtoConstraint { - x: Some(ProtoComponentClock { - specific_component: None, - clock_name: "0".to_string(), - }), - y: Some(ProtoComponentClock { - specific_component: Some(ProtoSpecificComponent { - component_name: "Machine".to_string(), - component_index: 0, - }), - clock_name: "y".to_string(), - }), - strict: false, - c: -2, - }], - }], - }), - }), - }), - edges: create_edges_from_L5(), - } -} - -// Create a simulation state with the Machine component and the decision point drawn below: -// -// -----coin? E3-----> -// / -// (ε,y>=0)-------tea! E5-----> -// -pub fn create_state_not_in_machine() -> ProtoState { - create_1tuple_state_with_single_constraint("", "Machine", 0, "0", "y", 0, false) -} - -// create a state such that can't transition via E5 -pub fn create_state_setup_for_mismatch() -> ProtoState { - create_1tuple_state_with_single_constraint("L5", "Machine", 0, "y", "0", 2, true) -} diff --git a/src/tests/failure_message/actions_test.rs b/src/tests/failure_message/actions_test.rs index 46750611..a0fe566d 100644 --- a/src/tests/failure_message/actions_test.rs +++ b/src/tests/failure_message/actions_test.rs @@ -3,94 +3,94 @@ mod test { use crate::tests::refinement::Helper::json_run_query; - use crate::System::local_consistency::DeterminismFailure; - use crate::System::{ - executable_query::QueryResult, - local_consistency::{ConsistencyFailure, ConsistencyResult, DeterminismResult}, - refine::{RefinementFailure, RefinementResult}, + use crate::System::query_failures::{ + ConsistencyFailure, DeterminismFailure, DeterminismResult, QueryResult, RefinementFailure, + RefinementPrecondition, }; - use crate::TransitionSystems::LocationID; - + use crate::System::specifics::SpecificLocation; const PATH: &str = "samples/json/Actions"; #[test] fn determinism_test() { let expected_action = String::from("1"); - let expected_location = LocationID::Simple { - location_id: (String::from("L1")), - component_id: Some(String::from("NonDeterminismCom")), - }; - if let QueryResult::Determinism(DeterminismResult::Failure( - DeterminismFailure::NotDeterministicFrom(actual_location, actual_action), - )) = json_run_query(PATH, "determinism: NonDeterministic1") + let expected_location = SpecificLocation::new("NonDeterministic1", "L1", 0); //LocationID::Simple(String::from("L1")); + if let QueryResult::Determinism(DeterminismResult::Err(DeterminismFailure { + state: actual_state, + action: actual_action, + system: actual_system, + })) = json_run_query(PATH, "determinism: NonDeterministic1").unwrap() { + let actual_location = actual_state.locations; assert_eq!( (expected_location, expected_action), - (actual_location, actual_action) + (actual_location, actual_action.name) ); + assert_eq!(actual_system, "NonDeterministic1"); } else { - panic!("Models in saples/action have been changed, REVERT!"); + panic!("Models in samples/action have been changed, REVERT!"); } } #[test] fn not_consistent_from_test() { - let expected_action = String::from(""); - let expected_location = LocationID::Simple { - location_id: (String::from("L17")), - component_id: Some(String::from("notConsistent")), - }; - if let QueryResult::Consistency(ConsistencyResult::Failure( - ConsistencyFailure::NotConsistentFrom(actual_location, actual_action), - )) = json_run_query(PATH, "consistency: NonConsistent") + let expected_location = SpecificLocation::new("NonConsistent", "L17", 0); + if let QueryResult::Consistency(Err(ConsistencyFailure::InconsistentFrom { + state: actual_state, + system: actual_system, + })) = json_run_query(PATH, "consistency: NonConsistent").unwrap() { - assert_eq!( - (expected_location, expected_action), - (actual_location, actual_action) - ); + let actual_location = actual_state.locations; + assert_eq!((expected_location), (actual_location)); + assert_eq!(actual_system, "NonConsistent"); } else { - panic!("Models in saples/action have been changed, REVERT!"); + panic!("Models in samples/action have been changed, REVERT!"); } } #[test] fn refinement_determinism_test() { let expected_action = String::from("1"); - let expected_location = LocationID::Simple { - location_id: (String::from("L1")), - component_id: Some(String::from("NonDeterminismCom")), - }; - if let QueryResult::Refinement(RefinementResult::Failure( - RefinementFailure::DeterminismFailure(actual_location, actual_action), - )) = json_run_query(PATH, "refinement: NonDeterministic1 <= NonDeterministic2") + let expected_location = SpecificLocation::new("NonDeterministic1", "L1", 0); + if let QueryResult::Refinement(Err(RefinementFailure::Precondition( + RefinementPrecondition::InconsistentChild( + ConsistencyFailure::NotDeterministic(DeterminismFailure { + state: actual_state, + action: actual_action, + system: actual_system, + }), + _, + ), + ))) = json_run_query(PATH, "refinement: NonDeterministic1 <= NonDeterministic2").unwrap() { + let actual_location = actual_state.locations; assert_eq!( (expected_location, expected_action), - (actual_location.unwrap(), actual_action.unwrap()) + (actual_location, actual_action.name) ); + assert_eq!(actual_system, "NonDeterministic1"); // Assuming left child is checked first } else { - panic!("Models in saples/action have been changed, REVERT!"); + panic!("Models in samples/action have been changed, REVERT!"); } } #[test] fn refinement_consistency_test() { - let expected_action = String::from(""); - let expected_location = LocationID::Simple { - location_id: (String::from("L17")), - component_id: Some(String::from("notConsistent")), - }; - - if let QueryResult::Refinement(RefinementResult::Failure( - RefinementFailure::ConsistencyFailure(actual_location, actual_action), - )) = json_run_query(PATH, "refinement: NonConsistent <= CorrectComponent") + let expected_location = SpecificLocation::new("NonConsistent", "L17", 0); + if let QueryResult::Refinement(Err(RefinementFailure::Precondition( + RefinementPrecondition::InconsistentChild( + ConsistencyFailure::InconsistentFrom { + state: actual_state, + system: actual_system, + }, + _, + ), + ))) = json_run_query(PATH, "refinement: NonConsistent <= CorrectComponent").unwrap() { - assert_eq!( - (expected_location, expected_action), - (actual_location.unwrap(), actual_action.unwrap()) - ); + let actual_location = actual_state.locations; + assert_eq!((expected_location), (actual_location)); + assert_eq!(actual_system, "NonConsistent"); } else { - panic!("Models in saples/action have been changed, REVERT!"); + panic!("Models in samples/action have been changed, REVERT!"); } } } diff --git a/src/tests/failure_message/consistency_test.rs b/src/tests/failure_message/consistency_test.rs index 8cdddd74..d5bb578a 100644 --- a/src/tests/failure_message/consistency_test.rs +++ b/src/tests/failure_message/consistency_test.rs @@ -1,18 +1,20 @@ #[cfg(test)] mod test { - use crate::System::local_consistency::{ConsistencyFailure, ConsistencyResult}; - use crate::{tests::refinement::Helper::json_run_query, System::executable_query::QueryResult}; + use crate::{ + tests::refinement::Helper::json_run_query, + System::query_failures::{ConsistencyFailure, ConsistencyResult, QueryResult}, + }; const PATH: &str = "samples/json/ConsistencyTest"; #[test] fn not_consistent_test() { - let actual = json_run_query(PATH, "consistency: notConsistent"); + let actual = json_run_query(PATH, "consistency: notConsistent").unwrap(); assert!(matches!( actual, - QueryResult::Consistency(ConsistencyResult::Failure( - ConsistencyFailure::NotConsistentFrom(..) + QueryResult::Consistency(ConsistencyResult::Err( + ConsistencyFailure::InconsistentFrom { .. } )) )); } diff --git a/src/tests/failure_message/determinism_test.rs b/src/tests/failure_message/determinism_test.rs index 14fa686b..c90130be 100644 --- a/src/tests/failure_message/determinism_test.rs +++ b/src/tests/failure_message/determinism_test.rs @@ -1,32 +1,33 @@ #[cfg(test)] mod test { - use crate::System::refine::{RefinementFailure, RefinementResult}; use crate::{ - tests::refinement::Helper::json_run_query, System::executable_query::QueryResult, - System::local_consistency::DeterminismResult, + tests::refinement::Helper::json_run_query, + System::query_failures::{ + ConsistencyFailure, QueryResult, RefinementFailure, RefinementPrecondition, + }, }; const PATH: &str = "samples/json/Determinism"; #[test] fn determinism_failure_test() { - let actual = json_run_query(PATH, "determinism: NonDeterminismCom"); + let actual = json_run_query(PATH, "determinism: NonDeterminismCom").unwrap(); - assert!(matches!( - actual, - QueryResult::Determinism(DeterminismResult::Failure(..)) - )); + assert!(matches!(actual, QueryResult::Determinism(Err(_)))); } #[test] fn determinism_failure_in_refinement_test() { - let actual = json_run_query(PATH, "refinement: NonDeterminismCom <= Component2"); + let actual = json_run_query(PATH, "refinement: NonDeterminismCom <= Component2").unwrap(); assert!(matches!( actual, - QueryResult::Refinement(RefinementResult::Failure( - RefinementFailure::DeterminismFailure(..) - )) - )); + QueryResult::Refinement(Err(RefinementFailure::Precondition( + RefinementPrecondition::InconsistentChild( + ConsistencyFailure::NotDeterministic(_), + _ + ) + ))) + )); // TODO: check the child name } } diff --git a/src/tests/failure_message/refinement_test.rs b/src/tests/failure_message/refinement_test.rs index b82101d6..46e31bb1 100644 --- a/src/tests/failure_message/refinement_test.rs +++ b/src/tests/failure_message/refinement_test.rs @@ -1,52 +1,49 @@ #[cfg(test)] mod test { - use crate::extract_system_rep::SystemRecipeFailure; - use crate::System::refine::{RefinementFailure, RefinementResult}; - use crate::{tests::refinement::Helper::json_run_query, System::executable_query::QueryResult}; + use crate::{ + tests::refinement::Helper::json_run_query, + System::query_failures::{ + ActionFailure, QueryResult, RefinementFailure, RefinementPrecondition, + }, + }; const PATH: &str = "samples/json/RefinementTests"; #[test] fn not_empty_result_test() { - let actual = json_run_query(PATH, "refinement: A <= B"); + let actual = json_run_query(PATH, "refinement: A <= B").unwrap(); assert!(matches!( actual, - QueryResult::Refinement(RefinementResult::Failure( - RefinementFailure::NotEmptyResult(_) - )) + QueryResult::Refinement(Err(RefinementFailure::CannotMatch { .. })) )); } #[test] fn empty_transition2s_test() { - let actual = json_run_query(PATH, "refinement: A <= A2"); + let actual = json_run_query(PATH, "refinement: A <= A2").unwrap(); assert!(matches!( actual, - QueryResult::Refinement(RefinementResult::Failure( - RefinementFailure::EmptyTransition2s(_) - )) + QueryResult::Refinement(Err(RefinementFailure::CannotMatch { .. })) )); } #[test] fn cuts_delay_solutions_test() { - let actual = json_run_query(PATH, "refinement: A2 <= B2"); + let actual = json_run_query(PATH, "refinement: A2 <= B2").unwrap(); assert!(matches!( actual, - QueryResult::Refinement(RefinementResult::Failure( - RefinementFailure::CutsDelaySolutions(_) - )) + QueryResult::Refinement(Err(RefinementFailure::CutsDelaySolutions { .. })) )); } #[test] fn initial_state_test() { - let actual = json_run_query(PATH, "refinement: C <= D"); + let actual = json_run_query(PATH, "refinement: C <= D").unwrap(); assert!(matches!( actual, - QueryResult::Refinement(RefinementResult::Failure(RefinementFailure::InitialState( - _ + QueryResult::Refinement(Err(RefinementFailure::Precondition( + RefinementPrecondition::EmptyInitialState { .. }, ))) )); } @@ -56,36 +53,36 @@ mod test { let actual = json_run_query( PATH, "refinement: notDisjointAndNotSubset1 <= notDisjointAndNotSubset2", - ); + ) + .unwrap(); assert!(matches!( actual, - QueryResult::Refinement(RefinementResult::Failure( - RefinementFailure::NotDisjointAndNotSubset(_) - )) + QueryResult::Refinement(Err(RefinementFailure::Precondition( + RefinementPrecondition::ActionMismatch(ActionFailure::NotDisjoint(_, _), _), + ))) )); } #[test] fn not_subset_test() { - let actual = json_run_query(PATH, "refinement: notSubset1 <= notSubset2"); + let actual = json_run_query(PATH, "refinement: notSubset1 <= notSubset2") + .ok() + .unwrap(); assert!(matches!( actual, - QueryResult::Refinement(RefinementResult::Failure(RefinementFailure::NotSubset)) + QueryResult::Refinement(Err(RefinementFailure::Precondition( + RefinementPrecondition::ActionMismatch(ActionFailure::NotSubset(_, _), _), + ))) )); } #[test] fn not_disjoint_test() { - let actual = json_run_query(PATH, "refinement: disJoint2 <= disJoint1"); + let actual = json_run_query(PATH, "refinement: disJoint2 <= disJoint1").unwrap(); assert!(matches!( actual, - QueryResult::Refinement(RefinementResult::Failure(RefinementFailure::NotDisjoint( - SystemRecipeFailure { - reason: _, - left_name: _, - right_name: _, - actions: _ - } + QueryResult::Refinement(Err(RefinementFailure::Precondition( + RefinementPrecondition::ActionMismatch(ActionFailure::NotDisjoint(_, _), _), ))), )); } diff --git a/src/tests/grpc/mod.rs b/src/tests/grpc/mod.rs index 700700a3..1ff0b93b 100644 --- a/src/tests/grpc/mod.rs +++ b/src/tests/grpc/mod.rs @@ -1,4 +1,2 @@ pub mod send_query; pub mod simulation; -pub mod start_simulation; -pub mod take_simulation_step; diff --git a/src/tests/grpc/send_query.rs b/src/tests/grpc/send_query.rs index 84d4d4b8..30d64b1d 100644 --- a/src/tests/grpc/send_query.rs +++ b/src/tests/grpc/send_query.rs @@ -23,8 +23,8 @@ mod refinements { let query_result = query_response.unwrap().into_inner(); let result = query_result.result.unwrap(); match result { - query_response::Result::Refinement(refine) => assert!(refine.success), - _ => panic!(), + query_response::Result::Success(_) => {} + _ => panic!("Expected success, got {:?}", result), } } @@ -34,13 +34,12 @@ mod refinements { let query_request = create_query_request("consistency: Machine"); let query_response = backend.send_query(query_request).await; - assert!(query_response.is_ok()); let query_result = query_response.unwrap().into_inner(); let result = query_result.result.unwrap(); match result { - query_response::Result::Consistency(consistent) => assert!(consistent.success), - _ => panic!(), + query_response::Result::Success(_) => {} + _ => panic!("Expected success, got {:?}", result), } } @@ -50,13 +49,12 @@ mod refinements { let query_request = create_query_request("determinism: Machine"); let query_response = backend.send_query(query_request).await; - assert!(query_response.is_ok()); let query_result = query_response.unwrap().into_inner(); let result = query_result.result.unwrap(); match result { - query_response::Result::Determinism(determinism) => assert!(determinism.success), - _ => panic!(), + query_response::Result::Success(_) => {} + _ => panic!("Expected success, got {:?}", result), } } @@ -74,7 +72,6 @@ mod refinements { }], components_hash: 0, }), - ignored_input_outputs: None, settings: Some(crate::tests::TEST_SETTINGS), }) } @@ -106,8 +103,8 @@ mod refinements { let query_result = query_response.unwrap().into_inner(); let result = query_result.result.unwrap(); match result { - query_response::Result::Refinement(refine) => assert!(refine.success), - _ => panic!(), + query_response::Result::Success(_) => {} + _ => panic!("Expected success, got {:?}", result), } } } diff --git a/src/tests/grpc/simulation.rs b/src/tests/grpc/simulation.rs index 1239aec4..c4f55231 100644 --- a/src/tests/grpc/simulation.rs +++ b/src/tests/grpc/simulation.rs @@ -1,7 +1,7 @@ #[cfg(test)] mod tests { use crate::{ - tests::Simulation::helper::{create_start_request, create_step_request}, + tests::Simulation::helper::{create_start_request, create_step_requests}, ProtobufServer::{self, services::ecdar_backend_server::EcdarBackend}, }; use test_case::test_case; @@ -22,6 +22,16 @@ mod tests { "samples/json/EcdarUniversity", "(Administration || Machine || Researcher)" )] + #[test_case( + &["Spec", "Machine"], + "samples/json/EcdarUniversity", + "(Spec // Machine)" + )] + #[test_case( + &["Machine", "Spec", "Researcher"], + "samples/json/EcdarUniversity", + "(Spec // Machine // Researcher)" + )] #[test_case( &["HalfAdm1", "HalfAdm2", "Machine", "Researcher"], "samples/json/EcdarUniversity", @@ -45,17 +55,27 @@ mod tests { let response = backend.start_simulation(request).await; // Arrange - let request = Request::new(create_step_request( - component_names, - components_path, - composition, - response, - )); + for request in create_step_requests(component_names, components_path, composition, response) + { + let request = Request::new(request); - // Act - let response = backend.take_simulation_step(request).await; + // Act + let response = backend.take_simulation_step(request).await; + + // Assert + assert!(response.is_ok(), "Response was not ok: {:?}", response); + + for request2 in + create_step_requests(component_names, components_path, composition, response) + { + let request2 = Request::new(request2); + + // Act + let response2 = backend.take_simulation_step(request2).await; - // Assert - assert!(response.is_ok()) + // Assert + assert!(response2.is_ok(), "Response was not ok: {:?}", response2); + } + } } } diff --git a/src/tests/grpc/start_simulation.rs b/src/tests/grpc/start_simulation.rs deleted file mode 100644 index 686a0779..00000000 --- a/src/tests/grpc/start_simulation.rs +++ /dev/null @@ -1,66 +0,0 @@ -#[cfg(test)] -mod test { - use crate::ProtobufServer::{ - self, - services::{ - ecdar_backend_server::EcdarBackend, SimulationStartRequest, SimulationStepResponse, - }, - }; - use test_case::test_case; - use tonic::{Request, Response, Status}; - - #[test_case( - crate::tests::Simulation::test_data::create_good_start_request(), - crate::tests::Simulation::test_data::create_expected_response_to_good_start_request(); - "given a good request, responds with correct state" - )] - #[test_case( - crate::tests::Simulation::test_data::create_composition_start_request(), - crate::tests::Simulation::test_data::create_expected_response_to_composition_start_request(); - "given a composition request, responds with correct component" - )] - #[test_case( - crate::tests::Simulation::test_data::create_conjunction_start_request(), - crate::tests::Simulation::test_data::create_expected_response_to_conjunction_start_request(); - "given a good conjunction request, responds with correct component" - )] - #[tokio::test] - async fn start_simulation__responds_as_expected( - request: Request, - expected_response: Result, Status>, - ) { - // Arrange - let backend = ProtobufServer::ConcreteEcdarBackend::default(); - - // Act - let actual_response = backend.start_simulation(request).await; - - // Assert - assert_eq!( - format!("{:?}", expected_response), - format!("{:?}", actual_response) - ); - } - - #[test_case( - crate::tests::Simulation::test_data::create_malformed_component_start_request(); - "given a request with a malformed component, respond with error" - )] - #[test_case( - crate::tests::Simulation::test_data::create_malformed_composition_start_request(); - "given a request with a malformed composition, respond with error" - )] - #[tokio::test] - async fn start_simulation__bad_data__responds_with_error( - request: Request, - ) { - // Arrange - let backend = ProtobufServer::ConcreteEcdarBackend::default(); - - // Act - let actual_response = backend.start_simulation(request).await; - - // Assert - assert!(actual_response.is_err()); - } -} diff --git a/src/tests/grpc/take_simulation_step.rs b/src/tests/grpc/take_simulation_step.rs deleted file mode 100644 index adb21a06..00000000 --- a/src/tests/grpc/take_simulation_step.rs +++ /dev/null @@ -1,71 +0,0 @@ -#[cfg(test)] -mod test { - use crate::tests::Simulation::test_data; - use crate::ProtobufServer::services::{SimulationStepRequest, SimulationStepResponse}; - use crate::ProtobufServer::{self, services::ecdar_backend_server::EcdarBackend}; - use test_case::test_case; - use tonic::{self, Request, Response, Status}; - - #[test_case( - test_data::create_good_request(), - test_data::create_expected_response_to_good_request(); - "given a good request, responds with correct state" - )] - #[test_case( - test_data::create_composition_request(), - test_data::create_expected_response_to_composition_request(); - "given a composition request, responds with correct component" - )] - #[test_case( - test_data::create_conjunction_request(), - test_data::create_expected_response_to_conjunction_request(); - "given a good conjunction request, responds with correct component" - )] - #[tokio::test] - async fn take_simulation_step__responds_as_expected( - request: Request, - expected_response: Result, Status>, - ) { - // Arrange - let backend = ProtobufServer::ConcreteEcdarBackend::default(); - - // Act - let actual_response = backend.take_simulation_step(request).await; - - // Assert - assert_eq!( - format!("{:?}", expected_response), - format!("{:?}", actual_response) - ); - } - - #[test_case( - test_data::create_mismatched_request_1(); - "given a request with component decision mismatch, decision referencing source not in the set of states, responds with invalid argument" - )] - #[test_case( - test_data::create_mismatched_request_2(); - "given a request with component decision mismatch, decision making transition that is not possible, responds with invalid argument" - )] - #[test_case( - test_data::create_malformed_component_request(); - "given a request with a malformed component, responds with invalid argument" - )] - #[test_case( - test_data::create_malformed_composition_request(); - "given a request with a malformed composition, responds with invalid argument" - )] - #[tokio::test] - async fn take_simulation_step__bad_data__responds_with_error( - request: Request, - ) { - // Arrange - let backend = ProtobufServer::ConcreteEcdarBackend::default(); - - // Act - let actual_response = backend.take_simulation_step(request).await; - - // Assert - assert!(actual_response.is_err()); - } -} diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 7e8144f0..980f0732 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -14,5 +14,5 @@ pub mod system_recipe; /// The default settings for Testing pub const TEST_SETTINGS: Settings = Settings { - disable_clock_reduction: true, + disable_clock_reduction: false, }; diff --git a/src/tests/reachability/parse_partial_state.rs b/src/tests/reachability/parse_partial_state.rs index 5c403b7e..7f813443 100644 --- a/src/tests/reachability/parse_partial_state.rs +++ b/src/tests/reachability/parse_partial_state.rs @@ -1,9 +1,12 @@ #[cfg(test)] mod reachability_parse_partial_state { use crate::{ - extract_system_rep, parse_queries, + extract_system_rep::{self, ExecutableQueryError}, + parse_queries, tests::reachability::helper_functions::reachability_test_helper_functions, - JsonProjectLoader, ModelObjects::representations::QueryExpression, System, + JsonProjectLoader, + ModelObjects::representations::QueryExpression, + System::{self}, }; use test_case::test_case; @@ -57,8 +60,10 @@ mod reachability_parse_partial_state { let result = extract_system_rep::create_executable_query(queries, &mut *comp_loader); if let Err(e) = result { assert_eq!( - (*e).to_string(), - "Start state is a partial state, which it must not be" + e, + ExecutableQueryError::Custom( + "Start state is a partial state, which it must not be".to_string() + ) ); } else { panic!("No error was returned") diff --git a/src/tests/reachability/partial_state.rs b/src/tests/reachability/partial_state.rs index 1aebdae3..416d2e72 100644 --- a/src/tests/reachability/partial_state.rs +++ b/src/tests/reachability/partial_state.rs @@ -2,93 +2,92 @@ mod reachability_partial_states_test { use crate::component::{Declarations, Location}; use crate::TransitionSystems::CompositionType; - use crate::{component::LocationType, TransitionSystems::LocationTuple}; + use crate::{component::LocationType, TransitionSystems::LocationTree}; use test_case::test_case; - fn build_location_tuple_helper(id: &str, location_type: LocationType) -> LocationTuple { - LocationTuple::simple( + fn build_location_tree_helper(id: &str, location_type: LocationType) -> LocationTree { + LocationTree::simple( &Location { id: id.to_string(), invariant: None, location_type, urgency: "".to_string(), }, - None, &Declarations::empty(), 0, ) } - #[test_case(LocationTuple::build_any_location_tuple(), - build_location_tuple_helper("L9", LocationType::Normal); + #[test_case(LocationTree::build_any_location_tree(), + build_location_tree_helper("L9", LocationType::Normal); "_ == L9")] - #[test_case(build_location_tuple_helper("L0", LocationType::Initial), - LocationTuple::build_any_location_tuple(); + #[test_case(build_location_tree_helper("L0", LocationType::Initial), + LocationTree::build_any_location_tree(); "L0 == _")] - #[test_case(build_location_tuple_helper("L5", LocationType::Normal), - build_location_tuple_helper("L5", LocationType::Normal); + #[test_case(build_location_tree_helper("L5", LocationType::Normal), + build_location_tree_helper("L5", LocationType::Normal); "L5 == L5")] - #[test_case(LocationTuple::merge_as_quotient(&build_location_tuple_helper("L5", LocationType::Normal), &LocationTuple::build_any_location_tuple()), - LocationTuple::merge_as_quotient(&build_location_tuple_helper("L5", LocationType::Normal), &build_location_tuple_helper("L1", LocationType::Normal)); + #[test_case(LocationTree::merge_as_quotient(&build_location_tree_helper("L5", LocationType::Normal), &LocationTree::build_any_location_tree()), + LocationTree::merge_as_quotient(&build_location_tree_helper("L5", LocationType::Normal), &build_location_tree_helper("L1", LocationType::Normal)); "L5//_ == L5//L1")] - #[test_case(LocationTuple::compose(&build_location_tuple_helper("L5", LocationType::Normal), &LocationTuple::build_any_location_tuple(), CompositionType::Conjunction), - LocationTuple::compose(&LocationTuple::build_any_location_tuple(), &build_location_tuple_helper("L1", LocationType::Normal), CompositionType::Conjunction); + #[test_case(LocationTree::compose(&build_location_tree_helper("L5", LocationType::Normal), &LocationTree::build_any_location_tree(), CompositionType::Conjunction), + LocationTree::compose(&LocationTree::build_any_location_tree(), &build_location_tree_helper("L1", LocationType::Normal), CompositionType::Conjunction); "L5&&_ == _&&L1")] - #[test_case(LocationTuple::compose(&build_location_tuple_helper("L7", LocationType::Normal), &LocationTuple::build_any_location_tuple(), CompositionType::Composition), - LocationTuple::compose(&build_location_tuple_helper("L7", LocationType::Normal), &build_location_tuple_helper("L1", LocationType::Normal), CompositionType::Composition); + #[test_case(LocationTree::compose(&build_location_tree_helper("L7", LocationType::Normal), &LocationTree::build_any_location_tree(), CompositionType::Composition), + LocationTree::compose(&build_location_tree_helper("L7", LocationType::Normal), &build_location_tree_helper("L1", LocationType::Normal), CompositionType::Composition); "L7||_ == L7||L1")] - #[test_case(LocationTuple::compose(&LocationTuple::build_any_location_tuple(), &LocationTuple::build_any_location_tuple(), CompositionType::Composition), - LocationTuple::compose(&build_location_tuple_helper("L2", LocationType::Normal), &build_location_tuple_helper("L1", LocationType::Normal), CompositionType::Composition); + #[test_case(LocationTree::compose(&LocationTree::build_any_location_tree(), &LocationTree::build_any_location_tree(), CompositionType::Composition), + LocationTree::compose(&build_location_tree_helper("L2", LocationType::Normal), &build_location_tree_helper("L1", LocationType::Normal), CompositionType::Composition); "_||_ == L2||L1")] - #[test_case(LocationTuple::compose(&LocationTuple::compose(&LocationTuple::build_any_location_tuple(), &LocationTuple::build_any_location_tuple(), CompositionType::Composition),&build_location_tuple_helper("L2", LocationType::Normal), CompositionType::Composition), - LocationTuple::compose(&LocationTuple::compose(&build_location_tuple_helper("L2", LocationType::Normal), &build_location_tuple_helper("L1", LocationType::Normal), CompositionType::Composition),&build_location_tuple_helper("L2", LocationType::Normal), CompositionType::Composition); + #[test_case(LocationTree::compose(&LocationTree::compose(&LocationTree::build_any_location_tree(), &LocationTree::build_any_location_tree(), CompositionType::Composition),&build_location_tree_helper("L2", LocationType::Normal), CompositionType::Composition), + LocationTree::compose(&LocationTree::compose(&build_location_tree_helper("L2", LocationType::Normal), &build_location_tree_helper("L1", LocationType::Normal), CompositionType::Composition),&build_location_tree_helper("L2", LocationType::Normal), CompositionType::Composition); "_||_||L2 == L2||L1||L2")] - #[test_case(build_location_tuple_helper("L_35", LocationType::Normal), - build_location_tuple_helper("L_35", LocationType::Normal); + #[test_case(build_location_tree_helper("L_35", LocationType::Normal), + build_location_tree_helper("L_35", LocationType::Normal); "L_35 == L_35")] - fn checks_cmp_locations_returns_true(loc1: LocationTuple, loc2: LocationTuple) { + fn checks_cmp_locations_returns_true(loc1: LocationTree, loc2: LocationTree) { assert!(loc1.compare_partial_locations(&loc2)); } - #[test_case(LocationTuple::compose(&build_location_tuple_helper("L2", LocationType::Normal), &build_location_tuple_helper("L5", LocationType::Normal), CompositionType::Composition), - LocationTuple::compose(&build_location_tuple_helper("L2", LocationType::Normal), &build_location_tuple_helper("L1", LocationType::Normal), CompositionType::Composition); + #[test_case(LocationTree::compose(&build_location_tree_helper("L2", LocationType::Normal), &build_location_tree_helper("L5", LocationType::Normal), CompositionType::Composition), + LocationTree::compose(&build_location_tree_helper("L2", LocationType::Normal), &build_location_tree_helper("L1", LocationType::Normal), CompositionType::Composition); "L2||L5 != L2||L1")] - #[test_case(LocationTuple::merge_as_quotient(&build_location_tuple_helper("L2", LocationType::Normal), &build_location_tuple_helper("L6", LocationType::Normal)), - LocationTuple::compose(&build_location_tuple_helper("L2", LocationType::Normal), &build_location_tuple_helper("L1", LocationType::Normal), CompositionType::Composition); + #[test_case(LocationTree::merge_as_quotient(&build_location_tree_helper("L2", LocationType::Normal), &build_location_tree_helper("L6", LocationType::Normal)), + LocationTree::compose(&build_location_tree_helper("L2", LocationType::Normal), &build_location_tree_helper("L1", LocationType::Normal), CompositionType::Composition); "L2//L6 != L2||L1")] - #[test_case(LocationTuple::merge_as_quotient(&build_location_tuple_helper("L7", LocationType::Normal), &build_location_tuple_helper("L6", LocationType::Normal)), - LocationTuple::compose(&build_location_tuple_helper("L2", LocationType::Normal), &build_location_tuple_helper("L1", LocationType::Normal), CompositionType::Conjunction); + #[test_case(LocationTree::merge_as_quotient(&build_location_tree_helper("L7", LocationType::Normal), &build_location_tree_helper("L6", LocationType::Normal)), + LocationTree::compose(&build_location_tree_helper("L2", LocationType::Normal), &build_location_tree_helper("L1", LocationType::Normal), CompositionType::Conjunction); "L7//L6 != L2&&L1")] - #[test_case(LocationTuple::merge_as_quotient(&build_location_tuple_helper("L8", LocationType::Normal), &LocationTuple::build_any_location_tuple()), - LocationTuple::compose(&build_location_tuple_helper("L2", LocationType::Normal), &build_location_tuple_helper("L1", LocationType::Normal), CompositionType::Conjunction); + #[test_case(LocationTree::merge_as_quotient(&build_location_tree_helper("L8", LocationType::Normal), &LocationTree::build_any_location_tree()), + LocationTree::compose(&build_location_tree_helper("L2", LocationType::Normal), &build_location_tree_helper("L1", LocationType::Normal), CompositionType::Conjunction); "L8//_ != L2&&L1")] - #[test_case(LocationTuple::build_any_location_tuple(), - LocationTuple::compose(&build_location_tuple_helper("L6", LocationType::Normal), &build_location_tuple_helper("L1", LocationType::Normal), CompositionType::Conjunction); + #[test_case(LocationTree::build_any_location_tree(), + LocationTree::compose(&build_location_tree_helper("L6", LocationType::Normal), &build_location_tree_helper("L1", LocationType::Normal), CompositionType::Conjunction); "_ != L6&&L1")] - #[test_case(LocationTuple::build_any_location_tuple(), - LocationTuple::compose(&LocationTuple::build_any_location_tuple(), &LocationTuple::build_any_location_tuple(), CompositionType::Conjunction); + #[test_case(LocationTree::build_any_location_tree(), + LocationTree::compose(&LocationTree::build_any_location_tree(), &LocationTree::build_any_location_tree(), CompositionType::Conjunction); "anylocation _ != _&&_")] - #[test_case(LocationTuple::compose(&build_location_tuple_helper("L2", LocationType::Normal), &build_location_tuple_helper("L4", LocationType::Normal), CompositionType::Conjunction), - LocationTuple::merge_as_quotient(&build_location_tuple_helper("L2", LocationType::Normal), &build_location_tuple_helper("L4", LocationType::Normal)); + #[test_case(LocationTree::compose(&build_location_tree_helper("L2", LocationType::Normal), &build_location_tree_helper("L4", LocationType::Normal), CompositionType::Conjunction), + LocationTree::merge_as_quotient(&build_location_tree_helper("L2", LocationType::Normal), &build_location_tree_helper("L4", LocationType::Normal)); "L2&&L4 != L2\\L4")] - #[test_case(LocationTuple::compose(&LocationTuple::compose(&LocationTuple::build_any_location_tuple(), &LocationTuple::build_any_location_tuple(), CompositionType::Composition),&build_location_tuple_helper("L2", LocationType::Normal), CompositionType::Conjunction), - LocationTuple::compose(&LocationTuple::compose(&build_location_tuple_helper("L2", LocationType::Normal), &build_location_tuple_helper("L1", LocationType::Normal), CompositionType::Composition),&build_location_tuple_helper("L2", LocationType::Normal), CompositionType::Composition); + #[test_case(LocationTree::compose(&LocationTree::compose(&LocationTree::build_any_location_tree(), &LocationTree::build_any_location_tree(), CompositionType::Composition),&build_location_tree_helper("L2", LocationType::Normal), CompositionType::Conjunction), + LocationTree::compose(&LocationTree::compose(&build_location_tree_helper("L2", LocationType::Normal), &build_location_tree_helper("L1", LocationType::Normal), CompositionType::Composition),&build_location_tree_helper("L2", LocationType::Normal), CompositionType::Composition); "_||_&&L2 == L2||L1||L2")] - #[test_case(LocationTuple::compose(&LocationTuple::compose(&build_location_tuple_helper("L2", LocationType::Normal), &LocationTuple::build_any_location_tuple(), CompositionType::Composition),&build_location_tuple_helper("L2", LocationType::Normal), CompositionType::Conjunction), - LocationTuple::compose(&LocationTuple::build_any_location_tuple(), &LocationTuple::build_any_location_tuple(), CompositionType::Conjunction); + #[test_case(LocationTree::compose(&LocationTree::compose(&build_location_tree_helper("L2", LocationType::Normal), &LocationTree::build_any_location_tree(), CompositionType::Composition),&build_location_tree_helper("L2", LocationType::Normal), CompositionType::Conjunction), + LocationTree::compose(&LocationTree::build_any_location_tree(), &LocationTree::build_any_location_tree(), CompositionType::Conjunction); "L2||_&&L2 == _&&_")] - #[test_case(build_location_tuple_helper("L7", LocationType::Normal), - build_location_tuple_helper("L5", LocationType::Normal); + #[test_case(build_location_tree_helper("L7", LocationType::Normal), + build_location_tree_helper("L5", LocationType::Normal); "L7 != L5")] - #[test_case(LocationTuple::merge_as_quotient(&LocationTuple::build_any_location_tuple(), &LocationTuple::build_any_location_tuple()), - LocationTuple::compose(&build_location_tuple_helper("L6", LocationType::Normal), &build_location_tuple_helper("L25", LocationType::Normal), CompositionType::Conjunction); + #[test_case(LocationTree::merge_as_quotient(&LocationTree::build_any_location_tree(), &LocationTree::build_any_location_tree()), + LocationTree::compose(&build_location_tree_helper("L6", LocationType::Normal), &build_location_tree_helper("L25", LocationType::Normal), CompositionType::Conjunction); "_//_ != L6&&L25")] - #[test_case(build_location_tuple_helper("_L1", LocationType::Normal), - build_location_tuple_helper("L1", LocationType::Normal); + #[test_case(build_location_tree_helper("_L1", LocationType::Normal), + build_location_tree_helper("L1", LocationType::Normal); "_L1 != L1")] - #[test_case(build_location_tuple_helper("__", LocationType::Normal), - build_location_tuple_helper("L7", LocationType::Normal); + #[test_case(build_location_tree_helper("__", LocationType::Normal), + build_location_tree_helper("L7", LocationType::Normal); "__ != L7")] - fn checks_cmp_locations_returns_false(loc1: LocationTuple, loc2: LocationTuple) { + fn checks_cmp_locations_returns_false(loc1: LocationTree, loc2: LocationTree) { assert!(!loc1.compare_partial_locations(&loc2)); } } diff --git a/src/tests/reachability/search_algorithm_test.rs b/src/tests/reachability/search_algorithm_test.rs index 1903293e..e282ddf9 100644 --- a/src/tests/reachability/search_algorithm_test.rs +++ b/src/tests/reachability/search_algorithm_test.rs @@ -1,8 +1,8 @@ #[cfg(test)] mod reachability_search_algorithm_test { - use crate::component::Transition; + use crate::tests::refinement::Helper::json_run_query; - use crate::QueryResult; + use crate::System::query_failures::QueryResult; use test_case::test_case; const PATH: &str = "samples/json/EcdarUniversity"; @@ -28,8 +28,8 @@ mod reachability_search_algorithm_test { #[test_case(PATH, "reachability: Researcher && Researcher -> [U0, U0](); [L6, U0]()", false; "Trivially unreachable")] #[test_case(PATH, "reachability: Researcher && Researcher -> [U0, U0](); [_, U0]()", true; "Trivially reachable because _ is U0")] fn search_algorithm_returns_result_university(path: &str, query: &str, expected: bool) { - match json_run_query(path, query) { - QueryResult::Reachability(path) => assert_eq!(path.was_reachable, expected), + match json_run_query(path, query).unwrap() { + QueryResult::Reachability(path) => assert_eq!(path.is_ok(), expected), _ => panic!("Inconsistent query result, expected Reachability"), } } @@ -50,36 +50,79 @@ mod reachability_search_algorithm_test { #[test_case(PATH2, "reachability: Component7 -> [L16](); [L19](y<2)", false; "Unreachable due to second clock")] #[test_case(PATH2, "reachability: Component3 && Component3 -> [L6, L6](); [L7, L7]()", true; "Simple conjunction")] fn search_algorithm_returns_result(path: &str, query: &str, expected: bool) { - match json_run_query(path, query) { - QueryResult::Reachability(path) => assert_eq!(path.was_reachable, expected), + match json_run_query(path, query).unwrap() { + QueryResult::Reachability(path) => assert_eq!(path.is_ok(), expected), _ => panic!("Inconsistent query result, expected Reachability"), } } - #[test_case(PATH2, "reachability: Component1 -> [L0](); [L2]()", Vec::from(["E3", "E2"]); "Path in Component1 from L0 to L2")] - #[test_case(PATH2, "reachability: Component3 -> [L6](); [L7]()", Vec::from(["E5"]); "Path in Component3 from L6 to L7")] - #[test_case(PATH2, "reachability: Component3 -> [L7](); [L8]()", Vec::from(["E6"]); "Path in Component3 from L7 to L8")] - #[test_case(PATH2, "reachability: Component5 -> [L11](); [L12]()", Vec::from(["E8"]); "Path in Component5 from L11 to L12")] - #[test_case(PATH2, "reachability: Component6 -> [L13](); [L15]()", Vec::from(["E12", "E11", "E9", "E10", "E13"]); "Path in Component6 from L13 to L15")] - #[test_case(PATH2, "reachability: Component7 -> [L16](); [L19]()", Vec::from(["E11", "E12", "E10"]); "Path in Component7 from L16 to L19")] - #[test_case(PATH2, "reachability: Component8 -> [L20](); [L22]()", Vec::from(["E13", "E15", "E14"]); "Path in Component8 from L20 to L22")] - #[test_case(PATH2, "reachability: Component9 -> [L23](x>5); [L26]()", Vec::from(["E17", "E18"]); "Path in Component9 from L23 x gt 5 to L26")] - #[test_case(PATH2, "reachability: Component9 -> [L23](x<5); [L26]()", Vec::from(["E16", "E19"]); "Path in Component9 from L23 x lt 5 to L26")] - #[test_case(PATH2, "reachability: Component3 && Component3 -> [L6, L6](); [L7, L7]()", Vec::from(["E5&&E5"]); "Path in Component3 && Component3 from L6 && L6 to L7 && L7")] - #[test_case(PATH, "reachability: Researcher && Researcher -> [U0, U0](); [_, U0]()", Vec::from([]); "Path in Researcher && Researcher from universal state to partial universal state")] + #[test_case(PATH2, "reachability: Component1 -> [L0](); [L2]()", vec!["E3", "E2"]; "Path in Component1 from L0 to L2")] + #[test_case(PATH2, "reachability: Component3 -> [L6](); [L7]()", vec!["E5"]; "Path in Component3 from L6 to L7")] + #[test_case(PATH2, "reachability: Component3 -> [L7](); [L8]()", vec!["E6"]; "Path in Component3 from L7 to L8")] + #[test_case(PATH2, "reachability: Component5 -> [L11](); [L12]()", vec!["E8"]; "Path in Component5 from L11 to L12")] + #[test_case(PATH2, "reachability: Component6 -> [L13](); [L15]()", vec!["E12", "E11", "E9", "E10", "E13"]; "Path in Component6 from L13 to L15")] + #[test_case(PATH2, "reachability: Component7 -> [L16](); [L19]()", vec!["E11", "E12", "E10"]; "Path in Component7 from L16 to L19")] + #[test_case(PATH2, "reachability: Component8 -> [L20](); [L22]()", vec!["E13", "E15", "E14"]; "Path in Component8 from L20 to L22")] + #[test_case(PATH2, "reachability: Component9 -> [L23](x>5); [L26]()", vec!["E17", "E18"]; "Path in Component9 from L23 x gt 5 to L26")] + #[test_case(PATH2, "reachability: Component9 -> [L23](x<5); [L26]()", vec!["E16", "E19"]; "Path in Component9 from L23 x lt 5 to L26")] fn path_gen_test_correct_path(folder_path: &str, query: &str, expected_path: Vec<&str>) { - match json_run_query(folder_path, query) { + match json_run_query(folder_path, query).unwrap() { + QueryResult::Reachability(actual_path) => { + let actual_path = actual_path.unwrap_or_else(|_| { + panic!( + "Query: {}\nEnd state is not reachable from start state \n", + query + ) + }); + let path = actual_path.path; + assert!(expected_path.len() == path.len(), "Query: {}\nThe length of the actual and expected are not the same.\nexpected_path.len = {}\nactual_path.len = {} \n", query, expected_path.len(),path.len()); + for i in 0..path.len() { + let edges: Vec<_> = path[i].edges.iter().map(|e| e.edge_id.clone()).collect(); + assert_eq!( + 1, + edges.len(), + "Query: {}\nThere should only be one edge in the path \n", + query + ); + assert!( + expected_path[i] == edges[0], + "Query: {}\nThe actual and expected is not the same \n", + query + ); + } + } + _ => panic!("Inconsistent query result, expected Reachability"), + } + } + + #[test_case(PATH2, "reachability: Component3 && Component3 -> [L6, L6](); [L7, L7]()", vec![vec!["E5","E5"]]; "Path in Component3 && Component3 from L6 && L6 to L7 && L7")] + #[test_case(PATH2, "reachability: Component3 && Component3 && Component3 -> [L6, L6, L6](); [L7, L7, L7]()", vec![vec!["E5","E5", "E5"]]; "Path in Component3 && Component3 && Component3 from L6 && L6 && L6 to L7 && L7 && L7")] + #[test_case(PATH, "reachability: Researcher && Researcher -> [U0, U0](); [_, U0]()", vec![]; "Path in Researcher && Researcher from universal state to partial universal state")] + fn path_gen_test_correct_path_vecvec( + folder_path: &str, + query: &str, + expected_path: Vec>, + ) { + match json_run_query(folder_path, query).unwrap() { QueryResult::Reachability(actual_path) => { - assert!( - actual_path.was_reachable, - "Query: {}\nEnd state is not reachable from start state \n", - query - ); - let path: Vec = actual_path.path.unwrap(); + let actual_path = actual_path.unwrap_or_else(|_| { + panic!( + "Query: {}\nEnd state is not reachable from start state \n", + query + ) + }); + let path = actual_path.path; assert!(expected_path.len() == path.len(), "Query: {}\nThe length of the actual and expected are not the same.\nexpected_path.len = {}\nactual_path.len = {} \n", query, expected_path.len(),path.len()); for i in 0..path.len() { + let edges: Vec<_> = path[i].edges.iter().map(|e| e.edge_id.clone()).collect(); + assert_eq!( + expected_path[i].len(), + edges.len(), + "Query: {}\nThere should only be one edge in the path \n", + query + ); assert!( - expected_path[i] == path[i].id.to_string(), + expected_path[i] == edges, "Query: {}\nThe actual and expected is not the same \n", query ); diff --git a/src/tests/refinement/Helper.rs b/src/tests/refinement/Helper.rs index c1eaa6bb..6ffa0149 100644 --- a/src/tests/refinement/Helper.rs +++ b/src/tests/refinement/Helper.rs @@ -1,10 +1,12 @@ +use crate::extract_system_rep::ExecutableQueryError; use crate::logging::setup_logger; use crate::DataReader::component_loader::{JsonProjectLoader, XmlProjectLoader}; use crate::DataReader::parse_queries; use crate::ModelObjects::queries::Query; -use crate::System::executable_query::QueryResult; use crate::System::extract_system_rep::create_executable_query; -use crate::System::refine::RefinementResult; +use crate::System::query_failures::QueryResult; +use crate::TransitionSystems::transition_system::component_loader_to_transition_system; +use crate::TransitionSystems::TransitionSystemPtr; fn try_setup_logging() { #[cfg(feature = "logging")] @@ -14,9 +16,9 @@ fn try_setup_logging() { pub fn xml_refinement_check(PATH: &str, QUERY: &str) -> bool { try_setup_logging(); match xml_run_query(PATH, QUERY) { - QueryResult::Refinement(RefinementResult::Success) => true, - QueryResult::Refinement(RefinementResult::Failure(_)) => false, - QueryResult::Error(err) => panic!("{}", err), + QueryResult::Refinement(Ok(())) => true, + QueryResult::Refinement(Err(_)) => false, + QueryResult::CustomError(err) => panic!("{}", err), _ => panic!("Not a refinement check"), } } @@ -24,10 +26,10 @@ pub fn xml_refinement_check(PATH: &str, QUERY: &str) -> bool { pub fn json_refinement_check(PATH: &str, QUERY: &str) -> bool { try_setup_logging(); - match json_run_query(PATH, QUERY) { - QueryResult::Refinement(RefinementResult::Success) => true, - QueryResult::Refinement(RefinementResult::Failure(_)) => false, - QueryResult::Error(err) => panic!("{}", err), + match json_run_query(PATH, QUERY).unwrap() { + QueryResult::Refinement(Ok(())) => true, + QueryResult::Refinement(Err(_)) => false, + QueryResult::CustomError(err) => panic!("{}", err), _ => panic!("Not a refinement check"), } } @@ -49,7 +51,7 @@ pub fn xml_run_query(PATH: &str, QUERY: &str) -> QueryResult { query.execute() } -pub fn json_run_query(PATH: &str, QUERY: &str) -> QueryResult { +pub fn json_run_query(PATH: &str, QUERY: &str) -> Result { let project_loader = JsonProjectLoader::new(String::from(PATH), crate::tests::TEST_SETTINGS); let query = parse_queries::parse_to_expression_tree(QUERY) .unwrap() @@ -60,7 +62,13 @@ pub fn json_run_query(PATH: &str, QUERY: &str) -> QueryResult { }; let mut comp_loader = project_loader.to_comp_loader(); - let query = create_executable_query(&q, &mut *comp_loader).unwrap(); + let query = create_executable_query(&q, &mut *comp_loader)?; - query.execute() + Ok(query.execute()) +} + +pub fn json_get_system(PATH: &str, COMP: &str) -> TransitionSystemPtr { + let project_loader = JsonProjectLoader::new(String::from(PATH), crate::tests::TEST_SETTINGS); + let mut loader = project_loader.to_comp_loader(); + component_loader_to_transition_system(&mut *loader, COMP) } diff --git a/src/tests/refinement/xml/consistency_tests.rs b/src/tests/refinement/xml/consistency_tests.rs index 807c99d7..e07b2028 100644 --- a/src/tests/refinement/xml/consistency_tests.rs +++ b/src/tests/refinement/xml/consistency_tests.rs @@ -1,13 +1,11 @@ #[cfg(test)] mod test { - use crate::tests::refinement::Helper::xml_run_query; - use crate::System::executable_query::QueryResult; - use crate::System::local_consistency::ConsistencyResult; + use crate::{tests::refinement::Helper::xml_run_query, System::query_failures::QueryResult}; static PATH: &str = "samples/xml/ConsTests.xml"; fn convert_to_bool(result: QueryResult) -> bool { - matches!(result, QueryResult::Consistency(ConsistencyResult::Success)) + matches!(result, QueryResult::Consistency(Ok(()))) } #[test] diff --git a/src/tests/refinement/xml/determinism_tests.rs b/src/tests/refinement/xml/determinism_tests.rs index 61bfd6ea..714aaa17 100644 --- a/src/tests/refinement/xml/determinism_tests.rs +++ b/src/tests/refinement/xml/determinism_tests.rs @@ -1,13 +1,14 @@ #[cfg(test)] mod test { - use crate::tests::refinement::Helper::xml_run_query; - use crate::System::executable_query::QueryResult; - use crate::System::local_consistency::DeterminismResult; + use crate::{ + tests::refinement::Helper::xml_run_query, + System::query_failures::{DeterminismResult, QueryResult}, + }; static PATH: &str = "samples/xml/ConsTests.xml"; fn convert_to_bool(result: DeterminismResult) -> bool { - matches!(result, DeterminismResult::Success) + matches!(result, Ok(())) } #[test] diff --git a/src/tests/save_component/save_comp_helper.rs b/src/tests/save_component/save_comp_helper.rs index f8ecb945..c433f20e 100644 --- a/src/tests/save_component/save_comp_helper.rs +++ b/src/tests/save_component/save_comp_helper.rs @@ -5,11 +5,10 @@ pub mod util { use crate::ModelObjects::representations::QueryExpression; use crate::System::extract_system_rep; use crate::System::extract_system_rep::SystemRecipe; + use crate::System::query_failures::ConsistencyResult; use crate::System::refine; - use crate::System::refine::RefinementResult; use crate::System::save_component::combine_components; use crate::System::save_component::PruningStrategy; - use crate::TransitionSystems::transition_system::PrecheckResult; use edbm::util::constraints::ClockIndex; pub fn json_reconstructed_component_refines_base_self(input_path: &str, system: &str) { @@ -64,19 +63,16 @@ pub mod util { if helper(&base_precheck) && helper(&new_precheck) { assert!(matches!( refine::check_refinement(new_comp.clone(), base_system.clone()), - RefinementResult::Success + Ok(()) )); assert!(matches!( refine::check_refinement(base_system.clone(), new_comp.clone()), - RefinementResult::Success + Ok(()) )); } } - fn helper(a: &PrecheckResult) -> bool { - if let PrecheckResult::Success = a { - return true; - } - false + fn helper(a: &ConsistencyResult) -> bool { + a.is_ok() } } diff --git a/src/tests/system_recipe/compiled_component.rs b/src/tests/system_recipe/compiled_component.rs index af91eb00..fa004a0c 100644 --- a/src/tests/system_recipe/compiled_component.rs +++ b/src/tests/system_recipe/compiled_component.rs @@ -1,85 +1,110 @@ #[cfg(test)] mod test { + use std::collections::HashSet; + + use crate::extract_system_rep::ExecutableQueryError; use crate::{ - extract_system_rep::SystemRecipeFailure, tests::refinement::Helper::json_run_query, - QueryResult, - System::local_consistency::{ConsistencyFailure, ConsistencyResult}, + System::query_failures::{ActionFailure, SystemRecipeFailure}, }; const PATH: &str = "samples/json/SystemRecipe/CompiledComponent"; #[test] fn compiled_component1_fails_correctly() { - let actual = json_run_query(PATH, "consistency: CompiledComponent1"); + let actual = json_run_query(PATH, "consistency: CompiledComponent1").unwrap_err(); assert!(matches!( actual, - QueryResult::Consistency(ConsistencyResult::Failure(ConsistencyFailure::NotDisjoint( - .. - ))) + ExecutableQueryError::SystemRecipeFailure(SystemRecipeFailure::Action( + ActionFailure::NotDisjoint(_, _), + _ + )) )); } #[test] fn compiled_component1_fails_with_correct_actions() { - let expected_actions = vec!["Input".to_string()]; - if let QueryResult::Consistency(ConsistencyResult::Failure( - ConsistencyFailure::NotDisjoint(SystemRecipeFailure { actions, .. }), - )) = json_run_query(PATH, "consistency: CompiledComponent1") + let expected_actions: HashSet<_> = HashSet::from(["Input".to_string()]); + if let Some(ExecutableQueryError::SystemRecipeFailure(SystemRecipeFailure::Action( + ActionFailure::NotDisjoint(left, right), + _, + ))) = json_run_query(PATH, "consistency: CompiledComponent1").err() { - assert_eq!(actions, expected_actions); + assert_eq!( + left.actions + .intersection(&right.actions) + .cloned() + .collect::>(), + expected_actions + ); } else { - panic!("Models in saples/action have been changed, REVERT!"); + panic!("Models in samples/action have been changed, REVERT!"); } } #[test] fn compiled_component2_fails_correctly() { - let actual = json_run_query(PATH, "consistency: CompiledComponent2"); + let actual = json_run_query(PATH, "consistency: CompiledComponent2").unwrap_err(); assert!(matches!( actual, - QueryResult::Consistency(ConsistencyResult::Failure(ConsistencyFailure::NotDisjoint( - .. - ))) + ExecutableQueryError::SystemRecipeFailure(SystemRecipeFailure::Action( + ActionFailure::NotDisjoint(_, _), + _ + )) )); } #[test] fn compiled_component2_fails_with_correct_actions() { - let expected_actions = vec!["Input1".to_string()]; - if let QueryResult::Consistency(ConsistencyResult::Failure( - ConsistencyFailure::NotDisjoint(SystemRecipeFailure { actions, .. }), - )) = json_run_query(PATH, "consistency: CompiledComponent2") + let expected_actions: HashSet<_> = HashSet::from(["Input1".to_string()]); + if let Some(ExecutableQueryError::SystemRecipeFailure(SystemRecipeFailure::Action( + ActionFailure::NotDisjoint(left, right), + _, + ))) = json_run_query(PATH, "consistency: CompiledComponent2").err() { - assert_eq!(actions, expected_actions); + assert_eq!( + left.actions + .intersection(&right.actions) + .cloned() + .collect::>(), + expected_actions + ); } else { - panic!("Models in saples/action have been changed, REVERT!"); + panic!("Models in samples/action have been changed, REVERT!"); } } #[test] fn compiled_component3_fails_correctly() { - let actual = json_run_query(PATH, "consistency: CompiledComponent3"); + let actual = json_run_query(PATH, "consistency: CompiledComponent3").unwrap_err(); assert!(matches!( actual, - QueryResult::Consistency(ConsistencyResult::Failure(ConsistencyFailure::NotDisjoint( - .. - ))) + ExecutableQueryError::SystemRecipeFailure(SystemRecipeFailure::Action( + ActionFailure::NotDisjoint(_, _), + _ + )) )); } #[test] fn compiled_component3_fails_with_correct_actions() { - let expected_actions = vec!["Input1".to_string(), "Input2".to_string()]; - if let QueryResult::Consistency(ConsistencyResult::Failure( - ConsistencyFailure::NotDisjoint(SystemRecipeFailure { mut actions, .. }), - )) = json_run_query(PATH, "consistency: CompiledComponent3") + let expected_actions: HashSet<_> = + HashSet::from(["Input1".to_string(), "Input2".to_string()]); + if let Some(ExecutableQueryError::SystemRecipeFailure(SystemRecipeFailure::Action( + ActionFailure::NotDisjoint(left, right), + _, + ))) = json_run_query(PATH, "consistency: CompiledComponent3").err() { - actions.sort(); - assert_eq!(actions, expected_actions); + assert_eq!( + left.actions + .intersection(&right.actions) + .cloned() + .collect::>(), + expected_actions + ); } else { - panic!("Models in saples/action have been changed, REVERT!"); + panic!("Models in samples/action have been changed, REVERT!"); } } } diff --git a/src/tests/system_recipe/composition.rs b/src/tests/system_recipe/composition.rs index 7239514d..8edcea5a 100644 --- a/src/tests/system_recipe/composition.rs +++ b/src/tests/system_recipe/composition.rs @@ -1,85 +1,112 @@ #[cfg(test)] mod test { + use std::collections::HashSet; + + use crate::extract_system_rep::ExecutableQueryError; use crate::{ - extract_system_rep::SystemRecipeFailure, tests::refinement::Helper::json_run_query, - QueryResult, - System::local_consistency::{ConsistencyFailure, ConsistencyResult}, + System::query_failures::{ActionFailure, SystemRecipeFailure}, }; const PATH: &str = "samples/json/SystemRecipe/Composition"; #[test] fn compostion1_fails_correctly() { - let actual = json_run_query(PATH, "consistency: LeftComposition1 || RightComposition1"); + let actual = + json_run_query(PATH, "consistency: LeftComposition1 || RightComposition1").unwrap_err(); assert!(matches!( actual, - QueryResult::Consistency(ConsistencyResult::Failure(ConsistencyFailure::NotDisjoint( - .. - ))) + ExecutableQueryError::SystemRecipeFailure(SystemRecipeFailure::Action( + ActionFailure::NotDisjoint(_, _), + _ + )) )); } #[test] fn composition1_fails_with_correct_actions() { - let expected_actions = vec!["Output1".to_string()]; - if let QueryResult::Consistency(ConsistencyResult::Failure( - ConsistencyFailure::NotDisjoint(SystemRecipeFailure { actions, .. }), - )) = json_run_query(PATH, "consistency: LeftComposition1 || RightComposition1") + let expected_actions = HashSet::from(["Output1".to_string()]); + if let Some(ExecutableQueryError::SystemRecipeFailure(SystemRecipeFailure::Action( + ActionFailure::NotDisjoint(left, right), + _, + ))) = json_run_query(PATH, "consistency: LeftComposition1 || RightComposition1").err() { - assert_eq!(actions, expected_actions); + assert_eq!( + left.actions + .intersection(&right.actions) + .cloned() + .collect::>(), + expected_actions + ); } else { - panic!("Models in saples/action have been changed, REVERT!"); + panic!("Models in samples/action have been changed, REVERT!"); } } #[test] fn compostion2_fails_correctly() { - let actual = json_run_query(PATH, "consistency: LeftComposition2 || RightComposition2"); + let actual = + json_run_query(PATH, "consistency: LeftComposition2 || RightComposition2").unwrap_err(); assert!(matches!( actual, - QueryResult::Consistency(ConsistencyResult::Failure(ConsistencyFailure::NotDisjoint( - .. - ))) + ExecutableQueryError::SystemRecipeFailure(SystemRecipeFailure::Action( + ActionFailure::NotDisjoint(_, _), + _ + )) )); } #[test] fn composition2_fails_with_correct_actions() { - let expected_actions = vec!["Output1".to_string(), "Output2".to_string()]; - if let QueryResult::Consistency(ConsistencyResult::Failure( - ConsistencyFailure::NotDisjoint(SystemRecipeFailure { mut actions, .. }), - )) = json_run_query(PATH, "consistency: LeftComposition2 || RightComposition2") + let expected_actions = HashSet::from(["Output1".to_string(), "Output2".to_string()]); + if let Some(ExecutableQueryError::SystemRecipeFailure(SystemRecipeFailure::Action( + ActionFailure::NotDisjoint(left, right), + _, + ))) = json_run_query(PATH, "consistency: LeftComposition2 || RightComposition2").err() { - actions.sort(); - assert_eq!(actions, expected_actions); + assert_eq!( + left.actions + .intersection(&right.actions) + .cloned() + .collect::>(), + expected_actions + ); } else { - panic!("Models in saples/action have been changed, REVERT!"); + panic!("Models in samples/action have been changed, REVERT!"); } } #[test] fn compostion3_fails_correctly() { - let actual = json_run_query(PATH, "consistency: LeftComposition3 || RightComposition3"); + let actual = + json_run_query(PATH, "consistency: LeftComposition3 || RightComposition3").unwrap_err(); assert!(matches!( actual, - QueryResult::Consistency(ConsistencyResult::Failure(ConsistencyFailure::NotDisjoint( - .. - ))) + ExecutableQueryError::SystemRecipeFailure(SystemRecipeFailure::Action( + ActionFailure::NotDisjoint(_, _), + _ + )) )); } #[test] fn composition3_fails_with_correct_actions() { - let expected_actions = vec!["Output2".to_string()]; - if let QueryResult::Consistency(ConsistencyResult::Failure( - ConsistencyFailure::NotDisjoint(SystemRecipeFailure { actions, .. }), - )) = json_run_query(PATH, "consistency: LeftComposition3 || RightComposition3") + let expected_actions = HashSet::from(["Output2".to_string()]); + if let Some(ExecutableQueryError::SystemRecipeFailure(SystemRecipeFailure::Action( + ActionFailure::NotDisjoint(left, right), + _, + ))) = json_run_query(PATH, "consistency: LeftComposition3 || RightComposition3").err() { - assert_eq!(actions, expected_actions); + assert_eq!( + left.actions + .intersection(&right.actions) + .cloned() + .collect::>(), + expected_actions + ); } else { - panic!("Models in saples/action have been changed, REVERT!"); + panic!("Models in samples/action have been changed, REVERT!"); } } } diff --git a/src/tests/system_recipe/conjunction.rs b/src/tests/system_recipe/conjunction.rs index b3755cf8..04f3fe4e 100644 --- a/src/tests/system_recipe/conjunction.rs +++ b/src/tests/system_recipe/conjunction.rs @@ -1,84 +1,112 @@ #[cfg(test)] mod test { + use std::collections::HashSet; + use crate::{ - extract_system_rep::SystemRecipeFailure, tests::refinement::Helper::json_run_query, - QueryResult, - System::local_consistency::{ConsistencyFailure, ConsistencyResult}, + System::extract_system_rep::ExecutableQueryError, + System::query_failures::{ActionFailure, SystemRecipeFailure}, }; const PATH: &str = "samples/json/SystemRecipe/Conjunction"; #[test] fn conjunction1_fails_correctly() { - let actual = json_run_query(PATH, "consistency: LeftConjunction1 && RightConjunction1"); + let actual = + json_run_query(PATH, "consistency: LeftConjunction1 && RightConjunction1").unwrap_err(); assert!(matches!( actual, - QueryResult::Consistency(ConsistencyResult::Failure(ConsistencyFailure::NotDisjoint( - .. - ))) + ExecutableQueryError::SystemRecipeFailure(SystemRecipeFailure::Action( + ActionFailure::NotDisjoint(_, _), + _ + )) )); } #[test] fn conjunction1_fails_with_correct_actions() { - let expected_actions = vec!["Input1".to_string(), "Output1".to_string()]; - if let QueryResult::Consistency(ConsistencyResult::Failure( - ConsistencyFailure::NotDisjoint(SystemRecipeFailure { actions, .. }), - )) = json_run_query(PATH, "consistency: LeftConjunction1 && RightConjunction1") + let expected_actions = HashSet::from(["Input1".to_string()]); // Assuming inputs are checked first + if let Some(ExecutableQueryError::SystemRecipeFailure(SystemRecipeFailure::Action( + ActionFailure::NotDisjoint(left, right), + _, + ))) = json_run_query(PATH, "consistency: LeftConjunction1 && RightConjunction1").err() { - assert_eq!(actions, expected_actions); + assert_eq!( + left.actions + .intersection(&right.actions) + .cloned() + .collect::>(), + expected_actions + ); } else { - panic!("Models in saples/action have been changed, REVERT!"); + panic!("Models in samples/action have been changed, REVERT!"); } } #[test] fn conjunction2_fails_correctly() { - let actual = json_run_query(PATH, "consistency: LeftConjunction2 && RightConjunction2"); + let actual = + json_run_query(PATH, "consistency: LeftConjunction2 && RightConjunction2").unwrap_err(); assert!(matches!( actual, - QueryResult::Consistency(ConsistencyResult::Failure(ConsistencyFailure::NotDisjoint( - .. - ))) + ExecutableQueryError::SystemRecipeFailure(SystemRecipeFailure::Action( + ActionFailure::NotDisjoint(_, _), + _ + )) )); } #[test] fn conjunction2_fails_with_correct_actions() { - let expected_actions = vec!["Input1".to_string()]; - if let QueryResult::Consistency(ConsistencyResult::Failure( - ConsistencyFailure::NotDisjoint(SystemRecipeFailure { actions, .. }), - )) = json_run_query(PATH, "consistency: LeftConjunction2 && RightConjunction2") + let expected_actions = HashSet::from(["Input1".to_string()]); + if let Some(ExecutableQueryError::SystemRecipeFailure(SystemRecipeFailure::Action( + ActionFailure::NotDisjoint(left, right), + _, + ))) = json_run_query(PATH, "consistency: LeftConjunction2 && RightConjunction2").err() { - assert_eq!(actions, expected_actions); + assert_eq!( + left.actions + .intersection(&right.actions) + .cloned() + .collect::>(), + expected_actions + ); } else { - panic!("Models in saples/action have been changed, REVERT!"); + panic!("Models in samples/action have been changed, REVERT!"); } } #[test] fn conjunction3_fails_correctly() { - let actual = json_run_query(PATH, "consistency: LeftConjunction3 && RightConjunction3"); + let actual = + json_run_query(PATH, "consistency: LeftConjunction3 && RightConjunction3").unwrap_err(); assert!(matches!( actual, - QueryResult::Consistency(ConsistencyResult::Failure(ConsistencyFailure::NotDisjoint( - .. - ))) + ExecutableQueryError::SystemRecipeFailure(SystemRecipeFailure::Action( + ActionFailure::NotDisjoint(_, _), + _ + )) )); } #[test] fn conjunction3_fails_with_correct_actions() { - let expected_actions = vec!["Output1".to_string()]; - if let QueryResult::Consistency(ConsistencyResult::Failure( - ConsistencyFailure::NotDisjoint(SystemRecipeFailure { actions, .. }), - )) = json_run_query(PATH, "consistency: LeftConjunction3 && RightConjunction3") + let expected_actions = HashSet::from(["Output1".to_string()]); + if let Some(ExecutableQueryError::SystemRecipeFailure(SystemRecipeFailure::Action( + ActionFailure::NotDisjoint(left, right), + _, + ))) = json_run_query(PATH, "consistency: LeftConjunction3 && RightConjunction3").err() { - assert_eq!(actions, expected_actions); + assert_eq!( + left.actions + .intersection(&right.actions) + .cloned() + .collect::>(), + expected_actions + ); } else { - panic!("Models in saples/action have been changed, REVERT!"); + panic!("Models in samples/action have been changed, REVERT!"); } } } diff --git a/src/tests/system_recipe/quotient.rs b/src/tests/system_recipe/quotient.rs index 84532e8a..1dd24e3c 100644 --- a/src/tests/system_recipe/quotient.rs +++ b/src/tests/system_recipe/quotient.rs @@ -1,34 +1,46 @@ #[cfg(test)] mod test { + use std::collections::HashSet; + use crate::{ - extract_system_rep::SystemRecipeFailure, tests::refinement::Helper::json_run_query, - QueryResult, - System::local_consistency::{ConsistencyFailure, ConsistencyResult}, + System::extract_system_rep::ExecutableQueryError, + System::query_failures::{ + ActionFailure, ConsistencyFailure, DeterminismFailure, SystemRecipeFailure, + }, }; const PATH: &str = "samples/json/SystemRecipe/Quotient"; #[test] fn quotient1_fails_correctly() { - let actual = json_run_query(PATH, "consistency: LeftQuotient1 // RightQuotient1"); + let actual = + json_run_query(PATH, "consistency: LeftQuotient1 // RightQuotient1").unwrap_err(); assert!(matches!( actual, - QueryResult::Consistency(ConsistencyResult::Failure(ConsistencyFailure::NotDisjoint( - .. - ))) - )) + ExecutableQueryError::SystemRecipeFailure(SystemRecipeFailure::Action( + ActionFailure::NotDisjoint(_, _), + _ + )) + )); } #[test] fn quotient1_fails_with_correct_actions() { - let expected_actions = vec!["Input1".to_string()]; - if let QueryResult::Consistency(ConsistencyResult::Failure( - ConsistencyFailure::NotDisjoint(SystemRecipeFailure { actions, .. }), - )) = json_run_query(PATH, "consistency: LeftQuotient1 // RightQuotient1") + let expected_actions = HashSet::from(["Input1".to_string()]); + if let Some(ExecutableQueryError::SystemRecipeFailure(SystemRecipeFailure::Action( + ActionFailure::NotDisjoint(left, right), + _, + ))) = json_run_query(PATH, "consistency: LeftQuotient1 // RightQuotient1").err() { - assert_eq!(actions, expected_actions); + assert_eq!( + left.actions + .intersection(&right.actions) + .cloned() + .collect::>(), + expected_actions + ); } else { panic!("Models in samples/action have been changed, REVERT!"); } @@ -39,13 +51,16 @@ mod test { let actual = json_run_query( PATH, "consistency: NotDeterministicQuotientComp // DeterministicQuotientComp", - ); + ) + .unwrap_err(); + println!("{:?}", actual); assert!(matches!( actual, - QueryResult::Consistency(ConsistencyResult::Failure(ConsistencyFailure::NotDisjoint( - .. - ))) - )) + ExecutableQueryError::SystemRecipeFailure(SystemRecipeFailure::Inconsistent( + ConsistencyFailure::NotDeterministic(DeterminismFailure { .. }), + _ + )) + )); } #[test] @@ -53,12 +68,15 @@ mod test { let actual = json_run_query( PATH, "consistency: DeterministicQuotientComp // NotDeterministicQuotientComp", - ); + ) + .unwrap_err(); + println!("{:?}", actual); assert!(matches!( actual, - QueryResult::Consistency(ConsistencyResult::Failure(ConsistencyFailure::NotDisjoint( - .. - ))) - )) + ExecutableQueryError::SystemRecipeFailure(SystemRecipeFailure::Inconsistent( + ConsistencyFailure::NotDeterministic(DeterminismFailure { .. }), + _ + )) + )); } }