Skip to content
27 changes: 22 additions & 5 deletions debugging_script.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from test_script import *
from test_ode_syntax import *
import sys

# This is here because pytest is too slow - so it's a faster test that avoid the collection step.
# Pytest is used in the GitHub while pushing
Expand Down Expand Up @@ -30,8 +32,8 @@
test_conditional_between_meta_species,
test_conditional_between_meta_species_2,
test_event_reaction_not_allowed,
all_test,
all_test_2,
test_all,
test_2_all,
test_error_mult,
test_set_counts,
test_bool_error,
Expand All @@ -45,11 +47,10 @@
test_string_events_assignment,
test_plotting,
test_volume_after_sim,
test_parameters_with_sbml,
test_shared_parameter_name,
test_set_counts_parameters,
test_repeated_parameters,
initial_expression_test,
test_initial_expression,
test_wrong_dimension_error,
test_more_than_used,
zero_rate_test,
Expand Down Expand Up @@ -105,6 +106,14 @@
test_2D_reaction_with_units,
test_parameters_as_initial_values,
test_parameters_in_lambda_expression,
test_ode_syntax_basic,
test_ode_syntax_two_species,
test_ode_applied_to_species,
test_ode_neg_test,
test_ode_compartments,
test_ode_complex_expressions,
test_ode_inheritance,
test_ode_with_functions
]

# test_no_species_in_asg
Expand All @@ -117,14 +126,22 @@
# sub_test = [test_parameters_with_sbml]
def perform_tests():
any_failed = False
failed_tests = []
for test in sub_test:
try:
test()
print(f"Test {test} passed")
except:
print("\033[91m" + f"Test {test} failed" + "\033[0m", file=sys.stderr)
message = "\033[91m" + f"Test {test} failed" + "\033[0m"
print(message, file=sys.stderr)
any_failed = True
failed_tests.append(message)
if any_failed:
print()
print("The following tests failed", file=sys.stderr)
for t in failed_tests:
print(t, file=sys.stderr)

assert False


Expand Down
12 changes: 5 additions & 7 deletions for_local_use.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
from mobspy import *
from mobspy.modules.ode_operator import dt
from mobspy.modules.functions import ms_exp
from testutils import compare_model, compare_model_ignore_order

if __name__ == "__main__":

A, B = BaseSpecies()
A.a1, A.a2, A.a3, B.b1, B.b2
C = A*B

~C.a1 >> Zero [10]
if __name__ == "__main__":

S = Simulation(A | C)
print(S.compile())
pass
22 changes: 18 additions & 4 deletions mobspy/modules/assignments_implementation.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,47 +52,61 @@ def check_context(self):

@staticmethod
def check_arguments(first, second):
spe_list_first = []
spe_list_second = []

# Only extract species if it's actually a Species or Reacting_Species
if hasattr(first, 'get_spe_object'):
spe_list_first = [first]

if hasattr(second, 'get_spe_object'):
spe_list_second = [second]

if type(first) == int or type(first) == float:
first = mbe_MobsPyExpression(
str(first),
None,
species_object= None,
dimension=None,
count_in_model=True,
concentration_in_model=False,
count_in_expression=False,
concentration_in_expression=False,
species_list_operation_order=spe_list_first
)

if type(second) == int or type(second) == float:
second = mbe_MobsPyExpression(
str(second),
None,
species_object= None,
dimension=None,
count_in_model=True,
concentration_in_model=False,
count_in_expression=False,
concentration_in_expression=False,
species_list_operation_order= spe_list_second
)

if not isinstance(first, mbe_MobsPyExpression):
first = mbe_MobsPyExpression(
"($asg_" + str(first) + ")",
None,
species_object=None,
dimension=None,
count_in_model=True,
concentration_in_model=False,
count_in_expression=False,
concentration_in_expression=False,
species_list_operation_order=spe_list_first
)
if not isinstance(second, mbe_MobsPyExpression):
second = mbe_MobsPyExpression(
"($asg_" + str(second) + ")",
None,
species_object=None,
dimension=None,
count_in_model=True,
concentration_in_model=False,
count_in_expression=False,
concentration_in_expression=False,
species_list_operation_order=spe_list_second
)
return first, second

Expand Down
1 change: 1 addition & 0 deletions mobspy/modules/event_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
search_for_parameters_in_str as frc_search_for_parameters_in_str,
)

# @TODO remove search parameters in string - don't use it anymore - slowly deprecate this function

