Skip to content

Commit 5202034

Browse files
authored
Merge branch 'main' into dafni-dev
2 parents a03a807 + 8419eb2 commit 5202034

File tree

8 files changed

+182
-13
lines changed

8 files changed

+182
-13
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ coverage.xml
5555

5656
# Sphinx documentation
5757
docs/build/
58+
docs/source/autoapi
5859

5960
# v/docs/build/scode
6061
.vscode/
@@ -63,4 +64,4 @@ docs/build/
6364
temp/
6465

6566
# log files
66-
*.log
67+
*.log

CITATION.cff

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
cff-version: "1.2.0"
2+
authors:
3+
- family-names: Foster
4+
given-names: Michael
5+
orcid: "https://orcid.org/0000-0001-8233-9873"
6+
- family-names: Clark
7+
given-names: Andrew
8+
orcid: "https://orcid.org/0000-0002-6830-0566"
9+
- family-names: Wild
10+
given-names: Christopher
11+
orcid: "https://orcid.org/0009-0009-1195-1497"
12+
- family-names: Allian
13+
given-names: Farhad
14+
orcid: "https://orcid.org/0000-0002-4569-0370"
15+
- family-names: Turner
16+
given-names: Robert
17+
orcid: "https://orcid.org/0000-0002-1353-1404"
18+
- family-names: Somers
19+
given-names: Richard
20+
orcid: "https://orcid.org/0009-0009-1195-1497"
21+
- family-names: Latimer
22+
given-names: Nicholas
23+
orcid: "https://orcid.org/0000-0001-5304-5585"
24+
- family-names: Walkinshaw
25+
given-names: Neil
26+
orcid: "https://orcid.org/0000-0003-2134-6548"
27+
- family-names: Hierons
28+
given-names: Robert M.
29+
orcid: "https://orcid.org/0000-0003-2134-6548"
30+
contact:
31+
- family-names: Foster
32+
given-names: Michael
33+
orcid: "https://orcid.org/0000-0001-8233-9873"
34+
doi: 10.15131/shef.data.28539398.v1
35+
message: If you use this software, please cite our article in the
36+
Journal of Open Source Software.
37+
preferred-citation:
38+
authors:
39+
- family-names: Foster
40+
given-names: Michael
41+
orcid: "https://orcid.org/0000-0001-8233-9873"
42+
- family-names: Clark
43+
given-names: Andrew
44+
orcid: "https://orcid.org/0000-0002-6830-0566"
45+
- family-names: Wild
46+
given-names: Christopher
47+
orcid: "https://orcid.org/0009-0009-1195-1497"
48+
- family-names: Allian
49+
given-names: Farhad
50+
orcid: "https://orcid.org/0000-0002-4569-0370"
51+
- family-names: Turner
52+
given-names: Robert
53+
orcid: "https://orcid.org/0000-0002-1353-1404"
54+
- family-names: Somers
55+
given-names: Richard
56+
orcid: "https://orcid.org/0009-0009-1195-1497"
57+
- family-names: Latimer
58+
given-names: Nicholas
59+
orcid: "https://orcid.org/0000-0001-5304-5585"
60+
- family-names: Walkinshaw
61+
given-names: Neil
62+
orcid: "https://orcid.org/0000-0003-2134-6548"
63+
- family-names: Hierons
64+
given-names: Robert M.
65+
orcid: "https://orcid.org/0000-0003-2134-6548"
66+
date-published: 2025-03-06
67+
doi: 10.21105/joss.07739
68+
issn: 2475-9066
69+
issue: 107
70+
journal: Journal of Open Source Software
71+
publisher:
72+
name: Open Journals
73+
start: 7739
74+
title: The Causal Testing Framework
75+
type: article
76+
url: "https://joss.theoj.org/papers/10.21105/joss.07739"
77+
volume: 10
78+
title: The Causal Testing Framework

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
[![Documentation Status](https://readthedocs.org/projects/causal-testing-framework/badge/?version=latest)](https://causal-testing-framework.readthedocs.io/en/latest/?badge=latest)
99
![Dynamic TOML Badge](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2FCITCOM-project%2FCausalTestingFramework%2Fmain%2Fpyproject.toml&query=%24.project%5B'requires-python'%5D&label=python)
1010
![PyPI - Version](https://img.shields.io/pypi/v/causal-testing-framework)
11-
[![DOI](https://t.ly/FCT1B)](https://orda.shef.ac.uk/articles/software/CITCOM_Software_Release/24427516)
1211
![GitHub Licens[schematic.tex](images%2Fschematic.tex)e](https://img.shields.io/github/license/CITCOM-project/CausalTestingFramework)
12+
[![DOI](https://joss.theoj.org/papers/10.21105/joss.07739/status.svg)](https://doi.org/10.21105/joss.07739)
13+
[![DOI](https://img.shields.io/badge/doi-10.26180/5c6e1160b8d8a-blue.svg?style=flat&labelColor=whitesmoke&logo=data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAAB8AAAAfCAYAAAAfrhY5AAAJsklEQVR42qWXd1DTaRrHf%2BiB2Hdt5zhrAUKz4IKEYu9IGiGFFJJQ0gkJCAKiWFDWBRdFhCQUF3UVdeVcRQEBxUI3yY9iEnQHb3bdW1fPubnyz%2F11M7lvEHfOQee2ZOYzPyDv%2B3yf9%2Fk95YX4fx%2BltfUt08GcFEuPR4U9hDDZ%2FVngIlhb%2FSiI6InkTgLzgDcgfvtnovhH4BzoVlrbwr55QnhCtBW4QHXnFrZbPBaQoBh4%2FSYH2EnpBEtqcDMVzB93wA%2F8AFwa23XFGcc8CkT3mxz%2BfXWtq9T9IQlLIXYEuHojudb%2BCM7Hgdq8ydi%2FAHiBXyY%2BLjwFlAEnS6Jnar%2FvnQVhvdzasad0eKvWZKe8hvDB2ofLZ%2FZEcWsh%2BhyIuyO5Bxs2iZIE4nRv7NWAb0EO8AC%2FWPxjYAWuOEX2MSXZVgPxzmRL3xKz3ScGpx6p6QnOx4mDIFqO0w6Q4fEhO5IzwxlSwyD2FYHzwAW%2BAZ4fEsf74gCumykwNHskLM7taQxLYjjIyy8MUtraGhTWdkfhkFJqtvuVl%2F9l2ZquDfEyrH8B0W06nnpH3JtIyRGpH1iJ6SfxDIHjRXHJmdQjLpfHeN54gnfFx4W9QRnovx%2FN20aXZeTD2J84hn3%2BqoF2Tqr14VqTPUCIcP%2B5%2Fly4qC%2BUL3sYxSvNj1NwsVYPsWdMUfomsdkYm3Tj0nbV0N1wRKwFe1MgKACDIBdMAhPE%2FwicwNWxll8Ag40w%2BFfhibJkGHmutjYeQ8gVlaN%2BjO51nDysa9TwNUFMqaGbKdRJZFfOJSp6mkRKsv0rRIpEVWjAvyFkxNOEpwvcAVPfEe%2Bl8ojeNTx3nXLBcWRrYGxSRjDEk0VlpxYrbe1ZmaQ5xuT0u3r%2B2qe5j0J5uytiZPGsRL2Jm32AldpxPUNJ3jmmsN4x62z1cXrbedXBQf2yvIFCeZrtyicZZG2U2nrrBJzYorI2EXLrvTfCSB43s41PKEvbZDEfQby6L4JTj%2FfIwam%2B4%2BwucBu%2BDgNK05Nle1rSt9HvR%2FKPC4U6LTfvUIaip1mjIa8fPzykii23h2eanT57zQ7fsyYH5QjywwlooAUcAdOh5QumgTHx6aAO7%2FL52eaQNEShrxfhL6albEDmfhGflrsT4tps8gTHNOJbeDeBlt0WJWDHSgxs6cW6lQqyg1FpD5ZVDfhn1HYFF1y4Eiaqa18pQf3zzYMBhcanlBjYfgWNayAf%2FASOgklu8bmgD7hADrk4cRlOL7NSOewEcbqSmaivT33QuFdHXj5sdvjlN5yMDrAECmdgDWG2L8P%2BAKLs9ZLZ7dJda%2BB4Xl84t7QvnKfvpXJv9obz2KgK8dXyqISyV0sXGZ0U47hOA%2FAiigbEMECJxC9aoKp86re5O5prxOlHkcksutSQJzxZRlPZmrOKhsQBF5zEZKybUC0vVjG8PqOnhOq46qyDTDnj5gZBriWCk4DvXrudQnXQmnXblebhAC2cCB6zIbM4PYgGl0elPSgIf3iFEA21aLdHYLHUQuVkpgi02SxFdrG862Y8ymYGMvXDzUmiX8DS5vKZyZlGmsSgQqfLub5RyLNS4zfDiZc9Edzh%2FtCE%2BX8j9k%2FqWB071rcZyMImne1SLkL4GRw4UPHMV3jjwEYpPG5uW5fAEot0aTSJnsGAwHJi2nvF1Y5OIqWziVCQd5NT7t6Q8guOSpgS%2Fa1dSRn8JGGaCD3BPXDyQRG4Bqhu8XrgAp0yy8DMSvvyVXDgJcJTcr1wQ2BvFKf65jqhvmxXUuDpGBlRvV36XvGjQzLi8KAKT2lYOnmxQPGorURSV0NhyTIuIyqOmKTMhQ%2BieEsgOgpc4KBbfDM4B3SIgFljvfHF6cef7qpyLBXAiQcXvg5l3Iunp%2FWv4dH6qFziO%2BL9PbrimQ9RY6MQphEfGUpOmma7KkGzuS8sPUFnCtIYcKCaI9EXo4HlQLgGrBjbiK5EqMj2AKWt9QWcIFMtnVvQVDQV9lXJJqdPVtUQpbh6gCI2Ov1nvZts7yYdsnvRgxiWFOtNJcOMVLn1vgptVi6qrNiFOfEjHCDB3J%2BHDLqUB77YgQGwX%2Fb1eYna3hGKdlqJKIyiE4nSbV8VFgxmxR4b5mVkkeUhMgs5YTi4ja2XZ009xJRHdkfwMi%2BfocaancuO7h%2FMlcLOa0V%2FSw6Dq47CumRQAKhgbOP8t%2BMTjuxjJGhXCY6XpmDDFqWlVYbQ1aDJ5Cptdw4oLbf3Ck%2BdWkVP0LpH7s9XLPXI%2FQX8ws%2Bj2In63IcRvOOo%2BTTjiN%2BlssfRsanW%2B3REVKoavBOAPTXABW4AL7e4NygHdpAKBscmlDh9Jysp4wxbnUNna3L3xBvyE1jyrGIkUHaqQMuxhHElV6oj1picvgL1QEuS5PyZTEaivqh5vUCKJqOuIgPFGESns8kyFk7%2FDxyima3cYxi%2FYOQCj%2F%2B9Ms2Ll%2Bhn4FmKnl7JkGXQGDKDAz9rUGL1TIlBpuJr9Be2JjK6qPzyDg495UxXYF7JY1qKimw9jWjF0iV6DRIqE%2B%2FeWG0J2ofmZTk0mLYVd4GLiFCOoKR0Cg727tWq981InYynvCuKW43aXgEjofVbxIqrm0VL76zlH3gQzWP3R3Bv9oXxclrlO7VVtgBRpSP4hMFWJ8BrUSBCJXC07l40X4jWuvtc42ofNCxtlX2JH6bdeojXgTh5TxOBKEyY5wvBE%2BACh8BtOPNPkApjoxi5h%2B%2FFMQQNpWvZaMH7MKFu5Ax8HoCQdmGkJrtnOiLHwD3uS5y8%2F2xTSDrE%2F4PT1yqtt6vGe8ldMBVMEPd6KwqiYECHDlfbvzphcWP%2BJiZuL5swoWQYlS%2Br7Yu5mNUiGD2retxBi9fl6RDGn4Ti9B1oyYy%2BMP5G87D%2FCpRlvdnuy0PY6RC8BzTA40NXqckQ9TaOUDywkYsudxJzPgyDoAWn%2BB6nEFbaVxxC6UXjJiuDkW9TWq7uRBOJocky9iMfUhGpv%2FdQuVVIuGjYqACbXf8aa%2BPeYNIHZsM7l4s5gAQuUAzRUoT51hnH3EWofXf2vkD5HJJ33vwE%2FaEWp36GHr6GpMaH4AAPuqM5eabH%2FhfG9zcCz4nN6cPinuAw6IHwtvyB%2FdO1toZciBaPh25U0ducR2PI3Zl7mokyLWKkSnEDOg1x5fCsJE9EKhH7HwFNhWMGMS7%2BqxyYsbHHRUDUH4I%2FAheQY7wujJNnFUH4KdCju83riuQeHU9WEqNzjsJFuF%2FdTDAZ%2FK7%2F1WaAU%2BAWymT59pVMT4g2AxcwNa0XEBDdBDpAPvgDIH73R25teeuAF5ime2Ul0OUIiG4GpSAEJeYW9wDTf43wfwHgHLKJoPznkwAAAABJRU5ErkJggg%3D%3D)](http://doi.org/10.15131/shef.data.24427516.v2)
1314

1415
Causal testing is a causal inference-driven framework for functional black-box testing. This framework utilises
1516
graphical causal inference (CI) techniques for the specification and functional testing of software from a black-box

causal_testing/main.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -281,17 +281,36 @@ def create_causal_test(self, test: dict, base_test: BaseTestCase) -> CausalTestC
281281
if estimator_class is None:
282282
raise ValueError(f"Unknown estimator: {test['estimator']}")
283283

284+
# Handle combined queries (global and test-specific)
285+
test_query = test.get("query")
286+
combined_query = None
287+
288+
if self.query and test_query:
289+
combined_query = f"({self.query}) and ({test_query})"
290+
logger.info(
291+
f"Combining global query '{self.query}' with test-specific query "
292+
f"'{test_query}' for test '{test['name']}'"
293+
)
294+
elif test_query:
295+
combined_query = test_query
296+
logger.info(f"Using test-specific query for '{test['name']}': {test_query}")
297+
elif self.query:
298+
combined_query = self.query
299+
logger.info(f"Using global query for '{test['name']}': {self.query}")
300+
301+
filtered_df = self.data.query(combined_query) if combined_query else self.data
302+
284303
# Create the estimator with correct parameters
285304
estimator = estimator_class(
286305
base_test_case=base_test,
287306
treatment_value=test.get("treatment_value"),
288307
control_value=test.get("control_value"),
289308
adjustment_set=test.get("adjustment_set", self.causal_specification.causal_dag.identification(base_test)),
290-
df=self.data,
309+
df=filtered_df,
291310
effect_modifiers=None,
292311
formula=test.get("formula"),
293312
alpha=test.get("alpha", 0.05),
294-
query="",
313+
query=combined_query,
295314
)
296315

297316
# Get effect type and create expected effect
@@ -424,7 +443,7 @@ def save_results(self, results: List[CausalTestResult]) -> None:
424443
"effect": test_config.get("effect", "direct"),
425444
"treatment_variable": test_config["treatment_variable"],
426445
"expected_effect": test_config["expected_effect"],
427-
"formula": test_config.get("formula"),
446+
"formula": result.estimator.formula if hasattr(result.estimator, "formula") else None,
428447
"alpha": test_config.get("alpha", 0.05),
429448
"skip": test_config.get("skip", False),
430449
"passed": test_passed,

docs/source/modules/causal_specification.rst

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ Causal Specification
1414
- A causal specification is simply the combination of these components: a series of requirements for the scenario-under-test and a causal DAG representing causality
1515
amongst the inputs and outputs. We will discuss these in more detail below.
1616

17+
- Collectively, the components of the causal specification provide both contextual information in the form of constraints and requirements, as well as causal information in the form of a causal DAG.
18+
Later on, these components will be used to design statistical experiments that can answer causal questions about the scenario-under-test, such as `Does opening a window impair the viruses ability to spread?`
19+
1720
Scenario Requirements
1821
---------------------
1922

@@ -30,14 +33,18 @@ Causal DAG
3033
----------
3134

3235
In order to apply CI techniques, we need to capture causality amongst the inputs and outputs in the scenario-under-test.
33-
Therefore, for each scenario, the user must define a causal DAG. While there is generally no guidance/algorithm that can be followed to create a causal DAG,
34-
there are a couple of requirements that should be satisfied.
36+
Therefore, for each scenario, the user must define a causal DAG.
37+
As an example, consider the DAG shown below for the Poisson line process, which can be found in our `examples` directory.
38+
Here, the model has three inputs: `width`, `height`, and `intensity`.
39+
These inputs control the number of lines (`num_lines_abs`) and polygons (`num_shapes_abs`) that are drawn, which then feed into the numbers of lines (`num_lines_unit`) and polygons (`num_shapes_unit`) per unit area.
40+
Note though that the `num_lines_abs` does not have a direct causal effect on `num_shapes_unit`, since the number of polygons per unit area is defined entirely by the total number of polygons and the area of the sampling window.
41+
42+
.. literalinclude:: ../../../examples/poisson-line-process/dag.dot
43+
:language: graphviz
44+
:caption: Example Causal DAG for the Poisson line process example.
3545

46+
Unfortunately, there is no universally applicable guidance or an algorithm that can be followed to create a causal DAG, there are three general requirements that should be satisfied.
3647

3748
#. The DAG must contain all inputs and outputs involved in the scenario-under-test.
3849
#. If there are any other variables which are not directly involved but are expected to have a causal relationship with the variables in the scenario-under-test, these should also be added to the graph. For example, the size of the room might be partially caused by the simulated location (house styles, average wealth etc.), in which case location should be added to the DAG with an edge to room size and any other variables it is deemed to influence.
3950
#. If in doubt, add an edge. It is a stronger assumption to exclude an edge (X and Y are independent) than to include one (X has some potentially negligiable causal effect on Y).
40-
41-
- Collectively, the components of the causal specification provide both contextual information in the form of constraints and requirements,
42-
as well as causal information in the form of a causal DAG. Later on, these components will be used to design statistical experiments that
43-
can answer causal questions about the scenario-under-test, such as `Does opening a window impair the viruses ability to spread?`

paper/paper.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ authors:
2525
orcid: 0000-0002-1353-1404
2626
affiliation: 1
2727
- name: Richard Somers
28-
orcid: 0009-0009-1195-1497
28+
orcid: 0000-0002-1101-9722
2929
affiliation: 1
3030
- name: Nicholas Latimer
3131
orcid: 0000-0001-5304-5585
@@ -34,7 +34,7 @@ authors:
3434
orcid: 0000-0003-2134-6548
3535
affiliation: 1
3636
- name: Robert M. Hierons
37-
orcid: 0000-0003-2134-6548
37+
orcid: 0000-0002-4771-1446
3838
affiliation: 1
3939
affiliations:
4040
- name: University of Sheffield, UK

tests/main_tests/test_main.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,67 @@ def test_ctf(self):
144144

145145
self.assertEqual(tests_passed, [True])
146146

147+
def test_global_query(self):
148+
framework = CausalTestingFramework(self.paths)
149+
framework.setup()
150+
151+
query_framework = CausalTestingFramework(self.paths, query="test_input > 0")
152+
query_framework.setup()
153+
154+
self.assertTrue(len(query_framework.data) > 0)
155+
self.assertTrue((query_framework.data["test_input"] > 0).all())
156+
157+
with open(self.test_config_path, "r", encoding="utf-8") as f:
158+
test_configs = json.load(f)
159+
160+
test_config = test_configs["tests"][0].copy()
161+
if "query" in test_config:
162+
del test_config["query"]
163+
164+
base_test = query_framework.create_base_test(test_config)
165+
causal_test = query_framework.create_causal_test(test_config, base_test)
166+
167+
self.assertTrue((causal_test.estimator.df["test_input"] > 0).all())
168+
169+
query_framework.create_variables()
170+
query_framework.create_scenario_and_specification()
171+
172+
self.assertIsNotNone(query_framework.scenario)
173+
self.assertIsNotNone(query_framework.causal_specification)
174+
175+
def test_test_specific_query(self):
176+
framework = CausalTestingFramework(self.paths)
177+
framework.setup()
178+
179+
with open(self.test_config_path, "r", encoding="utf-8") as f:
180+
test_configs = json.load(f)
181+
182+
test_config = test_configs["tests"][0].copy()
183+
test_config["query"] = "test_input > 0"
184+
185+
base_test = framework.create_base_test(test_config)
186+
causal_test = framework.create_causal_test(test_config, base_test)
187+
188+
self.assertTrue(len(causal_test.estimator.df) > 0)
189+
self.assertTrue((causal_test.estimator.df["test_input"] > 0).all())
190+
191+
def test_combined_queries(self):
192+
global_framework = CausalTestingFramework(self.paths, query="test_input > 0")
193+
global_framework.setup()
194+
195+
with open(self.test_config_path, "r", encoding="utf-8") as f:
196+
test_configs = json.load(f)
197+
198+
test_config = test_configs["tests"][0].copy()
199+
test_config["query"] = "test_output > 0"
200+
201+
base_test = global_framework.create_base_test(test_config)
202+
causal_test = global_framework.create_causal_test(test_config, base_test)
203+
204+
self.assertTrue(len(causal_test.estimator.df) > 0)
205+
self.assertTrue((causal_test.estimator.df["test_input"] > 0).all())
206+
self.assertTrue((causal_test.estimator.df["test_output"] > 0).all())
207+
147208
def test_parse_args(self):
148209
with unittest.mock.patch(
149210
"sys.argv",

tests/resources/data/tests.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"estimator": "LinearRegressionEstimator",
66
"estimate_type": "coefficient",
77
"effect_modifiers": [],
8+
"query": "test_input > 0",
89
"expected_effect": {"test_output": "NoEffect"},
910
"skip": false
1011
},
@@ -14,6 +15,7 @@
1415
"estimator": "LinearRegressionEstimator",
1516
"estimate_type": "coefficient",
1617
"effect_modifiers": [],
18+
"query": "test_input <= 5",
1719
"expected_effect": {"test_output": "NoEffect"},
1820
"skip": true
1921
}]

0 commit comments

Comments
 (0)