#!/usr/bin/env python # coding: utf-8 # Celine Hernandez # # # Table of contents # # # 1. [Introduction](#presentation) # 1. [Set up](#setup) # 1. [Calcium module](#calcium) # 1. [Presentation](#capres) # 1. [Core components and extracted sub-model](#cacomp) # 1. [Test cases](#catests) # 1. [In naive cell](#caquiescent) # 1. [When an activation signal is received](#caactive) # 1. [Run all module tests](#carunall) # 1. [LCK module](#lck) # 1. [Presentation](#lckpres) # 1. [Core components and extracted sub-model](#lckcomp) # 1. [Test cases](#lcktests) # 1. [Resting state](#lckresting) # 1. [Non-productive bindings](#lcknonprod) # 1. [Productive bindings](#lckprod) # 1. [Run all module tests](#lckrunall) # 1. [Cytoskeleton module](#skeleton) # 1. [Presentation](#skpres) # 1. [Core components and extracted sub-model](#skcomp) # 1. [Test cases](#sktests) # 1. [Absence of PIP2 and PIP3](#sknonbio) # 1. [Resting state](#skresting) # 1. [Activation](#skactive) # 1. [Run all module tests](#skrunall) # 1. [Anergy/activation/differentiation module](#aneractdiff) # 1. [Presentation](#aadpres) # 1. [Core components and extracted sub-model](#aadcomp) # 1. [Test cases](#aadtests) # 1. [Run all module tests](#aadrunall) # 1. [Run all tests](#runall) # # # Introduction # [(Back to top.)](#toc) # # As a model grows to tens and hundreds of nodes, it becomes difficult to monitor its behaviour. In order to be sure that known behaviours are effectively reproduced, a unit testing pipeline can be implemented. This framework allows to automate both local and global analyses of the model. # # Steps of analysis: # - extract a module fof interest from the global model. Dependent components become inputs, thus not modifying the core logical functions. # - define unit tests for the module # - run unit tests # - report success/failure # # # Set up # [(Back to top.)](#toc) # Load the CoLoMoTo library. # In[1]: from colomoto_jupyter import tabulate # Load the Unit Test framework. # In[2]: import unittest # Re-usable test runner runner = unittest.TextTestRunner(verbosity=2) # Container for all test suites all_suite = unittest.TestSuite() # Whether test cases should be run independently debug_test_cases = True # Whether individual test suites should be run independently debug_test_suites = True # Whether all tests should be run at the end run_all = True # Load the model to be tested. # In[3]: # Load model to be tested import biolqm lqm = biolqm.load("Hernandez_TcellCheckPoints_13april2020.zginml") # Accessory functions # In[4]: # Transforms a dictionary into a %-joined pattern # {A:0, D:1} => "A%0 D%1" def percent_pattern(dict_vals): return " ".join( [ "%".join((key, str(dict_vals.get(key)))) for key in dict_vals.keys() ] ) # Transforms a dictionary into a dash-like pattern used for space restrictions. # If a model has 4 components A, B, C, D in this order, # {A:0, D:1} => "0--1" def dash_pattern(model, dict_vals): specific_comps = dict_vals.keys() str_pattern = "" for comp in model.getComponents(): if comp.toString() in specific_comps: str_pattern += str(dict_vals.get(comp.toString())) else : str_pattern += "-" return(str_pattern) # # # Calcium module # [(Back to top.)](#toc) # # # ## Presentation # [(Back to top.)](#toc) # # Following TCR engagement, cytoplasmic Calcium ions is elevated whithin seconds. This paragraph aims at testing the behaviour of the model around this event, and is centered on the Endoplasmic Reticulum (ER), Mitochondria, and the cytoplasmic membrane. # # For a general review on Calcium flux during antigen-induced T cell activation, see [PMID:23860253](https://www.ncbi.nlm.nih.gov/pubmed/23860253). For the specific impact of Mitochondria on sustained signalling, refer to [PMID:24117814](https://www.ncbi.nlm.nih.gov/pubmed/24117814). # # Summary and expected behaviours of the model: # # In quiescent cells (absence of IP3): # - there is no fixed point with Calcium_cyt:1 or Calcium_cyt:2 without also Calcium_ER:1. If present, Calcium ions should always flow back to the ER. # - SERCA is not active if there is no calcium depletion in the ER. In any fixed point, if Calcium_ER:1 then SERCA should be 0. # # Following TCR engagement (and increase in IP3 levels): # - if ORAI1:0, Calcium_cyt value depend on IP3R1, as SERCA effect is not considered strong enough to counteract release from IP3R1. Be aware that eventually ORAI1 will be activated as IP3 inhibits Calcium_ER. # - if ORAI1:1, Calcium_cyt will reach level 2 and never decrease. # # PMCA with no activation of Mitochondria (translocation): # Here, in absence of IP3 signal, activation of PMCA as soon as Calcium_cyt reaches level 2 gives transient oscillations, as ORAI1 increases Calcium_cyt to 2 and PMCA decreases it back to 1. # If SERCA gets activated, Calcium_ER can reach level 1, de-activating OARI1. Calcium_cyt:2 is not stable any more. # With IP3 signal, there is a cyclic attractor as Calcium_ER is never refilled. Calcium_cyt oscillates between values 1 and 2 due to the combined effects of ORAI1 and PMCA. # # Mitochondria are translocated where calcium cytoplasmic increases. There, they act as buffers, absorbing Calcium and releasing it far from ORAI1 and PMCA, thus counter-acting ORAI1 inhibition and PMCA activation caused by a high level of Calcium_cyt. # # Note that there is a positive feedback loop on Calcium_ER and Calcium_cyt to avoid any unwanted disappearance of calcium outside of fluxes through the channels. # # # ## Core components and extracted sub-model # [(Back to top.)](#toc) # In[5]: # Test suite for all calcium-related tests. calcium_suite = unittest.TestSuite() class CalciumModuleTestCase(unittest.TestCase): """Sub-model to be tested.""" model = biolqm.submodel(lqm, "Calcium_cyt Calcium_ER IP3R1 Mitochondria ORAI1 PMCA SERCA STIM1") # In[6]: str([component.toString() for component in CalciumModuleTestCase.model.getComponents()]) # In[7]: biolqm.to_minibn(CalciumModuleTestCase.model) # # ## Test cases # [(Back to top.)](#toc) # # ### In naive cell (no IP3 signal) # [(Back to top.)](#toc) # # Typically in naive cells, the ER is full of Calcium ions. As long as there is no IP3 signal that pattern should be stable (whatever the other input values) providing that there was Calcium at some point in any of the compartments (ER or cytoplasm). # # - [x] All trap spaces correspond to either an absence of Calcium everywhere or presence of Calcium in the ER. # - [x] When there is no Calcium in the ER it means that there is no Calcium anywhere (biologically incorrect trap spaces). # - [x] There exist at least one case where the ER is filled with Calcium. # - [x] When the ER is filled, SERCA is inactive. # - [x] When there is no input signal and the ER is filled, Calcium never reaches a sufficient level to activate downstream signalling. # In[8]: class TestCalciumQuiescent(CalciumModuleTestCase): # Note: changing core_vals was restricting core components values, thus setting up unwanted perturbations. # To implement this test, I need to find a set up where I can specify that there is calcium either # in the ER or the cytoplasm in the initial state. input_vals = {} core_vals = {} pattern = None resting_tp = None @classmethod def setUpClass(cls): cls.pattern = dash_pattern(cls.model, {**cls.input_vals, **cls.core_vals}) cls.resting_tp = [tp for tp in biolqm.trapspaces(cls.model) if tp["IP3"]==0] def addcontext(self, message, observed): return(" ".join(("Pattern:", self.pattern, "\n", "Context:", str(self.input_vals), str(self.core_vals), "\n", "Tested:", message, "\n", "Observed:", str(observed), ".\n"))) # Test if all trap spaces correspond to either an absence of Calcium everywhere or presence of Calcium in the ER. def test_calc_tp_rest_nbtp(self): no_calcium = len([tp for tp in self.resting_tp if tp["Calcium_ER"]==0 and tp["Calcium_cyt_b1"]==0 and tp["Calcium_cyt_b1"]==0]) calcium_in_er = len([tp for tp in self.resting_tp if tp["Calcium_ER"]==1]) observed = no_calcium+calcium_in_er expected = len(self.resting_tp) self.assertEqual(observed, expected, self.addcontext("".join(("If {IP3:0} then all", str(expected), "TS correspond to no Ca or Ca in ER.")), observed)) # Test that when there is no Calcium in the ER it means that there is no Calcium anywhere # This is a biologically incorrect case (see class documentation). def test_calc_tp_rest_noCalcium(self): no_calcium_in_ER = len([tp for tp in self.resting_tp if tp["Calcium_ER"]==0]) no_calcium_anywhere = len([tp for tp in self.resting_tp if tp["Calcium_ER"]==0 and tp["Calcium_cyt_b1"]==0 and tp["Calcium_cyt_b1"]==0]) observed = no_calcium_in_ER expected = no_calcium_anywhere self.assertEqual(observed, expected, self.addcontext("".join(("If {IP3:0,Calcium_ER:0} then Calcium_cyt:0, nb cases: ", str(expected))), observed)) # Test that there exist at least one case where the ER is filled with Calcium. def test_calc_tp_rest_ER1_present(self): calcium_in_er = len([tp for tp in self.resting_tp if tp["Calcium_ER"]==1]) observed = calcium_in_er not_expected = 0 self.assertTrue(observed > not_expected, self.addcontext("".join(("If {IP3:0,Calcium_ER:1} then there is more than ", str(not_expected), " trap spaces.")), observed)) # Test that when the ER is filled, SERCA is inactive def test_calc_tp_rest_ER1_SERCA0(self): calcium_in_er = [tp for tp in self.resting_tp if tp["Calcium_ER"]==1] for tp in calcium_in_er: observed = tp["SERCA"] expected = 0 self.assertEqual(observed, expected, self.addcontext("".join(("If {IP3:0,Calcium_ER:1} then SERCA:", str(expected))), " ".join((str(observed), "in", str(tp))) )) # Test that when there is no input signal and the ER is filled, # Calcium never reaches a sufficient level to activate downstream signalling. def test_calc_tp_rest_ER1_cyt1(self): calcium_in_er = [tp for tp in self.resting_tp if tp["ZAP70"]==0 and tp["WAVE_cplx"]==0 and tp["Calcium_ER"]==1] for tp in calcium_in_er: observed = tp["Calcium_cyt_b2"] expected = 0 self.assertEqual(observed, expected, self.addcontext("".join(("If {IP3:0,ZAP70:0,WAVE_cplx:0,Calcium_ER:1} then Calcium_cyt_b2:", str(expected))), " ".join((str(observed), "in", str(tp))) )) # Add tests to the current suite calcium_suite.addTests(unittest.makeSuite(TestCalciumQuiescent, prefix="test_")) # Run these tests independently if debug_test_cases : runner.run(unittest.makeSuite(TestCalciumQuiescent, prefix="test_")) # # ### When an activation signal is received (IP3 signal is present) # [(Back to top.)](#toc) # # - [x] There exist at least one trap space. # - [x] When there is no cytoskeletal remodelling (no Mitochondrial translocation), cytoplasmic Calcium cannot reach its maximal level. # # # #### All input signals on # # Important analysis for TCR activation as this will determine the calcium signal sent downstream IP3 when all input signals are on. In summary, the ER compartment get emptied while cytoplasm becomes full (level2) thanks to the presence of Mitochondria buffering the calcium influx far from ORAI1 and PMCA. # # | IP3 | ZAP70 | WAVE_cplx | IP3R1 | Calcium_cyt | Calcium_ER | SERCA | STIM1 | ORAI1 | PMCA | Mitochondria | # | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | # | 1 | 1 | 1 | * | * | 1 | * | * | * | * | * | # | Expected : | | | | | | | | | | | # | 1 | 1 | 1 | 1 | 2 | 0 | 1 | 1 | 1 | 0 | 1 | # # More specifically: IP3R1 gets activated by IP3. STIM1 is activated by the drop in Calcium_ER. SERCA gets activated by the drop in Calcium_ER but is not able to refill ER as long as IP3R1 stays active. SERCA alone cannot make Calcium_cyt:2 drop. ORAI1 allows Calcium_cyt to increase to level 2 and is not de-activated by the increase of cytoplasmic Calcium thanks to the translocation of mitochondria acting as a buffer. PMCA is inactive also thanks to mitochondrial translocation. # # - [x] There exist only one trap space. # - [x] ER is empty. # - [x] SERCA is activated. # - [x] Calcium cytoplasmic reaches a sufficient level to activate downstream signalling. # - [x] We observe mitochondrial translocation. # In[9]: class TestCalciumActivation(CalciumModuleTestCase): # Note: changing core_vals was restricting core components values, thus setting up unwanted perturbations. # To implement this test, I need to find a set up where I can specify that there is calcium either # in the ER or the cytoplasm in the initial state. input_vals = {} core_vals = {} pattern = None active_tp = None @classmethod def setUpClass(cls): cls.pattern = dash_pattern(cls.model, {**cls.input_vals, **cls.core_vals}) cls.active_tp = [tp for tp in biolqm.trapspaces(cls.model) if tp["IP3"]==1] def addcontext(self, message, observed): return(" ".join(("Pattern:", self.pattern, "\n", "Context:", str(self.input_vals), str(self.core_vals), "\n", "Tested:", message, "\n", "Observed:", str(observed), ".\n"))) # Test that there exist at least one trap space. def test_calc_tp_act_tppresent(self): observed = len(self.active_tp) not_expected = 0 self.assertTrue(observed > not_expected, self.addcontext("".join(("If {IP3:1} then there is more than ", str(not_expected), " trap spaces.")), observed)) # Test that when there is no cytoskeletal remodelling there is no Mitochondrial translocation (and cytoplasmic Calcium cannot reach its maximal level). def test_calc_tp_act_nomitochondria(self): no_cytoskeleton = [tp for tp in self.active_tp if tp["ZAP70"]==0 and tp["WAVE_cplx"]==1] for tp in no_cytoskeleton: observed = tp["Mitochondria"] expected = 0 self.assertEqual(observed, expected, self.addcontext("".join(("If {IP3:1,ZAP70:0,WAVE_cplx:1} then Mitochondria:", str(expected))), observed)) # Test that when all input signals are present, there exist only one trap space. def test_calc_tp_act_allinput_tpalone(self): observed = len([tp for tp in self.active_tp if tp["ZAP70"]==1 and tp["WAVE_cplx"]==1]) expected = 1 self.assertEqual(observed, expected, self.addcontext("".join(("If {IP3:1,ZAP70:1,WAVE_cplx:1} then there is exactly ", str(expected), " trap spaces.")), observed)) # Test that when all input signals are present, ER is empty. def test_calc_tp_act_allinput_ER0(self): allinput = [tp for tp in self.active_tp if tp["ZAP70"]==1 and tp["WAVE_cplx"]==1] for tp in allinput: observed = tp["Calcium_ER"] expected = 0 self.assertEqual(observed, expected, self.addcontext("".join(("If {IP3:1,ZAP70:1,WAVE_cplx:1} then Calcium_ER:", str(expected))), " ".join((str(observed), "in", str(tp))) )) # Test that when all input signals are present, # SERCA is activated. def test_calc_tp_act_allinput_SERCA1(self): allinput = [tp for tp in self.active_tp if tp["ZAP70"]==1 and tp["WAVE_cplx"]==1] for tp in allinput: observed = tp["SERCA"] expected = 1 self.assertEqual(observed, expected, self.addcontext("".join(("If {IP3:1,ZAP70:1,WAVE_cplx:1} then SERCA:", str(expected))), " ".join((str(observed), "in", str(tp))) )) # Test that when all input signals are present, # Calcium cytoplasmic reaches a sufficient level to activate downstream signalling. def test_calc_tp_act_allinput_Cyt2(self): allinput = [tp for tp in self.active_tp if tp["ZAP70"]==1 and tp["WAVE_cplx"]==1] for tp in allinput: observed = tp["Calcium_cyt_b2"] expected = 1 self.assertEqual(observed, expected, self.addcontext("".join(("If {IP3:1,ZAP70:1,WAVE_cplx:1} then Calcium_cyt_b2:", str(expected))), " ".join((str(observed), "in", str(tp))) )) # Test that when all input signals are present, # we observe mitochondrial translocation. def test_calc_tp_act_allinput_Mit1(self): allinput = [tp for tp in self.active_tp if tp["ZAP70"]==1 and tp["WAVE_cplx"]==1] for tp in allinput: observed = tp["Mitochondria"] expected = 1 self.assertEqual(observed, expected, self.addcontext("".join(("If {IP3:1,ZAP70:1,WAVE_cplx:1} then Mitochondria:", str(expected))), " ".join((str(observed), "in", str(tp))) )) # Add tests to the current suite calcium_suite.addTests(unittest.makeSuite(TestCalciumActivation, prefix="test_")) # Run these tests independently if debug_test_cases : runner.run(unittest.makeSuite(TestCalciumActivation, prefix="test_")) # # ## Run all module tests # [(Back to top.)](#toc) # In[10]: # Add tests to the global suite all_suite.addTests(calcium_suite) # Run these tests independently if debug_test_suites : runner.run(calcium_suite) # # # LCK module # [(Back to top.)](#toc) # # # ## Presentation # [(Back to top.)](#toc) # # T Cell Receptors (TCRs) bind antigens, allowins both sensitivity and specificity in the subsequent signal transmission depending on affinity and dosage. # # LCK is the major kinase involved in transduction of the TCR signal as it is responsible for the phosphorylation of TCRs and of its main interactor, ZAP70. But its regulation follows complex rules based on its phosphorylation state, constraining its folding and by consequence its activity. In parallel, FYN is subject to a similar regulation, but our knowledge is less detailed. # # These two elements, TCR and LCK are at the core of the current module. For a review on the initiation of TCR signalling and the activity of LCK depending on its phosphorylation state, see [PMID:25137454](https://www.ncbi.nlm.nih.gov/pubmed/25137454). # # Additional hypotheses and decisions were needed to build the logical model: # # - It is possible to have pS59 phosphorylation of LCK without prior pY394 (independence of the nodes), even if in reality pS59 by MAPK1/MAPK3 cannot happen without transmission of the TCR signal by activated LCK (thus phosphorylated on Y394). # - pS59 on SH2 domain of LCK impacts the binding of: PTPN6? Yes, see [PMID:12577055](https://www.ncbi.nlm.nih.gov/pubmed/12577055). PTPN22? Unknown. PTPRC? Unknown. # - In an LCK/LIME1/CSK complex, can other phosphatases act on LCK? # - We know that CSK and LCK bind LIME1 through their SH2 domains ([PMID:25137454](https://www.ncbi.nlm.nih.gov/pubmed/25137454)). # - PTPN22 binds CSK on its SH3 domain ([PMID:25137454](https://www.ncbi.nlm.nih.gov/pubmed/25137454)). So, a priori, yes. But including this conpletely dirsupts the model... # - PTPN6 binds LCK on its SH2 domain ([PMID:25137454](https://www.ncbi.nlm.nih.gov/pubmed/25137454)) (and is disrupted by S59 phosphorylation). So, no, PTPN6 binding would be prevented by in-place LIME1 binding. # - PTPRC binds the SH2 domain of LCK ([PMID:25137454](https://www.ncbi.nlm.nih.gov/pubmed/25137454)) (and is physically excluded from micro-clusters formed after TCR triggering (PMID:23931554)). So, no. # # ## Core components and extracted sub-model # [(Back to top.)](#toc) # # In[11]: # Test suite for all LCK-related tests. lck_suite = unittest.TestSuite() class LCKModuleTestCase(unittest.TestCase): """Sub-model to be tested.""" model = biolqm.submodel(lqm, " ".join(("i_pMHCII_binding i_pMHCII_agonist i_pMHCII_dose i_pMHCII_affinity TCRalphabeta pTCR", "LCK pY505LCK pY394LCK pS59LCK LCK_activity", "PTPRC PAG1 CSK PTPN22 CD4 LIME1", "FYN" ))) # In[12]: str([component.toString() for component in LCKModuleTestCase.model.getComponents()]) # In[13]: biolqm.to_minibn(LCKModuleTestCase.model) # # ## Test cases # [(Back to top.)](#toc) # # ### Resting state # [(Back to top.)](#toc) # # If the T cell doesn't receive any signal through the TCR, i.e. if there is no antigen whatsoever binding to it, LCK should never be in a configuration that can give rise to sufficient phosphorylation of the neighbouring TCR complexes. # # * [x] If there is no antigen binding, LCK is inactive. # * [x] If there is no antigen binding, TCR is unphosphorylated. # In[14]: class TestLCKResting(LCKModuleTestCase): input_vals = {} core_vals = {} pattern = None resting_tp = None @classmethod def setUpClass(cls): cls.pattern = dash_pattern(cls.model, {**cls.input_vals, **cls.core_vals}) cls.resting_tp = [tp for tp in biolqm.trapspaces(cls.model) if tp["i_pMHCII_binding"]==0] def addcontext(self, message, observed): return(" ".join(("Pattern:", self.pattern, "\n", "Context:", str(self.input_vals), str(self.core_vals), "\n", "Tested:", message, "\n", "Observed:", str(observed), ".\n"))) # Test that in absence of any binding to the TCR, LCK is inactive def test_lck_tp_rest_lcknotactive(self): for tp in self.resting_tp: observed = tp["LCK_activity"] expected = 0 self.assertEqual(observed, expected, self.addcontext("".join(("If {i_pMHCII_binding:0} then LCK_activity:", str(expected))), " ".join((str(observed), "in", str(tp))) )) # Test that in absence of any binding to the TCR, LCK is inactive def test_lck_tp_rest_tcrnotp(self): for tp in self.resting_tp: observed = tp["pTCR"] expected = 0 self.assertEqual(observed, expected, self.addcontext("".join(("If {i_pMHCII_binding:0} then pTCR:", str(expected))), " ".join((str(observed), "in", str(tp))) )) # Add tests to the current suite lck_suite.addTests(unittest.makeSuite(TestLCKResting, prefix="test_")) # Run these tests independently if debug_test_cases : runner.run(unittest.makeSuite(TestLCKResting, prefix="test_")) # # ### Non-productive bindings # [(Back to top.)](#toc) # # T Cell receptors can be bound by many antigens, but not all of them are agonist nor have the correct affinity and dose to elicit activation of the T Cell. More precisely, in the model, a productive signal (as measured by the level of phosphorylated TCR) should not be observed in these cases. In parallel, the possible phosphorylation states of LCK change, allowing it to become active. But the global context should prevent it to have an impact on pTCR. # # Note that MAPK1 and MAPK3 being only activated by the feedback signal caused by a first activation of the signalling cascade downstream of the TCR, the cases where they are turned on are not considered in the following tests. # # * [x] If an antagonist antigen binds, pTCR is inactive. # * [x] If an agonist antigen binds with low dose and affinity, pTCR is inactive. # * [x] If an agonist antigen binds with high dose and affinity, pTCR is inactive. # In[15]: class TestLCKNonProd(LCKModuleTestCase): input_vals = {} core_vals = {} pattern = None nonprod_tp = None @classmethod def setUpClass(cls): cls.pattern = dash_pattern(cls.model, {**cls.input_vals, **cls.core_vals}) cls.nonprod_tp = [tp for tp in biolqm.trapspaces(cls.model) if tp["i_pMHCII_binding"]==1 and tp["MAPK1"]==0 and tp["MAPK3"]==0 and not (tp["i_pMHCII_agonist"]==1 and tp["i_pMHCII_dose"]==0 and tp["i_pMHCII_affinity"]==1 or tp["i_pMHCII_agonist"]==1 and tp["i_pMHCII_dose"]==1 and tp["i_pMHCII_affinity"]==0)] def addcontext(self, message, observed): return(" ".join(("Pattern:", self.pattern, "\n", "Context:", str(self.input_vals), str(self.core_vals), "\n", "Tested:", message, "\n", "Observed:", str(observed), ".\n"))) # Test that with an antagonist antigen binding, pTCR is always off def test_lck_tp_antigenantag_ptcr(self): antagonist_tp = [tp for tp in self.nonprod_tp if tp["i_pMHCII_agonist"]==0] for tp in antagonist_tp: observed = tp["pTCR"] expected = 0 self.assertEqual(observed, expected, self.addcontext("".join(("If {i_pMHCII_binding:1,i_pMHCII_agonist:0,MAPK1:0,MAPK3:0} then pTCR:", str(expected))), " ".join((str(observed), "in", str(tp))) )) # Test that with an agonist antigen binding, but with dose and affinity too low def test_lck_tp_antigenaglow_ptcr(self): antagonist_tp = [tp for tp in self.nonprod_tp if tp["i_pMHCII_agonist"]==1 and tp["i_pMHCII_dose"]==0 and tp["i_pMHCII_affinity"]==0] for tp in antagonist_tp: observed = tp["pTCR"] expected = 0 self.assertEqual(observed, expected, self.addcontext("".join(("If {i_pMHCII_binding:1,i_pMHCII_agonist:0,MAPK1:0,MAPK3:0,i_pMHCII_dose:0,i_pMHCII_affinity:0} then pTCR:", str(expected))), " ".join((str(observed), "in", str(tp))) )) # Test that with an agonist antigen binding, but with dose and affinity too low def test_lck_tp_antigenaghigh_ptcr(self): antagonist_tp = [tp for tp in self.nonprod_tp if tp["i_pMHCII_agonist"]==1 and tp["i_pMHCII_dose"]==1 and tp["i_pMHCII_affinity"]==1] for tp in antagonist_tp: observed = tp["pTCR"] expected = 0 self.assertEqual(observed, expected, self.addcontext("".join(("If {i_pMHCII_binding:1,i_pMHCII_agonist:0,MAPK1:0,MAPK3:0,i_pMHCII_dose:1,i_pMHCII_affinity:1} then pTCR:", str(expected))), " ".join((str(observed), "in", str(tp))) )) # Add tests to the current suite lck_suite.addTests(unittest.makeSuite(TestLCKNonProd, prefix="test_")) # Run these tests independently if debug_test_cases : runner.run(unittest.makeSuite(TestLCKNonProd, prefix="test_")) # # ### Productive bindings # [(Back to top.)](#toc) # # T Cell receptors bound by an agonist antigen with either a low dose and high affinity or high dose and low affinity can elicit a successful signalling downstream of the TCR. Whether this signal will elicit an activation of the cell depends on the activity of the necessary co-activator CD28. # # All tests below are performed in the context of a productive agonist (dose:low/affinity:high or dose:high/affinity:low). # # * [x] Without co-stimulation (CD28), without protective feedback, without co-inhibition, pTCR is always active (but T cells should not get activated). # * [x] With co-stimulation (CD28), without co-inhibition, pTCR is always active. # # We are also interested in the relationship between PTPN6 and MAPK1/MAPK3, activated by a feedback signal (after a first activation of the signalling cascade downstream of the TCR). # # * [ ] With co-stimulation (CD28), with co-inhibition (PTPN6), without protective feedback (pS59:0), pTCR is always active. # * [x] With co-stimulation (CD28), with co-inhibition (PTPN6), with protective feedback, pTCR is always active. # * [x] With co-inhibition (PTPN11), pTCR is always off. # # In[16]: class TestLCKProd(LCKModuleTestCase): input_vals = {} core_vals = {} pattern = None prod_tp = None @classmethod def setUpClass(cls): cls.pattern = dash_pattern(cls.model, {**cls.input_vals, **cls.core_vals}) cls.prod_tp = [tp for tp in biolqm.trapspaces(cls.model) if tp["i_pMHCII_binding"]==1 and tp["i_pMHCII_agonist"]==1 and (tp["i_pMHCII_dose"]==0 and tp["i_pMHCII_affinity"]==1 or tp["i_pMHCII_dose"]==1 and tp["i_pMHCII_affinity"]==0)] def addcontext(self, message, observed): return(" ".join(("Pattern:", self.pattern, "\n", "Context:", str(self.input_vals), str(self.core_vals), "\n", "Tested:", message, "\n", "Observed:", str(observed), ".\n"))) # Test that with an agonist antigen binding with good dose/affinity (no feedback, no costimulation), # pTCR is always on (this doesn't mean that the cell will be activated!) def test_lck_tp_antigenag_ptcr_nocostim_nofeedback(self): agonist10_tp = [tp for tp in self.prod_tp if tp["CD28"]==0 and tp["MAPK1"]==0 and tp["MAPK3"]==0 and tp["PTPN6"]==0 and tp["PTPN11"]==0] for tp in agonist10_tp: observed = tp["pTCR"] expected = 1 self.assertEqual(observed, expected, self.addcontext("".join(("If {CD28:0,MAPK1:0,MAPK3:0,PTPN6:0,PTPN11:0} then pTCR:", str(expected))), " ".join((str(observed), "in", str(tp))) )) # Test that with an agonist antigen binding with good dose/affinity and costimulation through CD28 # but no co-inhibition, pTCR is always on def test_lck_tp_antigenag_ptcr_costim_nonegcostim(self): agonist_tp = [tp for tp in self.prod_tp if tp["CD28"]==1 and tp["PTPN6"]==0 and tp["PTPN11"]==0] for tp in agonist_tp: observed = tp["pTCR"] expected = 1 self.assertEqual(observed, expected, self.addcontext("".join(("If {CD28:1,PTPN6:0,PTPN11:0} then pTCR:", str(expected))), " ".join((str(observed), "in", str(tp))) )) # Test negative co-stimulation by PTPN6 without protective feedback, pTCR is always off # NB: would fail when pS59LCK is initialized at 1 without MAPK1/MAPK3 being active... def test_lck_tp_antigenag_ptcr_negcostim_PTPN6_nofeedback(self): agonist_tp = [tp for tp in self.prod_tp if tp["PTPN6"]==1 and tp["PTPN11"]==0 and tp["MAPK1"]==0 and tp["MAPK3"]==0 and tp["pS59LCK"]==0] for tp in agonist_tp: observed = tp["pTCR"] expected = 0 self.assertEqual(observed, expected, self.addcontext("".join(("If {PTPN6:1,PTPN11:0,MAPK1:0,MAPK3:0,pS59LCK:0} then pTCR:", str(expected))), " ".join((str(observed), "in", str(tp))) )) # Test negative co-stimulation through PTPN6 with protective feedback, pTCR is always on def test_lck_tp_antigenag_ptcr_negcostim_PTPN6_feedback(self): agonist_tp = [tp for tp in self.prod_tp if tp["PTPN6"]==1 and tp["PTPN11"]==0 and (tp["MAPK1"]==1 or tp["MAPK3"]==1)] for tp in agonist_tp: observed = tp["pTCR"] expected = 1 self.assertEqual(observed, expected, self.addcontext("".join(("If {PTPN6:1,PTPN11:0,MAPK1:1/MAPK3:1} then pTCR:", str(expected))), " ".join((str(observed), "in", str(tp))) )) # Test negative co-stimulation through PTPN11 (whatever the context), pTCR is always off def test_lck_tp_antigenag_ptcr_negcostim_PTPN11(self): agonist_tp = [tp for tp in self.prod_tp if tp["PTPN11"]==1] for tp in agonist_tp: observed = tp["pTCR"] expected = 0 self.assertEqual(observed, expected, self.addcontext("".join(("If {PTPN11:1} then pTCR:", str(expected))), " ".join((str(observed), "in", str(tp))) )) # Add tests to the current suite lck_suite.addTests(unittest.makeSuite(TestLCKProd, prefix="test_")) # Run these tests independently if debug_test_cases : runner.run(unittest.makeSuite(TestLCKProd, prefix="test_")) # # ## Run all module tests # [(Back to top.)](#toc) # In[17]: # Add tests to the global suite all_suite.addTests(lck_suite) # Run these tests independently if debug_test_suites : runner.run(lck_suite) # # # Cytoskeleton module # [(Back to top.)](#toc) # # # ## Presentation # [(Back to top.)](#toc) # # As seen in the analysis of the [Calcium module](#calcium), activated T cells undergo strong cytoskeleton remodelling which has important consequences downstream of the TCR and for other processes. # # # ## Core components and extracted sub-model # [(Back to top.)](#toc) # In[18]: # Test suite for all cytoskeleton-related tests. sk_suite = unittest.TestSuite() class CytoskeletonModuleTestCase(unittest.TestCase): """Sub-model to be tested.""" model = biolqm.submodel(lqm, " ".join(("WAVE_cplx RAC1 NCK1 RHOA CDC42 PAK1 WAS ARP2_3 LIMK1 CFL1", "Actin_polymerisation HCLS1 ROCK1 MLCP Actin_contraction_migration" ))) # In[19]: str([component.toString() for component in CytoskeletonModuleTestCase.model.getComponents()]) # In[20]: biolqm.to_minibn(CytoskeletonModuleTestCase.model) # # ## Test cases # [(Back to top.)](#toc) # # ### Absence of PIP2 and PIP3 # [(Back to top.)](#toc) # # Complete absence of both PIP2 and PIP3 is not biologically correct. This case should not activate any of the phenotypes. The state of other components cannot be interpreted. # # * [x] No PIP2/PIP3, actin polymerisation phenotype is turned off. # * [x] No PIP2/PIP3, actin contraction phenotype is turned off. # # In[21]: class TestCytoskeletonNonBio(CytoskeletonModuleTestCase): input_vals = {} core_vals = {} pattern = None nonbio_tp = None @classmethod def setUpClass(cls): cls.pattern = dash_pattern(cls.model, {**cls.input_vals, **cls.core_vals}) cls.nonbio_tp = [tp for tp in biolqm.trapspaces(cls.model) if tp["PIP2"]==0] def addcontext(self, message, observed): return(" ".join(("Pattern:", self.pattern, "\n", "Context:", str(self.input_vals), str(self.core_vals), "\n", "Tested:", message, "\n", "Observed:", str(observed), ".\n"))) # Test that in absence of PIP2/PIP3, there is no migration def test_sk_tp_nopip_nomigration(self): nopip_tp = [tp for tp in self.nonbio_tp if tp["PIP3"]==0] for tp in nopip_tp: observed = tp["Actin_contraction_migration"] expected = 0 self.assertEqual(observed, expected, self.addcontext("".join(("If {PIP2:0,PIP3:0} then Actin_contraction_migration:", str(expected))), " ".join((str(observed), "in", str(tp))) )) # Test that in absence of PIP2/PIP3, there is no actin polymerisation def test_sk_tp_nopip_nopolymerisation(self): nopip_tp = [tp for tp in self.nonbio_tp if tp["PIP3"]==0] for tp in nopip_tp: observed = tp["Actin_polymerisation"] expected = 0 self.assertEqual(observed, expected, self.addcontext("".join(("If {PIP2:0,PIP3:0} then Actin_polymerisation:", str(expected))), " ".join((str(observed), "in", str(tp))) )) # Add tests to the current suite sk_suite.addTests(unittest.makeSuite(TestCytoskeletonNonBio, prefix="test_")) # Run these tests independently if debug_test_cases : runner.run(unittest.makeSuite(TestCytoskeletonNonBio, prefix="test_")) # # ### Resting state # [(Back to top.)](#toc) # # In the absence of any received signal (through LCK/LCP2 or CD28), VAV1 being inactive, T cells don't go through any cytoskeleton remodelling. WAS cannot be activated. Cofilin (CFL1) and Protein phosphatase 1 regulatory subunit 12A (MLCP) are active, inhibiting the processes. # # # * [x] No VAV1, CFL1 is active and cell migration is turned off. # * [x] No VAV1, MLCP is active and actin polymerisation is turned off. # * [x] With PIP2 but no signal from VAV1 or LCP2, the WAS complex is inactive. # In[22]: class TestCytoskeletonResting(CytoskeletonModuleTestCase): input_vals = {} core_vals = {} pattern = None resting_tp = None @classmethod def setUpClass(cls): cls.pattern = dash_pattern(cls.model, {**cls.input_vals, **cls.core_vals}) cls.resting_tp = [tp for tp in biolqm.trapspaces(cls.model) if not(tp["PIP2"]==0 and tp["PIP3"]==0)] def addcontext(self, message, observed): return(" ".join(("Pattern:", self.pattern, "\n", "Context:", str(self.input_vals), str(self.core_vals), "\n", "Tested:", message, "\n", "Observed:", str(observed), ".\n"))) # Test that in absence of kinase, there is no migration def test_sk_tp_rest_nomigration(self): inactive_tp = [tp for tp in self.resting_tp if tp["VAV1"]==0] for tp in inactive_tp: observed = tp["MLCP"] expected = 1 self.assertEqual(observed, expected, self.addcontext("".join(("If {VAV1:0} then MLCP:", str(expected))), " ".join((str(observed), "in", str(tp))) )) for tp in inactive_tp: observed = tp["Actin_contraction_migration"] expected = 0 self.assertEqual(observed, expected, self.addcontext("".join(("If {VAV1:0} then Actin_contraction_migration:", str(expected))), " ".join((str(observed), "in", str(tp))) )) # Test that in absence of kinase, there is no polymerisation def test_sk_tp_rest_nopolymerisation(self): inactive_tp = [tp for tp in self.resting_tp if tp["VAV1"]==0] for tp in inactive_tp: observed = tp["CFL1"] expected = 1 self.assertEqual(observed, expected, self.addcontext("".join(("If {VAV1:0} then CFL1:", str(expected))), " ".join((str(observed), "in", str(tp))) )) for tp in inactive_tp: observed = tp["Actin_polymerisation"] expected = 0 self.assertEqual(observed, expected, self.addcontext("".join(("If {VAV1:0} then Actin_polymerisation:", str(expected))), " ".join((str(observed), "in", str(tp))) )) # Test that with PIP2 but no signal from VAV1 or from LCP2, the WAS complex cannot be activated def test_sk_tp_rest_pip2novav1ornolcp2(self): inactive_tp = [tp for tp in self.resting_tp if tp["PIP2"]==1 and (tp["VAV1"]==0 or tp["LCP2"]==0)] for tp in inactive_tp: observed = tp["WAS"] expected = 0 self.assertEqual(observed, expected, self.addcontext("".join(("If {PIP2:1,VAV1:0/LCP2:0} then WAS:", str(expected))), " ".join((str(observed), "in", str(tp))) )) # Add tests to the current suite sk_suite.addTests(unittest.makeSuite(TestCytoskeletonResting, prefix="test_")) # Run these tests independently if debug_test_cases : runner.run(unittest.makeSuite(TestCytoskeletonResting, prefix="test_")) # # ### Activation # [(Back to top.)](#toc) # # During activation, T cells undergo cytoskeleton remodelling. In a first step, using PIP2, activated WAS induces actin polymerisation. Secondly, if a co-activating signal is also present, PIP3 production allows the WAVE complex to take over WAS activity. # # * [x] No PIP2 means that WAS cannot be activated. # * [x] No PIP3 means that the WAVE complex cannot be activated. # * [x] PIP2, VAV1 and LCP2 activate WAS. # * [x] PIP3 and CD28 activate the WAVE complex. # * [x] WAS activation induces Actin polymerisation. # * [x] WAVE complex activation induces Actin polymerisation. # * [x] No VAV1 means no Actin contraction nor migration. # In[23]: class TestCytoskeletonActivated(CytoskeletonModuleTestCase): input_vals = {} core_vals = {} pattern = None activ_tp = None @classmethod def setUpClass(cls): cls.pattern = dash_pattern(cls.model, {**cls.input_vals, **cls.core_vals}) cls.activ_tp = [tp for tp in biolqm.trapspaces(cls.model) if not(tp["PIP2"]==0 and tp["PIP3"]==0)] def addcontext(self, message, observed): return(" ".join(("Pattern:", self.pattern, "\n", "Context:", str(self.input_vals), str(self.core_vals), "\n", "Tested:", message, "\n", "Observed:", str(observed), ".\n"))) # Test that in absence of PIP2, WAS cannot be activated def test_sk_tp_nopip2_nowas(self): nopip2_tp = [tp for tp in self.activ_tp if tp["PIP2"]==0] for tp in nopip2_tp: observed = tp["WAS"] expected = 0 self.assertEqual(observed, expected, self.addcontext("".join(("If {PIP2:0} then WAS:", str(expected))), " ".join((str(observed), "in", str(tp))) )) # Test that in absence of PIP3, the WAVE complex cannot be activated def test_sk_tp_nopip3_nowave(self): nopip3_tp = [tp for tp in self.activ_tp if tp["PIP3"]==0] for tp in nopip3_tp: observed = tp["WAVE_cplx"] expected = 0 self.assertEqual(observed, expected, self.addcontext("".join(("If {PIP3:0} then WAVE_cplx:", str(expected))), " ".join((str(observed), "in", str(tp))) )) # Test that with PIP2 and VAV1 and LCP2, the WAS complex is activated def test_sk_tp_activ_pip2vav1lcp2(self): signal_tp = [tp for tp in self.activ_tp if tp["PIP2"]==1 and tp["VAV1"]==1 and tp["LCP2"]==1] for tp in signal_tp: observed = tp["WAS"] expected = 1 self.assertEqual(observed, expected, self.addcontext("".join(("If {PIP2:1,VAV1:1,LCP2:1} then WAS:", str(expected))), " ".join((str(observed), "in", str(tp))) )) # Test that with PIP3 and VAV1 and ABL1, the WAVE complex is activated def test_sk_tp_activ_pip3vav1abl1(self): signal_tp = [tp for tp in self.activ_tp if tp["PIP3"]==1 and tp["VAV1"]==1 and tp["ABL1"]==1] for tp in signal_tp: observed = tp["WAVE_cplx"] expected = 1 self.assertEqual(observed, expected, self.addcontext("".join(("If {PIP3:1,VAV1:1,ABL1:1} then WAVE_cplx:", str(expected))), " ".join((str(observed), "in", str(tp))) )) # Test that with WAS active, there is actin polymerisation def test_sk_tp_activ_was(self): signal_tp = [tp for tp in self.activ_tp if tp["WAS"]==1] for tp in signal_tp: observed = tp["Actin_polymerisation"] expected = 1 self.assertEqual(observed, expected, self.addcontext("".join(("If {WAS:1} then Actin_polymerisation:", str(expected))), " ".join((str(observed), "in", str(tp))) )) # Test that with WAVE_cplx active, there is actin polymerisation def test_sk_tp_activ_wave(self): signal_tp = [tp for tp in self.activ_tp if tp["WAVE_cplx"]==1] for tp in signal_tp: observed = tp["Actin_polymerisation"] expected = 1 self.assertEqual(observed, expected, self.addcontext("".join(("If {WAVE_cplx:1} then Actin_polymerisation:", str(expected))), " ".join((str(observed), "in", str(tp))) )) # Test that in absence of VAV1, there is no actin contraction/migration def test_sk_tp_activ_novav1_nomigration(self): nopip3_tp = [tp for tp in self.activ_tp if tp["VAV1"]==0] for tp in nopip3_tp: observed = tp["Actin_contraction_migration"] expected = 0 self.assertEqual(observed, expected, self.addcontext("".join(("If {VAV1:0} then Actin_contraction_migration:", str(expected))), " ".join((str(observed), "in", str(tp))) )) # Add tests to the current suite sk_suite.addTests(unittest.makeSuite(TestCytoskeletonActivated, prefix="test_")) # Run these tests independently if debug_test_cases : runner.run(unittest.makeSuite(TestCytoskeletonActivated, prefix="test_")) # # ## Run all module tests # [(Back to top.)](#toc) # In[24]: # Add tests to the global suite all_suite.addTests(sk_suite) # Run these tests independently if debug_test_suites : runner.run(sk_suite) # # # Anergy/activation/differentiation module # [(Back to top.)](#toc) # # # ## Presentation # [(Back to top.)](#toc) # # The model adresses two main events related to T cell stimulation. First, after the TCR receives a sufficient stimulation, it shifts from Quiescence to Proliferation. Second, an insufficient stimulation doesn't lead to IL2 production but to a different program called Anergy, where cells enter an iresponsive state ([PMID:15928679](https://www.ncbi.nlm.nih.gov/pubmed/15928679)). # # * [x] Proliferation and Quiescence cannot be activated together. # * [x] Quiescence and IL2 cannot be activated together. # # ## Core components and extracted sub-model # [(Back to top.)](#toc) # In[25]: # Test suite for this sub-model's related tests. aad_suite = unittest.TestSuite() class ActivationModuleTestCase(unittest.TestCase): """Sub-model to be tested.""" model = biolqm.submodel(lqm, " ".join(("Anergy Differentiation Treg IL2 NFAT_nuc DGKA FOXP3 CREBBP CTLA4_out PDCD1_out", "GSK3B FOXO1 TP53 CDKN1A CDKN1B CTNNB1 MYC PCNA CCND1 Quiescence Proliferation" ))) # In[26]: str([component.toString() for component in ActivationModuleTestCase.model.getComponents()]) # In[27]: biolqm.to_minibn(ActivationModuleTestCase.model) # # ## Test cases # [(Back to top.)](#toc) # In[28]: class TestActivationModule(ActivationModuleTestCase): input_vals = {} core_vals = {} pattern = None all_tp = None @classmethod def setUpClass(cls): cls.pattern = dash_pattern(cls.model, {**cls.input_vals, **cls.core_vals}) cls.all_tp = [tp for tp in biolqm.trapspaces(cls.model)] def addcontext(self, message, observed): return(" ".join(("Pattern:", self.pattern, "\n", "Context:", str(self.input_vals), str(self.core_vals), "\n", "Tested:", message, "\n", "Observed:", str(observed), ".\n"))) # Test that Proliferation and Quiescence cannot be activated together def test_aad_tp_quiescence_prolif(self): for tp in self.all_tp: observed = tp["Quiescence"] observed2 = tp["Proliferation"] not_expected = 1 self.assertFalse((observed==observed2 and observed==not_expected), self.addcontext("".join(("Quiescence:", str(observed), " and Proliferation:", str(observed2), " should be different ")), " ".join((str(observed!=observed2), "in", str(tp))) )) # Test that if Anergy is activated, IL2 is not def test_aad_tp_anergy_il2(self): for tp in self.all_tp: observed = tp["Anergy"] observed2 = tp["IL2"] not_expected = 1 self.assertFalse((observed==observed2 and observed==not_expected), self.addcontext("".join(("Anergy:", str(observed), " and IL2:", str(observed2), " should be different ")), " ".join((str(observed!=observed2), "in", str(tp))) )) # Add tests to the current suite aad_suite.addTests(unittest.makeSuite(TestActivationModule, prefix="test_")) # Run these tests independently if debug_test_cases : runner.run(unittest.makeSuite(TestActivationModule, prefix="test_")) # # ## Run all module tests # [(Back to top.)](#toc) # In[29]: # Add tests to the global suite all_suite.addTests(aad_suite) # Run these tests independently if debug_test_suites : runner.run(aad_suite) # # # Run all tests # # In[33]: # Run global suite if run_all: result = runner.run(all_suite)