def format_event_dictionary_for_sbml(
species_for_sbml,
Expand Down
9 changes: 1 addition & 8 deletions mobspy/modules/function_rate_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,6 @@ def extract_reaction_rate(
# Remove dollar sign symbols from expression object
reaction_rate_string = reaction_rate_string.replace("$", "")

if parameter_exist:
parameters_in_reaction = search_for_parameters_in_str(
reaction_rate_string, parameter_exist, parameters_in_reaction
)

elif isinstance(rate, mbe_MobsPyExpression):
# Having an expression variable implies it is a constructed expression - not mass action
if len(rate._expression_variables) > 0:
Expand Down Expand Up @@ -162,9 +157,6 @@ def extract_reaction_rate(
+ str(reactant_string_list)
)
elif type(reaction_rate_function) == str:
parameters_in_reaction = search_for_parameters_in_str(
reaction_rate_function, parameter_exist, parameters_in_reaction
)
reaction_rate_string = reaction_rate_function
else:
simlog_debug(type(reaction_rate_function))
Expand Down Expand Up @@ -303,6 +295,7 @@ def prepare_arguments_for_callable(
return argument_dict


# @TODO this function needs to be deprecated - Please slowly remove form the code
def search_for_parameters_in_str(
reaction_rate_string, parameters_exist, parameters_in_reaction
):
Expand Down
98 changes: 98 additions & 0 deletions mobspy/modules/functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
from mobspy.modules.meta_class import Species, Reacting_Species
from mobspy.modules.mobspy_expressions import MobsPyExpression, OverrideQuantity
from mobspy.modules.assignments_implementation import Assign
from mobspy.simulation_logging.log_scripts import error as simlog_error
import math

class MathFunctionWrapper:
"""Wrapper for mathematical functions that work with MobsPy expressions

Note for future developers. To add units to these functions,
use the unit compilation to check if dealing with concentrations or counts

The units must be compiled inside a function as the input is non-dimensional.
Therefore, the unit compilation must be performed here at this step

Currently, for the ODE application, I don't need units,
so I need to leave this for future needs
"""

def __init__(self, name):
self.name = name # COPASI function name: 'exp', 'sin', 'cos', etc.

def _create_expression(self, expression, new_operation):
"""Create new MobsPyExpression with this function applied."""
return MobsPyExpression(
species_string="$Null",
species_object=None,
operation=new_operation,
unit_count_op=1,
unit_conc_op=1,
dimension=expression._dimension,
expression_variables=set(expression._expression_variables),
parameter_set=set(expression._parameter_set),
count_in_model=expression._count_in_model,
concentration_in_model=expression._concentration_in_model,
count_in_expression=expression._count_in_expression,
concentration_in_expression=expression._concentration_in_expression,
has_units=expression._has_units,
species_list_operation_order=list(expression.species_list_operation_order)
)

def __call__(self, expression):

if not Assign.check_context():
simlog_error(
"The expression functions must only be called "
)

# MobsPy Expressions
if isinstance(expression, MobsPyExpression):

if expression._has_units == 'T':
simlog_error('At this current version, MobsPy functions do not support ')

new_operation = f"{self.name}({expression._operation})"
return self._create_expression(expression, new_operation)

# Species passed
elif ((isinstance(expression, Species) or isinstance(expression, Reacting_Species))
and Assign.check_context()):

if isinstance(expression, Reacting_Species):
if len(expression.list_of_reactants) > 1:
simlog_error(
message="Reacting species with multiple reactants should not be applied to a function",
full_exception_log=True
)

expression = Assign.mul(1, expression)
new_operation = f"{self.name}({expression._operation})"
return self._create_expression(expression, new_operation)

else:
simlog_error(
message="MobsPy functions were called on a non-valid context",
full_exception_log=True
)



# Create all the COPASI-compatible math functions
# Please add new functions with ms to avoid conflict with Python's existing modules
ms_exp = MathFunctionWrapper('exp')
ms_logn = MathFunctionWrapper('log')
ms_log10 = MathFunctionWrapper('log10')
ms_sin = MathFunctionWrapper('sin')
ms_cos = MathFunctionWrapper('cos')
ms_tan = MathFunctionWrapper('tan')
ms_asin = MathFunctionWrapper('asin')
ms_acos = MathFunctionWrapper('acos')
ms_atan = MathFunctionWrapper('atan')
ms_sinh = MathFunctionWrapper('sinh')
ms_cosh = MathFunctionWrapper('cosh')
ms_tanh = MathFunctionWrapper('tanh')
ms_floor = MathFunctionWrapper('floor')
ms_ceil = MathFunctionWrapper('ceil')
ms_abs = MathFunctionWrapper('abs')
ms_sqrt = MathFunctionWrapper('sqrt')
2 changes: 1 addition & 1 deletion mobspy/modules/logic_operator_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ def __eq__(self, other):
else:
return id(self) == id(other)

def __neg__(self, other):
def __ne__(self, other):
return id(self) != id(other)

def __hash__(self):
Expand Down
21 changes: 21 additions & 0 deletions mobspy/modules/meta_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,13 @@ def __radd__(self, other):
def __invert__(self):
return self.c('not$')

def __neg__(self):
if asgi_Assign.check_context():
return asgi_Assign.mul(-1, self)
else:
simlog_error("The negative operator was applied to a Reacting Species in the wrong context")


def __rshift__(self, other):
"""
The >> operator for defining reactions. It passes two instances of reacting species to construct the
Expand Down Expand Up @@ -1100,6 +1107,12 @@ def __radd__(self, other):
def __invert__(self):
return self.c('not$')

def __neg__(self):
if asgi_Assign.check_context():
return asgi_Assign.mul(-1, self)
else:
simlog_error("The negative operator was applied to a Species in the wrong context")

@classmethod
def _compile_defined_reaction(cls, code_line, line_number):
# Check that line ends with a ']' and after that only ')', ',', whitespace and comments
Expand All @@ -1110,10 +1123,18 @@ def _compile_defined_reaction(cls, code_line, line_number):
if re.search(set_pattern, code_line):
return True

# If line doesn't contain '>>', it's a multiline reaction, so we don't parse it
if '>>' not in code_line:
return True

# Make sure the reaction rate is present.
# If code_line ends with '>>' it might be a multiline reaction, so skip validation
if code_line.rstrip().endswith('>>'):
return True

# If code_line ends with '[' it's a multiline rate, skip validation
if code_line.rstrip().endswith('['):
return True

if not bool(re.search(pattern, code_line)):
simlog_error(
Expand Down
Loading
Loading