From 4147cbb503879a9a0c9ef158a681154f724d23aa Mon Sep 17 00:00:00 2001 From: Fabricio Cravo Date: Sat, 8 Nov 2025 16:27:17 -0500 Subject: [PATCH 1/2] Added not operator with inversion overriden --- mobspy/modules/compiler_operator_functions.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 mobspy/modules/compiler_operator_functions.py diff --git a/mobspy/modules/compiler_operator_functions.py b/mobspy/modules/compiler_operator_functions.py new file mode 100644 index 0000000..e69de29 From 643b820ae8a78363eb3b5058ddab83754bf4f5c9 Mon Sep 17 00:00:00 2001 From: Fabricio Cravo Date: Sat, 8 Nov 2025 16:27:28 -0500 Subject: [PATCH 2/2] Added not operator with inversion overriden --- for_local_use.py | 10 +- mobspy/modules/compiler.py | 6 + mobspy/modules/compiler_operator_functions.py | 129 +++++++++++ mobspy/modules/meta_class.py | 206 +++++++++--------- mobspy/modules/meta_class_utils.py | 2 - 5 files changed, 253 insertions(+), 100 deletions(-) diff --git a/for_local_use.py b/for_local_use.py index 23d13f0..af001a9 100644 --- a/for_local_use.py +++ b/for_local_use.py @@ -1,4 +1,12 @@ from mobspy import * if __name__ == "__main__": - pass + + A, B = BaseSpecies() + A.a1, A.a2, A.a3, B.b1, B.b2 + C = A*B + + ~C.a1 >> Zero [10] + + S = Simulation(A | C) + print(S.compile()) \ No newline at end of file diff --git a/mobspy/modules/compiler.py b/mobspy/modules/compiler.py index 2e77581..f7ab41e 100644 --- a/mobspy/modules/compiler.py +++ b/mobspy/modules/compiler.py @@ -1,3 +1,6 @@ +from mobspy.modules.compiler_operator_functions import ( + create_all_not_reactions as cof_create_all_not_reactions +) from mobspy.simulation_logging.log_scripts import ( error as simlog_error, warning as simlog_warning, @@ -326,6 +329,9 @@ def compile( parameters_used, parameters_for_sbml, parameters_in_counts ) + # Invert the not$ operators in all reactions + reactions_set = cof_create_all_not_reactions(reactions_set) + # BaseSpecies reactions for SBML with theirs respective parameters and rates # What do I have so far # Species_String_Dict and a set of reaction objects in Reactions_Set diff --git a/mobspy/modules/compiler_operator_functions.py b/mobspy/modules/compiler_operator_functions.py index e69de29..7325adb 100644 --- a/mobspy/modules/compiler_operator_functions.py +++ b/mobspy/modules/compiler_operator_functions.py @@ -0,0 +1,129 @@ +from itertools import product as ite_product + +from mobspy.modules.meta_class import Reactions +from mobspy.modules.meta_class import Reacting_Species +from mobspy.modules.meta_class import Zero + +""" + Species and meta-species supporting scripts +""" +def create_all_not_reactions(reactions): + + new_reactions_set = reactions + + def include_new_combinations(r, attribute_to_get): + ignore_flag = True + for x in getattr(r, attribute_to_get): + + if 'not$' in x['characteristics']: + new_reactions_set.remove(r) + ignore_flag = False + combinations = get_all_non_listed_characteristics( + x['object'], + x['characteristics'] - {'not$'}, + ) + + for comb in combinations: + x['characteristics'] = comb + new_r = new_reaction_with_new_characteristics(r, x['object'], comb) + new_reactions_set.add(new_r) + + return ignore_flag + + has_not = True + while has_not: + has_not = False + loop_reactions = set(new_reactions_set) # Snapshot + for r in loop_reactions: + if not include_new_combinations(r, "reactants"): + has_not = True + if not include_new_combinations(r, "products"): + has_not = True + + return new_reactions_set + + + +def get_all_non_listed_characteristics( + species, + characteristics + ): + """ + This function gets all characteristics related to a species except the ones listed bellow. + It uses inheritance to find all charactersitcs linked to a species + """ + operator_characteristics = {c for c in characteristics if '$' in c} + + multi_list_char_struct = [] + for spe in species.get_references(): + if characteristics: + dimension_cha = {e for e in spe.get_characteristics() if e not in characteristics} + else: + dimension_cha = {} + + if dimension_cha: + multi_list_char_struct.append(dimension_cha) + + combinations = [set(combo) | operator_characteristics for combo in ite_product(*multi_list_char_struct)] + + return combinations + + +def new_reaction_with_new_characteristics( + r, + spe_to_modify, + new_characteristics + ): + """ + Create a copy of this reaction, replacing characteristics of a specific dict + + :param dict_to_modify: The specific reactant/product dict to modify + :param new_characteristics: New characteristics set for that dict + :return: New Reactions object + """ + # Build new reactants list + React = Zero + for reactant in r.reactants: + if reactant['object'] == spe_to_modify: + # Use the new characteristics + rs = Reacting_Species( + reactant['object'], + new_characteristics, + reactant['stoichiometry'], + reactant['label'] + ) + else: + # Copy existing reactant + rs = Reacting_Species( + reactant['object'], + reactant['characteristics'], + reactant['stoichiometry'], + reactant['label'] + ) + + React = React + rs + + # Build new products list + Product = Zero + for product in r.products: + if product['object'] == spe_to_modify: + # Use the new characteristics + ps = Reacting_Species( + product['object'], + new_characteristics, + product['stoichiometry'], + product['label'] + ) + else: + # Copy existing product + ps = Reacting_Species( + product['object'], + product['characteristics'], + product['stoichiometry'], + product['label'] + ) + + Product = Product +ps + + # Create new reaction + return React >> Product [r.rate] \ No newline at end of file diff --git a/mobspy/modules/meta_class.py b/mobspy/modules/meta_class.py index adbda32..75da10f 100644 --- a/mobspy/modules/meta_class.py +++ b/mobspy/modules/meta_class.py @@ -122,51 +122,6 @@ class Reactions: :param order: (Order Operator) reaction order operator - default in a Round-Robin base :param rate: (int, float, callable, Quantity) reaction rate """ - - @staticmethod - def __create_reactants_string(list_of_reactants) -> str: - """ - Just a simple way to simlog.debug reactions for debbuging - Not relevant for simulation - Important: here reactants are used interchangeably with products, this works for a list_of_products too - - :param list_of_reactants: (list of Species) list of products or reactants in the reaction - """ - reaction_string = "" - for i, r in enumerate(list_of_reactants): - if r["stoichiometry"] > 1: - reaction_string += str(r["stoichiometry"]) + "*" + str(r["object"]) - else: - reaction_string += str(r["object"]) - - if len(r["characteristics"]) > 0: - reaction_string += "." + ".".join(r["characteristics"]) - - if i != len(list_of_reactants) - 1: - reaction_string += " " - reaction_string += "+" - reaction_string += " " - - return reaction_string - - def __str__(self) -> str: - """ - Prints the meta-reaction in the format A + B -> C + D - """ - return ( - self.__create_reactants_string(self.reactants) - + " -> " - + self.__create_reactants_string(self.products) - ) - - def __getitem__(self, item): - """ - Override of __getitem__ for dealing with reaction rates - - :param item: (int, float, callable, Quantity) = reaction rate - """ - return _Last_rate_storage.override_get_item(self, item) - def __init__(self, reactants, products, rate=None) -> None: """ Constructor of the reaction object. For the object construction only the reactants and products are @@ -263,6 +218,52 @@ def __init__(self, reactants, products, rate=None) -> None: for product in products: product["object"].add_reaction(self) + + + @staticmethod + def __create_reactants_string(list_of_reactants) -> str: + """ + Just a simple way to simlog.debug reactions for debbuging + Not relevant for simulation + Important: here reactants are used interchangeably with products, this works for a list_of_products too + + :param list_of_reactants: (list of Species) list of products or reactants in the reaction + """ + reaction_string = "" + for i, r in enumerate(list_of_reactants): + if r["stoichiometry"] > 1: + reaction_string += str(r["stoichiometry"]) + "*" + str(r["object"]) + else: + reaction_string += str(r["object"]) + + if len(r["characteristics"]) > 0: + reaction_string += "." + ".".join(r["characteristics"]) + + if i != len(list_of_reactants) - 1: + reaction_string += " " + reaction_string += "+" + reaction_string += " " + + return reaction_string + + def __str__(self) -> str: + """ + Prints the meta-reaction in the format A + B -> C + D + """ + return ( + self.__create_reactants_string(self.reactants) + + " -> " + + self.__create_reactants_string(self.products) + ) + + def __getitem__(self, item): + """ + Override of __getitem__ for dealing with reaction rates + + :param item: (int, float, callable, Quantity) = reaction rate + """ + return _Last_rate_storage.override_get_item(self, item) + def set_rate(self, rate): """ Sets the stored reaction rate. See _Last_rate_storage.override_get_item(self, item) @@ -378,6 +379,36 @@ class Reacting_Species(lop_ReactingSpeciesComparator, Assignment_Opp_Imp): species2 = Species("B") reaction = species1 >> species2 # Creates a Reaction object. """ + def __init__( + self, + object_reference, + characteristics, + stoichiometry: int | float = 1, + label: int | float | str | None = None + ) -> None: + """ + Reacting_Species constructor. It receives the meta-species object reference, the characteristics that + have been used as a query in the reaction, the stoichiometry of the meta-species in the reaction, and + finally a label if used. + + :param object_reference: (Species) meta-species object reference or (int, float, Quantity) for event + assignments with meta-species values + :param characteristics: (str) characteristics used to query over the meta-species inside this reaction + :param stoichiometry: (int, float) stoichiometry value of the meta-species in the reaction + :param label: (int, float, str) value for the label for matching used in this reaction + """ + super(Reacting_Species, self).__init__() + if object_reference.get_name() == "_S0" and characteristics == set(): + self.list_of_reactants = [] + else: + self.list_of_reactants = [ + { + "object": object_reference, + "characteristics": characteristics, + "stoichiometry": stoichiometry, + "label": label, + } + ] def __enter__(self): """ @@ -449,33 +480,6 @@ def __getitem__(self, item): """ return _Last_rate_storage.override_get_item(self, item) - def __init__( - self, object_reference, characteristics, stoichiometry: int | float = 1, label: int | float | str | None = None - ) -> None: - """ - Reacting_Species constructor. It receives the meta-species object reference, the characteristics that - have been used as a query in the reaction, the stoichiometry of the meta-species in the reaction, and - finally a label if used. - - :param object_reference: (Species) meta-species object reference or (int, float, Quantity) for event - assignments with meta-species values - :param characteristics: (str) characteristics used to query over the meta-species inside this reaction - :param stoichiometry: (int, float) stoichiometry value of the meta-species in the reaction - :param label: (int, float, str) value for the label for matching used in this reaction - """ - super(Reacting_Species, self).__init__() - if object_reference.get_name() == "_S0" and characteristics == set(): - self.list_of_reactants = [] - else: - self.list_of_reactants = [ - { - "object": object_reference, - "characteristics": characteristics, - "stoichiometry": stoichiometry, - "label": label, - } - ] - def get_spe_object(self): if len(self.list_of_reactants) != 1: simlog_error( @@ -540,6 +544,9 @@ def __radd__(self, other): else: return asgi_Assign.add(other, self) + def __invert__(self): + return self.c('not$') + def __rshift__(self, other): """ The >> operator for defining reactions. It passes two instances of reacting species to construct the @@ -847,6 +854,33 @@ class Species(lop_SpeciesComparator, Assignment_Opp_Imp): keys and the counts of values """ + # __init__ should be at the top of a class definition for easy access to the attributes names (sorry people) + def __init__(self, name): + """ + Object constructor - We recommend using BaseSpecies instead + + :param name: (str) Name of the species (can be placeholder if named with N$) + """ + super(Species, self).__init__() + self.name(name) + self._characteristics = set() + self._references = {self} + self._ordered_references = [] + self._reference_index_dictionary = {} + self._unit = "" + self._assignments = {} + self._linked_species = set() + + # This is necessary for the empty objects generated when we perform multiplication with more than 2 Properties + self.first_characteristic = None + + # Each object stores the reactions it is involved in + self._reactions = set() + + # This will store the quantities relating to the species counts + self._species_counts = [] + + @classmethod def check_if_valid_characteristic(cls, affected_object, char): """ @@ -1063,6 +1097,9 @@ def __radd__(self, other): else: return asgi_Assign.add(other, self) + def __invert__(self): + return self.c('not$') + @classmethod def _compile_defined_reaction(cls, code_line, line_number): # Check that line ends with a ']' and after that only ')', ',', whitespace and comments @@ -1296,31 +1333,6 @@ def __mul__(self, other): return new_entity - def __init__(self, name): - """ - Object constructor - We recommend using BaseSpecies instead - - :param name: (str) Name of the species (can be placeholder if named with N$) - """ - super(Species, self).__init__() - self.name(name) - self._characteristics = set() - self._references = {self} - self._ordered_references = [] - self._reference_index_dictionary = {} - self._unit = "" - self._assignments = {} - self._linked_species = set() - - # This is necessary for the empty objects generated when we perform multiplication with more than 2 Properties - self.first_characteristic = None - - # Each object stores the reactions it is involved in - self._reactions = set() - - # This will store the quantities relating to the species counts - self._species_counts = [] - def get_spe_object(self): return self diff --git a/mobspy/modules/meta_class_utils.py b/mobspy/modules/meta_class_utils.py index a89c147..4f194af 100644 --- a/mobspy/modules/meta_class_utils.py +++ b/mobspy/modules/meta_class_utils.py @@ -1,10 +1,8 @@ """This model stores function used by the meta_class.py module""" from collections.abc import Sequence - import mobspy.simulation_logging.log_scripts as simlog - def count_stoichiometry( lst: Sequence[tuple[float, str] | str], ) -> dict[str, float | int]: